577 lines
16 KiB
Markdown
577 lines
16 KiB
Markdown
# 会员区域字段需求技术方案
|
||
|
||
**版本**: 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*
|