pangu-user-platform/docs/05-技术方案/基础数据模块_前端开发文档.md

30 KiB
Raw Blame History

盘古用户平台 - 基础数据模块前端开发文档


文档信息 内容
文档版本 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 年级管理APIapi/grade.js

/**
 * 年级管理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 班级管理APIapi/class.js

/**
 * 班级管理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 学科管理APIapi/subject.js

/**
 * 学科管理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 区域管理APIapi/region.js

/**
 * 区域管理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 - 父区域ID0为顶级
 * @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

<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

<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 年级Mockmock/grade.js

/**
 * 年级管理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 区域Mockmock/region.js

/**
 * 区域管理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. 路由配置

// 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 路由配置

  • 基础数据菜单配置
  • 四个子页面路由配置

文档结束