pangu-user-platform/docs/05-模块技术方案/会员区域字段需求技术方案.md

577 lines
16 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.

# 会员区域字段需求技术方案
**版本**: v1.0
**编制人**: pangu
**编制日期**: 2026-02-05
**状态**: 待审核
---
## 一、需求概述
### 1.1 需求背景
当前会员管理系统中,会员数据缺少区域归属信息。在添加教育关系和亲子关系时,需要用户从头开始选择区域,用户体验不佳。
### 1.2 需求内容
| 序号 | 需求项 | 描述 |
|------|--------|------|
| 1 | 会员增加区域字段 | 在会员数据中记录所属区域 |
| 2 | H5注册时选择区域 | 用户注册时必须选择所在区域 |
| 3 | 弹窗区域默认值 | 添加教育关系/亲子关系时,区域默认选择会员所在区域,但允许修改 |
---
## 二、现状分析
### 2.1 数据库现状
```sql
-- pg_member 表已有 region_id 字段
CREATE TABLE `pg_member` (
...
`region_id` bigint DEFAULT NULL COMMENT '区域ID',
...
);
```
**结论**: 数据库表已具备 `region_id` 字段,无需修改表结构。
### 2.2 后端实体现状
```java
// PgMember.java 中缺少 regionId 字段
public class PgMember extends BaseEntity {
private Long memberId;
private String memberCode;
private String phone;
// ... 无 regionId 字段
}
```
**结论**: 实体类需要添加 `regionId` 字段。
### 2.3 H5注册现状
当前H5注册表单字段
- 手机号码 (phone)
- 图形验证码 (imageCaptcha)
- 短信验证码 (verificationCode)
- 密码 (password)
- 同意协议 (agreed)
**结论**: 注册流程无区域选择,需要新增。
### 2.4 弹窗组件现状
**管理后台前端**:
| 组件 | 区域选择 | 默认值 |
|------|---------|--------|
| EducationDialog (教育关系) | ✅ 有 | 无默认值 |
| StudentSelectDialog (亲子关系) | ❌ 无 | - |
**H5前端**:
| 组件 | 区域选择 | 默认值 |
|------|---------|--------|
| TeacherIdentityForm (教育身份) | ✅ 有 | 无默认值 |
| ParentChildrenForm (绑定学生) | ✅ 有 | 无默认值 |
**结论**:
- 管理后台EducationDialog 已有区域选择需添加默认值逻辑StudentSelectDialog 需添加区域筛选功能
- H5前端两个弹窗均已有区域选择只需添加默认区域逻辑
---
## 三、技术方案
### 3.1 总体架构
```
┌─────────────────────────────────────────────────────────────┐
│ H5前端 │
├─────────────────┬───────────────────────────────────────────┤
│ 注册页面 │ 添加区域选择器注册时提交regionId │
└────────┬────────┴───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 后端API │
├─────────────────┬───────────────────────────────────────────┤
│ H5AuthController│ register接口接收regionId │
│ PgMember实体 │ 新增regionId字段 │
└────────┬────────┴───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 管理后台前端 │
├─────────────────┬───────────────────────────────────────────┤
│ EducationDialog │ open时传入会员regionId作为默认值 │
│ StudentSelectDialog │ 新增区域筛选默认使用会员regionId │
│ MemberDialog │ 向子组件传递会员regionId │
└─────────────────┴───────────────────────────────────────────┘
```
### 3.2 详细设计
#### 3.2.1 后端修改
**文件**: `PgMember.java`
```java
// 新增字段
/**
* 区域ID
*/
private Long regionId;
```
**文件**: `H5AuthServiceImpl.java` - register方法
```java
// 修改注册逻辑接收并保存regionId
public R<LoginVo> register(RegisterDto dto) {
// ... 原有验证逻辑
PgMember member = new PgMember();
member.setPhone(dto.getPhone());
member.setPassword(encryptPassword(dto.getPassword()));
member.setRegionId(dto.getRegionId()); // 新增保存区域ID
member.setRegisterSource("2"); // H5注册
member.setRegisterTime(new Date());
// ... 保存会员
}
```
**文件**: `RegisterDto.java` (如不存在需创建)
```java
// 新增区域ID字段
private Long regionId;
```
#### 3.2.2 H5前端修改
##### 3.2.2.1 注册页面新增区域选择
**文件**: `user-front/src/views/register/index.vue`
```vue
<!-- 新增区域选择器使用el-cascader与现有弹窗风格一致 -->
<el-form-item label="所在区域" prop="regionPath" required>
<el-cascader
v-model="registerForm.regionPath"
:options="regionsData"
:props="{ value: 'regionId', label: 'regionName', children: 'children' }"
placeholder="请选择省市区"
clearable
style="width: 100%"
/>
</el-form-item>
```
```javascript
// 表单数据新增
registerForm = {
phone: '',
imageCaptcha: '',
verificationCode: '',
password: '',
agreed: false,
regionPath: [] // 新增:区域路径 [provinceId, cityId, districtId]
}
// 获取区域树(复用已有逻辑)
const regionsData = ref([])
const loadRegionsData = async () => {
const res = await request.get('/h5/region/tree')
regionsData.value = res.data || []
}
// 提交注册时传递regionId取数组最后一个元素
register({
// ... 原有字段
regionId: registerForm.regionPath[registerForm.regionPath.length - 1]
})
```
**文件**: `user-front/src/api/user.js`
```javascript
// 修改register函数添加regionId参数
export function register(data) {
return request({
url: '/h5/auth/register',
method: 'post',
data: {
phone: data.consumerName,
password: data.password,
smsCode: data.verifyCode,
captchaCode: data.captchaValue,
uuid: data.captchaUuid,
regionId: data.regionId // 新增
}
})
}
```
##### 3.2.2.2 弹窗默认区域
**文件**: `user-front/src/components/TeacherIdentityForm.vue`
```javascript
// 新增props接收默认区域
props: {
// ... 原有props
defaultRegionPath: { // 新增:默认区域路径
type: Array,
default: () => []
}
}
// 组件挂载或打开时设置默认值
watch(() => props.defaultRegionPath, (val) => {
if (val && val.length > 0 && formData.regionPath.length === 0) {
formData.regionPath = [...val]
// 触发学校加载
loadSchoolsByRegion(val[val.length - 1])
}
}, { immediate: true })
```
**文件**: `user-front/src/components/ParentChildrenForm.vue`
```javascript
// 新增props接收默认区域同上
props: {
// ... 原有props
defaultRegionPath: {
type: Array,
default: () => []
}
}
```
**文件**: `user-front/src/views/userCenter/index.vue`
```javascript
// 获取会员区域信息
const memberRegionPath = ref([])
// 加载会员信息时获取区域路径
const loadMemberInfo = async () => {
// ... 原有逻辑
if (memberInfo.regionId) {
memberRegionPath.value = await getRegionPath(memberInfo.regionId)
}
}
// 向弹窗组件传递默认区域
<teacher-identity-form
:default-region-path="memberRegionPath"
// ... 其他props
/>
<parent-children-form
:default-region-path="memberRegionPath"
// ... 其他props
/>
```
#### 3.2.3 管理后台前端修改
**文件**: `MemberDialog.vue`
```javascript
// 向子组件传递会员区域ID
const memberRegionId = ref(null)
// 编辑会员时获取区域ID
const open = async (row) => {
if (row) {
// ... 加载会员数据
memberRegionId.value = row.regionId
}
}
// 打开教育关系弹窗时传递默认区域
const handleAddEducation = () => {
educationDialogRef.value?.open(memberId.value, null, null, memberRegionId.value)
}
// 打开亲子关系弹窗时传递默认区域
const handleAddStudent = () => {
studentSelectDialogRef.value?.open({
memberId: memberId.value,
defaultRegionId: memberRegionId.value
})
}
```
**文件**: `EducationDialog.vue`
```javascript
// 修改open方法签名增加默认区域参数
const open = async (mId, row, index, defaultRegionId) => {
resetForm()
memberId.value = mId
isEdit.value = !!row
localIndex.value = index ?? null
visible.value = true
await Promise.all([loadRegionTree(), loadSubjectList()])
if (row) {
// 编辑模式:使用已有数据
// ... 原有逻辑
} else if (defaultRegionId) {
// 新增模式:使用会员默认区域
regionIds.value = await getRegionPath(defaultRegionId)
form.regionId = defaultRegionId
await loadSchoolList(defaultRegionId)
}
}
```
**文件**: `StudentSelectDialog.vue`
```vue
<!-- 新增区域筛选 -->
<el-form-item label="区域">
<el-cascader
v-model="queryParams.regionIds"
:options="regionTree"
:props="{ value: 'regionId', label: 'regionName', checkStrictly: true }"
placeholder="请选择区域"
clearable
style="width: 180px"
@change="handleRegionChange"
/>
</el-form-item>
```
```javascript
// 新增区域相关变量
const regionTree = ref([])
const defaultRegionId = ref(null)
// 修改查询参数
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
regionIds: [], // 新增
regionId: null // 新增
})
// 修改open方法
const open = async (options = {}) => {
memberId.value = options.memberId || null
defaultRegionId.value = options.defaultRegionId || null
excludeStudentIds.value = options.excludeIds || []
resetQuery()
// 加载区域树
await loadRegionTree()
// 设置默认区域
if (defaultRegionId.value) {
queryParams.regionIds = await getRegionPath(defaultRegionId.value)
queryParams.regionId = defaultRegionId.value
}
visible.value = true
getList()
}
// 区域变更处理
const handleRegionChange = (val) => {
queryParams.regionId = val && val.length ? val[val.length - 1] : null
handleQuery()
}
// 修改getList添加regionId参数
const getList = async () => {
const params = {
// ... 原有参数
regionId: queryParams.regionId || undefined // 新增
}
// ...
}
```
---
## 四、接口变更
### 4.1 H5注册接口
**路径**: `POST /h5/auth/register`
**请求体变更**:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| phone | String | 是 | 手机号 |
| password | String | 是 | 密码 |
| smsCode | String | 是 | 短信验证码 |
| captchaCode | String | 是 | 图形验证码 |
| uuid | String | 是 | 验证码UUID |
| **regionId** | **Long** | **是** | **区域ID新增** |
### 4.2 区域树接口H5端
**路径**: `GET /h5/region/tree`
**说明**: 如不存在需新增返回区域树结构供H5选择。
**响应体**:
```json
{
"code": 200,
"data": [
{
"regionId": 42,
"regionName": "湖北省",
"children": [
{
"regionId": 4201,
"regionName": "武汉市",
"children": [...]
}
]
}
]
}
```
### 4.3 学生列表接口
**路径**: `GET /business/student/available`
**请求参数变更**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| pageNum | Integer | 否 | 页码 |
| pageSize | Integer | 否 | 每页数量 |
| studentName | String | 否 | 学生姓名 |
| studentNo | String | 否 | 学号 |
| **regionId** | **Long** | **否** | **区域ID新增** |
---
## 五、数据库变更
### 5.1 表结构
**无变更** - `pg_member` 表已有 `region_id` 字段。
### 5.2 数据迁移
**无需迁移** - 新注册用户将自动填充区域ID历史用户区域ID保持为NULL。
---
## 六、影响范围
### 6.1 后端文件清单
| 文件 | 修改类型 | 说明 |
|------|---------|------|
| PgMember.java | 修改 | 新增regionId字段 |
| H5AuthServiceImpl.java | 修改 | register方法接收regionId |
| RegisterDto.java | 新增/修改 | 新增regionId字段 |
| H5RegionController.java | 新增 | 提供区域树接口 |
| PgStudentServiceImpl.java | 修改 | available方法支持regionId筛选 |
### 6.2 H5前端文件清单
| 文件 | 修改类型 | 说明 |
|------|---------|------|
| views/register/index.vue | 修改 | 新增区域选择器 |
| api/user.js | 修改 | register函数添加regionId |
| components/TeacherIdentityForm.vue | 修改 | 新增defaultRegionPath prop |
| components/ParentChildrenForm.vue | 修改 | 新增defaultRegionPath prop |
| views/userCenter/index.vue | 修改 | 获取会员区域并传递给弹窗 |
### 6.3 管理后台前端文件清单
| 文件 | 修改类型 | 说明 |
|------|---------|------|
| MemberDialog.vue | 修改 | 传递memberRegionId给子组件 |
| EducationDialog.vue | 修改 | open方法增加defaultRegionId参数 |
| StudentSelectDialog.vue | 修改 | 新增区域筛选功能 |
---
## 七、测试要点
### 7.1 功能测试
| 测试项 | 测试步骤 | 预期结果 |
|--------|---------|---------|
| H5注册-区域必填 | 不选区域直接注册 | 提示"请选择所在区域" |
| H5注册-区域保存 | 选择区域后注册 | 会员数据包含正确的regionId |
| H5教育身份-默认区域 | 有区域的会员打开"添加教育身份"弹窗 | 区域字段自动选中会员所在区域 |
| H5教育身份-可修改 | 修改默认选中的区域 | 可正常切换到其他区域,学校列表刷新 |
| H5绑定学生-默认区域 | 有区域的会员打开"绑定学生"弹窗 | 区域字段自动选中会员所在区域 |
| H5绑定学生-可修改 | 修改默认选中的区域 | 可正常切换到其他区域,学校列表刷新 |
| 管理后台-教育关系默认区域 | 为有区域的会员添加任教信息 | 区域字段自动选中会员所在区域 |
| 管理后台-亲子关系区域筛选 | 为有区域的会员添加亲子关系 | 区域筛选默认选中会员所在区域 |
### 7.2 兼容性测试
| 测试项 | 测试步骤 | 预期结果 |
|--------|---------|---------|
| 历史会员-无区域(H5) | 无区域会员打开"添加教育身份"弹窗 | 区域字段为空,需手动选择 |
| 历史会员-无区域(管理后台) | 为无区域的会员添加任教信息 | 区域字段为空,需手动选择 |
| 历史会员-弹窗正常 | 打开历史会员的各类弹窗 | 弹窗正常显示,无报错 |
---
## 八、风险评估
| 风险项 | 风险等级 | 应对措施 |
|--------|---------|---------|
| H5注册流程变更 | 🟡 中 | 充分测试,确保注册流程顺畅 |
| 历史数据无区域 | 🟢 低 | 兼容处理,无区域时默认值为空 |
| 弹窗组件改动 | 🟢 低 | 保持向后兼容,参数可选 |
---
## 九、工作量估算
| 模块 | 工作项 | 估算 |
|------|--------|------|
| 后端 | PgMember实体修改 | 0.5h |
| 后端 | 注册接口修改 | 1h |
| 后端 | 区域树接口H5端如不存在 | 1h |
| 后端 | 学生列表接口修改 | 0.5h |
| H5前端 | 注册页面区域选择 | 1.5h |
| H5前端 | TeacherIdentityForm默认区域 | 1h |
| H5前端 | ParentChildrenForm默认区域 | 1h |
| H5前端 | userCenter传递区域 | 0.5h |
| 管理后台 | MemberDialog修改 | 0.5h |
| 管理后台 | EducationDialog修改 | 1h |
| 管理后台 | StudentSelectDialog修改 | 2h |
| 测试 | 功能测试 | 2h |
| **合计** | | **12.5h** |
---
## 十、审批记录
| 角色 | 姓名 | 意见 | 日期 |
|------|------|------|------|
| 技术负责人 | | | |
| 产品负责人 | | | |
---
*文档生成时间: 2026-02-05*