盘古用户平台 - 应用管理模块前端技术方案
| 文档信息 |
内容 |
| 文档版本 |
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 组件状态
// 查询参数
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 完整代码
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-card shadow="never" class="search-wrapper">
<el-form :model="queryParams" :inline="true">
<el-form-item label="应用名称">
<el-input
v-model="queryParams.appName"
placeholder="请输入应用名称"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="应用编码">
<el-input
v-model="queryParams.appCode"
placeholder="请输入应用编码"
clearable
style="width: 150px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width: 100px">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 表格区域 -->
<el-card shadow="never" style="margin-top: 12px">
<el-row :gutter="10" style="margin-bottom: 12px">
<el-col :span="1.5">
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
</el-col>
</el-row>
<el-table
v-loading="loading"
:data="tableData"
border
stripe
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column prop="appName" label="应用名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="appCode" label="应用编码" width="120" />
<el-table-column prop="apis" label="授权接口" min-width="250">
<template #default="{ row }">
<template v-if="row.apis?.length">
<el-tag
v-for="api in row.apis.slice(0, 3)"
:key="api.apiCode"
size="small"
style="margin-right: 4px; margin-bottom: 2px"
>
{{ api.apiName }}
</el-tag>
<el-tag v-if="row.apis.length > 3" size="small" type="info">
+{{ row.apis.length - 3 }}
</el-tag>
</template>
<span v-else style="color: #909399">未授权</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
{{ row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="createBy" label="创建人" width="100" />
<el-table-column label="操作" width="200" fixed="right" align="center">
<template #default="{ row }">
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
<el-button type="warning" link :icon="Key" @click="handleResetSecret(row)">重置密钥</el-button>
<el-popconfirm
title="确定要删除该应用吗?"
confirm-button-text="确定"
cancel-button-text="取消"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button type="danger" link :icon="Delete">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
style="margin-top: 16px; justify-content: flex-end"
@size-change="getList"
@current-change="getList"
/>
</el-card>
<!-- 弹窗组件 -->
<AppDialog ref="appDialogRef" @success="getList" />
<SecretDialog ref="secretDialogRef" />
</div>
</template>
<script setup>
/**
* 应用管理列表页
* @author pangu
*/
import { listApplication, deleteApplication, resetAppSecret } from '@/api/application'
import { Delete, Edit, Key, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, ref } from 'vue'
import AppDialog from './components/AppDialog.vue'
import SecretDialog from './components/SecretDialog.vue'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
const queryParams = ref({
pageNum: 1,
pageSize: 10,
appName: '',
appCode: '',
status: ''
})
const appDialogRef = ref()
const secretDialogRef = ref()
/**
* 获取应用列表
*/
const getList = async () => {
loading.value = true
try {
const res = await listApplication(queryParams.value)
if (res.code === 200) {
tableData.value = res.rows
total.value = res.total
}
} finally {
loading.value = false
}
}
/**
* 搜索查询
*/
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
/**
* 重置查询条件
*/
const resetQuery = () => {
queryParams.value = {
pageNum: 1,
pageSize: 10,
appName: '',
appCode: '',
status: ''
}
getList()
}
/**
* 新增应用
*/
const handleAdd = () => {
appDialogRef.value?.open()
}
/**
* 编辑应用
*/
const handleEdit = (row) => {
appDialogRef.value?.open(row)
}
/**
* 重置密钥
*/
const handleResetSecret = (row) => {
ElMessageBox.confirm(
`确定要重置应用"${row.appName}"的密钥吗?重置后旧密钥将立即失效。`,
'重置密钥',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(async () => {
const res = await resetAppSecret(row.appId)
if (res.code === 200) {
ElMessage.success('密钥重置成功')
// 打开密钥展示弹窗
secretDialogRef.value?.open({
appName: row.appName,
appCode: row.appCode,
appSecret: res.data.appSecret
})
}
}).catch(() => {})
}
/**
* 删除应用
*/
const handleDelete = async (row) => {
const res = await deleteApplication(row.appId)
if (res.code === 200) {
ElMessage.success('删除成功')
getList()
}
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container {
padding: 16px;
}
.search-wrapper {
margin-bottom: 0;
}
</style>
3.2 新增/编辑弹窗 (AppDialog.vue)
3.2.1 组件功能
- 新增应用表单
- 编辑应用表单
- 接口授权勾选
- 表单验证
3.2.2 表单布局
┌─────────────────────────────────────────────────────────────────┐
│ 新增应用 / 编辑应用 [X] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用名称* [________________________] (必填,最大100字符) │
│ │
│ 应用编码 [保存后自动生成___________] (只读,系统生成) │
│ │
│ 应用描述 ┌────────────────────────┐ │
│ │ │ (选填,最大500字符) │
│ │ │ │
│ └────────────────────────┘ │
│ │
│ 联系人 [________________________] (选填) │
│ │
│ 联系电话 [________________________] (选填,手机号格式) │
│ │
│ 状态 ◉ 正常 ○ 停用 │
│ │
│ 接口授权 ┌────────────────────────────────────────────┐ │
│ │ [✓] 查询学生信息 [✓] 查询学校信息 │ │
│ │ [✓] 查询年级信息 [ ] 查询班级信息 │ │
│ │ [ ] 查询会员信息 [ ] 查询区域树 │ │
│ └────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [取消] [确定] │
└─────────────────────────────────────────────────────────────────┘
3.2.3 组件状态
// 弹窗状态
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 表单验证规则
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 完整代码
<template>
<el-dialog
v-model="visible"
:title="dialogTitle"
width="650px"
:close-on-click-modal="false"
destroy-on-close
@closed="handleClosed"
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="100px"
>
<el-form-item label="应用名称" prop="appName">
<el-input
v-model="form.appName"
placeholder="请输入应用名称"
maxlength="100"
show-word-limit
/>
</el-form-item>
<el-form-item label="应用编码">
<el-input
v-model="form.appCode"
placeholder="保存后自动生成"
disabled
/>
</el-form-item>
<el-form-item label="应用描述" prop="appDesc">
<el-input
v-model="form.appDesc"
type="textarea"
placeholder="请输入应用描述"
:rows="3"
maxlength="500"
show-word-limit
/>
</el-form-item>
<el-form-item label="联系人" prop="contactPerson">
<el-input
v-model="form.contactPerson"
placeholder="请输入联系人"
maxlength="50"
/>
</el-form-item>
<el-form-item label="联系电话" prop="contactPhone">
<el-input
v-model="form.contactPhone"
placeholder="请输入联系电话"
maxlength="11"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="接口授权" prop="apiCodes">
<el-checkbox-group v-model="form.apiCodes">
<el-checkbox
v-for="item in apiOptions"
:key="item.apiCode"
:value="item.apiCode"
style="width: 180px; margin-right: 0;"
>
{{ item.apiName }}
</el-checkbox>
</el-checkbox-group>
<div class="api-tips">
<el-text type="info" size="small">
勾选后,该应用可调用对应的开放API接口
</el-text>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup>
/**
* 应用管理 - 新增/编辑弹窗
* @author pangu
*/
import { addApplication, getApiList, getApplication, updateApplication } from '@/api/application'
import { ElMessage } from 'element-plus'
import { computed, nextTick, reactive, ref } from 'vue'
const emit = defineEmits(['success'])
const visible = ref(false)
const submitLoading = ref(false)
const formRef = ref(null)
const isEdit = ref(false)
const apiOptions = ref([])
const form = reactive({
appId: null,
appName: '',
appCode: '',
appDesc: '',
contactPerson: '',
contactPhone: '',
status: '0',
apiCodes: []
})
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' }
]
}
const dialogTitle = computed(() => isEdit.value ? '编辑应用' : '新增应用')
/**
* 加载接口授权选项
*/
const loadApiOptions = async () => {
try {
const res = await getApiList()
if (res.code === 200) {
apiOptions.value = res.data
}
} catch (error) {
console.error('加载接口授权选项失败:', error)
}
}
/**
* 加载应用详情
*/
const loadAppDetail = async (appId) => {
try {
const res = await getApplication(appId)
if (res.code === 200) {
const data = res.data
Object.assign(form, {
appId: data.appId,
appName: data.appName,
appCode: data.appCode,
appDesc: data.appDesc || '',
contactPerson: data.contactPerson || '',
contactPhone: data.contactPhone || '',
status: data.status || '0',
apiCodes: data.apis?.map(a => a.apiCode) || []
})
}
} catch (error) {
console.error('加载应用详情失败:', error)
}
}
/**
* 打开弹窗
* @param row 编辑时传入行数据,新增时不传
*/
const open = async (row = null) => {
visible.value = true
// 加载接口授权选项
await loadApiOptions()
if (row?.appId) {
isEdit.value = true
await loadAppDetail(row.appId)
} else {
isEdit.value = false
}
}
/**
* 提交表单
*/
const handleSubmit = async () => {
try {
await formRef.value.validate()
submitLoading.value = true
const submitData = {
appId: form.appId,
appName: form.appName,
appDesc: form.appDesc,
contactPerson: form.contactPerson,
contactPhone: form.contactPhone,
status: form.status,
apiCodes: form.apiCodes
}
const api = isEdit.value ? updateApplication : addApplication
const res = await api(submitData)
if (res.code === 200) {
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
visible.value = false
emit('success')
// 新增时可以显示密钥
// 根据业务需求决定是否在新增后显示密钥
} else {
ElMessage.error(res.msg || '操作失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('表单提交失败:', error)
}
} finally {
submitLoading.value = false
}
}
/**
* 关闭弹窗后重置表单
*/
const handleClosed = () => {
formRef.value?.resetFields()
Object.assign(form, {
appId: null,
appName: '',
appCode: '',
appDesc: '',
contactPerson: '',
contactPhone: '',
status: '0',
apiCodes: []
})
isEdit.value = false
}
defineExpose({ open })
</script>
<style scoped>
.api-tips {
margin-top: 8px;
}
</style>
3.3 密钥展示弹窗 (SecretDialog.vue)
3.3.1 组件功能
3.3.2 弹窗布局
┌─────────────────────────────────────────────────────────────────┐
│ 应用密钥 [X] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ⚠️ 请妥善保管密钥,密钥重置后旧密钥将立即失效 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 应用名称 AI智慧平台 │
│ │
│ 应用编码 YY000001 │
│ │
│ 应用密钥 ┌────────────────────────────────┬──────┐ │
│ │ a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 │ 复制 │ │
│ └────────────────────────────────┴──────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [关闭] │
└─────────────────────────────────────────────────────────────────┘
3.3.3 完整代码
<template>
<el-dialog
v-model="visible"
title="应用密钥"
width="550px"
:close-on-click-modal="false"
>
<el-alert
title="请妥善保管密钥,密钥重置后旧密钥将立即失效"
type="warning"
:closable="false"
show-icon
style="margin-bottom: 20px;"
/>
<el-descriptions :column="1" border>
<el-descriptions-item label="应用名称" label-class-name="desc-label">
{{ appInfo.appName }}
</el-descriptions-item>
<el-descriptions-item label="应用编码" label-class-name="desc-label">
{{ appInfo.appCode }}
</el-descriptions-item>
<el-descriptions-item label="应用密钥" label-class-name="desc-label">
<div class="secret-wrapper">
<el-input
v-model="appInfo.appSecret"
readonly
class="secret-input"
/>
<el-button type="primary" @click="handleCopy">
<el-icon style="margin-right: 4px;"><DocumentCopy /></el-icon>
复制
</el-button>
</div>
</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button type="primary" @click="visible = false">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup>
/**
* 应用管理 - 密钥展示弹窗
* @author pangu
*/
import { DocumentCopy } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue'
const visible = ref(false)
const appInfo = reactive({
appName: '',
appCode: '',
appSecret: ''
})
/**
* 打开弹窗
* @param data 包含 appName, appCode, appSecret
*/
const open = (data) => {
visible.value = true
Object.assign(appInfo, {
appName: data.appName || '',
appCode: data.appCode || '',
appSecret: data.appSecret || ''
})
}
/**
* 复制密钥到剪贴板
*/
const handleCopy = async () => {
try {
// 优先使用现代API
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(appInfo.appSecret)
ElMessage.success('复制成功')
return
}
// 降级方案:使用 execCommand
const textarea = document.createElement('textarea')
textarea.value = appInfo.appSecret
textarea.style.position = 'fixed'
textarea.style.left = '-9999px'
document.body.appendChild(textarea)
textarea.select()
document.execCommand('copy')
document.body.removeChild(textarea)
ElMessage.success('复制成功')
} catch (error) {
console.error('复制失败:', error)
ElMessage.error('复制失败,请手动复制')
}
}
defineExpose({ open })
</script>
<style scoped>
.secret-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.secret-input {
flex: 1;
}
:deep(.desc-label) {
width: 100px;
}
</style>
4. API接口封装
4.1 接口定义 (api/application.js)
/**
* 应用管理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)
开发阶段使用,后端接口完成后移除
/**
* 应用管理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 中添加路由:
{
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 代码规范
- 使用
<script setup> 语法
- 使用 Composition API
- 每个组件文件必须有作者注释
- 复杂逻辑添加注释说明
- API请求统一封装,不直接使用axios
7.3 交互规范
| 操作 |
交互方式 |
| 列表加载 |
表格显示 loading 状态 |
| 表单提交 |
按钮显示 loading 状态 |
| 删除操作 |
使用 el-popconfirm 二次确认 |
| 危险操作 |
使用 ElMessageBox.confirm 确认 |
| 成功提示 |
ElMessage.success |
| 错误提示 |
ElMessage.error |
8. 开发检查清单
8.1 开发前检查
8.2 开发中检查
8.3 开发后检查
文档结束