# 学生管理模块 - 前端技术方案 --- | 文档信息 | 内容 | |---------|------| | **文档版本** | V1.0 | | **项目名称** | 盘古用户平台(Pangu User Platform) | | **模块名称** | 学生管理模块 - 前端 | | **编写团队** | pangu | | **创建日期** | 2026-01-31 | | **审核状态** | 待审核 | --- ## 目录 1. [技术栈说明](#1-技术栈说明) 2. [目录结构](#2-目录结构) 3. [页面设计](#3-页面设计) 4. [组件设计](#4-组件设计) 5. [API接口](#5-api接口) 6. [Mock数据](#6-mock数据) 7. [状态管理](#7-状态管理) 8. [编码规范](#8-编码规范) 9. [测试方案](#9-测试方案) --- ## 1. 技术栈说明 | 技术 | 版本 | 用途 | |------|------|------| | Vue | 3.5.26 | 前端框架 | | Vite | 7.3.1 | 构建工具 | | Element Plus | 2.13.2 | UI组件库 | | Pinia | 3.0.4 | 状态管理 | | Vue Router | 4.6.4 | 路由管理 | | Axios | 1.13.4 | HTTP请求 | | MockJS | 1.1.0 | Mock数据 | --- ## 2. 目录结构 ``` src/ ├── api/ │ └── student.js # 学生管理API接口 ├── mock/ │ └── student.js # 学生管理Mock数据 ├── views/ │ └── student/ │ ├── index.vue # 学生管理主页面 │ └── components/ │ ├── StudentDialog.vue # 新增/编辑学生弹窗 │ ├── ImportDialog.vue # 批量导入弹窗 │ └── SchoolTree.vue # 学校树组件 └── router/ └── index.js # 路由配置(已包含学生路由) ``` --- ## 3. 页面设计 ### 3.1 页面布局 学生管理页面采用左右分栏布局: ``` ┌────────────────────────────────────────────────────────────────────┐ │ 学生管理 │ ├────────────────────────────────────────────────────────────────────┤ │ ┌──────────────┐ ┌────────────────────────────────────────────────┐│ │ │ │ │ 搜索区域 ││ │ │ │ │ [姓名] [学号] [性别▼] [手机号] [学科▼] ││ │ │ 学校树 │ │ [查询] [重置] ││ │ │ │ ├────────────────────────────────────────────────┤│ │ │ ▼ 武汉市第一 │ │ [+新增] [批量导入] ││ │ │ ├ 七年级 │ ├────────────────────────────────────────────────┤│ │ │ │ ├ 1班 │ │ ││ │ │ │ ├ 2班 │ │ 表格区域 ││ │ │ │ └ 3班 │ │ ┌────┬────┬────┬────┬────┬────┬────┬────┐ ││ │ │ ├ 八年级 │ │ │姓名│学号│性别│出生│地区│学校│年级│班级...│ ││ │ │ └ 九年级 │ │ ├────┼────┼────┼────┼────┼────┼────┼────┤ ││ │ │ │ │ │张三│STU.│ 男 │2015│湖北│武汉│七年│1班...│ ││ │ │ ▶ 武汉市第三 │ │ │李四│STU.│ 女 │2016│湖北│武汉│七年│2班...│ ││ │ │ │ │ └────┴────┴────┴────┴────┴────┴────┴────┘ ││ │ │ ▶ 武汉市水果 │ │ ││ │ │ │ │ 分页: 共100条 [< 1 2 3 4 5 >] 10条/页 ││ │ │ [搜索学校] │ │ ││ │ └──────────────┘ └────────────────────────────────────────────────┘│ └────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 页面组件结构 ```vue ``` ### 3.3 表格列定义 | 字段 | prop | 列宽 | 对齐 | 特殊处理 | |------|------|------|------|----------| | 姓名 | studentName | min-width="80" | left | show-overflow-tooltip | | 学号 | studentNo | width="100" | left | - | | 性别 | gender | width="60" | center | Tag组件 | | 出生年月 | birthday | width="100" | left | - | | 地区 | regionPath | min-width="150" | left | show-overflow-tooltip | | 学校 | schoolName | min-width="140" | left | show-overflow-tooltip | | 年级 | gradeName | width="80" | left | - | | 班级 | className | width="60" | left | - | | 学科 | subjectName | width="60" | left | - | | 用户昵称 | memberNickname | width="100" | left | - | | 用户手机号 | memberPhone | width="120" | left | 脱敏显示 | | 操作 | - | width="120" | center | fixed="right" | ### 3.4 搜索条件 | 字段 | 组件 | 宽度 | 说明 | |------|------|------|------| | 姓名 | el-input | 150px | 模糊查询 | | 学号 | el-input | 150px | 精确查询 | | 性别 | el-select | 100px | 下拉选择 | | 用户手机号 | el-input | 150px | 模糊查询 | | 学科 | el-select | 120px | 下拉选择 | --- ## 4. 组件设计 ### 4.1 SchoolTree.vue - 学校树组件 **功能说明**:左侧学校树,支持搜索过滤和节点点击筛选 **Props** | 属性 | 类型 | 必填 | 默认值 | 说明 | |------|------|:----:|--------|------| | defaultExpandAll | boolean | 否 | false | 是否默认展开所有节点 | **Events** | 事件名 | 参数 | 说明 | |--------|------|------| | node-click | { nodeType, nodeId, nodeData } | 节点点击事件 | **组件代码示例** ```vue ``` ### 4.2 StudentDialog.vue - 新增/编辑学生弹窗 **功能说明**:新增和编辑学生信息的弹窗表单 **Props** | 属性 | 类型 | 必填 | 默认值 | 说明 | |------|------|:----:|--------|------| | visible | boolean | 是 | false | 弹窗显示状态 | | studentId | number | 否 | null | 学生ID,编辑时传入 | **Events** | 事件名 | 参数 | 说明 | |--------|------|------| | update:visible | boolean | 更新显示状态 | | success | - | 保存成功回调 | **表单字段** | 字段 | 组件 | 必填 | 校验规则 | |------|------|:----:|----------| | 学生姓名 | el-input | ✓ | 非空,最大50字符 | | 学号 | el-input | - | 最大32字符 | | 性别 | el-radio-group | - | - | | 出生日期 | el-date-picker | - | 格式:YYYY-MM | | 所属区域 | el-cascader | ✓ | 三级级联 | | 所属学校 | el-select | ✓ | 依赖区域 | | 所属年级 | el-select | ✓ | 依赖学校 | | 所属班级 | el-select | ✓ | 依赖年级 | | 学科 | el-select | - | - | | 归属用户 | el-select | ✓ | 支持搜索 | **组件代码示例** ```vue ``` ### 4.3 ImportDialog.vue - 批量导入弹窗 **功能说明**:批量导入学生的弹窗,支持下载模板和上传Excel **组件代码示例** ```vue ``` --- ## 5. API接口 ### 5.1 接口定义文件 ```javascript // src/api/student.js import request from '@/utils/request' /** * 查询学生列表 * @param {Object} params 查询参数 * @returns {Promise} */ export function getStudentList(params) { return request({ url: '/api/student/list', method: 'get', params }) } /** * 获取学生详情 * @param {Number} studentId 学生ID * @returns {Promise} */ export function getStudent(studentId) { return request({ url: `/api/student/${studentId}`, method: 'get' }) } /** * 新增学生 * @param {Object} data 学生数据 * @returns {Promise} */ export function addStudent(data) { return request({ url: '/api/student', method: 'post', data }) } /** * 修改学生 * @param {Object} data 学生数据 * @returns {Promise} */ export function updateStudent(data) { return request({ url: '/api/student', method: 'put', data }) } /** * 删除学生 * @param {Number} studentId 学生ID * @returns {Promise} */ export function deleteStudent(studentId) { return request({ url: `/api/student/${studentId}`, method: 'delete' }) } /** * 下载导入模板 * @returns {Promise} */ export function downloadStudentTemplate() { return request({ url: '/api/student/template', method: 'get', responseType: 'blob' }).then(res => { const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = '学生导入模板.xlsx' link.click() URL.revokeObjectURL(link.href) }) } /** * 获取学校树 * @returns {Promise} */ export function getSchoolTree() { return request({ url: '/api/school/tree', method: 'get' }) } /** * 搜索会员列表(用于选择归属用户) * @param {Object} params 查询参数 * @returns {Promise} */ export function searchMemberList(params) { return request({ url: '/api/member/search', method: 'get', params }) } ``` ### 5.2 请求参数说明 **getStudentList 参数** | 参数 | 类型 | 必填 | 说明 | |------|------|:----:|------| | studentName | string | 否 | 学生姓名(模糊) | | studentNo | string | 否 | 学号 | | gender | string | 否 | 性别 | | schoolId | number | 否 | 学校ID | | schoolGradeId | number | 否 | 学校年级ID | | schoolClassId | number | 否 | 学校班级ID | | subjectId | number | 否 | 学科ID | | memberPhone | string | 否 | 归属用户手机号 | | pageNum | number | 是 | 页码 | | pageSize | number | 是 | 每页条数 | --- ## 6. Mock数据 ```javascript // src/mock/student.js import Mock from 'mockjs' // 学校树Mock数据 const schoolTree = [ { id: 1, label: '武汉市第一中学', nodeType: 'school', children: [ { id: 101, label: '七年级', nodeType: 'grade', schoolId: 1, children: [ { id: 1001, label: '1班', nodeType: 'class', schoolGradeId: 101 }, { id: 1002, label: '2班', nodeType: 'class', schoolGradeId: 101 }, { id: 1003, label: '3班', nodeType: 'class', schoolGradeId: 101 } ] }, { id: 102, label: '八年级', nodeType: 'grade', schoolId: 1, children: [] } ] }, { id: 3, label: '武汉市水果湖小学', nodeType: 'school', children: [ { id: 301, label: '一年级', nodeType: 'grade', schoolId: 3, children: [] }, { id: 302, label: '二年级', nodeType: 'grade', schoolId: 3, children: [] }, { id: 303, label: '三年级', nodeType: 'grade', schoolId: 3, children: [] } ] } ] // 学生列表Mock数据 const studentList = Mock.mock({ 'rows|20': [ { 'studentId|+1': 1, 'studentName': '@cname', 'studentNo': /STU2026\d{4}/, 'gender|1': ['0', '1', '2'], 'birthday': '@date("yyyy-MM")', 'regionPath': '湖北省-武汉市-武昌区', 'schoolName': '武汉市第一中学', 'gradeName': '七年级', 'className': '@pick(["1班", "2班", "3班"])', 'subjectName': '@pick(["语文", "数学", "英语", ""])', 'memberNickname': '@cname', 'memberPhone': /138\*\*\*\*\d{4}/ } ], 'total': 100 }) // 学校树 Mock.mock(/\/api\/school\/tree/, 'get', () => { return { code: 200, msg: '查询成功', data: schoolTree } }) // 学生列表 Mock.mock(/\/api\/student\/list/, 'get', (options) => { return { code: 200, msg: '查询成功', total: studentList.total, rows: studentList.rows } }) // 学生详情 Mock.mock(/\/api\/student\/\d+/, 'get', (options) => { const id = parseInt(options.url.match(/\/api\/student\/(\d+)/)[1]) return { code: 200, msg: '查询成功', data: { studentId: id, studentName: Mock.mock('@cname'), studentNo: 'STU20260001', gender: '1', birthday: '2015-03', regionIds: [1, 11, 111], regionId: 111, schoolId: 1, schoolGradeId: 101, schoolClassId: 1001, subjectId: 1, memberId: 1 } } }) // 新增学生 Mock.mock('/api/student', 'post', () => { return { code: 200, msg: '新增成功' } }) // 修改学生 Mock.mock('/api/student', 'put', () => { return { code: 200, msg: '修改成功' } }) // 删除学生 Mock.mock(/\/api\/student\/\d+/, 'delete', () => { return { code: 200, msg: '删除成功' } }) // 批量导入 Mock.mock('/api/student/import', 'post', () => { return { code: 200, msg: '导入成功', data: { successCount: 98, failCount: 2, failList: [ { row: 5, reason: '学号已存在' }, { row: 10, reason: '学校信息不匹配' } ] } } }) // 搜索会员 Mock.mock(/\/api\/member\/search/, 'get', () => { return { code: 200, msg: '查询成功', rows: [ { memberId: 1, nickname: '张三家长', phone: '13812345678' }, { memberId: 2, nickname: '李四家长', phone: '13912345678' } ] } }) ``` --- ## 7. 状态管理 本模块主要使用组件内部状态,暂不需要Pinia全局状态管理。 如需共享状态(如学校树数据缓存),可使用: ```javascript // src/store/modules/school.js import { defineStore } from 'pinia' import { getSchoolTree } from '@/api/student' export const useSchoolStore = defineStore('school', { state: () => ({ schoolTree: [], loading: false }), actions: { async fetchSchoolTree() { if (this.schoolTree.length > 0) return this.schoolTree this.loading = true try { const res = await getSchoolTree() this.schoolTree = res.data || [] return this.schoolTree } finally { this.loading = false } }, clearCache() { this.schoolTree = [] } } }) ``` --- ## 8. 编码规范 ### 8.1 表格规范 ```vue ``` ### 8.2 手机号脱敏 ```vue ``` ### 8.3 删除确认 ```javascript const handleDelete = (row) => { ElMessageBox.confirm(`确定要删除学生"${row.studentName}"吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { const res = await deleteStudent(row.studentId) if (res.code === 200) { ElMessage.success('删除成功') handleQuery() } }).catch(() => {}) } ``` --- ## 9. 测试方案 ### 9.1 功能测试用例 | 用例编号 | 测试场景 | 测试步骤 | 预期结果 | |---------|---------|----------|----------| | TC-001 | 学生列表查询 | 进入学生管理页面 | 正确显示学生列表和学校树 | | TC-002 | 条件筛选 | 输入姓名点击查询 | 列表按条件过滤 | | TC-003 | 学校树筛选 | 点击学校树节点 | 列表按学校/年级/班级过滤 | | TC-004 | 新增学生 | 点击新增,填写表单提交 | 新增成功,列表刷新 | | TC-005 | 编辑学生 | 点击编辑,修改信息提交 | 修改成功,列表刷新 | | TC-006 | 删除学生 | 点击删除,确认 | 删除成功,列表刷新 | | TC-007 | 批量导入 | 下载模板,填写数据,上传 | 导入成功,显示结果 | | TC-008 | 必填校验 | 不填必填项提交 | 提示错误信息 | | TC-009 | 学号重复校验 | 输入已存在学号 | 提示学号已存在 | | TC-010 | 分页功能 | 切换页码和每页条数 | 分页正确 | ### 9.2 兼容性测试 | 浏览器 | 版本 | 测试结果 | |--------|------|----------| | Chrome | 最新两版本 | 待测试 | | Edge | 最新两版本 | 待测试 | | Firefox | 最新两版本 | 待测试 | --- *文档结束*