1231 lines
30 KiB
Markdown
1231 lines
30 KiB
Markdown
# 盘古用户平台 - 基础数据模块前端开发文档
|
||
|
||
---
|
||
|
||
| 文档信息 | 内容 |
|
||
| -------- | --------------------------- |
|
||
| **文档版本** | V1.0 |
|
||
| **项目名称** | 盘古用户平台(Pangu User Platform) |
|
||
| **模块名称** | 基础数据管理-前端开发 |
|
||
| **编写团队** | pangu |
|
||
| **创建日期** | 2026-01-31 |
|
||
|
||
---
|
||
|
||
## 1. 开发环境要求
|
||
|
||
| 环境 | 版本要求 | 说明 |
|
||
| -------- | --------- | --------- |
|
||
| Node.js | ≥18.0.0 | 运行环境 |
|
||
| npm | ≥9.0.0 | 包管理器 |
|
||
| Vue | 3.5.x | 前端框架 |
|
||
| Vite | 7.3.x | 构建工具 |
|
||
| Element Plus | 2.13.x | UI组件库 |
|
||
|
||
---
|
||
|
||
## 2. 目录结构
|
||
|
||
```
|
||
pangu-ui/src/
|
||
├── api/ # API接口定义
|
||
│ ├── grade.js # 年级管理API
|
||
│ ├── class.js # 班级管理API
|
||
│ ├── subject.js # 学科管理API
|
||
│ └── region.js # 区域管理API
|
||
├── mock/ # Mock数据
|
||
│ ├── grade.js # 年级Mock
|
||
│ ├── class.js # 班级Mock
|
||
│ ├── subject.js # 学科Mock
|
||
│ └── region.js # 区域Mock
|
||
├── views/base/ # 页面组件
|
||
│ ├── grade/
|
||
│ │ └── index.vue # 年级管理页面
|
||
│ ├── class/
|
||
│ │ └── index.vue # 班级管理页面
|
||
│ ├── subject/
|
||
│ │ └── index.vue # 学科管理页面
|
||
│ └── region/
|
||
│ └── index.vue # 区域管理页面
|
||
└── router/
|
||
└── index.js # 路由配置
|
||
```
|
||
|
||
---
|
||
|
||
## 3. API接口定义
|
||
|
||
### 3.1 年级管理API(api/grade.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 年级管理API
|
||
* @author pangu
|
||
*/
|
||
import request from '@/utils/request'
|
||
|
||
/**
|
||
* 获取年级分页列表
|
||
* @param {Object} params - 查询参数
|
||
* @param {string} params.gradeName - 年级名称(模糊查询)
|
||
* @param {string} params.status - 状态(0正常 1停用)
|
||
* @param {number} params.pageNum - 页码
|
||
* @param {number} params.pageSize - 每页条数
|
||
*/
|
||
export function getGradeList(params) {
|
||
return request({
|
||
url: '/api/grade/list',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取年级选项列表(下拉选择用)
|
||
* @returns {Promise} 返回启用状态的年级列表
|
||
*/
|
||
export function getGradeOptions() {
|
||
return request({
|
||
url: '/api/grade/options',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取年级详情
|
||
* @param {number} gradeId - 年级ID
|
||
*/
|
||
export function getGradeDetail(gradeId) {
|
||
return request({
|
||
url: `/api/grade/${gradeId}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 新增年级
|
||
* @param {Object} data - 年级数据
|
||
* @param {string} data.gradeName - 年级名称(必填)
|
||
* @param {number} data.orderNum - 排序号
|
||
* @param {string} data.status - 状态
|
||
*/
|
||
export function addGrade(data) {
|
||
return request({
|
||
url: '/api/grade',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 修改年级
|
||
* @param {Object} data - 年级数据(含gradeId)
|
||
*/
|
||
export function updateGrade(data) {
|
||
return request({
|
||
url: '/api/grade',
|
||
method: 'put',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除年级
|
||
* @param {number} gradeId - 年级ID
|
||
*/
|
||
export function deleteGrade(gradeId) {
|
||
return request({
|
||
url: `/api/grade/${gradeId}`,
|
||
method: 'delete'
|
||
})
|
||
}
|
||
```
|
||
|
||
### 3.2 班级管理API(api/class.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 班级管理API
|
||
* @author pangu
|
||
*/
|
||
import request from '@/utils/request'
|
||
|
||
/**
|
||
* 获取班级分页列表
|
||
*/
|
||
export function getClassList(params) {
|
||
return request({
|
||
url: '/api/class/list',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取班级选项列表
|
||
*/
|
||
export function getClassOptions() {
|
||
return request({
|
||
url: '/api/class/options',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取班级详情
|
||
*/
|
||
export function getClassDetail(classId) {
|
||
return request({
|
||
url: `/api/class/${classId}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 新增班级
|
||
*/
|
||
export function addClass(data) {
|
||
return request({
|
||
url: '/api/class',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 修改班级
|
||
*/
|
||
export function updateClass(data) {
|
||
return request({
|
||
url: '/api/class',
|
||
method: 'put',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除班级
|
||
*/
|
||
export function deleteClass(classId) {
|
||
return request({
|
||
url: `/api/class/${classId}`,
|
||
method: 'delete'
|
||
})
|
||
}
|
||
```
|
||
|
||
### 3.3 学科管理API(api/subject.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 学科管理API
|
||
* @author pangu
|
||
*/
|
||
import request from '@/utils/request'
|
||
|
||
/**
|
||
* 获取学科分页列表
|
||
*/
|
||
export function getSubjectList(params) {
|
||
return request({
|
||
url: '/api/subject/list',
|
||
method: 'get',
|
||
params
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取学科选项列表
|
||
*/
|
||
export function getSubjectOptions() {
|
||
return request({
|
||
url: '/api/subject/options',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取学科详情
|
||
*/
|
||
export function getSubjectDetail(subjectId) {
|
||
return request({
|
||
url: `/api/subject/${subjectId}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 新增学科
|
||
*/
|
||
export function addSubject(data) {
|
||
return request({
|
||
url: '/api/subject',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 修改学科
|
||
*/
|
||
export function updateSubject(data) {
|
||
return request({
|
||
url: '/api/subject',
|
||
method: 'put',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除学科
|
||
*/
|
||
export function deleteSubject(subjectId) {
|
||
return request({
|
||
url: `/api/subject/${subjectId}`,
|
||
method: 'delete'
|
||
})
|
||
}
|
||
```
|
||
|
||
### 3.4 区域管理API(api/region.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 区域管理API
|
||
* @author pangu
|
||
*/
|
||
import request from '@/utils/request'
|
||
|
||
/**
|
||
* 获取区域树
|
||
* @returns {Promise} 返回树形结构的区域数据
|
||
*/
|
||
export function getRegionTree() {
|
||
return request({
|
||
url: '/api/region/tree',
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取区域详情
|
||
*/
|
||
export function getRegionDetail(regionId) {
|
||
return request({
|
||
url: `/api/region/${regionId}`,
|
||
method: 'get'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 新增区域
|
||
* @param {Object} data - 区域数据
|
||
* @param {number} data.parentId - 父区域ID(0为顶级)
|
||
* @param {string} data.regionName - 区域名称
|
||
*/
|
||
export function addRegion(data) {
|
||
return request({
|
||
url: '/api/region',
|
||
method: 'post',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 修改区域
|
||
*/
|
||
export function updateRegion(data) {
|
||
return request({
|
||
url: '/api/region',
|
||
method: 'put',
|
||
data
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除区域
|
||
*/
|
||
export function deleteRegion(regionId) {
|
||
return request({
|
||
url: `/api/region/${regionId}`,
|
||
method: 'delete'
|
||
})
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 页面组件开发
|
||
|
||
### 4.1 年级管理页面(views/base/grade/index.vue)
|
||
|
||
```vue
|
||
<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.gradeName"
|
||
placeholder="请输入年级名称"
|
||
clearable
|
||
style="width: 200px"
|
||
@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' }"
|
||
style="width: 100%"
|
||
>
|
||
<el-table-column prop="gradeName" label="年级名称" min-width="150" />
|
||
<el-table-column prop="gradeCode" label="年级编码" width="120" />
|
||
<el-table-column prop="orderNum" label="排序" width="80" align="center" />
|
||
<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 label="操作" width="150" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||
</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>
|
||
|
||
<!-- 新增/编辑弹窗 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogTitle"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
destroy-on-close
|
||
>
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||
<el-form-item label="年级名称" prop="gradeName">
|
||
<el-input v-model="form.gradeName" placeholder="请输入年级名称" maxlength="50" />
|
||
</el-form-item>
|
||
<el-form-item label="年级编码">
|
||
<el-input v-model="form.gradeCode" placeholder="自动生成" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="排序" prop="orderNum">
|
||
<el-input-number v-model="form.orderNum" :min="0" :max="999" />
|
||
</el-form-item>
|
||
<el-form-item label="状态" prop="status">
|
||
<el-switch
|
||
v-model="form.status"
|
||
active-value="0"
|
||
inactive-value="1"
|
||
active-text="正常"
|
||
inactive-text="停用"
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
/**
|
||
* 年级管理页面
|
||
* @author pangu
|
||
*/
|
||
import { Delete, Edit, Plus, Refresh, Search } from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { onMounted, ref } from 'vue'
|
||
import { getGradeList, addGrade, updateGrade, deleteGrade } from '@/api/grade'
|
||
|
||
// 状态变量
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const tableData = ref([])
|
||
const total = ref(0)
|
||
|
||
// 查询参数
|
||
const queryParams = ref({
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
gradeName: '',
|
||
status: ''
|
||
})
|
||
|
||
// 弹窗相关
|
||
const dialogVisible = ref(false)
|
||
const dialogTitle = ref('')
|
||
const formRef = ref()
|
||
const form = ref({
|
||
gradeId: null,
|
||
gradeName: '',
|
||
gradeCode: '',
|
||
orderNum: 0,
|
||
status: '0'
|
||
})
|
||
|
||
// 表单校验规则
|
||
const rules = {
|
||
gradeName: [
|
||
{ required: true, message: '请输入年级名称', trigger: 'blur' },
|
||
{ max: 50, message: '年级名称长度不能超过50个字符', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
/**
|
||
* 获取年级列表
|
||
*/
|
||
const getList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await getGradeList(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,
|
||
gradeName: '',
|
||
status: ''
|
||
}
|
||
getList()
|
||
}
|
||
|
||
/**
|
||
* 新增年级
|
||
*/
|
||
const handleAdd = () => {
|
||
dialogTitle.value = '新增年级'
|
||
form.value = {
|
||
gradeId: null,
|
||
gradeName: '',
|
||
gradeCode: '(自动生成)',
|
||
orderNum: tableData.value.length + 1,
|
||
status: '0'
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 编辑年级
|
||
*/
|
||
const handleEdit = (row) => {
|
||
dialogTitle.value = '编辑年级'
|
||
form.value = { ...row }
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 提交表单
|
||
*/
|
||
const handleSubmit = async () => {
|
||
await formRef.value?.validate()
|
||
submitLoading.value = true
|
||
try {
|
||
const isEdit = !!form.value.gradeId
|
||
const res = isEdit
|
||
? await updateGrade(form.value)
|
||
: await addGrade(form.value)
|
||
if (res.code === 200) {
|
||
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||
dialogVisible.value = false
|
||
getList()
|
||
} else {
|
||
ElMessage.error(res.msg || '操作失败')
|
||
}
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除年级
|
||
*/
|
||
const handleDelete = (row) => {
|
||
ElMessageBox.confirm(
|
||
`确定要删除年级"${row.gradeName}"吗?删除后不可恢复!`,
|
||
'删除确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(async () => {
|
||
const res = await deleteGrade(row.gradeId)
|
||
if (res.code === 200) {
|
||
ElMessage.success('删除成功')
|
||
getList()
|
||
} else {
|
||
// 被引用时显示后端返回的提示
|
||
ElMessage.error(res.msg || '删除失败')
|
||
}
|
||
}).catch(() => {
|
||
// 用户取消删除
|
||
})
|
||
}
|
||
|
||
// 页面加载时获取数据
|
||
onMounted(() => {
|
||
getList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.app-container {
|
||
padding: 16px;
|
||
}
|
||
.search-wrapper {
|
||
margin-bottom: 0;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
### 4.2 区域管理页面(views/base/region/index.vue)
|
||
|
||
```vue
|
||
<template>
|
||
<div class="app-container">
|
||
<!-- 操作区域 -->
|
||
<el-card shadow="never">
|
||
<el-row :gutter="10">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5">
|
||
<el-button :icon="Sort" @click="toggleExpandAll">
|
||
{{ isExpandAll ? '折叠全部' : '展开全部' }}
|
||
</el-button>
|
||
</el-col>
|
||
</el-row>
|
||
</el-card>
|
||
|
||
<!-- 树形表格 -->
|
||
<el-card shadow="never" style="margin-top: 12px">
|
||
<el-table
|
||
v-if="refreshTable"
|
||
v-loading="loading"
|
||
:data="treeData"
|
||
row-key="regionId"
|
||
border
|
||
:default-expand-all="isExpandAll"
|
||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||
style="width: 100%"
|
||
>
|
||
<el-table-column prop="regionName" label="区域名称" min-width="200" />
|
||
<el-table-column prop="regionCode" label="区域编码" width="150" />
|
||
<el-table-column prop="level" label="层级" width="80" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag :type="getLevelType(row.level)">
|
||
{{ getLevelText(row.level) }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="orderNum" label="排序" width="80" align="center" />
|
||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<!-- 只有省、市级可以新增下级 -->
|
||
<el-button
|
||
v-if="row.level < 3"
|
||
type="primary"
|
||
link
|
||
:icon="Plus"
|
||
@click="handleAddChild(row)"
|
||
>
|
||
新增下级
|
||
</el-button>
|
||
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-card>
|
||
|
||
<!-- 新增/编辑弹窗 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogTitle"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
destroy-on-close
|
||
>
|
||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||
<el-form-item label="上级区域" v-if="form.parentId !== 0">
|
||
<el-input v-model="form.parentName" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="区域名称" prop="regionName">
|
||
<el-input v-model="form.regionName" placeholder="请输入区域名称" maxlength="100" />
|
||
</el-form-item>
|
||
<el-form-item label="区域编码" v-if="form.regionId">
|
||
<el-input v-model="form.regionCode" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="排序" prop="orderNum">
|
||
<el-input-number v-model="form.orderNum" :min="0" :max="999" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
/**
|
||
* 区域管理页面
|
||
* @author pangu
|
||
*/
|
||
import { Delete, Edit, Plus, Sort } from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { nextTick, onMounted, ref } from 'vue'
|
||
import { getRegionTree, addRegion, updateRegion, deleteRegion } from '@/api/region'
|
||
|
||
// 状态变量
|
||
const loading = ref(false)
|
||
const submitLoading = ref(false)
|
||
const treeData = ref([])
|
||
const isExpandAll = ref(false)
|
||
const refreshTable = ref(true)
|
||
|
||
// 弹窗相关
|
||
const dialogVisible = ref(false)
|
||
const dialogTitle = ref('')
|
||
const formRef = ref()
|
||
const form = ref({
|
||
regionId: null,
|
||
parentId: 0,
|
||
parentName: '',
|
||
regionName: '',
|
||
regionCode: '',
|
||
orderNum: 0
|
||
})
|
||
|
||
// 表单校验规则
|
||
const rules = {
|
||
regionName: [
|
||
{ required: true, message: '请输入区域名称', trigger: 'blur' },
|
||
{ max: 100, message: '区域名称长度不能超过100个字符', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
/**
|
||
* 获取区域树
|
||
*/
|
||
const getTree = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await getRegionTree()
|
||
if (res.code === 200) {
|
||
treeData.value = res.data
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 展开/折叠全部
|
||
*/
|
||
const toggleExpandAll = () => {
|
||
refreshTable.value = false
|
||
isExpandAll.value = !isExpandAll.value
|
||
nextTick(() => {
|
||
refreshTable.value = true
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 获取层级文本
|
||
*/
|
||
const getLevelText = (level) => {
|
||
const map = { 1: '省', 2: '市', 3: '区/县' }
|
||
return map[level] || '未知'
|
||
}
|
||
|
||
/**
|
||
* 获取层级标签类型
|
||
*/
|
||
const getLevelType = (level) => {
|
||
const map = { 1: 'primary', 2: 'success', 3: 'info' }
|
||
return map[level] || 'info'
|
||
}
|
||
|
||
/**
|
||
* 新增顶级区域
|
||
*/
|
||
const handleAdd = () => {
|
||
dialogTitle.value = '新增区域'
|
||
form.value = {
|
||
regionId: null,
|
||
parentId: 0,
|
||
parentName: '',
|
||
regionName: '',
|
||
regionCode: '',
|
||
orderNum: 0
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 新增下级区域
|
||
*/
|
||
const handleAddChild = (row) => {
|
||
dialogTitle.value = '新增下级区域'
|
||
form.value = {
|
||
regionId: null,
|
||
parentId: row.regionId,
|
||
parentName: row.regionName,
|
||
regionName: '',
|
||
regionCode: '',
|
||
orderNum: 0
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 编辑区域
|
||
*/
|
||
const handleEdit = (row) => {
|
||
dialogTitle.value = '编辑区域'
|
||
form.value = {
|
||
regionId: row.regionId,
|
||
parentId: row.parentId,
|
||
parentName: findParentName(row.parentId),
|
||
regionName: row.regionName,
|
||
regionCode: row.regionCode,
|
||
orderNum: row.orderNum
|
||
}
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
/**
|
||
* 查找父区域名称
|
||
*/
|
||
const findParentName = (parentId) => {
|
||
if (parentId === 0) return ''
|
||
const findNode = (nodes, id) => {
|
||
for (const node of nodes) {
|
||
if (node.regionId === id) return node.regionName
|
||
if (node.children) {
|
||
const found = findNode(node.children, id)
|
||
if (found) return found
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
return findNode(treeData.value, parentId)
|
||
}
|
||
|
||
/**
|
||
* 提交表单
|
||
*/
|
||
const handleSubmit = async () => {
|
||
await formRef.value?.validate()
|
||
submitLoading.value = true
|
||
try {
|
||
const isEdit = !!form.value.regionId
|
||
const res = isEdit
|
||
? await updateRegion(form.value)
|
||
: await addRegion(form.value)
|
||
if (res.code === 200) {
|
||
ElMessage.success(isEdit ? '修改成功' : '新增成功')
|
||
dialogVisible.value = false
|
||
getTree()
|
||
} else {
|
||
ElMessage.error(res.msg || '操作失败')
|
||
}
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除区域
|
||
*/
|
||
const handleDelete = (row) => {
|
||
// 前端先检查是否有子级
|
||
if (row.children && row.children.length > 0) {
|
||
ElMessage.warning('存在下级区域,不能删除')
|
||
return
|
||
}
|
||
|
||
ElMessageBox.confirm(
|
||
`确定要删除区域"${row.regionName}"吗?`,
|
||
'删除确认',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}
|
||
).then(async () => {
|
||
const res = await deleteRegion(row.regionId)
|
||
if (res.code === 200) {
|
||
ElMessage.success('删除成功')
|
||
getTree()
|
||
} else {
|
||
// 被学校/会员引用时显示后端提示
|
||
ElMessage.error(res.msg || '删除失败')
|
||
}
|
||
}).catch(() => {
|
||
// 用户取消删除
|
||
})
|
||
}
|
||
|
||
// 页面加载时获取数据
|
||
onMounted(() => {
|
||
getTree()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.app-container {
|
||
padding: 16px;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Mock数据配置
|
||
|
||
### 5.1 年级Mock(mock/grade.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 年级管理Mock数据
|
||
* @author pangu
|
||
*/
|
||
import Mock from 'mockjs'
|
||
|
||
// 年级预置数据
|
||
const gradeData = [
|
||
{ gradeId: 1, gradeName: '一年级', gradeCode: 'GRD001', orderNum: 1, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 2, gradeName: '二年级', gradeCode: 'GRD002', orderNum: 2, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 3, gradeName: '三年级', gradeCode: 'GRD003', orderNum: 3, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 4, gradeName: '四年级', gradeCode: 'GRD004', orderNum: 4, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 5, gradeName: '五年级', gradeCode: 'GRD005', orderNum: 5, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 6, gradeName: '六年级', gradeCode: 'GRD006', orderNum: 6, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 7, gradeName: '七年级', gradeCode: 'GRD007', orderNum: 7, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 8, gradeName: '八年级', gradeCode: 'GRD008', orderNum: 8, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 9, gradeName: '九年级', gradeCode: 'GRD009', orderNum: 9, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 10, gradeName: '高一', gradeCode: 'GRD010', orderNum: 10, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 11, gradeName: '高二', gradeCode: 'GRD011', orderNum: 11, status: '0', createTime: '2026-01-01 10:00:00' },
|
||
{ gradeId: 12, gradeName: '高三', gradeCode: 'GRD012', orderNum: 12, status: '0', createTime: '2026-01-01 10:00:00' }
|
||
]
|
||
|
||
// 获取年级列表
|
||
Mock.mock(/\/api\/grade\/list/, 'get', (options) => {
|
||
const url = new URL('http://localhost' + options.url)
|
||
const gradeName = url.searchParams.get('gradeName') || ''
|
||
const status = url.searchParams.get('status')
|
||
const pageNum = parseInt(url.searchParams.get('pageNum')) || 1
|
||
const pageSize = parseInt(url.searchParams.get('pageSize')) || 10
|
||
|
||
let list = gradeData.filter(item => {
|
||
let match = true
|
||
if (gradeName) {
|
||
match = match && item.gradeName.includes(gradeName)
|
||
}
|
||
if (status !== null && status !== '') {
|
||
match = match && item.status === status
|
||
}
|
||
return match
|
||
})
|
||
|
||
const total = list.length
|
||
const start = (pageNum - 1) * pageSize
|
||
const rows = list.slice(start, start + pageSize)
|
||
|
||
return {
|
||
code: 200,
|
||
msg: '查询成功',
|
||
total,
|
||
rows
|
||
}
|
||
})
|
||
|
||
// 获取年级选项
|
||
Mock.mock(/\/api\/grade\/options/, 'get', () => {
|
||
return {
|
||
code: 200,
|
||
msg: '查询成功',
|
||
data: gradeData.filter(item => item.status === '0').map(item => ({
|
||
gradeId: item.gradeId,
|
||
gradeName: item.gradeName,
|
||
gradeCode: item.gradeCode
|
||
}))
|
||
}
|
||
})
|
||
|
||
// 新增年级
|
||
Mock.mock('/api/grade', 'post', () => {
|
||
return { code: 200, msg: '新增成功' }
|
||
})
|
||
|
||
// 修改年级
|
||
Mock.mock('/api/grade', 'put', () => {
|
||
return { code: 200, msg: '修改成功' }
|
||
})
|
||
|
||
// 删除年级
|
||
Mock.mock(/\/api\/grade\/\d+/, 'delete', () => {
|
||
// 模拟被引用时的删除失败
|
||
// return { code: 500, msg: '该年级已被学校使用,不能删除' }
|
||
return { code: 200, msg: '删除成功' }
|
||
})
|
||
```
|
||
|
||
### 5.2 区域Mock(mock/region.js)
|
||
|
||
```javascript
|
||
/**
|
||
* 区域管理Mock数据
|
||
* @author pangu
|
||
*/
|
||
import Mock from 'mockjs'
|
||
|
||
// 区域树形数据
|
||
const regionTree = [
|
||
{
|
||
regionId: 1,
|
||
parentId: 0,
|
||
regionName: '湖北',
|
||
regionCode: 'REG01',
|
||
level: 1,
|
||
orderNum: 1,
|
||
createTime: '2026-01-01 10:00:00',
|
||
children: [
|
||
{
|
||
regionId: 11,
|
||
parentId: 1,
|
||
regionName: '武汉',
|
||
regionCode: 'REG0101',
|
||
level: 2,
|
||
orderNum: 1,
|
||
createTime: '2026-01-01 10:00:00',
|
||
children: [
|
||
{ regionId: 111, parentId: 11, regionName: '武昌区', regionCode: 'REG010101', level: 3, orderNum: 1, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 112, parentId: 11, regionName: '汉口区', regionCode: 'REG010102', level: 3, orderNum: 2, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 113, parentId: 11, regionName: '汉阳区', regionCode: 'REG010103', level: 3, orderNum: 3, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 114, parentId: 11, regionName: '江夏区', regionCode: 'REG010104', level: 3, orderNum: 4, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 115, parentId: 11, regionName: '新洲区', regionCode: 'REG010105', level: 3, orderNum: 5, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 116, parentId: 11, regionName: '黄陂区', regionCode: 'REG010106', level: 3, orderNum: 6, createTime: '2026-01-01 10:00:00' }
|
||
]
|
||
},
|
||
{
|
||
regionId: 12,
|
||
parentId: 1,
|
||
regionName: '黄冈',
|
||
regionCode: 'REG0102',
|
||
level: 2,
|
||
orderNum: 2,
|
||
createTime: '2026-01-01 10:00:00',
|
||
children: [
|
||
{ regionId: 121, parentId: 12, regionName: '黄州区', regionCode: 'REG010201', level: 3, orderNum: 1, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 122, parentId: 12, regionName: '红安县', regionCode: 'REG010202', level: 3, orderNum: 2, createTime: '2026-01-01 10:00:00' },
|
||
{ regionId: 123, parentId: 12, regionName: '麻城市', regionCode: 'REG010203', level: 3, orderNum: 3, createTime: '2026-01-01 10:00:00' }
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{ regionId: 2, parentId: 0, regionName: '北京', regionCode: 'REG02', level: 1, orderNum: 2, createTime: '2026-01-01 10:00:00', children: [] },
|
||
{ regionId: 3, parentId: 0, regionName: '香港', regionCode: 'REG03', level: 1, orderNum: 3, createTime: '2026-01-01 10:00:00', children: [] },
|
||
{ regionId: 4, parentId: 0, regionName: '吉宁', regionCode: 'REG04', level: 1, orderNum: 4, createTime: '2026-01-01 10:00:00', children: [] }
|
||
]
|
||
|
||
// 获取区域树
|
||
Mock.mock('/api/region/tree', 'get', () => {
|
||
return {
|
||
code: 200,
|
||
msg: '查询成功',
|
||
data: regionTree
|
||
}
|
||
})
|
||
|
||
// 新增区域
|
||
Mock.mock('/api/region', 'post', () => {
|
||
return { code: 200, msg: '新增成功' }
|
||
})
|
||
|
||
// 修改区域
|
||
Mock.mock('/api/region', 'put', () => {
|
||
return { code: 200, msg: '修改成功' }
|
||
})
|
||
|
||
// 删除区域
|
||
Mock.mock(/\/api\/region\/\d+/, 'delete', () => {
|
||
return { code: 200, msg: '删除成功' }
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 路由配置
|
||
|
||
```javascript
|
||
// router/index.js
|
||
import { createRouter, createWebHistory } from 'vue-router'
|
||
import Layout from '@/layout/index.vue'
|
||
|
||
const routes = [
|
||
// ... 其他路由
|
||
|
||
{
|
||
path: '/base',
|
||
component: Layout,
|
||
redirect: '/base/grade',
|
||
name: 'Base',
|
||
meta: { title: '基础数据', icon: 'Setting' },
|
||
children: [
|
||
{
|
||
path: 'grade',
|
||
name: 'Grade',
|
||
component: () => import('@/views/base/grade/index.vue'),
|
||
meta: { title: '年级管理' }
|
||
},
|
||
{
|
||
path: 'class',
|
||
name: 'Class',
|
||
component: () => import('@/views/base/class/index.vue'),
|
||
meta: { title: '班级管理' }
|
||
},
|
||
{
|
||
path: 'subject',
|
||
name: 'Subject',
|
||
component: () => import('@/views/base/subject/index.vue'),
|
||
meta: { title: '学科管理' }
|
||
},
|
||
{
|
||
path: 'region',
|
||
name: 'Region',
|
||
component: () => import('@/views/base/region/index.vue'),
|
||
meta: { title: '区域管理' }
|
||
}
|
||
]
|
||
}
|
||
]
|
||
|
||
const router = createRouter({
|
||
history: createWebHistory(),
|
||
routes
|
||
})
|
||
|
||
export default router
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 开发检查清单
|
||
|
||
### 7.1 年级管理
|
||
|
||
- [ ] API接口封装(api/grade.js)
|
||
- [ ] Mock数据配置(mock/grade.js)
|
||
- [ ] 页面组件开发(views/base/grade/index.vue)
|
||
- [ ] 列表查询功能
|
||
- [ ] 分页功能
|
||
- [ ] 新增功能
|
||
- [ ] 编辑功能
|
||
- [ ] 删除功能(含删除确认)
|
||
- [ ] 表单校验
|
||
|
||
### 7.2 班级管理
|
||
|
||
- [ ] API接口封装
|
||
- [ ] Mock数据配置
|
||
- [ ] 页面组件开发
|
||
- [ ] CRUD功能
|
||
|
||
### 7.3 学科管理
|
||
|
||
- [ ] API接口封装
|
||
- [ ] Mock数据配置
|
||
- [ ] 页面组件开发
|
||
- [ ] CRUD功能
|
||
|
||
### 7.4 区域管理
|
||
|
||
- [ ] API接口封装
|
||
- [ ] Mock数据配置
|
||
- [ ] 页面组件开发(树形表格)
|
||
- [ ] 展开/折叠全部功能
|
||
- [ ] 新增下级功能
|
||
- [ ] 删除前子级检查
|
||
|
||
### 7.5 路由配置
|
||
|
||
- [ ] 基础数据菜单配置
|
||
- [ ] 四个子页面路由配置
|
||
|
||
---
|
||
|
||
*文档结束*
|