2026-01-31 23:09:12 +08:00
|
|
|
|
# 学校管理模块 - 完整开发计划 (Day 3-7)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📋 项目概览
|
|
|
|
|
|
|
|
|
|
|
|
**当前进度:** 70% (后端95%完成)
|
|
|
|
|
|
**剩余工作:** 前端开发、数据权限、接口联调、测试验收
|
|
|
|
|
|
**预计完成时间:** Day 7 (2026-02-06)
|
2026-01-31 23:14:11 +08:00
|
|
|
|
**开发团队 | pangu
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🎯 整体目标
|
|
|
|
|
|
|
|
|
|
|
|
完成学校管理模块的所有剩余开发任务,包括:
|
|
|
|
|
|
1. ✅ 后端开发 (已完成95%)
|
|
|
|
|
|
2. ⏳ 前端开发 (0% → 100%)
|
|
|
|
|
|
3. ⏳ 数据权限 (0% → 100%)
|
|
|
|
|
|
4. ⏳ 接口联调 (0% → 100%)
|
|
|
|
|
|
5. ⏳ 测试验收 (20% → 100%)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📅 Day 3: 前端基础框架 (8小时)
|
|
|
|
|
|
|
|
|
|
|
|
### 任务清单
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-01: 创建主页面框架 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/views/school/index.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="school-management">
|
|
|
|
|
|
<!-- 左侧:区域树 -->
|
|
|
|
|
|
<div class="left-panel">
|
|
|
|
|
|
<RegionTree @node-click="handleRegionClick" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 右侧:学校树和操作 -->
|
|
|
|
|
|
<div class="right-panel">
|
|
|
|
|
|
<!-- 搜索栏 -->
|
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
|
<el-input placeholder="搜索学校" />
|
|
|
|
|
|
<el-button type="primary">新增学校</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 学校树 -->
|
|
|
|
|
|
<SchoolTree :region-id="selectedRegionId" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 左右分栏布局完成
|
|
|
|
|
|
- ✅ 响应式设计
|
|
|
|
|
|
- ✅ 基础样式美观
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-02: RegionTree组件 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/components/school/RegionTree.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="region-tree">
|
|
|
|
|
|
<el-tree
|
|
|
|
|
|
:data="regionData"
|
|
|
|
|
|
:props="treeProps"
|
|
|
|
|
|
node-key="regionId"
|
|
|
|
|
|
highlight-current
|
|
|
|
|
|
@node-click="handleNodeClick"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
regionData: [],
|
|
|
|
|
|
treeProps: {
|
|
|
|
|
|
label: 'regionName',
|
|
|
|
|
|
children: 'children'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.loadRegionTree()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async loadRegionTree() {
|
|
|
|
|
|
// 调用区域树接口
|
|
|
|
|
|
const res = await this.$api.region.getTree()
|
|
|
|
|
|
this.regionData = res.data
|
|
|
|
|
|
},
|
|
|
|
|
|
handleNodeClick(node) {
|
|
|
|
|
|
this.$emit('node-click', node.regionId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 区域树正确展示
|
|
|
|
|
|
- ✅ 节点点击事件触发
|
|
|
|
|
|
- ✅ 高亮选中状态
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-03: SchoolTree组件 (4h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/components/school/SchoolTree.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="school-tree">
|
|
|
|
|
|
<el-table
|
|
|
|
|
|
:data="treeData"
|
|
|
|
|
|
row-key="id"
|
|
|
|
|
|
:tree-props="{children: 'children'}"
|
|
|
|
|
|
border
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-table-column prop="name" label="名称" width="300" />
|
|
|
|
|
|
<el-table-column prop="code" label="编码" width="150" />
|
|
|
|
|
|
<el-table-column prop="type" label="类型" width="100">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<el-tag v-if="scope.row.type === 'school'" type="primary">学校</el-tag>
|
|
|
|
|
|
<el-tag v-else-if="scope.row.type === 'grade'" type="success">年级</el-tag>
|
|
|
|
|
|
<el-tag v-else type="info">班级</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column prop="status" label="状态" width="80">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<el-tag v-if="scope.row.status === '0'" type="success">正常</el-tag>
|
|
|
|
|
|
<el-tag v-else type="danger">停用</el-tag>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
<el-table-column label="操作" width="300" fixed="right">
|
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
|
<!-- 学校操作 -->
|
|
|
|
|
|
<template v-if="scope.row.type === 'school'">
|
|
|
|
|
|
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
|
|
|
|
|
<el-button size="small" @click="handleBindGrade(scope.row)">挂载年级</el-button>
|
|
|
|
|
|
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 年级操作 -->
|
|
|
|
|
|
<template v-else-if="scope.row.type === 'grade'">
|
|
|
|
|
|
<el-button size="small" @click="handleBindClass(scope.row)">挂载班级</el-button>
|
|
|
|
|
|
<el-button size="small" type="danger" @click="handleDeleteGrade(scope.row)">删除</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 班级操作 -->
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-button size="small" type="danger" @click="handleDeleteClass(scope.row)">删除</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-table-column>
|
|
|
|
|
|
</el-table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
props: {
|
|
|
|
|
|
regionId: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: null
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
treeData: []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
regionId: {
|
|
|
|
|
|
handler(val) {
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
this.loadSchoolTree()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
immediate: true
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async loadSchoolTree() {
|
|
|
|
|
|
const res = await this.$api.school.getTree(this.regionId)
|
|
|
|
|
|
this.treeData = res.data
|
|
|
|
|
|
},
|
|
|
|
|
|
handleEdit(row) {
|
|
|
|
|
|
this.$emit('edit', row)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleBindGrade(row) {
|
|
|
|
|
|
this.$emit('bind-grade', row)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleBindClass(row) {
|
|
|
|
|
|
this.$emit('bind-class', row)
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleDelete(row) {
|
|
|
|
|
|
await this.$confirm('确认删除该学校吗?')
|
|
|
|
|
|
await this.$api.school.delete(row.schoolId)
|
|
|
|
|
|
this.$message.success('删除成功')
|
|
|
|
|
|
this.loadSchoolTree()
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleDeleteGrade(row) {
|
|
|
|
|
|
await this.$confirm('确认删除该年级吗?')
|
|
|
|
|
|
await this.$api.school.deleteGrade(row.schoolGradeId)
|
|
|
|
|
|
this.$message.success('删除成功')
|
|
|
|
|
|
this.loadSchoolTree()
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleDeleteClass(row) {
|
|
|
|
|
|
await this.$confirm('确认删除该班级吗?')
|
|
|
|
|
|
await this.$api.school.deleteClass(row.schoolClassId)
|
|
|
|
|
|
this.$message.success('删除成功')
|
|
|
|
|
|
this.loadSchoolTree()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 树形表格正确展示
|
|
|
|
|
|
- ✅ 三级树结构完整
|
|
|
|
|
|
- ✅ 操作按钮功能正常
|
|
|
|
|
|
- ✅ 删除操作有确认提示
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📅 Day 4: 前端弹窗与API (8小时)
|
|
|
|
|
|
|
|
|
|
|
|
### 任务清单
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-04: 学校编辑弹窗 (3h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/components/school/SchoolDialog.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-dialog
|
|
|
|
|
|
:title="isEdit ? '编辑学校' : '新增学校'"
|
|
|
|
|
|
v-model="visible"
|
|
|
|
|
|
width="600px"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
|
|
|
|
|
<el-form-item label="学校名称" prop="schoolName">
|
|
|
|
|
|
<el-input v-model="form.schoolName" placeholder="请输入学校名称" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="学校类型" prop="schoolType">
|
|
|
|
|
|
<el-select v-model="form.schoolType" placeholder="请选择学校类型">
|
|
|
|
|
|
<el-option label="小学" value="01" />
|
|
|
|
|
|
<el-option label="初中" value="02" />
|
|
|
|
|
|
<el-option label="高中" value="03" />
|
|
|
|
|
|
<el-option label="完全中学" value="04" />
|
|
|
|
|
|
<el-option label="其他" value="99" />
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="所属区域" prop="regionId">
|
|
|
|
|
|
<el-cascader
|
|
|
|
|
|
v-model="form.regionId"
|
|
|
|
|
|
:options="regionOptions"
|
|
|
|
|
|
:props="cascaderProps"
|
|
|
|
|
|
placeholder="请选择所属区域"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="详细地址" prop="address">
|
|
|
|
|
|
<el-input v-model="form.address" type="textarea" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="联系人" prop="contactPerson">
|
|
|
|
|
|
<el-input v-model="form.contactPerson" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="联系电话" prop="contactPhone">
|
|
|
|
|
|
<el-input v-model="form.contactPhone" />
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item label="状态" prop="status">
|
|
|
|
|
|
<el-radio-group v-model="form.status">
|
|
|
|
|
|
<el-radio label="0">正常</el-radio>
|
|
|
|
|
|
<el-radio label="1">停用</el-radio>
|
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="visible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
isEdit: false,
|
|
|
|
|
|
form: {
|
|
|
|
|
|
schoolName: '',
|
|
|
|
|
|
schoolType: '',
|
|
|
|
|
|
regionId: null,
|
|
|
|
|
|
address: '',
|
|
|
|
|
|
contactPerson: '',
|
|
|
|
|
|
contactPhone: '',
|
|
|
|
|
|
status: '0'
|
|
|
|
|
|
},
|
|
|
|
|
|
rules: {
|
|
|
|
|
|
schoolName: [
|
|
|
|
|
|
{ required: true, message: '请输入学校名称', trigger: 'blur' }
|
|
|
|
|
|
],
|
|
|
|
|
|
schoolType: [
|
|
|
|
|
|
{ required: true, message: '请选择学校类型', trigger: 'change' }
|
|
|
|
|
|
],
|
|
|
|
|
|
regionId: [
|
|
|
|
|
|
{ required: true, message: '请选择所属区域', trigger: 'change' }
|
|
|
|
|
|
],
|
|
|
|
|
|
contactPhone: [
|
|
|
|
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
regionOptions: [],
|
|
|
|
|
|
cascaderProps: {
|
|
|
|
|
|
label: 'regionName',
|
|
|
|
|
|
value: 'regionId',
|
|
|
|
|
|
children: 'children'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
open(row) {
|
|
|
|
|
|
this.visible = true
|
|
|
|
|
|
this.isEdit = !!row
|
|
|
|
|
|
if (row) {
|
|
|
|
|
|
this.form = { ...row }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.resetForm()
|
|
|
|
|
|
}
|
|
|
|
|
|
this.loadRegionOptions()
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadRegionOptions() {
|
|
|
|
|
|
const res = await this.$api.region.getTree()
|
|
|
|
|
|
this.regionOptions = res.data
|
|
|
|
|
|
},
|
|
|
|
|
|
resetForm() {
|
|
|
|
|
|
this.form = {
|
|
|
|
|
|
schoolName: '',
|
|
|
|
|
|
schoolType: '',
|
|
|
|
|
|
regionId: null,
|
|
|
|
|
|
address: '',
|
|
|
|
|
|
contactPerson: '',
|
|
|
|
|
|
contactPhone: '',
|
|
|
|
|
|
status: '0'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleSubmit() {
|
|
|
|
|
|
await this.$refs.formRef.validate()
|
|
|
|
|
|
|
|
|
|
|
|
if (this.isEdit) {
|
|
|
|
|
|
await this.$api.school.update(this.form)
|
|
|
|
|
|
this.$message.success('修改成功')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await this.$api.school.create(this.form)
|
|
|
|
|
|
this.$message.success('新增成功')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.visible = false
|
|
|
|
|
|
this.$emit('success')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 表单字段完整
|
|
|
|
|
|
- ✅ 表单验证正确
|
|
|
|
|
|
- ✅ 新增/编辑功能正常
|
|
|
|
|
|
- ✅ 区域级联选择器正常
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-05: 年级挂载弹窗 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/components/school/BindGradeDialog.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-dialog title="挂载年级" v-model="visible" width="500px">
|
|
|
|
|
|
<el-checkbox-group v-model="selectedGrades">
|
|
|
|
|
|
<el-checkbox
|
|
|
|
|
|
v-for="grade in gradeList"
|
|
|
|
|
|
:key="grade.gradeId"
|
|
|
|
|
|
:label="grade.gradeId"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ grade.gradeName }}
|
|
|
|
|
|
</el-checkbox>
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="visible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
schoolId: null,
|
|
|
|
|
|
gradeList: [],
|
|
|
|
|
|
selectedGrades: []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async open(schoolId) {
|
|
|
|
|
|
this.visible = true
|
|
|
|
|
|
this.schoolId = schoolId
|
|
|
|
|
|
await this.loadGradeList()
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadGradeList() {
|
|
|
|
|
|
const res = await this.$api.grade.getList()
|
|
|
|
|
|
this.gradeList = res.data
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleSubmit() {
|
|
|
|
|
|
if (this.selectedGrades.length === 0) {
|
|
|
|
|
|
this.$message.warning('请至少选择一个年级')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.$api.school.bindGrades({
|
|
|
|
|
|
schoolId: this.schoolId,
|
|
|
|
|
|
gradeIds: this.selectedGrades
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
this.$message.success('挂载成功')
|
|
|
|
|
|
this.visible = false
|
|
|
|
|
|
this.$emit('success')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 年级列表正确展示
|
|
|
|
|
|
- ✅ 多选功能正常
|
|
|
|
|
|
- ✅ 挂载接口调用成功
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-06: 班级挂载弹窗 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/components/school/BindClassDialog.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-dialog title="挂载班级" v-model="visible" width="500px">
|
|
|
|
|
|
<el-checkbox-group v-model="selectedClasses">
|
|
|
|
|
|
<el-checkbox
|
|
|
|
|
|
v-for="cls in classList"
|
|
|
|
|
|
:key="cls.classId"
|
|
|
|
|
|
:label="cls.classId"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ cls.className }}
|
|
|
|
|
|
</el-checkbox>
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<el-button @click="visible = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
visible: false,
|
|
|
|
|
|
schoolGradeId: null,
|
|
|
|
|
|
classList: [],
|
|
|
|
|
|
selectedClasses: []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async open(schoolGradeId) {
|
|
|
|
|
|
this.visible = true
|
|
|
|
|
|
this.schoolGradeId = schoolGradeId
|
|
|
|
|
|
await this.loadClassList()
|
|
|
|
|
|
},
|
|
|
|
|
|
async loadClassList() {
|
|
|
|
|
|
const res = await this.$api.class.getList()
|
|
|
|
|
|
this.classList = res.data
|
|
|
|
|
|
},
|
|
|
|
|
|
async handleSubmit() {
|
|
|
|
|
|
if (this.selectedClasses.length === 0) {
|
|
|
|
|
|
this.$message.warning('请至少选择一个班级')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await this.$api.school.bindClasses({
|
|
|
|
|
|
schoolGradeId: this.schoolGradeId,
|
|
|
|
|
|
classIds: this.selectedClasses
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
this.$message.success('挂载成功')
|
|
|
|
|
|
this.visible = false
|
|
|
|
|
|
this.$emit('success')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 班级列表正确展示
|
|
|
|
|
|
- ✅ 多选功能正常
|
|
|
|
|
|
- ✅ 挂载接口调用成功
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-07: API封装 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/api/school.js`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```javascript
|
|
|
|
|
|
import request from '@/utils/request'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
// 获取学校树
|
|
|
|
|
|
getTree(regionId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school/tree',
|
|
|
|
|
|
method: 'get',
|
|
|
|
|
|
params: { regionId }
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取学校列表(分页)
|
|
|
|
|
|
getList(query) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school/list',
|
|
|
|
|
|
method: 'get',
|
|
|
|
|
|
params: query
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取学校详情
|
|
|
|
|
|
getDetail(schoolId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/school/${schoolId}`,
|
|
|
|
|
|
method: 'get'
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 新增学校
|
|
|
|
|
|
create(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 修改学校
|
|
|
|
|
|
update(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school',
|
|
|
|
|
|
method: 'put',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 删除学校
|
|
|
|
|
|
delete(schoolId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/school/${schoolId}`,
|
|
|
|
|
|
method: 'delete'
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 挂载年级
|
|
|
|
|
|
bindGrades(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school/bindGrades',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 挂载班级
|
|
|
|
|
|
bindClasses(data) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: '/api/school/bindClasses',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 删除学校年级
|
|
|
|
|
|
deleteGrade(schoolGradeId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/school/grade/${schoolGradeId}`,
|
|
|
|
|
|
method: 'delete'
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 删除学校班级
|
|
|
|
|
|
deleteClass(schoolClassId) {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: `/api/school/class/${schoolClassId}`,
|
|
|
|
|
|
method: 'delete'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 所有接口封装完成
|
|
|
|
|
|
- ✅ 请求参数正确
|
|
|
|
|
|
- ✅ 响应处理正确
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📅 Day 5: 数据权限与联调 (8小时)
|
|
|
|
|
|
|
|
|
|
|
|
### 任务清单
|
|
|
|
|
|
|
|
|
|
|
|
#### BE-SCH-12: 数据权限控制 (3h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标:** 实现分公司用户只能看到自己区域的学校数据
|
|
|
|
|
|
|
|
|
|
|
|
**修改文件:**
|
|
|
|
|
|
- `SchoolServiceImpl.java`
|
|
|
|
|
|
- `SchoolMapper.xml`
|
|
|
|
|
|
|
|
|
|
|
|
**实现步骤:**
|
|
|
|
|
|
|
|
|
|
|
|
1. 在Service方法上添加@DataScope注解
|
|
|
|
|
|
```java
|
|
|
|
|
|
@Override
|
|
|
|
|
|
@DataScope(deptAlias = "s", userAlias = "s")
|
|
|
|
|
|
public List<SchoolVO> selectSchoolList(SchoolQueryDTO query) {
|
|
|
|
|
|
return schoolMapper.selectSchoolList(query);
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
2. 在Mapper XML中添加数据权限SQL片段
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<sql id="dataScopeFilter">
|
|
|
|
|
|
<!-- 数据权限过滤 -->
|
|
|
|
|
|
${params.dataScope}
|
|
|
|
|
|
</sql>
|
|
|
|
|
|
|
|
|
|
|
|
<select id="selectSchoolList" resultMap="SchoolVOResult">
|
|
|
|
|
|
<include refid="selectSchoolVo"/>
|
|
|
|
|
|
<where>
|
|
|
|
|
|
<if test="regionId != null">
|
|
|
|
|
|
AND s.region_id = #{regionId}
|
|
|
|
|
|
</if>
|
|
|
|
|
|
<if test="schoolName != null and schoolName != ''">
|
|
|
|
|
|
AND s.school_name LIKE CONCAT('%', #{schoolName}, '%')
|
|
|
|
|
|
</if>
|
|
|
|
|
|
<if test="status != null and status != ''">
|
|
|
|
|
|
AND s.status = #{status}
|
|
|
|
|
|
</if>
|
|
|
|
|
|
<!-- 数据权限 -->
|
|
|
|
|
|
<include refid="dataScopeFilter"/>
|
|
|
|
|
|
</where>
|
|
|
|
|
|
ORDER BY s.create_time DESC
|
|
|
|
|
|
</select>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
3. 配置用户-区域关联关系
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- 在用户表中关联区域ID
|
|
|
|
|
|
-- 或通过角色-区域关联表实现
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 管理员可以看到所有学校
|
|
|
|
|
|
- ✅ 分公司用户只能看到自己区域的学校
|
|
|
|
|
|
- ✅ 数据权限不影响性能
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### BE-SCH-14: 接口联调与Bug修复 (3h)
|
|
|
|
|
|
|
|
|
|
|
|
**测试清单:**
|
|
|
|
|
|
|
|
|
|
|
|
1. **学校CRUD测试**
|
|
|
|
|
|
- ✅ 新增学校
|
|
|
|
|
|
- ✅ 修改学校
|
|
|
|
|
|
- ✅ 删除学校
|
|
|
|
|
|
- ✅ 查询学校列表
|
|
|
|
|
|
- ✅ 查询学校详情
|
|
|
|
|
|
- ✅ 查询学校树
|
|
|
|
|
|
|
|
|
|
|
|
2. **年级挂载测试**
|
|
|
|
|
|
- ✅ 挂载年级
|
|
|
|
|
|
- ✅ 重复挂载(去重)
|
|
|
|
|
|
- ✅ 删除年级
|
|
|
|
|
|
- ✅ 删除有班级的年级(应失败)
|
|
|
|
|
|
|
|
|
|
|
|
3. **班级挂载测试**
|
|
|
|
|
|
- ✅ 挂载班级
|
|
|
|
|
|
- ✅ 重复挂载(去重)
|
|
|
|
|
|
- ✅ 删除班级
|
|
|
|
|
|
|
|
|
|
|
|
4. **数据权限测试**
|
|
|
|
|
|
- ✅ 管理员权限
|
|
|
|
|
|
- ✅ 分公司用户权限
|
|
|
|
|
|
|
|
|
|
|
|
**Bug修复流程:**
|
|
|
|
|
|
1. 记录Bug现象
|
|
|
|
|
|
2. 定位Bug原因
|
|
|
|
|
|
3. 修复Bug
|
|
|
|
|
|
4. 回归测试
|
|
|
|
|
|
5. 更新文档
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 所有接口测试通过
|
|
|
|
|
|
- ✅ 发现的Bug全部修复
|
|
|
|
|
|
- ✅ 无遗留问题
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-08: 主页面逻辑整合 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
2026-02-03 20:56:15 +08:00
|
|
|
|
- `frontend/src/views/school/index.vue`
|
2026-01-31 23:09:12 +08:00
|
|
|
|
|
|
|
|
|
|
**实现内容:**
|
|
|
|
|
|
```vue
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="school-management">
|
|
|
|
|
|
<div class="left-panel">
|
|
|
|
|
|
<RegionTree @node-click="handleRegionClick" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="right-panel">
|
|
|
|
|
|
<div class="search-bar">
|
|
|
|
|
|
<el-input
|
|
|
|
|
|
v-model="searchKeyword"
|
|
|
|
|
|
placeholder="搜索学校"
|
|
|
|
|
|
@keyup.enter="handleSearch"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-button type="primary" @click="handleAdd">新增学校</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<SchoolTree
|
|
|
|
|
|
ref="schoolTree"
|
|
|
|
|
|
:region-id="selectedRegionId"
|
|
|
|
|
|
@edit="handleEdit"
|
|
|
|
|
|
@bind-grade="handleBindGrade"
|
|
|
|
|
|
@bind-class="handleBindClass"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 弹窗 -->
|
|
|
|
|
|
<SchoolDialog ref="schoolDialog" @success="refreshTree" />
|
|
|
|
|
|
<BindGradeDialog ref="bindGradeDialog" @success="refreshTree" />
|
|
|
|
|
|
<BindClassDialog ref="bindClassDialog" @success="refreshTree" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import RegionTree from '@/components/school/RegionTree.vue'
|
|
|
|
|
|
import SchoolTree from '@/components/school/SchoolTree.vue'
|
|
|
|
|
|
import SchoolDialog from '@/components/school/SchoolDialog.vue'
|
|
|
|
|
|
import BindGradeDialog from '@/components/school/BindGradeDialog.vue'
|
|
|
|
|
|
import BindClassDialog from '@/components/school/BindClassDialog.vue'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
components: {
|
|
|
|
|
|
RegionTree,
|
|
|
|
|
|
SchoolTree,
|
|
|
|
|
|
SchoolDialog,
|
|
|
|
|
|
BindGradeDialog,
|
|
|
|
|
|
BindClassDialog
|
|
|
|
|
|
},
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
selectedRegionId: null,
|
|
|
|
|
|
searchKeyword: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
handleRegionClick(regionId) {
|
|
|
|
|
|
this.selectedRegionId = regionId
|
|
|
|
|
|
},
|
|
|
|
|
|
handleAdd() {
|
|
|
|
|
|
this.$refs.schoolDialog.open()
|
|
|
|
|
|
},
|
|
|
|
|
|
handleEdit(row) {
|
|
|
|
|
|
this.$refs.schoolDialog.open(row)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleBindGrade(row) {
|
|
|
|
|
|
this.$refs.bindGradeDialog.open(row.schoolId)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleBindClass(row) {
|
|
|
|
|
|
this.$refs.bindClassDialog.open(row.schoolGradeId)
|
|
|
|
|
|
},
|
|
|
|
|
|
handleSearch() {
|
|
|
|
|
|
this.$refs.schoolTree.search(this.searchKeyword)
|
|
|
|
|
|
},
|
|
|
|
|
|
refreshTree() {
|
|
|
|
|
|
this.$refs.schoolTree.loadSchoolTree()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.school-management {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
height: calc(100vh - 120px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.left-panel {
|
|
|
|
|
|
width: 300px;
|
|
|
|
|
|
border-right: 1px solid #dcdfe6;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.right-panel {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.search-bar .el-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 所有组件正确集成
|
|
|
|
|
|
- ✅ 事件传递正常
|
|
|
|
|
|
- ✅ 页面交互流畅
|
|
|
|
|
|
- ✅ 样式美观
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📅 Day 6: 样式优化与测试 (8小时)
|
|
|
|
|
|
|
|
|
|
|
|
### 任务清单
|
|
|
|
|
|
|
|
|
|
|
|
#### FE-SCH-09: 样式优化 (3h)
|
|
|
|
|
|
|
|
|
|
|
|
**优化内容:**
|
|
|
|
|
|
|
|
|
|
|
|
1. **响应式布局**
|
|
|
|
|
|
- 适配不同屏幕尺寸
|
|
|
|
|
|
- 移动端友好
|
|
|
|
|
|
|
|
|
|
|
|
2. **交互优化**
|
|
|
|
|
|
- 加载状态提示
|
|
|
|
|
|
- 操作反馈动画
|
|
|
|
|
|
- 错误提示优化
|
|
|
|
|
|
|
|
|
|
|
|
3. **视觉优化**
|
|
|
|
|
|
- 统一配色方案
|
|
|
|
|
|
- 图标美化
|
|
|
|
|
|
- 间距调整
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 页面美观大方
|
|
|
|
|
|
- ✅ 交互流畅自然
|
|
|
|
|
|
- ✅ 响应式效果良好
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### TEST-01: 接口测试 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**测试工具:** Postman
|
|
|
|
|
|
|
|
|
|
|
|
**测试用例:**
|
|
|
|
|
|
|
|
|
|
|
|
| 接口 | 方法 | 测试场景 | 预期结果 |
|
|
|
|
|
|
|-----|------|---------|---------|
|
|
|
|
|
|
| /api/school/tree | GET | 查询学校树 | 返回树形结构 |
|
|
|
|
|
|
| /api/school/list | GET | 查询学校列表 | 返回分页数据 |
|
|
|
|
|
|
| /api/school/{id} | GET | 查询学校详情 | 返回学校信息 |
|
|
|
|
|
|
| /api/school | POST | 新增学校 | 返回成功 |
|
|
|
|
|
|
| /api/school | PUT | 修改学校 | 返回成功 |
|
|
|
|
|
|
| /api/school/{id} | DELETE | 删除学校 | 返回成功 |
|
|
|
|
|
|
| /api/school/bindGrades | POST | 挂载年级 | 返回成功 |
|
|
|
|
|
|
| /api/school/bindClasses | POST | 挂载班级 | 返回成功 |
|
|
|
|
|
|
| /api/school/grade/{id} | DELETE | 删除年级 | 返回成功 |
|
|
|
|
|
|
| /api/school/class/{id} | DELETE | 删除班级 | 返回成功 |
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 所有接口测试通过
|
|
|
|
|
|
- ✅ 响应时间 < 500ms
|
|
|
|
|
|
- ✅ 错误处理正确
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### TEST-02: 功能测试 (2h)
|
|
|
|
|
|
|
|
|
|
|
|
**测试场景:**
|
|
|
|
|
|
|
|
|
|
|
|
1. **学校管理流程**
|
|
|
|
|
|
- ✅ 新增学校 → 查看 → 编辑 → 删除
|
|
|
|
|
|
|
|
|
|
|
|
2. **年级管理流程**
|
|
|
|
|
|
- ✅ 挂载年级 → 查看 → 删除
|
|
|
|
|
|
|
|
|
|
|
|
3. **班级管理流程**
|
|
|
|
|
|
- ✅ 挂载班级 → 查看 → 删除
|
|
|
|
|
|
|
|
|
|
|
|
4. **数据权限验证**
|
|
|
|
|
|
- ✅ 管理员登录 → 查看所有学校
|
|
|
|
|
|
- ✅ 分公司用户登录 → 只看到自己区域
|
|
|
|
|
|
|
|
|
|
|
|
5. **异常场景测试**
|
|
|
|
|
|
- ✅ 删除有子级的学校 → 提示错误
|
|
|
|
|
|
- ✅ 重复挂载年级 → 自动去重
|
|
|
|
|
|
- ✅ 必填项为空 → 提示错误
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 所有功能正常
|
|
|
|
|
|
- ✅ 异常处理正确
|
|
|
|
|
|
- ✅ 用户体验良好
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### TEST-03: 集成测试 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**测试内容:**
|
|
|
|
|
|
|
|
|
|
|
|
1. **前后端集成**
|
|
|
|
|
|
- ✅ 接口调用正常
|
|
|
|
|
|
- ✅ 数据展示正确
|
|
|
|
|
|
- ✅ 操作反馈及时
|
|
|
|
|
|
|
|
|
|
|
|
2. **数据一致性**
|
|
|
|
|
|
- ✅ 新增后立即可见
|
|
|
|
|
|
- ✅ 修改后立即更新
|
|
|
|
|
|
- ✅ 删除后立即消失
|
|
|
|
|
|
|
|
|
|
|
|
3. **性能测试**
|
|
|
|
|
|
- ✅ 页面加载时间 < 2s
|
|
|
|
|
|
- ✅ 树形数据渲染流畅
|
|
|
|
|
|
- ✅ 批量操作不卡顿
|
|
|
|
|
|
|
|
|
|
|
|
**验收标准:**
|
|
|
|
|
|
- ✅ 集成测试全部通过
|
|
|
|
|
|
- ✅ 性能指标达标
|
|
|
|
|
|
- ✅ 无遗留问题
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📅 Day 7: 文档完善与最终验收 (4小时)
|
|
|
|
|
|
|
|
|
|
|
|
### 任务清单
|
|
|
|
|
|
|
|
|
|
|
|
#### DOC-01: API文档生成 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**工具:** Swagger
|
|
|
|
|
|
|
|
|
|
|
|
**内容:**
|
|
|
|
|
|
- ✅ 接口列表
|
|
|
|
|
|
- ✅ 请求参数说明
|
|
|
|
|
|
- ✅ 响应格式说明
|
|
|
|
|
|
- ✅ 错误码说明
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### DOC-02: 用户手册 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
|
|
|
|
|
- `docs/05-模块技术方案/学校管理/用户手册.md`
|
|
|
|
|
|
|
|
|
|
|
|
**内容:**
|
|
|
|
|
|
1. 功能介绍
|
|
|
|
|
|
2. 操作指南
|
|
|
|
|
|
3. 常见问题
|
|
|
|
|
|
4. 注意事项
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### DOC-03: 开发总结报告 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**目标文件:**
|
|
|
|
|
|
- `docs/05-模块技术方案/学校管理/最终开发总结报告.md`
|
|
|
|
|
|
|
|
|
|
|
|
**内容:**
|
|
|
|
|
|
1. 项目概述
|
|
|
|
|
|
2. 完成情况统计
|
|
|
|
|
|
3. 技术亮点总结
|
|
|
|
|
|
4. 遇到的问题与解决方案
|
|
|
|
|
|
5. 经验教训
|
|
|
|
|
|
6. 后续优化建议
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
#### FINAL: 最终验收准备 (1h)
|
|
|
|
|
|
|
|
|
|
|
|
**准备内容:**
|
|
|
|
|
|
|
|
|
|
|
|
1. **代码检查**
|
|
|
|
|
|
- ✅ 代码编译通过
|
|
|
|
|
|
- ✅ 无Lint错误
|
|
|
|
|
|
- ✅ 注释完整
|
|
|
|
|
|
|
|
|
|
|
|
2. **功能演示**
|
|
|
|
|
|
- ✅ 准备演示数据
|
|
|
|
|
|
- ✅ 准备演示流程
|
|
|
|
|
|
- ✅ 准备演示环境
|
|
|
|
|
|
|
|
|
|
|
|
3. **文档整理**
|
|
|
|
|
|
- ✅ 所有文档齐全
|
|
|
|
|
|
- ✅ 文档格式统一
|
|
|
|
|
|
- ✅ 文档内容准确
|
|
|
|
|
|
|
|
|
|
|
|
4. **交付清单**
|
|
|
|
|
|
- ✅ 源代码
|
|
|
|
|
|
- ✅ 数据库脚本
|
|
|
|
|
|
- ✅ 技术文档
|
|
|
|
|
|
- ✅ 用户手册
|
|
|
|
|
|
- ✅ 测试报告
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📊 工时统计
|
|
|
|
|
|
|
|
|
|
|
|
| 阶段 | 任务数 | 计划工时 | 预计完成日期 |
|
|
|
|
|
|
|-----|:-----:|:-------:|:----------:|
|
|
|
|
|
|
| Day 3 | 3 | 8h | 2026-02-01 |
|
|
|
|
|
|
| Day 4 | 4 | 8h | 2026-02-02 |
|
|
|
|
|
|
| Day 5 | 3 | 8h | 2026-02-03 |
|
|
|
|
|
|
| Day 6 | 3 | 8h | 2026-02-04 |
|
|
|
|
|
|
| Day 7 | 4 | 4h | 2026-02-05 |
|
|
|
|
|
|
| **总计** | **17** | **36h** | - |
|
|
|
|
|
|
|
|
|
|
|
|
**累计工时(含Day 1-2):** 16.4h + 36h = 52.4h
|
|
|
|
|
|
**原计划工时:** 53.5h
|
|
|
|
|
|
**预计提前:** 1.1h ✨
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🎯 验收标准
|
|
|
|
|
|
|
|
|
|
|
|
### 功能验收
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 所有功能点实现完整
|
|
|
|
|
|
- ✅ 业务流程正确
|
|
|
|
|
|
- ✅ 数据权限生效
|
|
|
|
|
|
- ✅ 异常处理完善
|
|
|
|
|
|
|
|
|
|
|
|
### 性能验收
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 页面加载时间 < 2s
|
|
|
|
|
|
- ✅ 接口响应时间 < 500ms
|
|
|
|
|
|
- ✅ 树形数据渲染流畅
|
|
|
|
|
|
- ✅ 批量操作不卡顿
|
|
|
|
|
|
|
|
|
|
|
|
### 质量验收
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 代码编译通过
|
|
|
|
|
|
- ✅ 单元测试通过
|
|
|
|
|
|
- ✅ 集成测试通过
|
|
|
|
|
|
- ✅ 代码规范符合标准
|
|
|
|
|
|
- ✅ 注释完整清晰
|
|
|
|
|
|
|
|
|
|
|
|
### 文档验收
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 技术方案文档完整
|
|
|
|
|
|
- ✅ API文档齐全
|
|
|
|
|
|
- ✅ 用户手册清晰
|
|
|
|
|
|
- ✅ 测试报告详细
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 执行策略
|
|
|
|
|
|
|
|
|
|
|
|
### 开发顺序
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Day 3: 前端框架 → 基础组件
|
|
|
|
|
|
Day 4: 弹窗组件 → API封装
|
|
|
|
|
|
Day 5: 数据权限 → 接口联调
|
|
|
|
|
|
Day 6: 样式优化 → 功能测试
|
|
|
|
|
|
Day 7: 文档完善 → 最终验收
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 质量保证
|
|
|
|
|
|
|
|
|
|
|
|
1. **每日编译检查** - 确保代码无错误
|
|
|
|
|
|
2. **每日功能测试** - 确保新功能正常
|
|
|
|
|
|
3. **每日代码审查** - 确保代码质量
|
|
|
|
|
|
4. **每日进度更新** - 确保进度可控
|
|
|
|
|
|
|
|
|
|
|
|
### 风险控制
|
|
|
|
|
|
|
|
|
|
|
|
| 风险 | 应对措施 |
|
|
|
|
|
|
|-----|---------|
|
|
|
|
|
|
| 前端组件开发延期 | 简化UI,先实现核心功能 |
|
|
|
|
|
|
| 数据权限实现困难 | 参考现有模块,复用框架 |
|
|
|
|
|
|
| 接口联调发现Bug | 预留缓冲时间,及时修复 |
|
|
|
|
|
|
| 性能不达标 | 优化SQL,增加缓存 |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📞 沟通机制
|
|
|
|
|
|
|
|
|
|
|
|
### 进度汇报
|
|
|
|
|
|
|
|
|
|
|
|
- **每日下班前** - 更新开发进度文档
|
|
|
|
|
|
- **遇到阻塞** - 立即反馈
|
|
|
|
|
|
- **完成里程碑** - 提交阶段性成果
|
|
|
|
|
|
|
|
|
|
|
|
### 问题升级
|
|
|
|
|
|
|
|
|
|
|
|
- **技术问题** - 先自行研究,30分钟未解决则求助
|
|
|
|
|
|
- **需求问题** - 立即与产品确认
|
|
|
|
|
|
- **环境问题** - 联系运维解决
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ✅ 最终交付物清单
|
|
|
|
|
|
|
|
|
|
|
|
### 代码
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 后端代码(Entity、Mapper、Service、Controller)
|
|
|
|
|
|
- ✅ 前端代码(页面、组件、API)
|
|
|
|
|
|
- ✅ 单元测试代码
|
|
|
|
|
|
|
|
|
|
|
|
### 数据库
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 建表SQL脚本
|
|
|
|
|
|
- ✅ 初始化数据SQL
|
|
|
|
|
|
- ✅ 索引优化SQL
|
|
|
|
|
|
|
|
|
|
|
|
### 文档
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 技术方案文档
|
|
|
|
|
|
- ✅ API接口文档
|
|
|
|
|
|
- ✅ 用户操作手册
|
|
|
|
|
|
- ✅ 开发总结报告
|
|
|
|
|
|
- ✅ 测试报告
|
|
|
|
|
|
|
|
|
|
|
|
### 其他
|
|
|
|
|
|
|
|
|
|
|
|
- ✅ 开发进度记录
|
|
|
|
|
|
- ✅ Bug修复记录
|
|
|
|
|
|
- ✅ 代码审查记录
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🎊 预期成果
|
|
|
|
|
|
|
|
|
|
|
|
完成后,学校管理模块将具备:
|
|
|
|
|
|
|
|
|
|
|
|
1. ✅ **完整的CRUD功能** - 学校、年级、班级的增删改查
|
|
|
|
|
|
2. ✅ **灵活的树形展示** - 三级树形结构,清晰直观
|
|
|
|
|
|
3. ✅ **严格的数据权限** - 分公司用户只能看自己的数据
|
|
|
|
|
|
4. ✅ **完善的数据校验** - 多层校验,保证数据完整性
|
|
|
|
|
|
5. ✅ **优秀的用户体验** - 界面美观,交互流畅
|
|
|
|
|
|
6. ✅ **高质量的代码** - 规范、清晰、易维护
|
|
|
|
|
|
7. ✅ **完整的测试覆盖** - 单元测试、集成测试全覆盖
|
|
|
|
|
|
8. ✅ **齐全的文档资料** - 技术文档、用户手册一应俱全
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**🎯 目标:7天内完成学校管理模块的完整开发,交付高质量的产品!**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
*计划制定时间:2026-01-31 22:00*
|
|
|
|
|
|
*计划执行时间:2026-02-01 至 2026-02-05*
|
2026-01-31 23:14:11 +08:00
|
|
|
|
*制定人:pangu*
|