# WMMS Vue 重构实施计划 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 将 WMMS 前端从 jQuery 迁移到 Vue 3,完成 8 个页面的功能重构 **Architecture:** 前后端分离架构,Vue 3 SPA 使用 Vite 构建,Pinia 状态管理,Element Plus UI 组件库,Vue Router History 模式 **Tech Stack:** Vue 3 (Composition API) + Vite + Pinia + Vue Router 4 + Element Plus + Axios **Spec:** docs/superpowers/specs/2026-09-07-wmms-vue-migration-design.md --- ## Global Constraints - Vue 3.4+ (Composition API) - Vite 5.x - Pinia 2.x - Vue Router 4.x - Element Plus 2.x - Axios 1.x - 开发端口:8081 - 代理 API 到 localhost:8080 --- ## 文件结构 ``` frontend/ ├── src/ │ ├── main.js # 应用入口 │ ├── App.vue # 根组件 │ ├── api/ │ │ ├── index.js # Axios 实例 │ │ ├── auth.js # 认证 API │ │ ├── materials.js # 物料 API │ │ ├── inbound.js # 入库 API │ │ ├── outbound.js # 出库 API │ │ ├── stock.js # 库存 API │ │ ├── application.js # 申请 API │ │ ├── logs.js # 日志 API │ │ └── users.js # 用户 API │ ├── stores/ │ │ ├── auth.js # 认证 Store │ │ ├── materials.js # 物料 Store │ │ ├── stock.js # 库存 Store │ │ ├── inbound.js # 入库 Store │ │ ├── outbound.js # 出库 Store │ │ ├── application.js # 申请 Store │ │ ├── approval.js # 审批 Store │ │ ├── logs.js # 日志 Store │ │ ├── users.js # 用户 Store │ │ └── ui.js # UI 状态 Store │ ├── router/ │ │ └── index.js # 路由配置 + 守卫 │ ├── components/ │ │ ├── layout/ │ │ │ ├── AppHeader.vue # 顶部导航 │ │ │ └── AppSidebar.vue # 侧边菜单 │ │ └── business/ │ │ ├── BaseTable.vue # 通用表格 │ │ ├── BasePagination.vue # 分页组件 │ │ ├── BaseModal.vue # 弹窗组件 │ │ ├── StatusBadge.vue # 状态徽章 │ │ ├── RoleBadge.vue # 角色徽章 │ │ ├── DeleteConfirm.vue # 删除确认 │ │ ├── AlertTip.vue # 提示消息 │ │ └── LoadingOverlay.vue # 加载遮罩 │ └── views/ │ ├── Login.vue # 登录页 │ ├── Materials.vue # 物料管理 │ ├── Inbound.vue # 入库管理 │ ├── Outbound.vue # 出库管理 │ ├── Stock.vue # 库存查询 │ ├── Application.vue # 物料申请 │ ├── Approval.vue # 申请审批 │ ├── Logs.vue # 日志记录 │ └── Users.vue # 用户管理 ├── index.html ├── vite.config.js └── package.json ``` --- ## Day 1: 项目初始化 + API 层 + Pinia Store ### Task 1: 初始化 Vue 项目 **Files:** - Create: `frontend/package.json` - Create: `frontend/vite.config.js` - Create: `frontend/index.html` - Create: `frontend/src/main.js` - Create: `frontend/src/App.vue` **Interfaces:** - Produces: 可运行的 Vue 3 + Vite 项目,端口 8081,代理 /api 到 localhost:8080 - [ ] **Step 1: 创建 package.json** ```json { "name": "wmms-frontend", "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "vue": "^3.4.0", "vue-router": "^4.2.0", "pinia": "^2.1.0", "element-plus": "^2.5.0", "axios": "^1.6.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.0", "vite": "^5.0.0" } } ``` - [ ] **Step 2: 创建 vite.config.js** ```js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true }, '/ws': { target: 'ws://localhost:8080', ws: true } } } }) ``` - [ ] **Step 3: 创建 index.html** ```html 仓库物料管理系统
``` - [ ] **Step 4: 创建 main.js** ```js import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' const app = createApp(App) app.use(createPinia()) app.use(router) app.use(ElementPlus) app.mount('#app') ``` - [ ] **Step 5: 创建 App.vue** ```vue ``` - [ ] **Step 6: 创建基础路由文件 router/index.js** ```js import { createRouter, createWebHistory } from 'vue-router' const routes = [] const router = createRouter({ history: createWebHistory(), routes }) export default router ``` - [ ] **Step 7: 安装依赖并验证项目运行** Run: `cd frontend && npm install && npm run dev` Expected: 访问 localhost:8081 显示空白页面,无报错 --- ### Task 2: 创建 Axios 实例和 API 层 **Files:** - Create: `frontend/src/api/index.js` - Create: `frontend/src/api/auth.js` - Create: `frontend/src/api/materials.js` - Create: `frontend/src/api/inbound.js` - Create: `frontend/src/api/outbound.js` - Create: `frontend/src/api/stock.js` - Create: `frontend/src/api/application.js` - Create: `frontend/src/api/logs.js` - Create: `frontend/src/api/users.js` **Interfaces:** - Consumes: 无 - Produces: Axios 实例,模块化 API 函数,供 Store 调用 - [ ] **Step 1: 创建 api/index.js (Axios 实例)** ```js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 30000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器 request.interceptors.request.use( (config) => { const token = localStorage.getItem('wmms_token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => Promise.reject(error) ) // 响应拦截器 request.interceptors.response.use( (response) => { const res = response.data if (res.code === 0) { return res } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, (error) => { if (error.response?.status === 401) { localStorage.removeItem('wmms_token') localStorage.removeItem('wmms_user') router.push('/login') ElMessage.error('登录已过期,请重新登录') } else { ElMessage.error(error.message || '网络错误') } return Promise.reject(error) } ) export default request ``` - [ ] **Step 2: 创建 api/auth.js** ```js import request from './index' export const authApi = { login(username, password) { return request.post('/auth/login', { username, password }) }, getCurrentUser() { return request.get('/auth/me') } } ``` - [ ] **Step 3: 创建 api/materials.js** ```js import request from './index' export const materialsApi = { getAll(params = {}) { return request.get('/materials', { params }) }, getById(id) { return request.get(`/materials/${id}`) }, create(data) { return request.post('/materials', data) }, update(id, data) { return request.put(`/materials/${id}`, data) }, delete(id) { return request.delete(`/materials/${id}`) }, uploadImage(formData) { return request.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) } } ``` - [ ] **Step 4: 创建 api/inbound.js** ```js import request from './index' export const inboundApi = { getAll(params = {}) { return request.get('/inbound', { params }) }, getById(id) { return request.get(`/inbound/${id}`) }, create(data) { return request.post('/inbound', data) }, update(id, data) { return request.put(`/inbound/${id}`, data) }, delete(id) { return request.delete(`/inbound/${id}`) } } ``` - [ ] **Step 5: 创建 api/outbound.js** ```js import request from './index' export const outboundApi = { getAll(params = {}) { return request.get('/outbound', { params }) }, getById(id) { return request.get(`/outbound/${id}`) }, create(data) { return request.post('/outbound', data) }, update(id, data) { return request.put(`/outbound/${id}`, data) }, delete(id) { return request.delete(`/outbound/${id}`) } } ``` - [ ] **Step 6: 创建 api/stock.js** ```js import request from './index' export const stockApi = { getAll(params = {}) { return request.get('/stock', { params }) }, getWarning(params = {}) { return request.get('/stock/warning', { params }) }, getLogs(materialId, params = {}) { return request.get(`/stock/logs/${materialId}`, { params }) } } ``` - [ ] **Step 7: 创建 api/application.js** ```js import request from './index' export const applicationApi = { create(data) { return request.post('/applications', data) }, getMy(status = '', page = 1, pageSize = 20) { return request.get('/applications/my', { params: { status, page, pageSize } }) }, getAll(status = '', page = 1, pageSize = 20) { return request.get('/applications', { params: { status, page, pageSize } }) }, cancel(id) { return request.post(`/applications/${id}/cancel`, {}) }, approve(id, data) { return request.post(`/applications/${id}/approve`, data) }, reject(id, data) { return request.post(`/applications/${id}/reject`, data) } } ``` - [ ] **Step 8: 创建 api/logs.js** ```js import request from './index' export const logsApi = { getAll(params = {}) { return request.get('/logs', { params }) } } ``` - [ ] **Step 9: 创建 api/users.js** ```js import request from './index' export const usersApi = { getAll(params = {}) { return request.get('/users', { params }) }, create(data) { return request.post('/users', data) }, update(id, data) { return request.put(`/users/${id}`, data) }, delete(id) { return request.delete(`/users/${id}`) } } ``` - [ ] **Step 10: 验证 API 层可正常调用** Run: `cd frontend && npm run dev`,在浏览器控制台测试 API 调用 Expected: 无报错,API 能正常请求到后端 --- ### Task 3: 创建 Pinia Store **Files:** - Create: `frontend/src/stores/auth.js` - Create: `frontend/src/stores/materials.js` - Create: `frontend/src/stores/stock.js` - Create: `frontend/src/stores/inbound.js` - Create: `frontend/src/stores/outbound.js` - Create: `frontend/src/stores/application.js` - Create: `frontend/src/stores/approval.js` - Create: `frontend/src/stores/logs.js` - Create: `frontend/src/stores/users.js` - Create: `frontend/src/stores/ui.js` **Interfaces:** - Consumes: API 层函数 - Produces: 各模块的响应式状态和 actions - [ ] **Step 1: 创建 stores/auth.js** ```js import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { authApi } from '../api/auth' export const useAuthStore = defineStore('auth', () => { const token = ref(localStorage.getItem('wmms_token') || '') const user = ref(JSON.parse(localStorage.getItem('wmms_user') || 'null')) const isLoggedIn = computed(() => !!token.value) const isAdmin = computed(() => user.value?.role === 'admin') function saveAuth(newToken, newUser) { token.value = newToken user.value = newUser localStorage.setItem('wmms_token', newToken) localStorage.setItem('wmms_user', JSON.stringify(newUser)) } function clearAuth() { token.value = '' user.value = null localStorage.removeItem('wmms_token') localStorage.removeItem('wmms_user') } async function login(username, password) { const res = await authApi.login(username, password) saveAuth(res.data.token, res.data.user) return res.data } async function fetchCurrentUser() { if (!token.value) return null try { const res = await authApi.getCurrentUser() user.value = res.data return res.data } catch { clearAuth() return null } } function logout() { clearAuth() } return { token, user, isLoggedIn, isAdmin, saveAuth, clearAuth, login, fetchCurrentUser, logout } }) ``` - [ ] **Step 2: 创建 stores/materials.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { materialsApi } from '../api/materials' export const useMaterialsStore = defineStore('materials', () => { const materials = ref([]) const loading = ref(false) const pagination = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) const categoryMap = { 'A01': '标准件', 'A02': '成品件', 'A03': '简易加工件', 'A04': '精密加工件', 'A05': '钣金件', 'A06': '塑胶件', 'A07': '其他', 'B01': '成品件', 'B02': '简易加工件', 'B03': 'PCB硬板', 'B04': 'FPC软板', 'B05': '线束', 'B06': '组装组件', 'B07': '其他', 'C01': '连接耗材', 'C02': '密封耗材', 'C03': '其他', 'D01': '自研程序固件', 'D02': '外购软件授权', 'D03': '授权介质', 'D04': '其他', 'E01': '成品', 'E02': '半成品', 'F01': '资料文档', 'F02': '标识标牌', 'F03': '包装物料', 'F04': '随机工具配件', 'F05': '其他' } function getCategoryDisplay(category) { if (!category) return '-' const name = categoryMap[category] || '' return name ? `${category} - ${name}` : category } async function fetchMaterials(page = 1, search = '') { loading.value = true try { const pageSize = pagination.value.pageSize const offset = (page - 1) * pageSize const res = await materialsApi.getAll({ search, limit: pageSize, offset }) materials.value = res.data?.items || res.data || [] pagination.value.total = res.data?.total || materials.value.length pagination.value.currentPage = page pagination.value.totalPages = Math.ceil(pagination.value.total / pageSize) || 1 } finally { loading.value = false } } async function createMaterial(data) { const res = await materialsApi.create(data) await fetchMaterials(1) return res } async function updateMaterial(id, data) { const res = await materialsApi.update(id, data) await fetchMaterials(pagination.value.currentPage) return res } async function deleteMaterial(id) { const res = await materialsApi.delete(id) await fetchMaterials(pagination.value.currentPage) return res } return { materials, loading, pagination, categoryMap, getCategoryDisplay, fetchMaterials, createMaterial, updateMaterial, deleteMaterial } }) ``` - [ ] **Step 3: 创建 stores/stock.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { stockApi } from '../api/stock' export const useStockStore = defineStore('stock', () => { const stockList = ref([]) const warningList = ref([]) const logs = ref([]) const loading = ref(false) const stockPage = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) const warningPage = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) const logsPage = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchStock(page = 1) { loading.value = true try { const pageSize = stockPage.value.pageSize const offset = (page - 1) * pageSize const res = await stockApi.getAll({ limit: pageSize, offset }) stockList.value = res.data?.items || res.data || [] stockPage.value.total = res.data?.total || stockList.value.length stockPage.value.currentPage = page } finally { loading.value = false } } async function fetchStockWarning(page = 1) { loading.value = true try { const pageSize = warningPage.value.pageSize const offset = (page - 1) * pageSize const res = await stockApi.getWarning({ limit: pageSize, offset }) warningList.value = res.data?.items || res.data || [] warningPage.value.total = res.data?.total || warningList.value.length warningPage.value.currentPage = page } finally { loading.value = false } } async function fetchStockLogs(materialId, page = 1) { loading.value = true try { const pageSize = logsPage.value.pageSize const offset = (page - 1) * pageSize const res = await stockApi.getLogs(materialId, { limit: pageSize, offset }) logs.value = res.data?.items || res.data || [] logsPage.value.total = res.data?.total || logs.value.length logsPage.value.currentPage = page } finally { loading.value = false } } return { stockList, warningList, logs, loading, stockPage, warningPage, logsPage, fetchStock, fetchStockWarning, fetchStockLogs } }) ``` - [ ] **Step 4: 创建 stores/inbound.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { inboundApi } from '../api/inbound' export const useInboundStore = defineStore('inbound', () => { const inboundList = ref([]) const loading = ref(false) const page = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchInboundList(pageNum = 1) { loading.value = true try { const pageSize = page.value.pageSize const offset = (pageNum - 1) * pageSize const res = await inboundApi.getAll({ limit: pageSize, offset }) inboundList.value = res.data?.items || res.data || [] page.value.total = res.data?.total || inboundList.value.length page.value.currentPage = pageNum page.value.totalPages = Math.ceil(page.value.total / pageSize) || 1 } finally { loading.value = false } } async function createInbound(data) { const res = await inboundApi.create(data) await fetchInboundList(1) return res } async function updateInbound(id, data) { const res = await inboundApi.update(id, data) await fetchInboundList(page.value.currentPage) return res } async function deleteInbound(id) { const res = await inboundApi.delete(id) await fetchInboundList(page.value.currentPage) return res } return { inboundList, loading, page, fetchInboundList, createInbound, updateInbound, deleteInbound } }) ``` - [ ] **Step 5: 创建 stores/outbound.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { outboundApi } from '../api/outbound' export const useOutboundStore = defineStore('outbound', () => { const outboundList = ref([]) const loading = ref(false) const page = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchOutboundList(pageNum = 1) { loading.value = true try { const pageSize = page.value.pageSize const offset = (pageNum - 1) * pageSize const res = await outboundApi.getAll({ limit: pageSize, offset }) outboundList.value = res.data?.items || res.data || [] page.value.total = res.data?.total || outboundList.value.length page.value.currentPage = pageNum page.value.totalPages = Math.ceil(page.value.total / pageSize) || 1 } finally { loading.value = false } } async function createOutbound(data) { const res = await outboundApi.create(data) await fetchOutboundList(1) return res } async function updateOutbound(id, data) { const res = await outboundApi.update(id, data) await fetchOutboundList(page.value.currentPage) return res } async function deleteOutbound(id) { const res = await outboundApi.delete(id) await fetchOutboundList(page.value.currentPage) return res } return { outboundList, loading, page, fetchOutboundList, createOutbound, updateOutbound, deleteOutbound } }) ``` - [ ] **Step 6: 创建 stores/application.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { applicationApi } from '../api/application' export const useApplicationStore = defineStore('application', () => { const myApplications = ref([]) const loading = ref(false) const myPage = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchMyApplications(status = '', pageNum = 1) { loading.value = true try { const res = await applicationApi.getMy(status, pageNum, myPage.value.pageSize) myApplications.value = res.data?.items || res.data || [] myPage.value.total = res.data?.total || myApplications.value.length myPage.value.currentPage = pageNum myPage.value.totalPages = Math.ceil(myPage.value.total / myPage.value.pageSize) || 1 } finally { loading.value = false } } async function createApplication(data) { const res = await applicationApi.create(data) await fetchMyApplications('', 1) return res } async function cancelApplication(id) { const res = await applicationApi.cancel(id) await fetchMyApplications('', myPage.value.currentPage) return res } return { myApplications, loading, myPage, fetchMyApplications, createApplication, cancelApplication } }) ``` - [ ] **Step 7: 创建 stores/approval.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { applicationApi } from '../api/application' export const useApprovalStore = defineStore('approval', () => { const applications = ref([]) const loading = ref(false) const page = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchPendingApplications(status = '', pageNum = 1) { loading.value = true try { const res = await applicationApi.getAll(status, pageNum, page.value.pageSize) applications.value = res.data?.items || res.data || [] page.value.total = res.data?.total || applications.value.length page.value.currentPage = pageNum page.value.totalPages = Math.ceil(page.value.total / page.value.pageSize) || 1 } finally { loading.value = false } } async function approveApplication(id, data = {}) { const res = await applicationApi.approve(id, data) await fetchPendingApplications('', page.value.currentPage) return res } async function rejectApplication(id, data = {}) { const res = await applicationApi.reject(id, data) await fetchPendingApplications('', page.value.currentPage) return res } return { applications, loading, page, fetchPendingApplications, approveApplication, rejectApplication } }) ``` - [ ] **Step 8: 创建 stores/logs.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { logsApi } from '../api/logs' export const useLogsStore = defineStore('logs', () => { const logs = ref([]) const loading = ref(false) const page = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) const filters = ref({ operationType: '', startDate: '', endDate: '' }) async function fetchLogs(pageNum = 1) { loading.value = true try { const pageSize = page.value.pageSize const offset = (pageNum - 1) * pageSize const params = { limit: pageSize, offset, ...filters.value } const res = await logsApi.getAll(params) logs.value = res.data?.items || res.data || [] page.value.total = res.data?.total || logs.value.length page.value.currentPage = pageNum page.value.totalPages = Math.ceil(page.value.total / pageSize) || 1 } finally { loading.value = false } } function setFilters(newFilters) { filters.value = { ...filters.value, ...newFilters } } function resetFilters() { filters.value = { operationType: '', startDate: '', endDate: '' } } return { logs, loading, page, filters, fetchLogs, setFilters, resetFilters } }) ``` - [ ] **Step 9: 创建 stores/users.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' import { usersApi } from '../api/users' export const useUsersStore = defineStore('users', () => { const users = ref([]) const loading = ref(false) const page = ref({ currentPage: 1, pageSize: 20, total: 0, totalPages: 0 }) async function fetchUsers(pageNum = 1) { loading.value = true try { const pageSize = page.value.pageSize const offset = (pageNum - 1) * pageSize const res = await usersApi.getAll({ limit: pageSize, offset }) users.value = res.data?.items || res.data || [] page.value.total = res.data?.total || users.value.length page.value.currentPage = pageNum page.value.totalPages = Math.ceil(page.value.total / pageSize) || 1 } finally { loading.value = false } } async function createUser(data) { const res = await usersApi.create(data) await fetchUsers(1) return res } async function updateUser(id, data) { const res = await usersApi.update(id, data) await fetchUsers(page.value.currentPage) return res } async function deleteUser(id) { const res = await usersApi.delete(id) await fetchUsers(page.value.currentPage) return res } return { users, loading, page, fetchUsers, createUser, updateUser, deleteUser } }) ``` - [ ] **Step 10: 创建 stores/ui.js** ```js import { defineStore } from 'pinia' import { ref } from 'vue' export const useUiStore = defineStore('ui', () => { const loading = ref(false) const loadingText = ref('') const sidebarCollapsed = ref(false) function setLoading(visible, text = '') { loading.value = visible loadingText.value = text } function toggleSidebar() { sidebarCollapsed.value = !sidebarCollapsed.value } return { loading, loadingText, sidebarCollapsed, setLoading, toggleSidebar } }) ``` - [ ] **Step 11: 验证 Store 可正常工作** Run: 在浏览器控制台测试各 Store 的 actions 是否正常 Expected: 无报错,状态正确更新 --- ### Task 4: 创建布局组件和路由守卫 **Files:** - Create: `frontend/src/components/layout/AppHeader.vue` - Create: `frontend/src/components/layout/AppSidebar.vue` - Modify: `frontend/src/router/index.js` - Modify: `frontend/src/App.vue` **Interfaces:** - Consumes: auth Store - Produces: 带路由守卫的完整路由配置,布局组件 - [ ] **Step 1: 创建 AppHeader.vue** ```vue ``` - [ ] **Step 2: 创建 AppSidebar.vue** ```vue ``` - [ ] **Step 3: 更新 router/index.js 添加路由和守卫** ```js import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '../stores/auth' const routes = [ { path: '/login', name: 'Login', component: () => import('../views/Login.vue'), meta: { guestOnly: true } }, { path: '/', redirect: '/stock' }, { path: '/materials', name: 'Materials', component: () => import('../views/Materials.vue'), meta: { requiresAuth: true, adminOnly: true } }, { path: '/inbound', name: 'Inbound', component: () => import('../views/Inbound.vue'), meta: { requiresAuth: true, adminOnly: true } }, { path: '/outbound', name: 'Outbound', component: () => import('../views/Outbound.vue'), meta: { requiresAuth: true, adminOnly: true } }, { path: '/stock', name: 'Stock', component: () => import('../views/Stock.vue'), meta: { requiresAuth: true } }, { path: '/application', name: 'Application', component: () => import('../views/Application.vue'), meta: { requiresAuth: true } }, { path: '/approval', name: 'Approval', component: () => import('../views/Approval.vue'), meta: { requiresAuth: true, adminOnly: true } }, { path: '/logs', name: 'Logs', component: () => import('../views/Logs.vue'), meta: { requiresAuth: true, adminOnly: true } }, { path: '/users', name: 'Users', component: () => import('../views/Users.vue'), meta: { requiresAuth: true, adminOnly: true } } ] const router = createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) => { const authStore = useAuthStore() if (to.meta.guestOnly && authStore.isLoggedIn) { return next('/stock') } if (to.meta.requiresAuth && !authStore.isLoggedIn) { return next('/login') } if (to.meta.adminOnly && !authStore.isAdmin) { return next('/stock') } next() }) export default router ``` - [ ] **Step 4: 更新 App.vue 添加布局** ```vue ``` - [ ] **Step 5: 创建 Login.vue 基础版本** ```vue ``` - [ ] **Step 6: 验证 Day 1 完成的功能** Run: `cd frontend && npm run dev` Expected: - 访问 localhost:8081 自动跳转 /login - 登录页显示正常 - 登录成功后跳转 /stock - 顶部导航和侧边栏显示正常 - 侧边栏根据 admin 角色显示菜单项 --- ## Day 2: 基础组件 + 登录/物料/库存页面 ### Task 5: 创建基础业务组件 **Files:** - Create: `frontend/src/components/business/StatusBadge.vue` - Create: `frontend/src/components/business/RoleBadge.vue` - Create: `frontend/src/components/business/DeleteConfirm.vue` - Create: `frontend/src/components/business/AlertTip.vue` - Create: `frontend/src/components/business/LoadingOverlay.vue` - Create: `frontend/src/components/business/BasePagination.vue` - Create: `frontend/src/components/business/BaseTable.vue` **Interfaces:** - Consumes: 无 - Produces: 可复用的基础业务组件 - [ ] **Step 1: 创建 StatusBadge.vue** ```vue ``` - [ ] **Step 2: 创建 RoleBadge.vue** ```vue ``` - [ ] **Step 3: 创建 DeleteConfirm.vue** ```vue ``` - [ ] **Step 4: 创建 AlertTip.vue** ```vue ``` - [ ] **Step 5: 创建 LoadingOverlay.vue** ```vue ``` - [ ] **Step 6: 创建 BasePagination.vue** ```vue ``` - [ ] **Step 7: 创建 BaseTable.vue** ```vue ``` - [ ] **Step 8: 验证基础组件可用** Run: 在 Vue 组件中引入测试 Expected: 组件正常渲染,无报错 --- ### Task 6: 创建 Materials 页面 **Files:** - Create: `frontend/src/views/Materials.vue` **Interfaces:** - Consumes: materials Store, materials API - Produces: 物料管理完整页面 - [ ] **Step 1: 创建 Materials.vue** ```vue ``` - [ ] **Step 2: 验证 Materials 页面** Run: 访问 /materials Expected: 物料列表显示正常,分页正常,搜索正常,新增/编辑/删除功能正常 --- ### Task 7: 创建 Stock 页面 **Files:** - Create: `frontend/src/views/Stock.vue` **Interfaces:** - Consumes: stock Store - Produces: 库存查询、预警、日志页面 - [ ] **Step 1: 创建 Stock.vue** ```vue ``` - [ ] **Step 2: 验证 Stock 页面** Run: 访问 /stock Expected: 三个 Tab 页切换正常,库存列表、预警、日志显示正常 --- ## Day 3: 出入库/申请审批/日志用户/权限 ### Task 8: 创建 Inbound/Outbound 页面 **Files:** - Create: `frontend/src/views/Inbound.vue` - Create: `frontend/src/views/Outbound.vue` **Interfaces:** - Consumes: inbound Store, outbound Store, materials Store - Produces: 入库/出库管理完整页面 - [ ] **Step 1: 创建 Inbound.vue** ```vue ``` - [ ] **Step 2: 创建 Outbound.vue(类似 Inbound.vue 结构)** ```vue ``` - [ ] **Step 3: 验证 Inbound/Outbound 页面** Run: 访问 /inbound 和 /outbound Expected: 入库/出库单列表显示正常,新增功能正常 --- ### Task 9: 创建 Application/Approval/Logs/Users 页面 **Files:** - Create: `frontend/src/views/Application.vue` - Create: `frontend/src/views/Approval.vue` - Create: `frontend/src/views/Logs.vue` - Create: `frontend/src/views/Users.vue` - [ ] **Step 1: 创建 Application.vue(物料申请表单 + 我的申请列表)** ```vue ``` - [ ] **Step 2: 创建 Approval.vue(申请审批列表)** ```vue ``` - [ ] **Step 3: 创建 Logs.vue(日志记录)** ```vue ``` - [ ] **Step 4: 创建 Users.vue(用户管理)** ```vue ``` - [ ] **Step 5: 验证所有页面** Run: 访问 /application, /approval, /logs, /users Expected: 所有页面功能正常,权限控制生效 --- ## 自检清单 ### Spec 覆盖检查 - [ ] 8 个页面全部实现(Login, Materials, Inbound, Outbound, Stock, Application, Approval, Logs, Users) - [ ] 路由守卫实现(authRequired, adminOnly, guestOnly) - [ ] Element Plus UI 组件库使用 - [ ] Pinia 状态管理 - [ ] Axios API 层封装 - [ ] History 路由模式 ### 占位符检查 - [ ] 无 "TBD"、"TODO" 等占位符 - [ ] 所有代码块完整可执行 ### 类型一致性检查 - [ ] Store 的 state 和 actions 命名一致 - [ ] API 函数命名与后端接口对应 - [ ] 组件 props/emits 定义清晰 --- ## 执行方式选择 **Plan complete and saved to `docs/superpowers/plans/2026-09-07-wmms-vue-migration-plan.md`.** Two execution options: **1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration **2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints **Which approach?**