# 盘古用户平台 - 应用管理模块前端技术方案 --- | 文档信息 | 内容 | |---------|------| | **文档版本** | V1.0 | | **模块名称** | 应用管理模块 - 前端 | | **编写团队 | pangu | | **创建日期** | 2026-01-31 | | **审核状态** | 待评审 | --- ## 1. 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | Vue | 3.x | 前端框架 | | Element Plus | 2.x | UI组件库 | | Axios | 1.x | HTTP客户端 | | Pinia | 2.x | 状态管理 | | Vite | 5.x | 构建工具 | --- ## 2. 目录结构 ``` pangu-ui/src/ ├── api/ │ └── application.js # 应用管理API接口封装 ├── views/ │ └── application/ │ ├── index.vue # 应用列表页(主页面) │ └── components/ │ ├── AppDialog.vue # 新增/编辑弹窗组件 │ └── SecretDialog.vue # 密钥展示弹窗组件 ├── mock/ │ └── application.js # Mock数据(开发阶段使用) └── router/ └── index.js # 路由配置(添加应用管理路由) ``` --- ## 3. 页面组件设计 ### 3.1 应用列表页 (index.vue) #### 3.1.1 页面功能 - 应用列表展示(分页) - 按应用名称、编码、状态筛选 - 新增应用入口 - 编辑应用入口 - 重置密钥操作 - 删除应用操作 #### 3.1.2 页面布局 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 搜索区域(el-card) │ │ ┌────────────────┐ ┌────────────────┐ ┌──────────┐ ┌────┐ ┌────┐ │ │ │ 应用名称 │ │ 应用编码 │ │ 状态 ▼ │ │搜索│ │重置│ │ │ └────────────────┘ └────────────────┘ └──────────┘ └────┘ └────┘ │ └─────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────┐ │ 操作区域 │ │ [+ 新增] │ ├─────────────────────────────────────────────────────────────────────┤ │ 表格区域(el-table) │ │ ┌───────┬──────────┬─────────────────────┬────────┬────────┬──────┐│ │ │应用名称│ 应用编码 │ 授权接口 │ 状态 │ 创建时间│ 操作 ││ │ ├───────┼──────────┼─────────────────────┼────────┼────────┼──────┤│ │ │AI智慧 │ YY000001 │ [学校][年级][班级] │ ● 正常 │01-01 │ 编辑 ││ │ │平台 │ │ │ │10:00 │ 重置 ││ │ │ │ │ │ │ │ 删除 ││ │ ├───────┼──────────┼─────────────────────┼────────┼────────┼──────┤│ │ │在线课 │ YY000002 │ [学生][会员]+2 │ ● 停用 │01-02 │ 编辑 ││ │ │堂系统 │ │ │ │14:30 │ 重置 ││ │ │ │ │ │ │ │ 删除 ││ │ └───────┴──────────┴─────────────────────┴────────┴────────┴──────┘│ ├─────────────────────────────────────────────────────────────────────┤ │ 分页区域 │ │ 共20条 [<] 1 2 3 ... [>] │ └─────────────────────────────────────────────────────────────────────┘ ``` #### 3.1.3 组件状态 ```javascript // 查询参数 const queryParams = ref({ pageNum: 1, pageSize: 10, appName: '', // 应用名称 appCode: '', // 应用编码 status: '' // 状态:空=全部,0=正常,1=停用 }) // 列表数据 const loading = ref(false) // 加载状态 const tableData = ref([]) // 表格数据 const total = ref(0) // 总记录数 // 子组件引用 const appDialogRef = ref() // 新增/编辑弹窗 const secretDialogRef = ref() // 密钥弹窗 ``` #### 3.1.4 核心方法 | 方法名 | 功能说明 | 调用时机 | |-------|---------|---------| | `getList` | 获取应用列表 | 页面加载、搜索、分页 | | `handleQuery` | 搜索查询 | 点击搜索按钮 | | `resetQuery` | 重置查询 | 点击重置按钮 | | `handleAdd` | 新增应用 | 点击新增按钮 | | `handleEdit` | 编辑应用 | 点击编辑按钮 | | `handleResetSecret` | 重置密钥 | 点击重置密钥按钮 | | `handleDelete` | 删除应用 | 点击删除按钮 | #### 3.1.5 完整代码 ```vue ``` --- ### 3.2 新增/编辑弹窗 (AppDialog.vue) #### 3.2.1 组件功能 - 新增应用表单 - 编辑应用表单 - 接口授权勾选 - 表单验证 #### 3.2.2 表单布局 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 新增应用 / 编辑应用 [X] │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 应用名称* [________________________] (必填,最大100字符) │ │ │ │ 应用编码 [保存后自动生成___________] (只读,系统生成) │ │ │ │ 应用描述 ┌────────────────────────┐ │ │ │ │ (选填,最大500字符) │ │ │ │ │ │ └────────────────────────┘ │ │ │ │ 联系人 [________________________] (选填) │ │ │ │ 联系电话 [________________________] (选填,手机号格式) │ │ │ │ 状态 ◉ 正常 ○ 停用 │ │ │ │ 接口授权 ┌────────────────────────────────────────────┐ │ │ │ [✓] 查询学生信息 [✓] 查询学校信息 │ │ │ │ [✓] 查询年级信息 [ ] 查询班级信息 │ │ │ │ [ ] 查询会员信息 [ ] 查询区域树 │ │ │ └────────────────────────────────────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ [取消] [确定] │ └─────────────────────────────────────────────────────────────────┘ ``` #### 3.2.3 组件状态 ```javascript // 弹窗状态 const visible = ref(false) const submitLoading = ref(false) const isEdit = ref(false) // 表单数据 const form = reactive({ appId: null, appName: '', appCode: '', appDesc: '', contactPerson: '', contactPhone: '', status: '0', apiCodes: [] }) // 接口授权选项 const apiOptions = ref([]) ``` #### 3.2.4 表单验证规则 ```javascript const rules = { appName: [ { required: true, message: '请输入应用名称', trigger: 'blur' }, { max: 100, message: '应用名称不能超过100个字符', trigger: 'blur' } ], contactPhone: [ { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' } ], appDesc: [ { max: 500, message: '应用描述不能超过500个字符', trigger: 'blur' } ] } ``` #### 3.2.5 完整代码 ```vue ``` --- ### 3.3 密钥展示弹窗 (SecretDialog.vue) #### 3.3.1 组件功能 - 展示应用密钥 - 提供复制功能 - 安全提示 #### 3.3.2 弹窗布局 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 应用密钥 [X] │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ⚠️ 请妥善保管密钥,密钥重置后旧密钥将立即失效 │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 应用名称 AI智慧平台 │ │ │ │ 应用编码 YY000001 │ │ │ │ 应用密钥 ┌────────────────────────────────┬──────┐ │ │ │ a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 │ 复制 │ │ │ └────────────────────────────────┴──────┘ │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ [关闭] │ └─────────────────────────────────────────────────────────────────┘ ``` #### 3.3.3 完整代码 ```vue ``` --- ## 4. API接口封装 ### 4.1 接口定义 (api/application.js) ```javascript /** * 应用管理API接口 * @author pangu */ import request from '@/utils/request' /** * 获取应用列表(分页) * @param {Object} params 查询参数 * @param {string} params.appName 应用名称 * @param {string} params.appCode 应用编码 * @param {string} params.status 状态:0正常,1停用 * @param {number} params.pageNum 页码 * @param {number} params.pageSize 每页条数 */ export function listApplication(params) { return request({ url: '/api/application/list', method: 'get', params }) } /** * 获取应用详情 * @param {number} appId 应用ID */ export function getApplication(appId) { return request({ url: `/api/application/${appId}`, method: 'get' }) } /** * 新增应用 * @param {Object} data 应用数据 */ export function addApplication(data) { return request({ url: '/api/application', method: 'post', data }) } /** * 修改应用 * @param {Object} data 应用数据 */ export function updateApplication(data) { return request({ url: '/api/application', method: 'put', data }) } /** * 删除应用 * @param {number} appId 应用ID */ export function deleteApplication(appId) { return request({ url: `/api/application/${appId}`, method: 'delete' }) } /** * 重置应用密钥 * @param {number} appId 应用ID */ export function resetAppSecret(appId) { return request({ url: `/api/application/resetSecret/${appId}`, method: 'put' }) } /** * 获取API接口列表(用于授权选择) */ export function getApiList() { return request({ url: '/api/application/apiList', method: 'get' }) } ``` --- ## 5. Mock数据 (mock/application.js) > 开发阶段使用,后端接口完成后移除 ```javascript /** * 应用管理Mock数据 * @author pangu */ import Mock from 'mockjs' // API接口字典 const apiDict = [ { apiCode: 'STUDENT_LIST', apiName: '查询学生信息', apiPath: '/open/student/list' }, { apiCode: 'SCHOOL_LIST', apiName: '查询学校信息', apiPath: '/open/school/list' }, { apiCode: 'GRADE_LIST', apiName: '查询年级信息', apiPath: '/open/grade/list' }, { apiCode: 'CLASS_LIST', apiName: '查询班级信息', apiPath: '/open/class/list' }, { apiCode: 'MEMBER_LIST', apiName: '查询会员信息', apiPath: '/open/member/list' }, { apiCode: 'REGION_TREE', apiName: '查询区域树', apiPath: '/open/region/tree' } ] // 生成应用编码 let appSeq = 1 const generateAppCode = () => `YY${String(appSeq++).padStart(6, '0')}` // 生成32位密钥 const generateSecret = () => Mock.Random.string('abcdefghijklmnopqrstuvwxyz0123456789', 32) // 模拟应用数据 let applicationList = [ { appId: 1, appCode: 'YY000001', appName: 'AI智慧平台', appSecret: generateSecret(), appDesc: 'AI智慧教育平台', contactPerson: '张经理', contactPhone: '13812345678', status: '0', apis: [ { apiCode: 'SCHOOL_LIST', apiName: '查询学校信息', apiPath: '/open/school/list' }, { apiCode: 'GRADE_LIST', apiName: '查询年级信息', apiPath: '/open/grade/list' }, { apiCode: 'CLASS_LIST', apiName: '查询班级信息', apiPath: '/open/class/list' } ], createTime: '2026-01-01 10:00:00', createBy: 'admin' } ] // 获取应用列表 Mock.mock(/\/api\/application\/list/, 'get', (options) => { const url = new URL('http://localhost' + options.url) const appName = url.searchParams.get('appName') || '' const appCode = url.searchParams.get('appCode') || '' const status = url.searchParams.get('status') || '' const pageNum = parseInt(url.searchParams.get('pageNum')) || 1 const pageSize = parseInt(url.searchParams.get('pageSize')) || 10 let filtered = applicationList.filter(item => { let match = true if (appName) match = match && item.appName.includes(appName) if (appCode) match = match && item.appCode.includes(appCode) if (status) match = match && item.status === status return match }) const total = filtered.length const start = (pageNum - 1) * pageSize const rows = filtered.slice(start, start + pageSize) return { code: 200, msg: '查询成功', total, rows } }) // 获取应用详情 Mock.mock(/\/api\/application\/\d+$/, 'get', (options) => { const appId = parseInt(options.url.match(/\/api\/application\/(\d+)/)[1]) const app = applicationList.find(item => item.appId === appId) if (app) { return { code: 200, msg: '查询成功', data: app } } return { code: 500, msg: '应用不存在' } }) // 新增应用 Mock.mock('/api/application', 'post', (options) => { const data = JSON.parse(options.body) const newApp = { appId: applicationList.length + 1, appCode: generateAppCode(), appSecret: generateSecret(), appName: data.appName, appDesc: data.appDesc, contactPerson: data.contactPerson, contactPhone: data.contactPhone, status: data.status || '0', apis: apiDict.filter(api => data.apiCodes?.includes(api.apiCode)), createTime: Mock.Random.now('yyyy-MM-dd HH:mm:ss'), createBy: 'admin' } applicationList.unshift(newApp) return { code: 200, msg: '新增成功', data: { appCode: newApp.appCode, appSecret: newApp.appSecret } } }) // 修改应用 Mock.mock('/api/application', 'put', (options) => { const data = JSON.parse(options.body) const index = applicationList.findIndex(item => item.appId === data.appId) if (index !== -1) { applicationList[index] = { ...applicationList[index], appName: data.appName, appDesc: data.appDesc, contactPerson: data.contactPerson, contactPhone: data.contactPhone, status: data.status, apis: apiDict.filter(api => data.apiCodes?.includes(api.apiCode)) } return { code: 200, msg: '修改成功' } } return { code: 500, msg: '应用不存在' } }) // 删除应用 Mock.mock(/\/api\/application\/\d+$/, 'delete', (options) => { const appId = parseInt(options.url.match(/\/api\/application\/(\d+)/)[1]) const index = applicationList.findIndex(item => item.appId === appId) if (index !== -1) { applicationList.splice(index, 1) return { code: 200, msg: '删除成功' } } return { code: 500, msg: '应用不存在' } }) // 重置密钥 Mock.mock(/\/api\/application\/resetSecret\/\d+/, 'put', (options) => { const appId = parseInt(options.url.match(/\/api\/application\/resetSecret\/(\d+)/)[1]) const app = applicationList.find(item => item.appId === appId) if (app) { const newSecret = generateSecret() app.appSecret = newSecret return { code: 200, msg: '重置成功', data: { appSecret: newSecret } } } return { code: 500, msg: '应用不存在' } }) // 获取API接口列表 Mock.mock('/api/application/apiList', 'get', () => { return { code: 200, msg: '查询成功', data: apiDict } }) ``` --- ## 6. 路由配置 在 `router/index.js` 中添加路由: ```javascript { path: '/application', component: Layout, children: [ { path: '', name: 'Application', component: () => import('@/views/application/index.vue'), meta: { title: '应用管理', icon: 'app', roles: ['admin'] } } ] } ``` --- ## 7. 开发规范 ### 7.1 命名规范 | 类型 | 规范 | 示例 | |------|------|------| | 组件文件名 | 大驼峰 | `AppDialog.vue` | | 组件名 | 大驼峰 | `AppDialog` | | 变量名 | 小驼峰 | `tableData` | | 常量名 | 大写下划线 | `API_BASE_URL` | | 方法名 | 小驼峰,事件用handle前缀 | `handleQuery` | | CSS类名 | 短横线分隔 | `app-container` | ### 7.2 代码规范 - 使用 `