# 会员区域字段需求技术方案 **版本**: 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 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 ``` ```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) } } // 向弹窗组件传递默认区域 ``` #### 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 ``` ```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*