Implementing an Enterprise Management System with Vue 3 and TypeScript

1. Routing Configuration

1.1 Router Instance Setup

Initialize the Vue Router using the createRouter and createWebHistory functions. Define the routing mode and default scroll behavior to reset the position on navigation.

import { createRouter, createWebHistory } from 'vue-router'
import { staticRoutes } from './routes'

const appRouter = createRouter({
  history: createWebHistory(),
  routes: staticRoutes,
  scrollBehavior() {
    return { left: 0, top: 0 }
  },
})

export default appRouter

1.2 Route Definitions

Define the constant routes, including the authentication page, the main layout, and a catch-all route for 404 errors.

export const staticRoutes = [
  {
    path: '/auth',
    component: () => import('@/views/auth/index.vue'),
    name: 'Auth',
  },
  {
    path: '/',
    component: () => import('@/views/layout/index.vue'),
    name: 'Layout',
    redirect: '/home',
    children: [
      {
        path: '/home',
        component: () => import('@/views/home/index.vue'),
        name: 'Home',
        meta: { title: 'Dashboard' }
      }
    ]
  },
  {
    path: '/:pathMatch(.*)*',
    redirect: '/404',
    name: 'NotFound',
  },
]

2. Authentication Module

2.1 Login Interface Structure

Implement the login view using Element Plus components. Create a split layout with a background image on one side and the login form on the other.

<template>
  <div class="login-wrapper">
    
      
      
        
      
    
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue'
import { User, Lock } from '@element-plus/icons-vue'
import { useAuthStore } from '@/store/modules/auth'
import { useRouter } from 'vue-router'

const authStore = useAuthStore()
const router = useRouter()
const isLoading = ref(false)

const credentials = reactive({ account: 'admin', secret: '123456' })

const rules = {
  account: [{ required: true, message: 'Account is required', trigger: 'blur' }],
  secret: [{ required: true, message: 'Password is required', trigger: 'blur' }]
}

const handleLogin = async () => {
  isLoading.value = true
  try {
    await authStore.login(credentials)
    router.push('/')
  } catch (error) {
    console.error(error)
  } finally {
    isLoading.value = false
  }
}
</script>

2.2 Pinia Store for Authentication

Create a Pinia store to manage user state, token storage, and login/logout actions.

import { defineStore } from 'pinia'
import { loginReq, getUserInfoReq } from '@/api/auth'
import { SET_TOKEN, GET_TOKEN, REMOVE_TOKEN } from '@/utils/token'

interface AuthState {
  token: string | null
  userInfo: any
}

export const useAuthStore = defineStore('Auth', {
  state: (): AuthState => ({
    token: GET_TOKEN(),
    userInfo: null
  }),
  actions: {
    async login(data: any) {
      const res = await loginReq(data)
      if (res.code === 200) {
        this.token = res.data
        SET_TOKEN(res.data)
        return 'ok'
      } else {
        return Promise.reject(new Error(res.message))
      }
    },
    async getInfo() {
      const res = await getUserInfoReq()
      if (res.code === 200) {
        this.userInfo = res.data
      }
    },
    logout() {
      this.token = ''
      this.userInfo = null
      REMOVE_TOKEN()
    }
  }
})

3. Layout Architecture

3.1 Main Layout Component

Construct the layout shell consisting of a sidebar, top navigation bar, and main content area. Use CSS variables and fixed positioning for the tab bar.

<template>
  <div class="layout-container">
    <div class="layout-sidebar">
      <Logo />
      <Menu />
    </div>
    <div class="layout-tabbar">
      <Breadcrumb />
      <Settings />
    </div>
    <div class="layout-main">
      <MainContent />
    </div>
  </div>
</template>

<style lang="scss" scoped>
.layout-container {
  width: 100%;
  height: 100vh;
  .layout-sidebar {
    width: $base-sidebar-width;
    height: 100%;
    background: $base-sidebar-bg;
  }
  .layout-tabbar {
    position: fixed;
    width: calc(100% - $base-sidebar-width);
    height: $base-tabbar-height;
    background: #fff;
    top: 0;
    left: $base-sidebar-width;
    border-bottom: 1px solid #eee;
  }
  .layout-main {
    position: absolute;
    top: $base-tabbar-height;
    left: $base-sidebar-width;
    width: calc(100% - $base-sidebar-width);
    height: calc(100vh - $base-tabbar-height);
    padding: 20px;
    overflow: auto;
    background: #f0f2f5;
  }
}
</style>

3.2 Dynamic Menu Generation

Implement a recursive menu component that renders based on the route configuration. Use el-menu and handle route meta information for icons and titles.

<template>
  <template v-for="item in menuRoutes" :key="item.path">
    
      <component :is="item.meta.icon" />
      <span>{{ item.meta.title }}</span>
    
    
      <template #title>
        <component :is="item.meta.icon" />
        <span>{{ item.meta.title }}</span>
      </template>
      <Menu :menuRoutes="item.children" />
    
  </template>
</template>

<script setup lang="ts">
defineProps(['menuRoutes'])
</script>

<script lang="ts">
export default { name: 'Menu' }
</script>

4. Brand Management Module

4.1 Data Table and Pagination

Create a table view to display brand information. Implement pagination controls to handle large datasets.

<template>
  
    Add Brand
    
      
      
      
        <template #default="{ row }">
          <img :src="row.logoUrl" style="width: 50px; height: 50px" />
        </template>
      
      
        <template #default="{ row }">
          
          
            <template #reference>
              
            </template>
          
        </template>
      
    
    
  
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { reqBrandList, reqAddOrUpdate, reqDeleteBrand } from '@/api/product/brand'
import type { BrandData } from '@/api/product/brand/type'

const pageNo = ref(1)
const limit = ref(3)
const total = ref(0)
const brandList = ref([])

const fetchBrands = async () => {
  const res = await reqBrandList(pageNo.value, limit.value)
  if (res.code === 200) {
    brandList.value = res.data.records
    total.value = res.data.total
  }
}

onMounted(fetchBrands)
</script>

4.2 Add/Edit Brand Dialog

Implement a dialog form for adding and editing brands. Include form validation and image upload functionality.


  
    
      
    
    
      
        <img v-if="brandParams.logoUrl" :src="brandParams.logoUrl" class="avatar" />
        <Plus />
      
    
  
  <template #footer>
    Cancel
    Confirm
  </template>

5. Attribute Management Module

5.1 Category Selector

Create a global component for selecting three-level categories. Use cascading logic where selecting a parent category fetches its children.

<template>
  
    
      
        
          
        
      
      
        
          
        
      
      
        
          
        
      
    
  
</template>

<script setup lang="ts">
import { onMounted } from 'vue'
import useCategoryStore from '@/store/modules/category'

const categoryStore = useCategoryStore()

onMounted(() => {
  categoryStore.fetchC1()
})

const handleC1Change = () => {
  categoryStore.c2Id = ''
  categoryStore.c3Id = ''
  categoryStore.c3Arr = []
  categoryStore.fetchC2()
}

const handleC2Change = () => {
  categoryStore.c3Id = ''
  categoryStore.fetchC3()
}
</script>

6. SPU and SKU Management

6.1 SPU Listing

Display a list of Standard Product Units. Include actions to add SKUs, edit the SPU, or delete it.

// API Call for SPU List
import { reqSpuList } from '@/api/product/spu'

const fetchSpuList = async (page = 1) => {
  pageNo.value = page
  const res = await reqSpuList(pageNo.value, pageSize.value, categoryStore.c3Id)
  if (res.code === 200) {
    spuRecords.value = res.data.records
    total.value = res.data.total
  }
}

6.2 SKU Management

Implement the SKU list view with the ability to toggle the "On Sale" status. Use a drawer to display detailed SKU information.

<template>
  
    
    
    
      <template #default="{ row }">
        
          {{ row.isSale === 1 ? 'Off Sale' : 'On Sale' }}
        
        Details
      </template>
    
  
  
  
    
      {{ currentSku.skuName }}
      {{ currentSku.price }}
      {{ currentSku.weight }}g
    
  
</template>

Tags: Vue 3 TypeScript Pinia Element Plus Frontend Architecture

Posted on Fri, 15 May 2026 14:35:16 +0000 by astronaut