feat: 会员区域字段、管理后台区域编辑与列表展示;分公司账号密码重置脚本;业务按钮权限修复方案
This commit is contained in:
parent
add00c9992
commit
e04cf47bdf
|
|
@ -0,0 +1,13 @@
|
|||
-- ============================================================
|
||||
-- 重置分公司用户 wuhan 的登录密码为 admin123
|
||||
-- 使用方式:在对应库中执行本脚本后,用 wuhan / admin123 登录
|
||||
-- ============================================================
|
||||
|
||||
USE `pguser-db`;
|
||||
|
||||
-- 与 init 脚本中相同的 BCrypt 哈希(admin123)
|
||||
UPDATE sys_user
|
||||
SET password = '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2'
|
||||
WHERE user_name = 'wuhan';
|
||||
|
||||
-- 若上面影响行数为 0,说明 wuhan 用户不存在,请先执行 system_init_data.sql 初始化
|
||||
|
|
@ -22,4 +22,7 @@ public class H5MemberUpdateDto {
|
|||
|
||||
@Schema(description = "生日", example = "1990-01-15")
|
||||
private Date birthday;
|
||||
|
||||
@Schema(description = "所在区域ID", example = "420102")
|
||||
private Long regionId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,4 +36,7 @@ public class H5RegisterDto {
|
|||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, message = "密码至少6位")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "所在区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "420102")
|
||||
private Long regionId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ public class H5MemberInfoVo {
|
|||
@Schema(description = "注册时间", example = "2024-01-01 12:00:00")
|
||||
private Date registerTime;
|
||||
|
||||
@Schema(description = "所在区域ID", example = "420102")
|
||||
private Long regionId;
|
||||
|
||||
@Schema(description = "教育身份列表")
|
||||
private List<H5EducationVo> educations;
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ public class H5AuthServiceImpl implements H5AuthService {
|
|||
member.setPassword(BCrypt.hashpw(dto.getPassword()));
|
||||
member.setMemberCode(generateMemberCode());
|
||||
member.setNickname("user_" + dto.getPhone().substring(7));
|
||||
member.setRegionId(dto.getRegionId()); // 保存区域ID
|
||||
member.setRegisterSource("2"); // H5注册
|
||||
member.setRegisterTime(new Date());
|
||||
member.setStatus("0");
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
vo.setGender(member.getGender());
|
||||
vo.setBirthday(member.getBirthday());
|
||||
vo.setRegisterTime(member.getRegisterTime());
|
||||
vo.setRegionId(member.getRegionId());
|
||||
|
||||
// 获取教育身份列表
|
||||
vo.setEducations(getEducations());
|
||||
|
|
@ -107,7 +108,7 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
throw new ServiceException("会员不存在");
|
||||
}
|
||||
|
||||
// 只允许修改昵称、性别、生日
|
||||
// 只允许修改昵称、性别、生日、区域
|
||||
if (StringUtils.isNotBlank(dto.getNickname())) {
|
||||
member.setNickname(dto.getNickname());
|
||||
}
|
||||
|
|
@ -117,6 +118,9 @@ public class H5MemberServiceImpl implements H5MemberService {
|
|||
if (dto.getBirthday() != null) {
|
||||
member.setBirthday(dto.getBirthday());
|
||||
}
|
||||
if (dto.getRegionId() != null) {
|
||||
member.setRegionId(dto.getRegionId());
|
||||
}
|
||||
|
||||
memberMapper.updateById(member);
|
||||
log.info("H5会员信息修改: memberId={}", memberId);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ public class PgMember extends BaseEntity {
|
|||
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long regionId;
|
||||
|
||||
@TableLogic
|
||||
private String delFlag;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ public class MemberSaveDto {
|
|||
@Schema(description = "状态(0正常 1停用)")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "所在区域ID")
|
||||
private Long regionId;
|
||||
|
||||
@Schema(description = "教育身份列表")
|
||||
private List<EducationDto> educations;
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
member.setGender(dto.getGender());
|
||||
member.setBirthday(dto.getBirthday());
|
||||
member.setStatus(dto.getStatus());
|
||||
member.setRegionId(dto.getRegionId());
|
||||
insert(member);
|
||||
|
||||
Long memberId = member.getMemberId();
|
||||
|
|
@ -148,6 +149,7 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
|||
member.setGender(dto.getGender());
|
||||
member.setBirthday(dto.getBirthday());
|
||||
member.setStatus(dto.getStatus());
|
||||
member.setRegionId(dto.getRegionId());
|
||||
update(member);
|
||||
|
||||
// 注意:编辑时教育身份通过单独的接口管理,这里不处理
|
||||
|
|
|
|||
|
|
@ -0,0 +1,576 @@
|
|||
# 会员区域字段需求技术方案
|
||||
|
||||
**版本**: 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*
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
# 业务功能按钮权限统一修复方案
|
||||
|
||||
**问题**:角色管理中已取消「新增/修改/删除」等菜单权限后,学生管理、会员管理等自建业务页面上,对应按钮仍显示且可操作,与权限配置不一致。
|
||||
|
||||
**原因**:后端接口已使用 `@SaCheckPermission` 做权限校验(无权限会 403),但前端列表/表格上的「新增」「编辑」「删除」「导入」等按钮**未做权限控制**,未使用项目已有的 `v-hasPermi` 指令,导致仅靠菜单可见性无法隐藏这些操作按钮。
|
||||
|
||||
**修复思路**:在**前端**为所有业务模块的增删改等操作按钮增加 `v-hasPermi="['权限字符']"`,与后端 `@SaCheckPermission` 使用的权限字符一致。不修改后端(后端已校验),不修改菜单/角色配置方式。
|
||||
|
||||
---
|
||||
|
||||
## 一、涉及范围
|
||||
|
||||
以下为**自建业务**页面,需统一加按钮权限控制(系统管理下用户/角色/部门等已有 `v-hasPermi`,不在此次范围):
|
||||
|
||||
| 模块 | 前端路径 | 后端权限前缀 |
|
||||
|------------|----------|--------------|
|
||||
| 学生管理 | `views/business/student/index.vue` | `business:student:*` |
|
||||
| 会员管理 | `views/business/member/index.vue` | `business:member:*` |
|
||||
| 学校管理 | `views/business/school/index.vue` | `business:school:*` |
|
||||
| 应用管理 | `views/business/application/index.vue` | `business:application:*` |
|
||||
| 接口字典 | `views/business/apiDict/index.vue` | `business:apiDict:*` |
|
||||
| 基础数据-年级 | `views/business/base/grade/index.vue` | `business:grade:*` |
|
||||
| 基础数据-班级 | `views/business/base/class/index.vue` | `business:class:*` |
|
||||
| 基础数据-学科 | `views/business/base/subject/index.vue` | `business:subject:*` |
|
||||
| 基础数据-区域 | `views/business/base/region/index.vue` | `business:region:*` |
|
||||
|
||||
---
|
||||
|
||||
## 二、各页面具体修改
|
||||
|
||||
### 1. 学生管理 `student/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:student:add']"`
|
||||
- 工具栏「导入」:`v-hasPermi="['business:student:import']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:student:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:student:remove']"`
|
||||
|
||||
### 2. 会员管理 `member/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:member:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:member:edit']"`
|
||||
- 表格操作「重置密码」:`v-hasPermi="['business:member:resetPwd']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:member:remove']"`
|
||||
|
||||
### 3. 学校管理 `school/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:school:add']"`
|
||||
- 表格内「编辑」:`v-hasPermi="['business:school:edit']"`
|
||||
- 表格内「新增年级」:`v-hasPermi="['business:school:edit']"`
|
||||
- 表格内「新增班级」:`v-hasPermi="['business:school:edit']"`
|
||||
- 表格内「删除」(学校/年级/班级):`v-hasPermi="['business:school:remove']"`
|
||||
|
||||
### 4. 应用管理 `application/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:application:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:application:edit']"`
|
||||
- 表格操作「重置密钥」:`v-hasPermi="['business:application:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:application:remove']"`
|
||||
|
||||
### 5. 接口字典 `apiDict/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:apiDict:add']"`
|
||||
- 工具栏「批量删除」:`v-hasPermi="['business:apiDict:remove']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:apiDict:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:apiDict:remove']"`
|
||||
|
||||
### 6. 基础数据 - 年级 `base/grade/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:grade:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:grade:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:grade:remove']"`
|
||||
|
||||
### 7. 基础数据 - 班级 `base/class/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:class:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:class:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:class:remove']"`
|
||||
|
||||
### 8. 基础数据 - 学科 `base/subject/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:subject:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:subject:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:subject:remove']"`
|
||||
|
||||
### 9. 基础数据 - 区域 `base/region/index.vue`
|
||||
|
||||
- 工具栏「新增」:`v-hasPermi="['business:region:add']"`
|
||||
- 表格操作「新增下级」:`v-hasPermi="['business:region:add']"`
|
||||
- 表格操作「编辑」:`v-hasPermi="['business:region:edit']"`
|
||||
- 表格操作「删除」:`v-hasPermi="['business:region:remove']"`
|
||||
|
||||
---
|
||||
|
||||
## 三、技术说明
|
||||
|
||||
- **指令**:使用现有 `v-hasPermi`(`directive/permission/hasPermi.js`),无权限时移除 DOM,与系统管理页面用法一致。
|
||||
- **权限字符**:与后端 `@SaCheckPermission` 完全一致,保证「角色未勾选该权限 → 前端不显示按钮、后端接口 403」。
|
||||
- **后端**:不改动,已具备接口级校验。
|
||||
- **菜单/角色**:不改动,仅依赖现有「菜单权限」配置(角色管理里勾选的子权限如「学生新增」「学生修改」等)。
|
||||
|
||||
---
|
||||
|
||||
## 四、验收方式
|
||||
|
||||
1. 使用**分公司用户**(如 wuhan)登录,在角色管理中仅保留「学生管理」下的「学生查询」,去掉「学生新增」「学生修改」「学生删除」「学生导入」。
|
||||
2. 进入学生管理页:不应出现「新增」「导入」按钮,表格操作列不应出现「编辑」「删除」。
|
||||
3. 对会员、学校、应用、接口字典、年级/班级/学科/区域做同类配置,验证对应按钮按权限显隐。
|
||||
4. 超级管理员或拥有全部权限的角色,所有按钮仍正常显示。
|
||||
|
||||
---
|
||||
|
||||
**请确认是否按此方案执行修改,同意后再进行代码改动。**
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
# 增量发布计划
|
||||
|
||||
**发布日期**: 2026-02-05
|
||||
**目标服务器**: 192.168.71.56
|
||||
**发布类型**: 增量发布
|
||||
**编制人**: pangu
|
||||
|
||||
---
|
||||
|
||||
## 一、发布概览
|
||||
|
||||
| 项目 | 本地最新版本 | 服务器当前版本 | 待发布提交数 |
|
||||
|------|-------------|---------------|-------------|
|
||||
| pangu-user-platform (后端+管理后台) | add00c9 | 2026-02-03 22:29 部署 | 5 个 |
|
||||
| user_authentication_center_front (H5前端) | 70fc1ad | 2026-02-03 22:04 部署 | 2 个 |
|
||||
|
||||
---
|
||||
|
||||
## 二、pangu-user-platform 发布内容
|
||||
|
||||
### 2.1 待发布提交清单
|
||||
|
||||
| 序号 | Commit ID | 提交说明 | 类型 |
|
||||
|------|----------|---------|------|
|
||||
| 1 | add00c9 | 新增学校自动添加年级 + 修复区域层级查询 + 清理区域数据 | feat |
|
||||
| 2 | 72cb666 | 年级管理增加学段字段(小学/初中/高中/中专/大学) | feat |
|
||||
| 3 | 80dd406 | 新增OpenApi基础数据接口 + 学生完整数据接口 + UI文案优化 | feat |
|
||||
| 4 | 1a0b75e | 同步需求与技术方案文档 | docs |
|
||||
| 5 | 6027a8c | 修改后端欢迎语为盘古后台管理系统 | refactor |
|
||||
|
||||
### 2.2 功能变更摘要
|
||||
|
||||
1. **学校管理优化**
|
||||
- 新增学校时自动添加对应学段的年级
|
||||
- 修复选择省/市时无法显示学校的Bug(支持区域层级查询)
|
||||
- 区域树默认只展开湖北省,平行显示市级
|
||||
|
||||
2. **年级管理增强**
|
||||
- 新增学段字段(小学/初中/高中/中专/大学)
|
||||
- 支持按学段筛选年级
|
||||
|
||||
3. **OpenAPI接口扩展**
|
||||
- 新增学校/年级/班级基础数据查询接口
|
||||
- 新增学生完整数据接口(不脱敏,需特殊授权)
|
||||
|
||||
4. **UI文案优化**
|
||||
- "教育身份" 改为 "任教信息"
|
||||
- 后端欢迎语改为 "盘古后台管理系统"
|
||||
|
||||
### 2.3 数据库增量脚本
|
||||
|
||||
| 序号 | 脚本文件 | 说明 | 影响范围 | 风险等级 |
|
||||
|------|---------|------|---------|---------|
|
||||
| 1 | V1.0.3__open_api_dict.sql | OpenAPI接口字典数据 | pg_api_dict表 | 🟢 低 |
|
||||
| 2 | V1.0.4__grade_add_stage.sql | 年级表增加学段字段 | pg_grade表结构 | 🟡 中 |
|
||||
| 3 | V1.0.5__clean_region_data.sql | 清理非湖北省区域数据 | pg_region表 | 🔴 高 |
|
||||
|
||||
#### 脚本详细说明
|
||||
|
||||
**V1.0.3__open_api_dict.sql**
|
||||
- 新增11条API字典记录
|
||||
- 使用 `ON DUPLICATE KEY UPDATE` 幂等设计
|
||||
- 可重复执行,无副作用
|
||||
|
||||
**V1.0.4__grade_add_stage.sql**
|
||||
- 为 `pg_grade` 表新增 `stage` 字段
|
||||
- 自动根据年级名称初始化学段数据
|
||||
- ⚠️ 注意:执行前需确认 `pg_grade` 表无 `stage` 字段
|
||||
|
||||
**V1.0.5__clean_region_data.sql**
|
||||
- ⚠️ **高风险脚本 - 物理删除操作**
|
||||
- 删除所有非湖北省的区域数据(约3309条)
|
||||
- 仅保留湖北省及其下级区域(约120条)
|
||||
- ⚠️ **执行前必须备份 `pg_region` 表**
|
||||
|
||||
### 2.4 服务器部署路径
|
||||
|
||||
| 组件 | 路径 |
|
||||
|------|------|
|
||||
| 后端 JAR | /opt/pangu-user-platform/backend/pangu-admin.jar |
|
||||
| 管理后台前端 | /opt/pangu-user-platform/frontend/admin-manage/ |
|
||||
| 启动脚本 | /opt/pangu-user-platform/scripts/service.sh |
|
||||
| 配置文件 | /opt/pangu-user-platform/backend/application-test.yml |
|
||||
| 日志目录 | /opt/pangu-user-platform/backend/logs/ |
|
||||
|
||||
---
|
||||
|
||||
## 三、user_authentication_center_front 发布内容
|
||||
|
||||
### 3.1 待发布提交清单
|
||||
|
||||
| 序号 | Commit ID | 提交说明 | 类型 |
|
||||
|------|----------|---------|------|
|
||||
| 1 | 70fc1ad | 优化登录注册页面交互和弹窗内容 | feat |
|
||||
| 2 | 842d64f | 允许通过IP地址访问H5前端 | fix |
|
||||
|
||||
### 3.2 功能变更摘要
|
||||
|
||||
1. **登录注册优化**
|
||||
- 优化登录注册页面交互体验
|
||||
- 改进弹窗内容展示
|
||||
|
||||
2. **访问限制修复**
|
||||
- 允许通过IP地址直接访问H5前端
|
||||
|
||||
### 3.3 服务器部署路径
|
||||
|
||||
| 组件 | 路径 |
|
||||
|------|------|
|
||||
| H5前端 | /opt/pangu-user-platform/frontend/user-front/ |
|
||||
|
||||
---
|
||||
|
||||
## 四、发布步骤
|
||||
|
||||
### 4.1 发布前准备
|
||||
|
||||
```bash
|
||||
# 1. 备份数据库
|
||||
ssh root@192.168.71.56
|
||||
mysqldump -h 8.148.25.55 -uroot -p pguser-db pg_region > /opt/backup/pg_region_$(date +%Y%m%d_%H%M%S).sql
|
||||
mysqldump -h 8.148.25.55 -uroot -p pguser-db pg_grade > /opt/backup/pg_grade_$(date +%Y%m%d_%H%M%S).sql
|
||||
mysqldump -h 8.148.25.55 -uroot -p pguser-db pg_api_dict > /opt/backup/pg_api_dict_$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# 2. 备份当前部署版本
|
||||
cp -r /opt/pangu-user-platform/backend/pangu-admin.jar /opt/backup/pangu-admin.jar.bak
|
||||
cp -r /opt/pangu-user-platform/frontend/admin-manage /opt/backup/admin-manage.bak
|
||||
cp -r /opt/pangu-user-platform/frontend/user-front /opt/backup/user-front.bak
|
||||
```
|
||||
|
||||
### 4.2 本地构建
|
||||
|
||||
```bash
|
||||
# 1. 构建后端
|
||||
cd /Users/felix/pgWorkSpace/pangu-user-platform/backend
|
||||
./build.sh -q -f
|
||||
|
||||
# 2. 构建管理后台前端
|
||||
cd /Users/felix/pgWorkSpace/pangu-user-platform/frontend
|
||||
npm run build
|
||||
|
||||
# 3. 构建H5前端
|
||||
cd /Users/felix/pgWorkSpace/user_authentication_center_front/user-front
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 4.3 上传部署包
|
||||
|
||||
```bash
|
||||
# 1. 上传后端JAR
|
||||
scp /Users/felix/pgWorkSpace/pangu-user-platform/backend/pangu-admin/target/pangu-admin.jar root@192.168.71.56:/opt/pangu-user-platform/backend/
|
||||
|
||||
# 2. 上传管理后台前端
|
||||
scp -r /Users/felix/pgWorkSpace/pangu-user-platform/frontend/dist/* root@192.168.71.56:/opt/pangu-user-platform/frontend/admin-manage/
|
||||
|
||||
# 3. 上传H5前端
|
||||
scp -r /Users/felix/pgWorkSpace/user_authentication_center_front/user-front/dist/* root@192.168.71.56:/opt/pangu-user-platform/frontend/user-front/
|
||||
|
||||
# 4. 上传SQL脚本
|
||||
scp /Users/felix/pgWorkSpace/pangu-user-platform/scripts/sql/V1.0.*.sql root@192.168.71.56:/opt/pangu-user-platform/scripts/
|
||||
```
|
||||
|
||||
### 4.4 执行数据库脚本
|
||||
|
||||
```bash
|
||||
ssh root@192.168.71.56
|
||||
|
||||
# 按顺序执行SQL脚本
|
||||
mysql -h 8.148.25.55 -uroot -paly2024A pguser-db < /opt/pangu-user-platform/scripts/V1.0.3__open_api_dict.sql
|
||||
mysql -h 8.148.25.55 -uroot -paly2024A pguser-db < /opt/pangu-user-platform/scripts/V1.0.4__grade_add_stage.sql
|
||||
|
||||
# ⚠️ V1.0.5 需要单独确认后执行(高风险)
|
||||
# mysql -h 8.148.25.55 -uroot -paly2024A pguser-db < /opt/pangu-user-platform/scripts/V1.0.5__clean_region_data.sql
|
||||
```
|
||||
|
||||
### 4.5 重启服务
|
||||
|
||||
```bash
|
||||
ssh root@192.168.71.56
|
||||
|
||||
# 重启后端服务
|
||||
cd /opt/pangu-user-platform/scripts
|
||||
./service.sh restart
|
||||
|
||||
# 检查服务状态
|
||||
./service.sh status
|
||||
|
||||
# 查看启动日志
|
||||
./service.sh logs
|
||||
```
|
||||
|
||||
### 4.6 验证测试
|
||||
|
||||
| 验证项 | 验证方法 | 预期结果 |
|
||||
|--------|---------|---------|
|
||||
| 后端服务 | `curl http://localhost:9083/actuator/health` | {"status":"UP"} |
|
||||
| 管理后台登录 | 浏览器访问管理后台 | 正常登录 |
|
||||
| 学校管理-区域树 | 查看区域树展开状态 | 默认展开湖北省 |
|
||||
| 学校管理-层级查询 | 选择武汉市查看学校 | 显示武汉市下所有学校 |
|
||||
| 年级管理-学段 | 查看年级列表 | 显示学段列 |
|
||||
| OpenAPI接口 | 调用新增的接口 | 正常返回数据 |
|
||||
| H5前端登录 | 浏览器访问H5 | 正常登录 |
|
||||
|
||||
---
|
||||
|
||||
## 五、回滚方案
|
||||
|
||||
### 5.1 后端回滚
|
||||
|
||||
```bash
|
||||
ssh root@192.168.71.56
|
||||
cd /opt/pangu-user-platform/scripts
|
||||
|
||||
# 停止服务
|
||||
./service.sh stop
|
||||
|
||||
# 恢复JAR包
|
||||
cp /opt/backup/pangu-admin.jar.bak /opt/pangu-user-platform/backend/pangu-admin.jar
|
||||
|
||||
# 启动服务
|
||||
./service.sh start
|
||||
```
|
||||
|
||||
### 5.2 前端回滚
|
||||
|
||||
```bash
|
||||
# 恢复管理后台前端
|
||||
rm -rf /opt/pangu-user-platform/frontend/admin-manage/*
|
||||
cp -r /opt/backup/admin-manage.bak/* /opt/pangu-user-platform/frontend/admin-manage/
|
||||
|
||||
# 恢复H5前端
|
||||
rm -rf /opt/pangu-user-platform/frontend/user-front/*
|
||||
cp -r /opt/backup/user-front.bak/* /opt/pangu-user-platform/frontend/user-front/
|
||||
```
|
||||
|
||||
### 5.3 数据库回滚
|
||||
|
||||
```bash
|
||||
# 恢复pg_grade表(如果执行了V1.0.4)
|
||||
mysql -h 8.148.25.55 -uroot -paly2024A pguser-db -e "ALTER TABLE pg_grade DROP COLUMN stage;"
|
||||
|
||||
# 恢复pg_region表(如果执行了V1.0.5)
|
||||
mysql -h 8.148.25.55 -uroot -paly2024A pguser-db < /opt/backup/pg_region_YYYYMMDD_HHMMSS.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、风险评估
|
||||
|
||||
| 风险项 | 风险等级 | 应对措施 |
|
||||
|--------|---------|---------|
|
||||
| 数据库结构变更 | 🟡 中 | 执行前备份相关表 |
|
||||
| 区域数据物理删除 | 🔴 高 | 必须备份,可单独安排执行 |
|
||||
| 后端服务中断 | 🟢 低 | 使用热部署,中断时间<30秒 |
|
||||
| 前端静态资源更新 | 🟢 低 | nginx无需重启,刷新即生效 |
|
||||
|
||||
---
|
||||
|
||||
## 七、审批确认
|
||||
|
||||
| 角色 | 姓名 | 确认签字 | 日期 |
|
||||
|------|------|---------|------|
|
||||
| 开发负责人 | | | |
|
||||
| 测试负责人 | | | |
|
||||
| 运维负责人 | | | |
|
||||
| 项目经理 | | | |
|
||||
|
||||
---
|
||||
|
||||
## 八、特别说明
|
||||
|
||||
### 8.1 关于 V1.0.5__clean_region_data.sql
|
||||
|
||||
此脚本将**物理删除**所有非湖北省的区域数据(约3309条),仅保留湖北省及其下级区域(约120条)。
|
||||
|
||||
**建议处理方式**:
|
||||
1. 此脚本可在本次发布中暂不执行
|
||||
2. 单独安排时间,在业务低峰期执行
|
||||
3. 执行前必须完成 `pg_region` 表的完整备份
|
||||
4. 执行后需验证学校管理功能正常
|
||||
|
||||
### 8.2 注意事项
|
||||
|
||||
1. 发布前确保本地代码已全部提交并推送
|
||||
2. 建议选择业务低峰期(如晚间或周末)发布
|
||||
3. 发布后持续关注服务日志,确保无异常
|
||||
|
||||
---
|
||||
|
||||
*文档生成时间: 2026-02-05*
|
||||
|
|
@ -63,11 +63,19 @@
|
|||
<script setup>
|
||||
import request from '@/utils/request'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import useBaseDataStore from '@/store/modules/baseData'
|
||||
|
||||
const baseDataStore = useBaseDataStore()
|
||||
|
||||
const props = defineProps({
|
||||
// 默认区域路径(用于新增时预填)
|
||||
defaultRegionPath: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['success', 'add', 'update'])
|
||||
|
||||
const visible = ref(false)
|
||||
|
|
@ -148,6 +156,20 @@ const open = async (mId, row, index) => {
|
|||
if (row.schoolGradeId) {
|
||||
await loadClassList(row.schoolGradeId)
|
||||
}
|
||||
} else {
|
||||
// 新增模式:如果有默认区域路径,使用它
|
||||
applyDefaultRegion()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用默认区域路径
|
||||
*/
|
||||
const applyDefaultRegion = async () => {
|
||||
if (props.defaultRegionPath && props.defaultRegionPath.length > 0 && regionIds.value.length === 0) {
|
||||
regionIds.value = [...props.defaultRegionPath]
|
||||
form.regionId = props.defaultRegionPath[props.defaultRegionPath.length - 1]
|
||||
await loadSchoolList(form.regionId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,19 @@
|
|||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所在区域" prop="regionId">
|
||||
<el-cascader
|
||||
v-model="regionIds"
|
||||
:options="regionTree"
|
||||
:props="{ value: 'regionId', label: 'regionName', checkStrictly: true }"
|
||||
placeholder="请选择所在区域"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="handleMemberRegionChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 任教信息 -->
|
||||
|
|
@ -131,7 +144,7 @@
|
|||
<StudentSelectDialog ref="studentSelectRef" @success="loadBoundStudents" @add="handleLocalAddStudents" />
|
||||
|
||||
<!-- 任教信息编辑弹窗 -->
|
||||
<EducationDialog ref="educationDialogRef" @success="loadEducations" @add="handleLocalAddEducation" @update="handleLocalUpdateEducation" />
|
||||
<EducationDialog ref="educationDialogRef" :default-region-path="memberRegionPath" @success="loadEducations" @add="handleLocalAddEducation" @update="handleLocalUpdateEducation" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
|
|
@ -142,6 +155,9 @@ import { ElMessage } from 'element-plus'
|
|||
import { reactive, ref } from 'vue'
|
||||
import StudentSelectDialog from './StudentSelectDialog.vue'
|
||||
import EducationDialog from './EducationDialog.vue'
|
||||
import useBaseDataStore from '@/store/modules/baseData'
|
||||
|
||||
const baseDataStore = useBaseDataStore()
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
|
|
@ -154,6 +170,8 @@ const educationDialogRef = ref(null)
|
|||
|
||||
// 任教信息列表
|
||||
const educations = ref([])
|
||||
// 会员默认区域路径(用于弹窗默认值)
|
||||
const memberRegionPath = ref([])
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
|
|
@ -163,9 +181,14 @@ const form = reactive({
|
|||
gender: '0',
|
||||
birthday: '',
|
||||
status: '0',
|
||||
regionId: null,
|
||||
students: []
|
||||
})
|
||||
|
||||
// 区域相关
|
||||
const regionIds = ref([])
|
||||
const regionTree = ref([])
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
phone: [
|
||||
|
|
@ -182,12 +205,20 @@ const open = async (row) => {
|
|||
isEdit.value = !!row
|
||||
visible.value = true
|
||||
|
||||
// 加载区域树
|
||||
await loadRegionTree()
|
||||
|
||||
// 编辑模式加载会员数据
|
||||
if (row && row.memberId) {
|
||||
try {
|
||||
const res = await request.get(`/business/member/${row.memberId}`)
|
||||
if (res.code === 200 && res.data) {
|
||||
Object.assign(form, res.data)
|
||||
// 计算会员区域路径并设置regionIds
|
||||
await computeMemberRegionPath(res.data.regionId)
|
||||
if (res.data.regionId) {
|
||||
regionIds.value = [...memberRegionPath.value]
|
||||
}
|
||||
// 加载任教信息和学生列表
|
||||
await Promise.all([loadEducations(), loadBoundStudents()])
|
||||
}
|
||||
|
|
@ -197,6 +228,58 @@ const open = async (row) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载区域树
|
||||
*/
|
||||
const loadRegionTree = async () => {
|
||||
try {
|
||||
regionTree.value = await baseDataStore.fetchRegionTree()
|
||||
} catch (e) {
|
||||
regionTree.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会员区域变更
|
||||
*/
|
||||
const handleMemberRegionChange = (val) => {
|
||||
form.regionId = val && val.length ? val[val.length - 1] : null
|
||||
// 更新 memberRegionPath 供子组件使用
|
||||
memberRegionPath.value = val || []
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算会员区域路径
|
||||
*/
|
||||
const computeMemberRegionPath = async (regionId) => {
|
||||
if (!regionId) {
|
||||
memberRegionPath.value = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 获取区域树
|
||||
const regionTree = await baseDataStore.fetchRegionTree()
|
||||
// 递归查找区域路径
|
||||
const findPath = (nodes, targetId, path = []) => {
|
||||
for (const node of nodes) {
|
||||
const currentPath = [...path, node.regionId]
|
||||
if (node.regionId === targetId) {
|
||||
return currentPath
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const found = findPath(node.children, targetId, currentPath)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
const path = findPath(regionTree, regionId)
|
||||
memberRegionPath.value = path || []
|
||||
} catch (e) {
|
||||
memberRegionPath.value = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载任教信息列表
|
||||
*/
|
||||
|
|
@ -239,8 +322,11 @@ const resetForm = () => {
|
|||
form.gender = '0'
|
||||
form.birthday = ''
|
||||
form.status = '0'
|
||||
form.regionId = null
|
||||
form.students = []
|
||||
educations.value = []
|
||||
memberRegionPath.value = []
|
||||
regionIds.value = []
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -408,7 +494,8 @@ const handleSubmit = async () => {
|
|||
nickname: form.nickname,
|
||||
gender: form.gender,
|
||||
birthday: form.birthday,
|
||||
status: form.status
|
||||
status: form.status,
|
||||
regionId: form.regionId
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="nickname" label="昵称" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="regionId" label="所在区域" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ getRegionNameById(row.regionId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="gender" label="性别" width="60" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
|
||||
|
|
@ -131,9 +136,11 @@ import { Delete, Edit, Hide, Key, Plus, Refresh, Search, View } from '@element-p
|
|||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import request from '@/utils/request'
|
||||
import useBaseDataStore from '@/store/modules/baseData'
|
||||
import MemberDialog from './components/MemberDialog.vue'
|
||||
import ResetPwdDialog from './components/ResetPwdDialog.vue'
|
||||
|
||||
const baseDataStore = useBaseDataStore()
|
||||
const loading = ref(false)
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
|
|
@ -186,6 +193,24 @@ const formatBirthday = (birthday) => {
|
|||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 根据 regionId 在区域树中查找并返回完整路径名称(如 "湖北省 / 武汉市 / 江岸区")
|
||||
const getRegionNameById = (regionId) => {
|
||||
if (regionId == null || regionId === '') return ''
|
||||
const tree = baseDataStore.regionTree || []
|
||||
const findPath = (nodes, targetId, pathNames = []) => {
|
||||
for (const node of nodes) {
|
||||
const names = [...pathNames, node.regionName]
|
||||
if (node.regionId === targetId) return names.join(' / ')
|
||||
if (node.children?.length) {
|
||||
const found = findPath(node.children, targetId, names)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
return findPath(tree, regionId)
|
||||
}
|
||||
|
||||
// 获取会员列表
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
|
|
@ -284,7 +309,8 @@ const handleStatusChange = async (row) => {
|
|||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await baseDataStore.fetchRegionTree()
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue