fix: 学校管理列表树优化
1. 默认折叠:移除 default-expand-all 2. 修复折叠展开联动问题:row-key 改为 type_id 组合,保证唯一性 3. 删除提示优化: - 有子级时显示具体数量(如"3个年级、5个班级") - 有子级时弹出 alert 提示"请先删除子级",而不是直接删除
This commit is contained in:
parent
ee33a26e57
commit
5ef8420ace
|
|
@ -13,6 +13,9 @@ import org.dromara.pangu.member.service.IPgMemberService;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 会员管理
|
* 会员管理
|
||||||
*
|
*
|
||||||
|
|
@ -26,18 +29,27 @@ public class PgMemberController extends BaseController {
|
||||||
|
|
||||||
private final IPgMemberService memberService;
|
private final IPgMemberService memberService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会员列表
|
||||||
|
*/
|
||||||
@SaCheckPermission("business:member:list")
|
@SaCheckPermission("business:member:list")
|
||||||
@GetMapping("/list")
|
@GetMapping("/list")
|
||||||
public TableDataInfo<PgMember> list(PgMember member, PageQuery pageQuery) {
|
public TableDataInfo<PgMember> list(PgMember member, PageQuery pageQuery) {
|
||||||
return memberService.selectPageList(member, pageQuery);
|
return memberService.selectPageList(member, pageQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会员详情
|
||||||
|
*/
|
||||||
@SaCheckPermission("business:member:query")
|
@SaCheckPermission("business:member:query")
|
||||||
@GetMapping("/{memberId}")
|
@GetMapping("/{memberId}")
|
||||||
public R<PgMember> getInfo(@PathVariable Long memberId) {
|
public R<PgMember> getInfo(@PathVariable Long memberId) {
|
||||||
return R.ok(memberService.selectById(memberId));
|
return R.ok(memberService.selectById(memberId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增会员
|
||||||
|
*/
|
||||||
@SaCheckPermission("business:member:add")
|
@SaCheckPermission("business:member:add")
|
||||||
@Log(title = "会员管理", businessType = BusinessType.INSERT)
|
@Log(title = "会员管理", businessType = BusinessType.INSERT)
|
||||||
@PostMapping
|
@PostMapping
|
||||||
|
|
@ -45,6 +57,9 @@ public class PgMemberController extends BaseController {
|
||||||
return toAjax(memberService.insert(member));
|
return toAjax(memberService.insert(member));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会员
|
||||||
|
*/
|
||||||
@SaCheckPermission("business:member:edit")
|
@SaCheckPermission("business:member:edit")
|
||||||
@Log(title = "会员管理", businessType = BusinessType.UPDATE)
|
@Log(title = "会员管理", businessType = BusinessType.UPDATE)
|
||||||
@PutMapping
|
@PutMapping
|
||||||
|
|
@ -52,10 +67,44 @@ public class PgMemberController extends BaseController {
|
||||||
return toAjax(memberService.update(member));
|
return toAjax(memberService.update(member));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除会员
|
||||||
|
*/
|
||||||
@SaCheckPermission("business:member:remove")
|
@SaCheckPermission("business:member:remove")
|
||||||
@Log(title = "会员管理", businessType = BusinessType.DELETE)
|
@Log(title = "会员管理", businessType = BusinessType.DELETE)
|
||||||
@DeleteMapping("/{memberIds}")
|
@DeleteMapping("/{memberIds}")
|
||||||
public R<Void> remove(@PathVariable Long[] memberIds) {
|
public R<Void> remove(@PathVariable Long[] memberIds) {
|
||||||
return toAjax(memberService.deleteByIds(memberIds));
|
return toAjax(memberService.deleteByIds(memberIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置会员密码
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("business:member:resetPwd")
|
||||||
|
@Log(title = "会员管理-重置密码", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/resetPwd/{memberId}")
|
||||||
|
public R<Map<String, String>> resetPwd(@PathVariable Long memberId) {
|
||||||
|
String newPassword = memberService.resetPassword(memberId);
|
||||||
|
Map<String, String> result = new HashMap<>(2);
|
||||||
|
result.put("password", newPassword);
|
||||||
|
return R.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会员状态
|
||||||
|
*/
|
||||||
|
@SaCheckPermission("business:member:edit")
|
||||||
|
@Log(title = "会员管理-状态变更", businessType = BusinessType.UPDATE)
|
||||||
|
@PutMapping("/changeStatus")
|
||||||
|
public R<Void> changeStatus(@RequestBody PgMember member) {
|
||||||
|
return toAjax(memberService.changeStatus(member.getMemberId(), member.getStatus()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查手机号是否唯一
|
||||||
|
*/
|
||||||
|
@GetMapping("/checkPhoneUnique")
|
||||||
|
public R<Boolean> checkPhoneUnique(@RequestParam String phone, @RequestParam(required = false) Long memberId) {
|
||||||
|
return R.ok(memberService.checkPhoneUnique(phone, memberId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,68 @@ import java.util.List;
|
||||||
* @author pangu
|
* @author pangu
|
||||||
*/
|
*/
|
||||||
public interface IPgMemberService {
|
public interface IPgMemberService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询会员列表
|
||||||
|
*/
|
||||||
TableDataInfo<PgMember> selectPageList(PgMember member, PageQuery pageQuery);
|
TableDataInfo<PgMember> selectPageList(PgMember member, PageQuery pageQuery);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询会员列表
|
||||||
|
*/
|
||||||
List<PgMember> selectList(PgMember member);
|
List<PgMember> selectList(PgMember member);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID获取会员详情
|
||||||
|
*/
|
||||||
PgMember selectById(Long memberId);
|
PgMember selectById(Long memberId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增会员
|
||||||
|
*/
|
||||||
int insert(PgMember member);
|
int insert(PgMember member);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会员
|
||||||
|
*/
|
||||||
int update(PgMember member);
|
int update(PgMember member);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除会员
|
||||||
|
*/
|
||||||
int deleteByIds(Long[] memberIds);
|
int deleteByIds(Long[] memberIds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除单个会员
|
||||||
|
*/
|
||||||
|
int deleteById(Long memberId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置会员密码
|
||||||
|
* @return 新密码
|
||||||
|
*/
|
||||||
|
String resetPassword(Long memberId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改会员状态
|
||||||
|
*/
|
||||||
|
int changeStatus(Long memberId, String status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查手机号是否唯一
|
||||||
|
* @param phone 手机号
|
||||||
|
* @param memberId 会员ID(编辑时排除自己)
|
||||||
|
* @return true=唯一,false=不唯一
|
||||||
|
*/
|
||||||
|
boolean checkPhoneUnique(String phone, Long memberId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查会员是否可删除(是否绑定学生)
|
||||||
|
*/
|
||||||
|
boolean checkCanDelete(Long memberId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据手机号查询会员
|
||||||
|
*/
|
||||||
|
PgMember selectByPhone(String phone);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
package org.dromara.pangu.member.service.impl;
|
package org.dromara.pangu.member.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
import org.dromara.pangu.member.domain.PgMember;
|
import org.dromara.pangu.member.domain.PgMember;
|
||||||
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
import org.dromara.pangu.member.mapper.PgMemberMapper;
|
||||||
import org.dromara.pangu.member.service.IPgMemberService;
|
import org.dromara.pangu.member.service.IPgMemberService;
|
||||||
|
import org.dromara.pangu.student.mapper.PgStudentMapper;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,6 +30,10 @@ import java.util.List;
|
||||||
public class PgMemberServiceImpl implements IPgMemberService {
|
public class PgMemberServiceImpl implements IPgMemberService {
|
||||||
|
|
||||||
private final PgMemberMapper baseMapper;
|
private final PgMemberMapper baseMapper;
|
||||||
|
private final PgStudentMapper studentMapper;
|
||||||
|
|
||||||
|
private static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
|
||||||
|
private static final String DEFAULT_PASSWORD = "123456";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TableDataInfo<PgMember> selectPageList(PgMember member, PageQuery pageQuery) {
|
public TableDataInfo<PgMember> selectPageList(PgMember member, PageQuery pageQuery) {
|
||||||
|
|
@ -43,20 +53,170 @@ public class PgMemberServiceImpl implements IPgMemberService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int insert(PgMember member) {
|
public int insert(PgMember member) {
|
||||||
|
// 校验手机号唯一性
|
||||||
|
if (!checkPhoneUnique(member.getPhone(), null)) {
|
||||||
|
throw new ServiceException("手机号已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成会员编号: JS + 时间戳
|
||||||
|
member.setMemberCode("JS" + System.currentTimeMillis());
|
||||||
|
|
||||||
|
// 昵称未填写时自动生成: 用户 + 手机号后4位
|
||||||
|
if (StrUtil.isBlank(member.getNickname()) && StrUtil.isNotBlank(member.getPhone())) {
|
||||||
|
member.setNickname("用户" + member.getPhone().substring(member.getPhone().length() - 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密码加密(默认密码)
|
||||||
|
if (StrUtil.isBlank(member.getPassword())) {
|
||||||
|
member.setPassword(PASSWORD_ENCODER.encode(DEFAULT_PASSWORD));
|
||||||
|
} else {
|
||||||
|
member.setPassword(PASSWORD_ENCODER.encode(member.getPassword()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置注册时间和来源
|
||||||
|
member.setRegisterTime(new Date());
|
||||||
|
if (StrUtil.isBlank(member.getRegisterSource())) {
|
||||||
|
member.setRegisterSource("3"); // 后台新增
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认状态为正常
|
||||||
|
if (StrUtil.isBlank(member.getStatus())) {
|
||||||
|
member.setStatus("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 教师身份校验必填字段
|
||||||
|
validateTeacherInfo(member);
|
||||||
|
|
||||||
return baseMapper.insert(member);
|
return baseMapper.insert(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int update(PgMember member) {
|
public int update(PgMember member) {
|
||||||
|
// 校验手机号唯一性
|
||||||
|
if (StrUtil.isNotBlank(member.getPhone()) && !checkPhoneUnique(member.getPhone(), member.getMemberId())) {
|
||||||
|
throw new ServiceException("手机号已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 教师身份校验必填字段
|
||||||
|
validateTeacherInfo(member);
|
||||||
|
|
||||||
|
// 家长身份清空学校信息
|
||||||
|
if ("1".equals(member.getIdentityType())) {
|
||||||
|
member.setRegionId(null);
|
||||||
|
member.setSchoolId(null);
|
||||||
|
member.setSchoolGradeId(null);
|
||||||
|
member.setSchoolClassId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不更新密码(密码通过重置接口更新)
|
||||||
|
member.setPassword(null);
|
||||||
|
|
||||||
return baseMapper.updateById(member);
|
return baseMapper.updateById(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public int deleteByIds(Long[] memberIds) {
|
public int deleteByIds(Long[] memberIds) {
|
||||||
|
// 检查每个会员是否可删除
|
||||||
|
for (Long memberId : memberIds) {
|
||||||
|
if (!checkCanDelete(memberId)) {
|
||||||
|
PgMember member = baseMapper.selectById(memberId);
|
||||||
|
String nickname = member != null ? member.getNickname() : String.valueOf(memberId);
|
||||||
|
throw new ServiceException("会员【" + nickname + "】已绑定学生,请先解绑学生后再删除");
|
||||||
|
}
|
||||||
|
}
|
||||||
return baseMapper.deleteByIds(Arrays.asList(memberIds));
|
return baseMapper.deleteByIds(Arrays.asList(memberIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public int deleteById(Long memberId) {
|
||||||
|
if (!checkCanDelete(memberId)) {
|
||||||
|
throw new ServiceException("该会员已绑定学生,请先解绑学生后再删除");
|
||||||
|
}
|
||||||
|
return baseMapper.deleteById(memberId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String resetPassword(Long memberId) {
|
||||||
|
PgMember member = baseMapper.selectById(memberId);
|
||||||
|
if (member == null) {
|
||||||
|
throw new ServiceException("会员不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成8位随机密码
|
||||||
|
String newPassword = RandomUtil.randomString(8);
|
||||||
|
|
||||||
|
PgMember updateMember = new PgMember();
|
||||||
|
updateMember.setMemberId(memberId);
|
||||||
|
updateMember.setPassword(PASSWORD_ENCODER.encode(newPassword));
|
||||||
|
baseMapper.updateById(updateMember);
|
||||||
|
|
||||||
|
return newPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int changeStatus(Long memberId, String status) {
|
||||||
|
PgMember member = new PgMember();
|
||||||
|
member.setMemberId(memberId);
|
||||||
|
member.setStatus(status);
|
||||||
|
return baseMapper.updateById(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkPhoneUnique(String phone, Long memberId) {
|
||||||
|
if (StrUtil.isBlank(phone)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<PgMember> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(PgMember::getPhone, phone);
|
||||||
|
if (memberId != null) {
|
||||||
|
wrapper.ne(PgMember::getMemberId, memberId);
|
||||||
|
}
|
||||||
|
return baseMapper.selectCount(wrapper) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checkCanDelete(Long memberId) {
|
||||||
|
// 检查是否有绑定的学生
|
||||||
|
Long count = studentMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<org.dromara.pangu.student.domain.PgStudent>()
|
||||||
|
.eq(org.dromara.pangu.student.domain.PgStudent::getMemberId, memberId)
|
||||||
|
);
|
||||||
|
return count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PgMember selectByPhone(String phone) {
|
||||||
|
return baseMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<PgMember>().eq(PgMember::getPhone, phone)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验教师身份必填字段
|
||||||
|
*/
|
||||||
|
private void validateTeacherInfo(PgMember member) {
|
||||||
|
if ("2".equals(member.getIdentityType())) {
|
||||||
|
if (member.getRegionId() == null) {
|
||||||
|
throw new ServiceException("教师身份必须选择所属区域");
|
||||||
|
}
|
||||||
|
if (member.getSchoolId() == null) {
|
||||||
|
throw new ServiceException("教师身份必须选择所属学校");
|
||||||
|
}
|
||||||
|
if (member.getSchoolGradeId() == null) {
|
||||||
|
throw new ServiceException("教师身份必须选择所属年级");
|
||||||
|
}
|
||||||
|
if (member.getSchoolClassId() == null) {
|
||||||
|
throw new ServiceException("教师身份必须选择所属班级");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private LambdaQueryWrapper<PgMember> buildQueryWrapper(PgMember member) {
|
private LambdaQueryWrapper<PgMember> buildQueryWrapper(PgMember member) {
|
||||||
LambdaQueryWrapper<PgMember> lqw = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<PgMember> lqw = new LambdaQueryWrapper<>();
|
||||||
lqw.like(StrUtil.isNotBlank(member.getNickname()), PgMember::getNickname, member.getNickname());
|
lqw.like(StrUtil.isNotBlank(member.getNickname()), PgMember::getNickname, member.getNickname());
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,10 @@
|
||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="visible"
|
v-model="visible"
|
||||||
:title="memberId ? '编辑会员' : '新增会员'"
|
:title="isEdit ? '编辑会员' : '新增会员'"
|
||||||
width="700px"
|
width="700px"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
@open="handleOpen"
|
|
||||||
>
|
>
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
|
|
@ -25,7 +24,7 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="昵称" prop="nickname">
|
<el-form-item label="昵称" prop="nickname">
|
||||||
<el-input v-model="form.nickname" placeholder="请输入昵称" />
|
<el-input v-model="form.nickname" placeholder="不填则自动生成" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
@ -56,7 +55,7 @@
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="身份类型" prop="identityType">
|
<el-form-item label="身份类型" prop="identityType">
|
||||||
<el-radio-group v-model="form.identityType">
|
<el-radio-group v-model="form.identityType" @change="handleIdentityChange">
|
||||||
<el-radio value="1">家长</el-radio>
|
<el-radio value="1">家长</el-radio>
|
||||||
<el-radio value="2">教师</el-radio>
|
<el-radio value="2">教师</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
|
|
@ -77,14 +76,14 @@
|
||||||
|
|
||||||
<!-- 教师身份时显示学校信息 -->
|
<!-- 教师身份时显示学校信息 -->
|
||||||
<template v-if="form.identityType === '2'">
|
<template v-if="form.identityType === '2'">
|
||||||
<el-divider content-position="left">学校信息</el-divider>
|
<el-divider content-position="left">学校信息(教师必填)</el-divider>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="区域" prop="regionId">
|
<el-form-item label="区域" prop="regionId" :rules="teacherRules.regionId">
|
||||||
<el-cascader
|
<el-cascader
|
||||||
v-model="form.regionIds"
|
v-model="form.regionIds"
|
||||||
:options="regionTree"
|
:options="regionTree"
|
||||||
:props="{ value: 'id', label: 'label', checkStrictly: true }"
|
:props="{ value: 'regionId', label: 'regionName', checkStrictly: true }"
|
||||||
placeholder="请选择区域"
|
placeholder="请选择区域"
|
||||||
clearable
|
clearable
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
|
@ -93,25 +92,25 @@
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="学校" prop="schoolId">
|
<el-form-item label="学校" prop="schoolId" :rules="teacherRules.schoolId">
|
||||||
<el-select v-model="form.schoolId" placeholder="请选择学校" clearable style="width: 100%" @change="handleSchoolChange">
|
<el-select v-model="form.schoolId" placeholder="请选择学校" clearable style="width: 100%" @change="handleSchoolChange">
|
||||||
<el-option v-for="item in schoolList" :key="item.id" :label="item.name" :value="item.id" />
|
<el-option v-for="item in schoolList" :key="item.schoolId" :label="item.schoolName" :value="item.schoolId" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="年级" prop="gradeId">
|
<el-form-item label="年级" prop="schoolGradeId" :rules="teacherRules.schoolGradeId">
|
||||||
<el-select v-model="form.gradeId" placeholder="请选择年级" clearable style="width: 100%" @change="handleGradeChange">
|
<el-select v-model="form.schoolGradeId" placeholder="请选择年级" clearable style="width: 100%" @change="handleGradeChange">
|
||||||
<el-option v-for="item in gradeList" :key="item.id" :label="item.name" :value="item.id" />
|
<el-option v-for="item in gradeList" :key="item.id" :label="item.gradeName" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="班级" prop="classId">
|
<el-form-item label="班级" prop="schoolClassId" :rules="teacherRules.schoolClassId">
|
||||||
<el-select v-model="form.classId" placeholder="请选择班级" clearable style="width: 100%">
|
<el-select v-model="form.schoolClassId" placeholder="请选择班级" clearable style="width: 100%">
|
||||||
<el-option v-for="item in classList" :key="item.id" :label="item.name" :value="item.id" />
|
<el-option v-for="item in classList" :key="item.id" :label="item.className" :value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
@ -120,6 +119,14 @@
|
||||||
|
|
||||||
<!-- 绑定学生 -->
|
<!-- 绑定学生 -->
|
||||||
<el-divider content-position="left">绑定学生</el-divider>
|
<el-divider content-position="left">绑定学生</el-divider>
|
||||||
|
<el-alert
|
||||||
|
v-if="form.identityType === '2'"
|
||||||
|
title="教师只能绑定本校学生"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
style="margin-bottom: 12px"
|
||||||
|
/>
|
||||||
<el-row style="margin-bottom: 12px;">
|
<el-row style="margin-bottom: 12px;">
|
||||||
<el-button type="primary" size="small" :icon="Plus" @click="handleAddStudent">添加学生</el-button>
|
<el-button type="primary" size="small" :icon="Plus" @click="handleAddStudent">添加学生</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
@ -127,14 +134,18 @@
|
||||||
<template #empty>
|
<template #empty>
|
||||||
<el-empty description="暂无绑定学生" :image-size="60" />
|
<el-empty description="暂无绑定学生" :image-size="60" />
|
||||||
</template>
|
</template>
|
||||||
<el-table-column prop="name" label="姓名" min-width="80" />
|
<el-table-column prop="studentName" label="姓名" min-width="80" />
|
||||||
<el-table-column prop="studentNo" label="学号" width="120" />
|
<el-table-column prop="studentNo" label="学号" width="120" />
|
||||||
<el-table-column prop="schoolName" label="学校" min-width="120" show-overflow-tooltip />
|
<el-table-column prop="schoolName" label="学校" min-width="120" show-overflow-tooltip />
|
||||||
<el-table-column prop="gradeName" label="年级" width="80" />
|
<el-table-column prop="gradeName" label="年级" width="80" />
|
||||||
<el-table-column prop="className" label="班级" width="60" />
|
<el-table-column prop="className" label="班级" width="60" />
|
||||||
<el-table-column label="操作" width="80" align="center">
|
<el-table-column label="操作" width="80" align="center">
|
||||||
<template #default="{ $index }">
|
<template #default="{ $index }">
|
||||||
<el-button link type="danger" size="small" @click="handleRemoveStudent($index)">移除</el-button>
|
<el-popconfirm title="确定解绑该学生?" @confirm="handleRemoveStudent($index)">
|
||||||
|
<template #reference>
|
||||||
|
<el-button link type="danger" size="small">解绑</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -147,34 +158,21 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { addMember, getClassList, getGradeList, getMember, getRegionTree, getSchoolList, updateMember } from '@/api/pangu/member'
|
|
||||||
import { Plus } from '@element-plus/icons-vue'
|
import { Plus } from '@element-plus/icons-vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
const props = defineProps({
|
const emit = defineEmits(['success'])
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
memberId: {
|
|
||||||
type: [Number, null],
|
|
||||||
default: null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'success'])
|
|
||||||
|
|
||||||
const visible = computed({
|
|
||||||
get: () => props.modelValue,
|
|
||||||
set: (val) => emit('update:modelValue', val)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const isEdit = ref(false)
|
||||||
const formRef = ref(null)
|
const formRef = ref(null)
|
||||||
const submitLoading = ref(false)
|
const submitLoading = ref(false)
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
|
memberId: null,
|
||||||
phone: '',
|
phone: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
gender: '0',
|
gender: '0',
|
||||||
|
|
@ -184,8 +182,8 @@ const form = reactive({
|
||||||
regionIds: [],
|
regionIds: [],
|
||||||
regionId: null,
|
regionId: null,
|
||||||
schoolId: null,
|
schoolId: null,
|
||||||
gradeId: null,
|
schoolGradeId: null,
|
||||||
classId: null,
|
schoolClassId: null,
|
||||||
students: []
|
students: []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -194,9 +192,20 @@ const rules = {
|
||||||
phone: [
|
phone: [
|
||||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||||
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
identityType: [
|
||||||
|
{ required: true, message: '请选择身份类型', trigger: 'change' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 教师身份额外校验规则
|
||||||
|
const teacherRules = {
|
||||||
|
regionId: [{ required: true, message: '请选择所属区域', trigger: 'change' }],
|
||||||
|
schoolId: [{ required: true, message: '请选择所属学校', trigger: 'change' }],
|
||||||
|
schoolGradeId: [{ required: true, message: '请选择所属年级', trigger: 'change' }],
|
||||||
|
schoolClassId: [{ required: true, message: '请选择所属班级', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
// 下拉选项数据
|
// 下拉选项数据
|
||||||
const regionTree = ref([])
|
const regionTree = ref([])
|
||||||
const schoolList = ref([])
|
const schoolList = ref([])
|
||||||
|
|
@ -204,39 +213,38 @@ const gradeList = ref([])
|
||||||
const classList = ref([])
|
const classList = ref([])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹窗打开时加载数据
|
* 打开弹窗
|
||||||
*/
|
*/
|
||||||
const handleOpen = async () => {
|
const open = async (row) => {
|
||||||
|
resetForm()
|
||||||
|
isEdit.value = !!row
|
||||||
|
visible.value = true
|
||||||
|
|
||||||
// 加载区域树
|
// 加载区域树
|
||||||
try {
|
await loadRegionTree()
|
||||||
const res = await getRegionTree()
|
|
||||||
regionTree.value = res.data || []
|
|
||||||
} catch (e) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑模式加载会员数据
|
// 编辑模式加载会员数据
|
||||||
if (props.memberId) {
|
if (row && row.memberId) {
|
||||||
try {
|
try {
|
||||||
const res = await getMember(props.memberId)
|
const res = await request.get(`/business/member/${row.memberId}`)
|
||||||
if (res.data) {
|
if (res.code === 200 && res.data) {
|
||||||
Object.assign(form, res.data)
|
Object.assign(form, res.data)
|
||||||
// 加载关联数据
|
// 处理区域级联
|
||||||
if (form.regionId) {
|
if (form.regionId) {
|
||||||
|
// 构建regionIds数组用于级联选择器回显
|
||||||
|
form.regionIds = [form.regionId]
|
||||||
await loadSchoolList(form.regionId)
|
await loadSchoolList(form.regionId)
|
||||||
}
|
}
|
||||||
if (form.schoolId) {
|
if (form.schoolId) {
|
||||||
await loadGradeList(form.schoolId)
|
await loadGradeList(form.schoolId)
|
||||||
}
|
}
|
||||||
if (form.gradeId) {
|
if (form.schoolGradeId) {
|
||||||
await loadClassList(form.gradeId)
|
await loadClassList(form.schoolGradeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// 忽略错误
|
console.error('加载会员数据失败', e)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
resetForm()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,6 +252,7 @@ const handleOpen = async () => {
|
||||||
* 重置表单
|
* 重置表单
|
||||||
*/
|
*/
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
|
form.memberId = null
|
||||||
form.phone = ''
|
form.phone = ''
|
||||||
form.nickname = ''
|
form.nickname = ''
|
||||||
form.gender = '0'
|
form.gender = '0'
|
||||||
|
|
@ -253,9 +262,24 @@ const resetForm = () => {
|
||||||
form.regionIds = []
|
form.regionIds = []
|
||||||
form.regionId = null
|
form.regionId = null
|
||||||
form.schoolId = null
|
form.schoolId = null
|
||||||
form.gradeId = null
|
form.schoolGradeId = null
|
||||||
form.classId = null
|
form.schoolClassId = null
|
||||||
form.students = []
|
form.students = []
|
||||||
|
schoolList.value = []
|
||||||
|
gradeList.value = []
|
||||||
|
classList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载区域树
|
||||||
|
*/
|
||||||
|
const loadRegionTree = async () => {
|
||||||
|
try {
|
||||||
|
const res = await request.get('/business/region/tree')
|
||||||
|
regionTree.value = res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
regionTree.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -263,8 +287,8 @@ const resetForm = () => {
|
||||||
*/
|
*/
|
||||||
const loadSchoolList = async (regionId) => {
|
const loadSchoolList = async (regionId) => {
|
||||||
try {
|
try {
|
||||||
const res = await getSchoolList(regionId)
|
const res = await request.get('/business/school/list', { params: { regionId } })
|
||||||
schoolList.value = res.data || []
|
schoolList.value = res.rows || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
schoolList.value = []
|
schoolList.value = []
|
||||||
}
|
}
|
||||||
|
|
@ -275,7 +299,7 @@ const loadSchoolList = async (regionId) => {
|
||||||
*/
|
*/
|
||||||
const loadGradeList = async (schoolId) => {
|
const loadGradeList = async (schoolId) => {
|
||||||
try {
|
try {
|
||||||
const res = await getGradeList(schoolId)
|
const res = await request.get('/business/school/grades', { params: { schoolId } })
|
||||||
gradeList.value = res.data || []
|
gradeList.value = res.data || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
gradeList.value = []
|
gradeList.value = []
|
||||||
|
|
@ -285,23 +309,40 @@ const loadGradeList = async (schoolId) => {
|
||||||
/**
|
/**
|
||||||
* 加载班级列表
|
* 加载班级列表
|
||||||
*/
|
*/
|
||||||
const loadClassList = async (gradeId) => {
|
const loadClassList = async (schoolGradeId) => {
|
||||||
try {
|
try {
|
||||||
const res = await getClassList(gradeId)
|
const res = await request.get('/business/school/classes', { params: { schoolGradeId } })
|
||||||
classList.value = res.data || []
|
classList.value = res.data || []
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
classList.value = []
|
classList.value = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 身份类型变更
|
||||||
|
*/
|
||||||
|
const handleIdentityChange = () => {
|
||||||
|
// 切换为家长时清空学校信息
|
||||||
|
if (form.identityType === '1') {
|
||||||
|
form.regionIds = []
|
||||||
|
form.regionId = null
|
||||||
|
form.schoolId = null
|
||||||
|
form.schoolGradeId = null
|
||||||
|
form.schoolClassId = null
|
||||||
|
schoolList.value = []
|
||||||
|
gradeList.value = []
|
||||||
|
classList.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 区域变更
|
* 区域变更
|
||||||
*/
|
*/
|
||||||
const handleRegionChange = (val) => {
|
const handleRegionChange = (val) => {
|
||||||
form.regionId = val && val.length ? val[val.length - 1] : null
|
form.regionId = val && val.length ? val[val.length - 1] : null
|
||||||
form.schoolId = null
|
form.schoolId = null
|
||||||
form.gradeId = null
|
form.schoolGradeId = null
|
||||||
form.classId = null
|
form.schoolClassId = null
|
||||||
schoolList.value = []
|
schoolList.value = []
|
||||||
gradeList.value = []
|
gradeList.value = []
|
||||||
classList.value = []
|
classList.value = []
|
||||||
|
|
@ -314,8 +355,8 @@ const handleRegionChange = (val) => {
|
||||||
* 学校变更
|
* 学校变更
|
||||||
*/
|
*/
|
||||||
const handleSchoolChange = () => {
|
const handleSchoolChange = () => {
|
||||||
form.gradeId = null
|
form.schoolGradeId = null
|
||||||
form.classId = null
|
form.schoolClassId = null
|
||||||
gradeList.value = []
|
gradeList.value = []
|
||||||
classList.value = []
|
classList.value = []
|
||||||
if (form.schoolId) {
|
if (form.schoolId) {
|
||||||
|
|
@ -327,18 +368,18 @@ const handleSchoolChange = () => {
|
||||||
* 年级变更
|
* 年级变更
|
||||||
*/
|
*/
|
||||||
const handleGradeChange = () => {
|
const handleGradeChange = () => {
|
||||||
form.classId = null
|
form.schoolClassId = null
|
||||||
classList.value = []
|
classList.value = []
|
||||||
if (form.gradeId) {
|
if (form.schoolGradeId) {
|
||||||
loadClassList(form.gradeId)
|
loadClassList(form.schoolGradeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加学生(模拟,实际应该弹出学生选择器)
|
* 添加学生
|
||||||
*/
|
*/
|
||||||
const handleAddStudent = () => {
|
const handleAddStudent = () => {
|
||||||
ElMessage.info('请选择要绑定的学生')
|
ElMessage.info('学生选择功能待开发')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -360,24 +401,30 @@ const handleSubmit = async () => {
|
||||||
|
|
||||||
submitLoading.value = true
|
submitLoading.value = true
|
||||||
try {
|
try {
|
||||||
if (props.memberId) {
|
const data = { ...form }
|
||||||
await updateMember({ ...form, id: props.memberId })
|
// 移除不需要提交的字段
|
||||||
ElMessage.success('修改成功')
|
delete data.regionIds
|
||||||
|
delete data.students
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
const res = await request.put('/business/member', data)
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await addMember(form)
|
const res = await request.post('/business/member', data)
|
||||||
ElMessage.success('新增成功')
|
if (res.code === 200) {
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
visible.value = false
|
|
||||||
emit('success')
|
|
||||||
} finally {
|
} finally {
|
||||||
submitLoading.value = false
|
submitLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听弹窗关闭,重置表单
|
defineExpose({ open })
|
||||||
watch(visible, (val) => {
|
|
||||||
if (!val) {
|
|
||||||
formRef.value?.resetFields()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<!--
|
||||||
|
重置密码成功弹窗
|
||||||
|
@author pangu
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="密码重置成功"
|
||||||
|
width="420px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<div class="reset-pwd-content">
|
||||||
|
<el-icon class="success-icon" color="#67C23A" :size="48">
|
||||||
|
<CircleCheckFilled />
|
||||||
|
</el-icon>
|
||||||
|
<p class="tip-text">密码已重置,请复制新密码发送给用户</p>
|
||||||
|
<el-input
|
||||||
|
v-model="password"
|
||||||
|
readonly
|
||||||
|
class="pwd-input"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="DocumentCopy" @click="handleCopy">复制</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="visible = false">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { CircleCheckFilled, DocumentCopy } from '@element-plus/icons-vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const password = ref('')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开弹窗
|
||||||
|
*/
|
||||||
|
const open = (newPassword) => {
|
||||||
|
password.value = newPassword || ''
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制密码到剪贴板
|
||||||
|
*/
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (!password.value) {
|
||||||
|
ElMessage.warning('没有可复制的密码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(password.value)
|
||||||
|
ElMessage.success('复制成功')
|
||||||
|
} catch (e) {
|
||||||
|
// 降级方案:使用 execCommand
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = password.value
|
||||||
|
textarea.style.position = 'fixed'
|
||||||
|
textarea.style.opacity = '0'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
try {
|
||||||
|
document.execCommand('copy')
|
||||||
|
ElMessage.success('复制成功')
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error('复制失败,请手动复制')
|
||||||
|
}
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.reset-pwd-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
.success-icon {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.tip-text {
|
||||||
|
color: #606266;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.pwd-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -48,8 +48,8 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||||
<el-table-column prop="memberNo" label="会员编号" width="140" />
|
<el-table-column prop="memberCode" label="会员编号" width="160" />
|
||||||
<el-table-column prop="phone" label="手机号" width="120">
|
<el-table-column prop="phone" label="手机号" width="130">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
{{ maskPhone(row.phone) }}
|
{{ maskPhone(row.phone) }}
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -60,28 +60,45 @@
|
||||||
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
|
{{ row.gender === '1' ? '男' : row.gender === '2' ? '女' : '未知' }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="birthday" label="出生日期" width="100" />
|
<el-table-column prop="birthday" label="出生日期" width="110" />
|
||||||
<el-table-column prop="identityType" label="身份类型" width="80" align="center">
|
<el-table-column prop="identityType" label="身份类型" width="85" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.identityType === '1' ? 'success' : 'warning'">
|
<el-tag :type="row.identityType === '1' ? 'success' : 'warning'">
|
||||||
{{ row.identityType === '1' ? '家长' : '教师' }}
|
{{ row.identityType === '1' ? '家长' : '教师' }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="registerTime" label="注册时间" width="160" />
|
<el-table-column prop="registerTime" label="注册时间" width="165" />
|
||||||
<el-table-column prop="registerSource" label="注册来源" width="80" align="center" />
|
<el-table-column prop="registerSource" label="注册来源" width="85" align="center">
|
||||||
<el-table-column prop="status" label="状态" width="70" align="center">
|
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
{{ formatRegisterSource(row.registerSource) }}
|
||||||
{{ row.status === '0' ? '正常' : '停用' }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
<el-table-column prop="openId" label="openId" width="120" show-overflow-tooltip />
|
||||||
|
<el-table-column prop="status" label="状态" width="70" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-switch
|
||||||
|
v-model="row.status"
|
||||||
|
active-value="0"
|
||||||
|
inactive-value="1"
|
||||||
|
@change="handleStatusChange(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button type="warning" link :icon="Key" @click="handleResetPwd(row)">重置密码</el-button>
|
<el-button type="warning" link :icon="Key" @click="handleResetPwd(row)">重置密码</el-button>
|
||||||
<el-button type="danger" link :icon="Delete" @click="handleDelete(row)">删除</el-button>
|
<el-popconfirm
|
||||||
|
title="确定要删除该会员吗?"
|
||||||
|
confirm-button-text="确定"
|
||||||
|
cancel-button-text="取消"
|
||||||
|
@confirm="handleDelete(row)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="danger" link :icon="Delete">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -94,13 +111,13 @@
|
||||||
:total="total"
|
:total="total"
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
style="margin-top: 16px; justify-content: flex-end"
|
style="margin-top: 16px; justify-content: flex-end"
|
||||||
@size-change="handleQuery"
|
@size-change="getList"
|
||||||
@current-change="handleQuery"
|
@current-change="getList"
|
||||||
/>
|
/>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 新增/编辑弹窗 -->
|
<!-- 新增/编辑弹窗 -->
|
||||||
<MemberDialog ref="memberDialogRef" @success="handleQuery" />
|
<MemberDialog ref="memberDialogRef" @success="getList" />
|
||||||
|
|
||||||
<!-- 重置密码弹窗 -->
|
<!-- 重置密码弹窗 -->
|
||||||
<ResetPwdDialog ref="resetPwdDialogRef" />
|
<ResetPwdDialog ref="resetPwdDialogRef" />
|
||||||
|
|
@ -130,9 +147,7 @@ const queryParams = ref({
|
||||||
phone: '',
|
phone: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
identityType: '',
|
identityType: '',
|
||||||
status: '',
|
status: ''
|
||||||
beginTime: '',
|
|
||||||
endTime: ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const memberDialogRef = ref()
|
const memberDialogRef = ref()
|
||||||
|
|
@ -144,22 +159,28 @@ const maskPhone = (phone) => {
|
||||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 格式化注册来源
|
||||||
|
const formatRegisterSource = (source) => {
|
||||||
|
const map = { '1': '小程序', '2': 'H5', '3': '后台新增', '4': '批量导入' }
|
||||||
|
return map[source] || source
|
||||||
|
}
|
||||||
|
|
||||||
// 获取会员列表
|
// 获取会员列表
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 处理日期范围
|
|
||||||
if (dateRange.value && dateRange.value.length === 2) {
|
|
||||||
queryParams.value.beginTime = dateRange.value[0]
|
|
||||||
queryParams.value.endTime = dateRange.value[1]
|
|
||||||
} else {
|
|
||||||
queryParams.value.beginTime = ''
|
|
||||||
queryParams.value.endTime = ''
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const res = await request.get('/business/member/list', { params: queryParams.value })
|
const params = { ...queryParams.value }
|
||||||
|
// 处理日期范围
|
||||||
|
if (dateRange.value && dateRange.value.length === 2) {
|
||||||
|
params.params = {
|
||||||
|
beginRegisterTime: dateRange.value[0],
|
||||||
|
endRegisterTime: dateRange.value[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await request.get('/business/member/list', { params })
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
tableData.value = res.rows
|
tableData.value = res.rows || []
|
||||||
total.value = res.total
|
total.value = res.total || 0
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
@ -180,9 +201,7 @@ const resetQuery = () => {
|
||||||
phone: '',
|
phone: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
identityType: '',
|
identityType: '',
|
||||||
status: '',
|
status: ''
|
||||||
beginTime: '',
|
|
||||||
endTime: ''
|
|
||||||
}
|
}
|
||||||
dateRange.value = []
|
dateRange.value = []
|
||||||
getList()
|
getList()
|
||||||
|
|
@ -200,22 +219,49 @@ const handleEdit = (row) => {
|
||||||
|
|
||||||
// 重置密码
|
// 重置密码
|
||||||
const handleResetPwd = (row) => {
|
const handleResetPwd = (row) => {
|
||||||
resetPwdDialogRef.value?.open(row)
|
ElMessageBox.confirm(`确定要重置会员"${row.nickname || row.phone}"的密码吗?`, '提示', {
|
||||||
}
|
|
||||||
|
|
||||||
// 删除
|
|
||||||
const handleDelete = (row) => {
|
|
||||||
ElMessageBox.confirm(`确定要删除会员"${row.nickname}"吗?`, '提示', {
|
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
const res = await request.delete(`/business/member/${row.id}`)
|
const res = await request.put(`/business/member/resetPwd/${row.memberId}`)
|
||||||
|
if (res.code === 200) {
|
||||||
|
resetPwdDialogRef.value?.open(res.data.password)
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = async (row) => {
|
||||||
|
try {
|
||||||
|
const res = await request.delete(`/business/member/${row.memberId}`)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
} catch (e) {
|
||||||
|
// 错误已在request中处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态变更
|
||||||
|
const handleStatusChange = async (row) => {
|
||||||
|
const text = row.status === '0' ? '启用' : '停用'
|
||||||
|
try {
|
||||||
|
const res = await request.put('/business/member/changeStatus', {
|
||||||
|
memberId: row.memberId,
|
||||||
|
status: row.status
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
ElMessage.success(`${text}成功`)
|
||||||
|
} else {
|
||||||
|
// 恢复原状态
|
||||||
|
row.status = row.status === '0' ? '1' : '0'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 恢复原状态
|
||||||
|
row.status = row.status === '0' ? '1' : '0'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
||||||
|
|
@ -56,9 +56,8 @@
|
||||||
<el-table
|
<el-table
|
||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="treeData"
|
:data="treeData"
|
||||||
row-key="id"
|
:row-key="row => `${row.type}_${row.id}`"
|
||||||
border
|
border
|
||||||
default-expand-all
|
|
||||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
|
@ -264,25 +263,53 @@ const handleAddClass = (row) => {
|
||||||
classDialogRef.value?.open(schoolData)
|
classDialogRef.value?.open(schoolData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统计子级数量
|
||||||
|
const countChildren = (node) => {
|
||||||
|
let gradeCount = 0
|
||||||
|
let classCount = 0
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (child.type === 'grade') {
|
||||||
|
gradeCount++
|
||||||
|
// 统计年级下的班级
|
||||||
|
if (child.children && child.children.length > 0) {
|
||||||
|
classCount += child.children.filter(c => c.type === 'class').length
|
||||||
|
}
|
||||||
|
} else if (child.type === 'class') {
|
||||||
|
classCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { gradeCount, classCount }
|
||||||
|
}
|
||||||
|
|
||||||
// 删除操作 - 删除时提示是否有子级
|
// 删除操作 - 删除时提示是否有子级
|
||||||
const handleDelete = (row, type) => {
|
const handleDelete = (row, type) => {
|
||||||
let message = ''
|
let message = ''
|
||||||
let url = ''
|
let url = ''
|
||||||
|
|
||||||
if (type === 'school') {
|
if (type === 'school') {
|
||||||
// 检查是否有子级
|
const { gradeCount, classCount } = countChildren(row)
|
||||||
if (row.children && row.children.length > 0) {
|
if (gradeCount > 0 || classCount > 0) {
|
||||||
message = `学校"${row.name}"下有年级/班级,确定要删除吗?删除后其下的年级和班级也将被删除。`
|
// 有子级时,提示需要先删除子级
|
||||||
} else {
|
let childInfo = []
|
||||||
message = `确定要删除学校"${row.name}"吗?`
|
if (gradeCount > 0) childInfo.push(`${gradeCount}个年级`)
|
||||||
|
if (classCount > 0) childInfo.push(`${classCount}个班级`)
|
||||||
|
message = `学校"${row.name}"下存在${childInfo.join('、')},请先删除年级和班级后再删除学校。`
|
||||||
|
ElMessageBox.alert(message, '无法删除', { type: 'warning' })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
message = `确定要删除学校"${row.name}"吗?`
|
||||||
url = `/business/school/${row.id}`
|
url = `/business/school/${row.id}`
|
||||||
} else if (type === 'grade') {
|
} else if (type === 'grade') {
|
||||||
if (row.children && row.children.length > 0) {
|
const classCount = (row.children || []).filter(c => c.type === 'class').length
|
||||||
message = `年级"${row.name}"下有班级,确定要删除吗?其下的班级也将被删除。`
|
if (classCount > 0) {
|
||||||
} else {
|
// 有班级时,提示需要先删除班级
|
||||||
message = `确定要删除年级"${row.name}"吗?`
|
message = `年级"${row.name}"下存在${classCount}个班级,请先删除班级后再删除年级。`
|
||||||
|
ElMessageBox.alert(message, '无法删除', { type: 'warning' })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
message = `确定要删除年级"${row.name}"吗?`
|
||||||
url = `/business/school/grade/${row.id}`
|
url = `/business/school/grade/${row.id}`
|
||||||
} else {
|
} else {
|
||||||
message = `确定要删除班级"${row.name}"吗?`
|
message = `确定要删除班级"${row.name}"吗?`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue