# Design Document: Admin Frontend Migration ## Overview 本设计文档描述了 HoneyBox 后台管理系统前端业务模块的迁移方案。基于现有的 Vue 3 + Element Plus + TypeScript 技术栈,在 `admin-web` 项目中添加业务模块页面,对接已完成的后端 API(HoneyBox.Admin.Business)。 ## Architecture ### 整体架构 ``` admin-web/ ├── src/ │ ├── api/ # API 服务层 │ │ ├── auth.ts # 认证 API (已有) │ │ ├── menu.ts # 菜单 API (已有) │ │ ├── role.ts # 角色 API (已有) │ │ ├── dashboard.ts # 仪表盘 API (新增) │ │ ├── user.ts # 用户管理 API (新增) │ │ ├── vip.ts # VIP管理 API (新增) │ │ ├── goods.ts # 商品管理 API (新增) │ │ ├── goodsType.ts # 商品类型 API (新增) │ │ ├── order.ts # 订单管理 API (新增) │ │ ├── finance.ts # 财务管理 API (新增) │ │ └── config.ts # 系统配置 API (新增) │ │ │ ├── views/ │ │ ├── dashboard/ # 仪表盘 (升级) │ │ ├── system/ # 系统管理 (已有) │ │ └── business/ # 业务模块 (新增) │ │ ├── user/ # 用户管理 │ │ ├── vip/ # VIP管理 │ │ ├── goods/ # 商品管理 │ │ ├── goodsType/ # 商品类型 │ │ ├── order/ # 订单管理 │ │ ├── finance/ # 财务管理 │ │ └── config/ # 系统配置 │ │ │ ├── router/ # 路由配置 │ ├── store/ # 状态管理 │ └── utils/ # 工具函数 ``` ### 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | Vue.js | 3.5.x | 前端框架 | | Element Plus | 2.9.x | UI 组件库 | | TypeScript | 5.6.x | 类型系统 | | Pinia | 3.0.x | 状态管理 | | Vue Router | 4.5.x | 路由管理 | | Axios | 1.7.x | HTTP 请求 | | Vite | 6.0.x | 构建工具 | ## Components and Interfaces ### API 服务层接口 #### Dashboard API (`src/api/dashboard.ts`) ```typescript interface DashboardOverview { todayUsers: number; todayOrders: number; todayAmount: number; totalUsers: number; totalOrders: number; totalAmount: number; } interface AdAccount { id: number; imgUrl: string; linkUrl?: string; sort: number; status: number; createTime: string; } // API Functions export function getDashboardOverview(): Promise>; export function getAdAccounts(): Promise>; export function createAdAccount(data: { imgUrl: string; linkUrl?: string }): Promise>; export function deleteAdAccount(id: number): Promise>; ``` #### User API (`src/api/user.ts`) ```typescript interface UserListRequest { page: number; pageSize: number; userId?: number; nickname?: string; mobile?: string; status?: number; } interface UserInfo { id: number; nickname: string; avatar: string; mobile: string; money: number; integral: number; diamond: number; vipLevel: number; status: number; isTest: number; createTime: string; } interface UserDetail extends UserInfo { openid: string; inviteCode: string; parentId: number; totalConsume: number; totalWin: number; totalRecovery: number; } // API Functions export function getUserList(params: UserListRequest): Promise>>; export function getUserDetail(id: number): Promise>; export function changeUserMoney(id: number, data: { type: string; amount: number; remark: string }): Promise>; export function setUserStatus(id: number, status: number): Promise>; export function setTestAccount(id: number, isTest: number): Promise>; export function clearMobile(id: number): Promise>; export function clearWeChat(id: number): Promise>; export function giftCoupon(id: number, data: { couponId: number; count: number }): Promise>; export function giftCard(id: number, data: { goodsId: number; prizeId: number; count: number }): Promise>; export function getUserProfitLoss(id: number, startDate?: string, endDate?: string): Promise>; export function getSubordinateUsers(id: number, page: number, pageSize: number): Promise>>; ``` #### Goods API (`src/api/goods.ts`) ```typescript interface GoodsListRequest { page: number; pageSize: number; name?: string; typeId?: number; status?: number; } interface GoodsInfo { id: number; name: string; typeId: number; typeName: string; price: number; image: string; status: number; sort: number; createTime: string; } interface GoodsDetail extends GoodsInfo { description: string; images: string[]; rules: string; prizes: PrizeInfo[]; } interface PrizeInfo { id: number; name: string; level: number; levelName: string; stock: number; probability: number; image: string; price: number; } // API Functions export function getGoodsList(params: GoodsListRequest): Promise>>; export function getGoodsDetail(id: number): Promise>; export function createGoods(data: GoodsCreateRequest): Promise>; export function updateGoods(id: number, data: GoodsUpdateRequest): Promise>; export function deleteGoods(id: number): Promise>; export function setGoodsStatus(id: number, status: number): Promise>; export function getPrizes(goodsId: number): Promise>; export function addPrize(goodsId: number, data: PrizeCreateRequest): Promise>; export function updatePrize(id: number, data: PrizeUpdateRequest): Promise>; export function deletePrize(id: number): Promise>; ``` #### Order API (`src/api/order.ts`) ```typescript interface OrderListRequest { page: number; pageSize: number; orderNo?: string; userId?: number; status?: number; startDate?: string; endDate?: string; } interface OrderInfo { id: number; orderNo: string; userId: number; nickname: string; goodsName: string; amount: number; status: number; statusText: string; createTime: string; } interface ShippingOrder { id: number; orderNo: string; userId: number; nickname: string; address: string; mobile: string; status: number; courierName?: string; courierNumber?: string; createTime: string; } // API Functions export function getOrderList(params: OrderListRequest): Promise>>; export function getOrderDetail(id: number): Promise>; export function getStuckOrders(params: OrderListRequest): Promise>>; export function getRecoveryOrders(params: OrderListRequest): Promise>>; export function getShippingOrders(params: ShippingOrderListRequest): Promise>>; export function shipOrder(id: number, data: { courierName: string; courierNumber: string }): Promise>; export function cancelShippingOrder(id: number): Promise>; export function exportOrders(params: OrderExportRequest): Promise; ``` #### Finance API (`src/api/finance.ts`) ```typescript interface FinanceRecord { id: number; userId: number; nickname: string; type: string; amount: number; balance: number; remark: string; createTime: string; } interface RankingItem { rank: number; userId: number; nickname: string; avatar: string; totalAmount: number; } // API Functions export function getConsumptionRanking(params: { page: number; pageSize: number; startDate?: string; endDate?: string }): Promise>>; export function getMoneyRecords(params: FinanceListRequest): Promise>>; export function getIntegralRecords(params: FinanceListRequest): Promise>>; export function getDiamondRecords(params: FinanceListRequest): Promise>>; export function getRechargeRecords(params: FinanceListRequest): Promise>>; ``` #### Config API (`src/api/config.ts`) ```typescript interface ConfigGroup { group: string; name: string; items: ConfigItem[]; } interface ConfigItem { key: string; name: string; value: string; type: 'text' | 'number' | 'switch' | 'image' | 'textarea'; description?: string; } // API Functions export function getConfigGroups(): Promise>; export function getConfigByGroup(group: string): Promise>; export function updateConfig(group: string, items: { key: string; value: string }[]): Promise>; ``` ### 页面组件结构 #### 用户管理页面 (`src/views/business/user/index.vue`) ```vue ``` ## Data Models ### 通用响应格式 ```typescript interface ApiResponse { code: number; message: string; data: T; } interface PagedResult { list: T[]; total: number; page: number; pageSize: number; } ``` ### 业务数据模型 详见上述 Components and Interfaces 部分的接口定义。 ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: 列表分页数据一致性 *For any* 列表页面(用户、商品、订单等),当请求分页数据时,返回的列表长度应小于等于请求的 pageSize,且 total 应大于等于列表长度。 **Validates: Requirements 2.1, 4.1, 6.1** ### Property 2: 动态菜单权限过滤 *For any* 菜单数据和用户权限配置,生成的导航菜单应只包含用户有权限访问的菜单项,无权限的菜单项不应出现在导航中。 **Validates: Requirements 9.2, 9.4** ### Property 3: 请求拦截器Token注入 *For any* 需要认证的 API 请求,请求头中应包含有效的 Authorization Token,且 Token 格式应为 "Bearer {token}"。 **Validates: Requirements 10.3, 10.4** ### Property 4: 配置项类型渲染 *For any* 配置项数据,根据配置项的 type 字段,应渲染对应类型的输入组件(text 对应输入框,number 对应数字输入框,switch 对应开关,image 对应图片上传)。 **Validates: Requirements 8.4** ### Property 5: 错误响应处理 *For any* API 响应,当 code 不为 0 时,应显示错误提示信息,且错误信息应来自响应的 message 字段。 **Validates: Requirements 1.4, 10.3** ## Error Handling ### API 错误处理 ```typescript // src/utils/request.ts import axios from 'axios' import { ElMessage } from 'element-plus' import { getToken, removeToken } from './auth' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 30000 }) // 请求拦截器 request.interceptors.request.use( config => { const token = getToken() 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) { ElMessage.error(res.message || '请求失败') // Token 过期 if (res.code === 40001) { removeToken() router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { ElMessage.error(error.message || '网络错误') return Promise.reject(error) } ) export default request ``` ### 表单验证错误 ```typescript // 使用 Element Plus 表单验证 const rules = { amount: [ { required: true, message: '请输入金额', trigger: 'blur' }, { type: 'number', min: 0.01, message: '金额必须大于0', trigger: 'blur' } ], remark: [ { required: true, message: '请输入备注', trigger: 'blur' } ] } ``` ## Testing Strategy ### 单元测试 使用 Vitest 进行单元测试,测试以下内容: 1. **API 服务层测试** - 验证 API 函数正确调用 axios 2. **工具函数测试** - 验证 Token 管理、请求拦截器等 3. **组件测试** - 验证页面组件渲染和交互 ### 属性测试 使用 fast-check 进行属性测试,验证以下属性: 1. **Property 1** - 分页数据一致性 2. **Property 2** - 菜单权限过滤 3. **Property 3** - Token 注入 4. **Property 4** - 配置项类型渲染 5. **Property 5** - 错误响应处理 ### 测试配置 ```typescript // vitest.config.ts import { defineConfig } from 'vitest/config' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'jsdom', globals: true, coverage: { reporter: ['text', 'json', 'html'] } } }) ``` ### 测试示例 ```typescript // src/api/__tests__/user.test.ts import { describe, it, expect, vi } from 'vitest' import fc from 'fast-check' import { getUserList } from '../user' describe('User API', () => { // Property 1: 分页数据一致性 it('should return list length <= pageSize', async () => { await fc.assert( fc.asyncProperty( fc.integer({ min: 1, max: 100 }), fc.integer({ min: 1, max: 50 }), async (page, pageSize) => { const result = await getUserList({ page, pageSize }) expect(result.data.list.length).toBeLessThanOrEqual(pageSize) expect(result.data.total).toBeGreaterThanOrEqual(result.data.list.length) } ), { numRuns: 100 } ) }) }) ```