pangu-user-platform/docs/会员教育信息重构方案.md

819 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 会员教育信息重构方案
> 作者pangu
> 创建时间2026-02-03
> 状态:**已审核通过**
---
## 一、需求变更说明
### 1.1 变更内容
1. **去掉身份类型**:会员不再区分"家长"和"教师"
2. **重构会员信息表**:会员与教育信息变为**一对多**关系,一个会员可以有多个教育身份
3. **pg_member 表移除字段**
- `region_id` - 区域ID
- `school_id` - 学校ID
- `school_grade_id` - 年级ID
- `school_class_id` - 班级ID
- `identity_type` - 身份类型
- ~~`union_id`~~ - **保留**(微信多应用打通需要)
### 1.2 变更原因
- 业务场景:一个用户可能同时是多个学校/班级的教师
- 简化会员模型:会员表只存储基础信息,教育信息独立管理
---
## 二、当前架构分析
### 2.1 现有数据结构
**pg_member 表**(部分字段):
| 字段 | 类型 | 说明 | 变更 |
|------|------|------|------|
| member_id | bigint | 主键 | 保留 |
| member_code | varchar(32) | 会员编号 | 保留 |
| phone | varchar(20) | 手机号 | 保留 |
| password | varchar(100) | 密码 | 保留 |
| nickname | varchar(50) | 昵称 | 保留 |
| avatar | varchar(500) | 头像 | 保留 |
| gender | char(1) | 性别 | 保留 |
| birthday | date | 生日 | 保留 |
| **identity_type** | char(1) | 身份类型 | **删除** |
| open_id | varchar(100) | 微信OpenID | 保留 |
| union_id | varchar(100) | 微信UnionID | 保留 |
| **region_id** | bigint | 区域ID | **删除** |
| **school_id** | bigint | 学校ID | **删除** |
| **school_grade_id** | bigint | 年级ID | **删除** |
| **school_class_id** | bigint | 班级ID | **删除** |
| register_source | char(1) | 注册来源 | 保留 |
| register_time | datetime | 注册时间 | 保留 |
| status | char(1) | 状态 | 保留 |
### 2.2 现有关联关系
```
pg_member会员
├── 一对一:教育信息(存在会员表中) ← 需要改为一对多
└── 一对多pg_student学生 ← 已改为多对多
```
---
## 三、目标架构设计
### 3.1 新数据结构
```
pg_member会员- 只存基础信息
├── 一对多pg_education会员教育信息 ← 新建
└── 多对多pg_member_student ↔ pg_student ← 已重构(支持多会员绑定同一学生)
```
### 3.2 新建表pg_member_education
**设计理念**:一个教育身份 = 一个班级的教学关系
```
张老师教3个班 → 3条教育身份记录
教育身份1: 二中 - 高二 - 1班 - 数学
教育身份2: 二中 - 高二 - 2班 - 数学
教育身份3: 二中 - 高二 - 3班 - 数学
```
```sql
CREATE TABLE pg_member_education (
education_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '教育身份ID',
member_id BIGINT NOT NULL COMMENT '会员ID',
region_id BIGINT COMMENT '区域ID',
school_id BIGINT NOT NULL COMMENT '学校ID',
school_grade_id BIGINT NOT NULL COMMENT '年级IDschool_grade关联ID',
school_class_id BIGINT NOT NULL COMMENT '班级IDschool_class关联ID',
subject_id BIGINT COMMENT '学科ID',
is_default CHAR(1) DEFAULT '0' COMMENT '是否默认身份0否 1是',
status CHAR(1) DEFAULT '0' COMMENT '状态0正常 1停用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志',
KEY idx_member_id (member_id),
KEY idx_school_id (school_id),
KEY idx_class_id (school_class_id)
) COMMENT '会员教育信息表';
```
### 3.3 最终关系图
```
pg_member会员基础信息
├── 一对多 → pg_member_education教育身份每班一条
└── 一对多 → pg_student学生/孩子)
```
---
## 四、影响范围分析
### 4.1 数据库层
| 操作 | 对象 | 说明 |
|------|------|------|
| 修改 | pg_member | 删除5个字段 |
| 新建 | pg_member_education | 会员教育信息表(含班级字段) |
| 迁移 | 现有教育数据 | 迁移到新表 |
### 4.2 后端层
| 文件 | 改动类型 | 说明 |
|------|----------|------|
| `PgMember.java` | 修改 | 删除5个字段保留unionId |
| `PgMemberEducation.java` | 新建 | 教育身份实体 |
| `PgMemberEducationMapper.java` | 新建 | Mapper |
| `H5MemberServiceImpl.java` | 重构 | 教育相关逻辑 |
| `H5EducationDto.java` | 修改 | 添加educationId |
| `H5EducationVo.java` | 修改 | 添加educationId等字段 |
| `H5MemberController.java` | 修改 | 接口调整 |
| `H5MemberInfoVo.java` | 修改 | 删除identityType |
| `PgMemberController.java` | 修改 | 删除身份类型相关 |
| `PgMemberServiceImpl.java` | 修改 | 删除教育信息处理 |
### 4.3 管理后台前端
| 文件 | 改动类型 | 说明 |
|------|----------|------|
| `business/member/index.vue` | 修改 | 删除身份类型筛选和列 |
| `business/member/components/MemberDialog.vue` | 重构 | 删除身份类型和教师信息区块 |
| `business/member/components/EducationTab.vue` | **新建** | 教育身份管理Tab |
| `business/member/components/MemberDetail.vue` | **新建** | 会员详情页含教育身份Tab |
| `api/pangu/member.js` | 修改 | 删除/新增接口 |
| `api/pangu/memberEducation.js` | **新建** | 教育身份接口 |
### 4.4 H5前端
| 文件 | 改动类型 | 说明 |
|------|----------|------|
| `stores/user.ts` | 修改 | 删除 identityType |
| `views/userCenter/index.vue` | 修改 | 教育身份列表支持多个 |
| `components/TeacherIdentityForm.vue` | 修改 | 新增/编辑某个教育身份 |
| `api/user.js` | 修改 | 接口调整 |
---
## 五、接口变更
### 5.1 H5接口变更
#### 5.1.1 获取会员信息(修改)
**接口**`GET /h5/member/info`
**响应变更**
```json
// 修改前
{
"code": 200,
"data": {
"consumerId": 1,
"phone": "138****0000",
"nickname": "测试用户",
"identityType": "2", // ← 删除
"education": { ... }, // 单个
"students": [...]
}
}
// 修改后
{
"code": 200,
"data": {
"consumerId": 1,
"phone": "138****0000",
"nickname": "测试用户",
"educations": [...], // 多个教育身份
"students": [...]
}
}
```
#### 5.1.2 教育身份列表(修改)
**接口**`GET /h5/member/educations`
**响应**
```json
{
"code": 200,
"data": [
{
"educationId": 1,
"schoolId": 1,
"schoolName": "武汉市第二中学",
"schoolGradeId": 5,
"gradeName": "高二",
"schoolClassId": 104,
"className": "1班",
"subjectId": 1,
"subjectName": "数学",
"isDefault": "1"
},
{
"educationId": 2,
"schoolId": 1,
"schoolName": "武汉市第二中学",
"schoolGradeId": 5,
"gradeName": "高二",
"schoolClassId": 105,
"className": "2班",
"subjectId": 1,
"subjectName": "数学",
"isDefault": "0"
}
]
}
```
#### 5.1.3 新增教育身份
**接口**`POST /h5/member/educations`
**请求参数**
```json
{
"schoolId": 1,
"schoolGradeId": 5,
"schoolClassId": 104,
"subjectId": 1
}
```
#### 5.1.4 编辑教育身份
**接口**`PUT /h5/member/educations/{educationId}`
**请求参数**
```json
{
"schoolId": 1,
"schoolGradeId": 5,
"schoolClassId": 104,
"subjectId": 1
}
```
#### 5.1.5 删除教育身份
**接口**`DELETE /h5/member/educations/{educationId}`
#### 5.1.6 设置默认教育身份
**接口**`PUT /h5/member/educations/{educationId}/default`
### 5.2 管理后台接口变更
#### 5.2.1 会员列表(修改)
- 删除 `identityType` 筛选参数
- 响应中删除 `identityType` 字段
#### 5.2.2 会员详情(修改)
- 删除教育信息字段
- 新增 `educations` 列表
#### 5.2.3 新增/编辑会员(修改)
- 删除 `identityType`、`regionId`、`schoolId`、`schoolGradeId`、`schoolClassId` 参数
#### 5.2.4 会员教育身份管理(新增)
| 接口 | 方法 | 说明 |
|------|------|------|
| `/business/member/{memberId}/educations` | GET | 获取会员教育身份列表 |
| `/business/member/{memberId}/education` | POST | 添加教育身份 |
| `/business/member/{memberId}/education/{educationId}` | PUT | 修改教育身份 |
| `/business/member/{memberId}/education/{educationId}` | DELETE | 删除教育身份 |
---
## 六、详细技术方案
### 6.1 数据库改造
#### 6.1.1 DDL脚本
```sql
-- 新建会员教育信息表(一个教育身份 = 一个班级的教学关系)
CREATE TABLE pg_member_education (
education_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '教育身份ID',
member_id BIGINT NOT NULL COMMENT '会员ID',
region_id BIGINT COMMENT '区域ID',
school_id BIGINT NOT NULL COMMENT '学校ID',
school_grade_id BIGINT NOT NULL COMMENT '年级ID',
school_class_id BIGINT NOT NULL COMMENT '班级ID',
subject_id BIGINT COMMENT '学科ID',
is_default CHAR(1) DEFAULT '0' COMMENT '是否默认身份0否 1是',
status CHAR(1) DEFAULT '0' COMMENT '状态0正常 1停用',
tenant_id VARCHAR(20) DEFAULT '000000' COMMENT '租户编号',
create_dept BIGINT COMMENT '创建部门',
create_by BIGINT COMMENT '创建者',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_by BIGINT COMMENT '更新者',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
del_flag CHAR(1) DEFAULT '0' COMMENT '删除标志',
KEY idx_member_id (member_id),
KEY idx_school_id (school_id),
KEY idx_class_id (school_class_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='会员教育信息表';
```
#### 6.1.2 数据迁移脚本
```sql
-- 迁移现有教育数据identity_type = '2' 的教师数据)
INSERT INTO pg_member_education (
member_id, region_id, school_id, school_grade_id, school_class_id, subject_id, is_default, status, tenant_id, create_time
)
SELECT
member_id, region_id, school_id, school_grade_id, school_class_id, NULL, '1', '0', tenant_id, NOW()
FROM pg_member
WHERE identity_type = '2'
AND school_id IS NOT NULL
AND school_class_id IS NOT NULL
AND del_flag = '0';
```
#### 6.1.3 删除字段(迁移完成后执行)
```sql
-- 删除 pg_member 表中的字段(建议先备份)
-- 注意union_id 保留,不删除
ALTER TABLE pg_member
DROP COLUMN identity_type,
DROP COLUMN region_id,
DROP COLUMN school_id,
DROP COLUMN school_grade_id,
DROP COLUMN school_class_id;
```
### 6.2 后端改造
#### 6.2.1 新建实体类
**PgMemberEducation.java**
```java
package org.dromara.pangu.member.domain;
@Data
@TableName("pg_member_education")
public class PgMemberEducation extends BaseEntity {
@TableId(type = IdType.AUTO)
private Long educationId;
private Long memberId;
private Long regionId;
private Long schoolId;
private Long schoolGradeId;
private Long schoolClassId;
private Long subjectId;
private String isDefault;
private String status;
private String tenantId;
@TableLogic
private String delFlag;
// 非数据库字段用于VO展示
@TableField(exist = false)
private String schoolName;
@TableField(exist = false)
private String gradeName;
@TableField(exist = false)
private String className;
@TableField(exist = false)
private String subjectName;
}
```
#### 6.2.2 修改 PgMember.java
删除以下字段:
- identityType
- regionId
- regionIds@TableField(exist = false)
- schoolId
- schoolGradeId
- schoolClassId
**保留字段**
- unionId微信多应用打通需要
#### 6.2.3 新建 Mapper
- PgMemberEducationMapper.java
#### 6.2.4 修改 H5 Service
**H5MemberServiceImpl.java 主要改动**
1. `getEducation()``getEducations()` 返回列表
2. `saveEducation()` 支持新增/编辑
3. `deleteEducation(Long educationId)` 按ID删除
4. 新增 `setDefaultEducation(Long educationId)` 设置默认
### 6.3 管理后台前端改造
#### 6.3.1 会员列表页index.vue
**删除内容**
```vue
<!-- 删除身份类型筛选 -->
<el-form-item label="身份类型">
<el-select v-model="queryParams.identityType" ... />
</el-form-item>
<!-- 删除身份类型列 -->
<el-table-column prop="identityType" label="身份类型" ... />
```
#### 6.3.2 会员编辑弹窗MemberDialog.vue
**删除内容**
```vue
<!-- 删除身份类型选择 -->
<el-form-item label="身份类型" prop="identityType">
<el-radio-group v-model="form.identityType" ... />
</el-form-item>
<!-- 删除:教师信息区块 -->
<template v-if="form.identityType === '2'">
<el-divider>学校信息教师必填</el-divider>
<!-- 区域学校年级班级选择 -->
</template>
```
#### 6.3.3 新建会员详情页
**MemberDetail.vue**(新建)
```vue
<template>
<el-dialog v-model="visible" title="会员详情" width="900px">
<el-tabs v-model="activeTab">
<el-tab-pane label="基本信息" name="basic">
<!-- 会员基本信息展示 -->
</el-tab-pane>
<el-tab-pane label="教育身份" name="education">
<EducationTab :member-id="memberId" />
</el-tab-pane>
<el-tab-pane label="绑定学生" name="students">
<!-- 学生列表 -->
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
```
#### 6.3.4 新建教育身份Tab
**EducationTab.vue**(新建)
```vue
<template>
<div>
<el-button type="primary" @click="handleAdd">添加教育身份</el-button>
<el-table :data="educationList">
<el-table-column prop="schoolName" label="学校" />
<el-table-column prop="gradeName" label="年级" />
<el-table-column label="班级">
<template #default="{ row }">
{{ row.classNames?.join('、') }}
</template>
</el-table-column>
<el-table-column prop="subjectName" label="学科" />
<el-table-column prop="isDefault" label="默认" width="80">
<template #default="{ row }">
<el-tag v-if="row.isDefault === '1'" type="success">是</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="{ row }">
<el-button link @click="handleEdit(row)">编辑</el-button>
<el-button link @click="handleSetDefault(row)">设为默认</el-button>
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 编辑弹窗 -->
<EducationDialog ref="dialogRef" @success="loadData" />
</div>
</template>
```
### 6.4 H5前端改造
#### 6.4.1 user.ts 修改
```typescript
// 删除
identityType: string
// 修改
education: any // 单个 →
educations: any[] // 多个
```
#### 6.4.2 userCenter/index.vue 修改
支持展示多个教育身份,而不是只展示一个。
#### 6.4.3 TeacherIdentityForm.vue 修改
新增 `educationId` 字段用于编辑。
---
## 七、文件改动清单
### 7.1 数据库
| 文件/操作 | 类型 |
|-----------|------|
| DDL: pg_member_education | 新建表(含 school_class_id |
| DML: 数据迁移脚本 | 执行 |
| DDL: pg_member 删除字段 | 修改表 |
### 7.2 后端(共 12+ 文件)
| 文件 | 类型 | 说明 |
|------|------|------|
| `member/domain/PgMember.java` | 修改 | 删除5个字段保留unionId |
| `member/domain/PgMemberEducation.java` | 新建 | |
| `member/mapper/PgMemberEducationMapper.java` | 新建 | |
| `member/service/PgMemberEducationService.java` | 新建 | |
| `member/service/impl/PgMemberEducationServiceImpl.java` | 新建 | |
| `member/controller/PgMemberController.java` | 修改 | 删除身份类型相关 |
| `member/service/impl/PgMemberServiceImpl.java` | 修改 | |
| `h5/domain/dto/H5EducationDto.java` | 修改 | 添加educationId |
| `h5/domain/vo/H5EducationVo.java` | 修改 | |
| `h5/domain/vo/H5MemberInfoVo.java` | 修改 | 删除identityType |
| `h5/controller/H5MemberController.java` | 修改 | 接口调整 |
| `h5/service/impl/H5MemberServiceImpl.java` | 重构 | 教育相关逻辑 |
### 7.3 管理后台前端(共 8+ 文件)
| 文件 | 类型 | 说明 |
|------|------|------|
| `views/business/member/index.vue` | 修改 | 删除身份类型 |
| `views/business/member/components/MemberDialog.vue` | 重构 | 删除教师信息 |
| `views/business/member/components/MemberDetail.vue` | 新建 | 会员详情 |
| `views/business/member/components/EducationTab.vue` | 新建 | 教育身份Tab |
| `views/business/member/components/EducationDialog.vue` | 新建 | 教育身份编辑 |
| `api/pangu/member.js` | 修改 | |
| `api/pangu/memberEducation.js` | 新建 | |
### 7.4 H5前端共 5+ 文件)
| 文件 | 类型 | 说明 |
|------|------|------|
| `stores/user.ts` | 修改 | 删除identityType |
| `views/userCenter/index.vue` | 修改 | 支持多教育身份 |
| `components/TeacherIdentityForm.vue` | 修改 | 添加educationId |
| `api/user.js` | 修改 | 接口调整 |
| `views/register/index.vue` | 可能修改 | 如有身份类型相关 |
---
## 八、测试要点
| 场景 | 测试内容 |
|------|----------|
| 数据迁移 | 现有教师数据正确迁移到新表 |
| H5-新增教育身份 | 首次添加、多次添加(每班一条记录) |
| H5-编辑教育身份 | 修改学校/年级/班级/学科 |
| H5-删除教育身份 | 删除非默认、删除默认 |
| H5-设置默认 | 切换默认身份 |
| H5-多教育身份 | 一个老师添加多条教育身份(教多个班) |
| 管理后台-会员列表 | 不显示身份类型 |
| 管理后台-会员详情 | 查看多个教育身份 |
| 管理后台-教育身份管理 | 增删改查 |
---
## 九、上线计划
| 步骤 | 内容 | 说明 |
|------|------|------|
| 1 | 备份数据库 | 全量备份 pg_member |
| 2 | 执行建表DDL | 创建新表 |
| 3 | 执行数据迁移 | 迁移现有教育数据 |
| 4 | 部署后端 | 新代码 |
| 5 | 部署管理后台前端 | 新代码 |
| 6 | 部署H5前端 | 新代码 |
| 7 | 验证测试 | 回归测试 |
| 8 | 执行删除字段DDL | 确认无问题后 |
---
## 十、回滚方案
1. 后端代码回滚
2. 前端代码回滚
3. 数据库:
- 从新表恢复数据到 pg_member
- 删除新建的两张表
---
## 十一、风险评估
| 风险 | 等级 | 应对措施 |
|------|------|----------|
| 数据迁移丢失 | 高 | 完整备份,测试环境验证 |
| 接口不兼容 | 高 | 前后端同步上线 |
| 性能问题 | 低 | 新表已加索引 |
| 业务中断 | 中 | 选择低峰期上线 |
---
## 附录A现有数据统计上线前执行
```sql
-- 统计需要迁移的教育数据
SELECT COUNT(*) FROM pg_member
WHERE identity_type = '2' AND school_id IS NOT NULL AND del_flag = '0';
-- 统计有班级信息的数据
SELECT COUNT(*) FROM pg_member
WHERE identity_type = '2' AND school_class_id IS NOT NULL AND del_flag = '0';
```
---
## 附录B学生多会员绑定重构2026-02-03
### B.1 需求说明
**原设计**:一个学生只能被一个会员绑定(`pg_student.member_id` 单值字段)
**新设计**:一个学生可以被多个会员绑定(如爸爸和妈妈都能绑定同一个孩子)
### B.2 数据库变更
#### 新建关联表 `pg_member_student`
```sql
CREATE TABLE pg_member_student (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
member_id BIGINT NOT NULL COMMENT '会员ID',
student_id BIGINT NOT NULL COMMENT '学生ID',
relation VARCHAR(20) COMMENT '关系(父亲/母亲/其他)',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_member_student (member_id, student_id),
KEY idx_member_id (member_id),
KEY idx_student_id (student_id)
) COMMENT='会员学生关联表';
```
#### 数据迁移
```sql
-- 迁移现有绑定关系
INSERT IGNORE INTO pg_member_student (member_id, student_id)
SELECT member_id, student_id FROM pg_student WHERE member_id IS NOT NULL;
-- 移除 pg_student.member_id 字段
ALTER TABLE pg_student DROP COLUMN member_id;
```
### B.3 后端变更
| 文件 | 变更 |
|------|------|
| PgMemberStudent.java | 新建关联实体 |
| PgMemberStudentMapper.java | 新建 Mapper |
| PgStudent.java | 删除 memberId 字段 |
| StudentVo.java | memberCount + List<MemberSimpleVo> members |
| PgStudentServiceImpl.java | 绑定/解绑/查询改用关联表 |
| H5MemberServiceImpl.java | 绑定/解绑/查询改用关联表 |
| H5StudentVo.java | 新增 relation 字段 |
| H5StudentBindDto.java | 新增 relation 字段 |
### B.4 前端变更
| 文件 | 变更 |
|------|------|
| MemberDialog.vue | 解绑接口路径调整 |
| StudentSelectDialog.vue | 绑定状态显示多会员数量 |
### B.5 关系模型
```
变更前:会员 --1:N--> 学生(通过 pg_student.member_id
变更后:会员 --N:M--> 学生(通过 pg_member_student 关联表)
```
---
## 附录CH5 前端同步变更
> 完成时间2026-02-03
> 项目user_authentication_center_front
### C.1 变更概述
H5 前端同步适配后端「会员教育信息重构」和「学生多会员绑定」变更。
### C.2 注册流程简化
删除注册页面的身份类型选择步骤:
| 变更前 | 变更后 |
|--------|--------|
| 步骤1: 基本信息 → 步骤2: 身份选择 → 步骤3: 详细信息 | 仅基本信息,注册后跳转用户中心 |
**涉及文件**
- `views/register/index.vue` - 删除身份选择和详细信息步骤
### C.3 教育身份改为数组
| 文件 | 变更 |
|------|------|
| `stores/user.ts` | 删除 `identityType``education: any` → `educations: any[]` |
| `api/user.js` | 接口路径 `/h5/member/education``/h5/member/educations` |
| `views/userCenter/index.vue` | 列表展示多教育身份,支持删除指定 ID |
**接口变更**
```javascript
// 变更前
POST /h5/member/education // 新增(覆盖式)
GET /h5/member/education // 获取单个
DELETE /h5/member/education // 删除
// 变更后
POST /h5/member/educations // 新增
PUT /h5/member/educations/{id} // 修改
GET /h5/member/educations // 获取列表
DELETE /h5/member/educations/{id} // 删除指定
PUT /h5/member/educations/{id}/default // 设为默认
```
### C.4 基础数据前端缓存
新建 `stores/baseData.ts` 实现区域和学科数据的前端缓存:
```typescript
// 缓存结构
{
regionTree: [], // 区域树
subjects: [], // 学科列表
expire: timestamp, // 过期时间24小时
}
// 存储位置
localStorage.setItem('h5:baseData', JSON.stringify(data))
```
**数据流**
```
用户登录成功
强制刷新基础数据fetchRegionTree(true), fetchSubjects(true)
存入 Pinia Store + localStorage
后续页面从 Store 读取,秒加载
```
**涉及文件**
| 文件 | 变更 |
|------|------|
| `stores/baseData.ts` | **新建** - 基础数据 Store |
| `views/login/index.vue` | 登录成功后调用 `fetchRegionTree(true)``fetchSubjects(true)` |
| `components/TeacherIdentityForm.vue` | 区域和学科使用 Store 缓存 |
| `components/ParentChildrenForm.vue` | 区域使用 Store 缓存 |
### C.5 缓存策略说明
| 数据 | 是否缓存 | 原因 |
|------|----------|------|
| 区域树 `/h5/base/regions` | ✅ 缓存 | 数据量大且变化少 |
| 学科列表 `/h5/base/subjects` | ✅ 缓存 | 基础数据,变化少 |
| 学校列表 `/h5/base/schools` | ❌ 不缓存 | 依赖区域动态查询 |
| 年级列表 `/h5/base/grades` | ❌ 不缓存 | 依赖学校动态查询 |
| 班级列表 `/h5/base/classes` | ❌ 不缓存 | 依赖学校+年级动态查询 |