新增培训:实现教师主数据P0能力
This commit is contained in:
1 parent
7fcaea29b8
commit
043c08823a
9 files changed
+586
-293
No files matched your search
+57
-40
@@ -1,13 +1,12 @@
|
||||
package com.ruoyi.web.controller.train;
|
||||
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -15,90 +14,108 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.train.domain.TrainTeacher;
|
||||
import com.ruoyi.train.service.ITrainTeacherService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
|
||||
/**
|
||||
* 教师信息Controller
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/train/teacher")
|
||||
public class TrainTeacherController extends BaseController
|
||||
{
|
||||
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
@Autowired
|
||||
private ITrainTeacherService trainTeacherService;
|
||||
|
||||
/**
|
||||
* 查询教师信息列表
|
||||
* 查询教师信息列表 (GET /train/teacher/list)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:list')")
|
||||
@PreAuthorize("@ss.hasPermi('train:teacher:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(TrainTeacher trainTeacher)
|
||||
{
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
Integer pageSize = pageDomain.getPageSize();
|
||||
if (pageSize != null && pageSize > 100)
|
||||
{
|
||||
throw new ServiceException("分页大小不能超过100");
|
||||
}
|
||||
startPage();
|
||||
List<TrainTeacher> list = trainTeacherService.selectTrainTeacherList(trainTeacher);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出教师信息列表
|
||||
* 获取教师信息详细信息 (GET /train/teacher/{tId})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:export')")
|
||||
@Log(title = "教师信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(HttpServletResponse response, TrainTeacher trainTeacher)
|
||||
{
|
||||
List<TrainTeacher> list = trainTeacherService.selectTrainTeacherList(trainTeacher);
|
||||
ExcelUtil<TrainTeacher> util = new ExcelUtil<TrainTeacher>(TrainTeacher.class);
|
||||
util.exportExcel(response, list, "教师信息数据");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取教师信息详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:query')")
|
||||
@PreAuthorize("@ss.hasPermi('train:teacher:query')")
|
||||
@GetMapping(value = "/{tId}")
|
||||
public AjaxResult getInfo(@PathVariable("tId") String tId)
|
||||
{
|
||||
return success(trainTeacherService.selectTrainTeacherByTId(tId));
|
||||
TrainTeacher teacher = trainTeacherService.selectTrainTeacherByTId(tId);
|
||||
if (teacher == null)
|
||||
{
|
||||
return AjaxResult.error(40401, "教师不存在");
|
||||
}
|
||||
return success(teacher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教师信息
|
||||
* 新增教师信息 (POST /train/teacher)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:add')")
|
||||
@Log(title = "教师信息", businessType = BusinessType.INSERT)
|
||||
@PreAuthorize("@ss.hasPermi('train:teacher:add')")
|
||||
@Log(title = "教师管理", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@RequestBody TrainTeacher trainTeacher)
|
||||
{
|
||||
if (StringUtils.isEmpty(trainTeacher.gettName()))
|
||||
{
|
||||
return AjaxResult.error("教师姓名不能为空");
|
||||
}
|
||||
if (trainTeacher.gettName().trim().length() > 50)
|
||||
{
|
||||
return AjaxResult.error("教师姓名长度不能超过50个字符");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !isValidPhone(trainTeacher.gettPhone()))
|
||||
{
|
||||
return AjaxResult.error("手机号码格式不正确");
|
||||
}
|
||||
return toAjax(trainTeacherService.insertTrainTeacher(trainTeacher));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改教师信息
|
||||
* 修改教师信息 (PUT /train/teacher/{tId})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:edit')")
|
||||
@Log(title = "教师信息", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
public AjaxResult edit(@RequestBody TrainTeacher trainTeacher)
|
||||
@PreAuthorize("@ss.hasPermi('train:teacher:edit')")
|
||||
@Log(title = "教师管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping(value = "/{tId}")
|
||||
public AjaxResult edit(@PathVariable("tId") String tId, @RequestBody TrainTeacher trainTeacher)
|
||||
{
|
||||
trainTeacher.settId(tId);
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettName()) && trainTeacher.gettName().trim().length() > 50)
|
||||
{
|
||||
return AjaxResult.error("教师姓名长度不能超过50个字符");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !isValidPhone(trainTeacher.gettPhone()))
|
||||
{
|
||||
return AjaxResult.error("手机号码格式不正确");
|
||||
}
|
||||
return toAjax(trainTeacherService.updateTrainTeacher(trainTeacher));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教师信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:teacher:remove')")
|
||||
@Log(title = "教师信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{tIds}")
|
||||
public AjaxResult remove(@PathVariable String[] tIds)
|
||||
private boolean isValidPhone(String phone)
|
||||
{
|
||||
return toAjax(trainTeacherService.deleteTrainTeacherByTIds(tIds));
|
||||
return PHONE_PATTERN.matcher(phone).matches();
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,15 @@ import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 教师信息对象 train_teacher
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
public class TrainTeacher extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 教师工号 */
|
||||
/** 教师工号/标识 */
|
||||
private String tId;
|
||||
|
||||
/** 教师姓名 */
|
||||
@@ -25,7 +25,7 @@ public class TrainTeacher extends BaseEntity
|
||||
private String tName;
|
||||
|
||||
/** 性别:男 / 女 */
|
||||
@Excel(name = "性别:男 / 女")
|
||||
@Excel(name = "性别")
|
||||
private String tGender;
|
||||
|
||||
/** 出生日期 */
|
||||
@@ -33,13 +33,16 @@ public class TrainTeacher extends BaseEntity
|
||||
@Excel(name = "出生日期", width = 30, dateFormat = "yyyy-MM-dd")
|
||||
private Date tBirth;
|
||||
|
||||
/** 所属院系 */
|
||||
@Excel(name = "所属院系")
|
||||
private String tDepartment;
|
||||
/** sys_user登录账号逻辑关联ID */
|
||||
private Long userId;
|
||||
|
||||
/** 职称:讲师 / 副教授 / 教授 */
|
||||
@Excel(name = "职称:讲师 / 副教授 / 教授")
|
||||
private String tTitle;
|
||||
/** 所属单位/部门名称 (由旧 t_department 迁移) */
|
||||
@Excel(name = "所属单位")
|
||||
private String orgName;
|
||||
|
||||
/** 职务/职称 (由旧 t_title 迁移) */
|
||||
@Excel(name = "职务/职称")
|
||||
private String jobTitle;
|
||||
|
||||
/** 联系电话 */
|
||||
@Excel(name = "联系电话")
|
||||
@@ -49,98 +52,121 @@ public class TrainTeacher extends BaseEntity
|
||||
@Excel(name = "邮箱")
|
||||
private String tEmail;
|
||||
|
||||
/** 创建人 (管理员 / 教师) */
|
||||
@Excel(name = "创建人 (管理员 / 教师)")
|
||||
private String createUser;
|
||||
/** 状态 (1:正常, 0:停用) */
|
||||
@Excel(name = "状态", readConverterExp = "1=正常,0=停用")
|
||||
private Integer status;
|
||||
|
||||
public void settId(String tId)
|
||||
/** 删除标志 (0:存在, 2:删除) */
|
||||
private String delFlag;
|
||||
|
||||
public void settId(String tId)
|
||||
{
|
||||
this.tId = tId;
|
||||
}
|
||||
|
||||
public String gettId()
|
||||
public String gettId()
|
||||
{
|
||||
return tId;
|
||||
}
|
||||
|
||||
public void settName(String tName)
|
||||
public void settName(String tName)
|
||||
{
|
||||
this.tName = tName;
|
||||
}
|
||||
|
||||
public String gettName()
|
||||
public String gettName()
|
||||
{
|
||||
return tName;
|
||||
}
|
||||
|
||||
public void settGender(String tGender)
|
||||
public void settGender(String tGender)
|
||||
{
|
||||
this.tGender = tGender;
|
||||
}
|
||||
|
||||
public String gettGender()
|
||||
public String gettGender()
|
||||
{
|
||||
return tGender;
|
||||
}
|
||||
|
||||
public void settBirth(Date tBirth)
|
||||
public void settBirth(Date tBirth)
|
||||
{
|
||||
this.tBirth = tBirth;
|
||||
}
|
||||
|
||||
public Date gettBirth()
|
||||
public Date gettBirth()
|
||||
{
|
||||
return tBirth;
|
||||
}
|
||||
|
||||
public void settDepartment(String tDepartment)
|
||||
public void setUserId(Long userId)
|
||||
{
|
||||
this.tDepartment = tDepartment;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String gettDepartment()
|
||||
public Long getUserId()
|
||||
{
|
||||
return tDepartment;
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void settTitle(String tTitle)
|
||||
public void setOrgName(String orgName)
|
||||
{
|
||||
this.tTitle = tTitle;
|
||||
this.orgName = orgName;
|
||||
}
|
||||
|
||||
public String gettTitle()
|
||||
public String getOrgName()
|
||||
{
|
||||
return tTitle;
|
||||
return orgName;
|
||||
}
|
||||
|
||||
public void settPhone(String tPhone)
|
||||
public void setJobTitle(String jobTitle)
|
||||
{
|
||||
this.jobTitle = jobTitle;
|
||||
}
|
||||
|
||||
public String getJobTitle()
|
||||
{
|
||||
return jobTitle;
|
||||
}
|
||||
|
||||
public void settPhone(String tPhone)
|
||||
{
|
||||
this.tPhone = tPhone;
|
||||
}
|
||||
|
||||
public String gettPhone()
|
||||
public String gettPhone()
|
||||
{
|
||||
return tPhone;
|
||||
}
|
||||
|
||||
public void settEmail(String tEmail)
|
||||
public void settEmail(String tEmail)
|
||||
{
|
||||
this.tEmail = tEmail;
|
||||
}
|
||||
|
||||
public String gettEmail()
|
||||
public String gettEmail()
|
||||
{
|
||||
return tEmail;
|
||||
}
|
||||
|
||||
public void setCreateUser(String createUser)
|
||||
public void setStatus(Integer status)
|
||||
{
|
||||
this.createUser = createUser;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getCreateUser()
|
||||
public Integer getStatus()
|
||||
{
|
||||
return createUser;
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getDelFlag()
|
||||
{
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -150,12 +176,16 @@ public class TrainTeacher extends BaseEntity
|
||||
.append("tName", gettName())
|
||||
.append("tGender", gettGender())
|
||||
.append("tBirth", gettBirth())
|
||||
.append("tDepartment", gettDepartment())
|
||||
.append("tTitle", gettTitle())
|
||||
.append("userId", getUserId())
|
||||
.append("orgName", getOrgName())
|
||||
.append("jobTitle", getJobTitle())
|
||||
.append("tPhone", gettPhone())
|
||||
.append("tEmail", gettEmail())
|
||||
.append("createUser", getCreateUser())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
|
||||
@@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainTeacher;
|
||||
|
||||
/**
|
||||
* 教师信息Mapper接口
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
*/
|
||||
public interface TrainTeacherMapper
|
||||
public interface TrainTeacherMapper
|
||||
{
|
||||
/**
|
||||
* 查询教师信息
|
||||
*
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 教师信息
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ public interface TrainTeacherMapper
|
||||
|
||||
/**
|
||||
* 查询教师信息列表
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 教师信息集合
|
||||
*/
|
||||
@@ -29,7 +29,7 @@ public interface TrainTeacherMapper
|
||||
|
||||
/**
|
||||
* 新增教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -37,15 +37,23 @@ public interface TrainTeacherMapper
|
||||
|
||||
/**
|
||||
* 修改教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTrainTeacher(TrainTeacher trainTeacher);
|
||||
|
||||
/**
|
||||
* 根据手机号码查询教师(用于全局查重)
|
||||
*
|
||||
* @param tPhone 手机号码
|
||||
* @return 教师信息
|
||||
*/
|
||||
public TrainTeacher checkPhoneUnique(String tPhone);
|
||||
|
||||
/**
|
||||
* 删除教师信息
|
||||
*
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -53,7 +61,7 @@ public interface TrainTeacherMapper
|
||||
|
||||
/**
|
||||
* 批量删除教师信息
|
||||
*
|
||||
*
|
||||
* @param tIds 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
+16
-8
@@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainTeacher;
|
||||
|
||||
/**
|
||||
* 教师信息Service接口
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
*/
|
||||
public interface ITrainTeacherService
|
||||
public interface ITrainTeacherService
|
||||
{
|
||||
/**
|
||||
* 查询教师信息
|
||||
*
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 教师信息
|
||||
*/
|
||||
@@ -21,7 +21,7 @@ public interface ITrainTeacherService
|
||||
|
||||
/**
|
||||
* 查询教师信息列表
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 教师信息集合
|
||||
*/
|
||||
@@ -29,7 +29,7 @@ public interface ITrainTeacherService
|
||||
|
||||
/**
|
||||
* 新增教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -37,15 +37,23 @@ public interface ITrainTeacherService
|
||||
|
||||
/**
|
||||
* 修改教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTrainTeacher(TrainTeacher trainTeacher);
|
||||
|
||||
/**
|
||||
* 校验教师手机号码是否唯一
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean checkPhoneUnique(TrainTeacher trainTeacher);
|
||||
|
||||
/**
|
||||
* 批量删除教师信息
|
||||
*
|
||||
*
|
||||
* @param tIds 需要删除的教师信息主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -53,7 +61,7 @@ public interface ITrainTeacherService
|
||||
|
||||
/**
|
||||
* 删除教师信息信息
|
||||
*
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
+105
-11
@@ -1,7 +1,11 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.train.mapper.TrainTeacherMapper;
|
||||
@@ -10,19 +14,19 @@ import com.ruoyi.train.service.ITrainTeacherService;
|
||||
|
||||
/**
|
||||
* 教师信息Service业务层处理
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2026-09-08
|
||||
* @date 2026-09-14
|
||||
*/
|
||||
@Service
|
||||
public class TrainTeacherServiceImpl implements ITrainTeacherService
|
||||
public class TrainTeacherServiceImpl implements ITrainTeacherService
|
||||
{
|
||||
@Autowired
|
||||
private TrainTeacherMapper trainTeacherMapper;
|
||||
|
||||
/**
|
||||
* 查询教师信息
|
||||
*
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 教师信息
|
||||
*/
|
||||
@@ -34,7 +38,7 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService
|
||||
|
||||
/**
|
||||
* 查询教师信息列表
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 教师信息
|
||||
*/
|
||||
@@ -44,35 +48,125 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService
|
||||
return trainTeacherMapper.selectTrainTeacherList(trainTeacher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验教师手机号码是否唯一
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return true 唯一,false 重复
|
||||
*/
|
||||
@Override
|
||||
public boolean checkPhoneUnique(TrainTeacher trainTeacher)
|
||||
{
|
||||
if (StringUtils.isEmpty(trainTeacher.gettPhone()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
TrainTeacher info = trainTeacherMapper.checkPhoneUnique(trainTeacher.gettPhone());
|
||||
if (StringUtils.isNotNull(info) && !StringUtils.equals(info.gettId(), trainTeacher.gettId()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertTrainTeacher(TrainTeacher trainTeacher)
|
||||
{
|
||||
// 1. 姓名校验
|
||||
if (StringUtils.isEmpty(trainTeacher.gettName()))
|
||||
{
|
||||
throw new ServiceException("教师姓名不能为空");
|
||||
}
|
||||
if (trainTeacher.gettName().trim().length() > 50)
|
||||
{
|
||||
throw new ServiceException("教师姓名长度不能超过50个字符");
|
||||
}
|
||||
|
||||
// 2. 手机号系统全局查重 (AC-02: 40001 TEACHER_PHONE_DUPLICATED)
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !checkPhoneUnique(trainTeacher))
|
||||
{
|
||||
throw new ServiceException("教师手机号码已存在", 40001);
|
||||
}
|
||||
|
||||
// 3. 主键生成:支持自定义工号或自动生成32位UUID
|
||||
if (StringUtils.isEmpty(trainTeacher.gettId()))
|
||||
{
|
||||
trainTeacher.settId(IdUtils.fastSimpleUUID());
|
||||
}
|
||||
|
||||
// 4. 默认值与审计字段
|
||||
if (trainTeacher.getStatus() == null)
|
||||
{
|
||||
trainTeacher.setStatus(1);
|
||||
}
|
||||
trainTeacher.setDelFlag("0");
|
||||
trainTeacher.setCreateTime(DateUtils.getNowDate());
|
||||
try
|
||||
{
|
||||
trainTeacher.setCreateBy(SecurityUtils.getUsername());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 非登录上下文防崩溃
|
||||
}
|
||||
|
||||
return trainTeacherMapper.insertTrainTeacher(trainTeacher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改教师信息
|
||||
*
|
||||
*
|
||||
* @param trainTeacher 教师信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateTrainTeacher(TrainTeacher trainTeacher)
|
||||
{
|
||||
// 1. 检查教师是否存在 (40401 TEACHER_NOT_FOUND)
|
||||
if (StringUtils.isEmpty(trainTeacher.gettId()))
|
||||
{
|
||||
throw new ServiceException("教师工号不能为空");
|
||||
}
|
||||
TrainTeacher existing = trainTeacherMapper.selectTrainTeacherByTId(trainTeacher.gettId());
|
||||
if (existing == null)
|
||||
{
|
||||
throw new ServiceException("教师不存在", 40401);
|
||||
}
|
||||
|
||||
// 2. 姓名校验
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettName()) && trainTeacher.gettName().trim().length() > 50)
|
||||
{
|
||||
throw new ServiceException("教师姓名长度不能超过50个字符");
|
||||
}
|
||||
|
||||
// 3. 手机号查重 (AC-02: 40001 TEACHER_PHONE_DUPLICATED)
|
||||
if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !checkPhoneUnique(trainTeacher))
|
||||
{
|
||||
throw new ServiceException("教师手机号码已存在", 40001);
|
||||
}
|
||||
|
||||
trainTeacher.setUpdateTime(DateUtils.getNowDate());
|
||||
try
|
||||
{
|
||||
trainTeacher.setUpdateBy(SecurityUtils.getUsername());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// 非登录上下文防崩溃
|
||||
}
|
||||
|
||||
return trainTeacherMapper.updateTrainTeacher(trainTeacher);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除教师信息
|
||||
*
|
||||
* 批量删除教师信息(逻辑删除)
|
||||
*
|
||||
* @param tIds 需要删除的教师信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
@@ -83,8 +177,8 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教师信息信息
|
||||
*
|
||||
* 删除教师信息(逻辑删除)
|
||||
*
|
||||
* @param tId 教师信息主键
|
||||
* @return 结果
|
||||
*/
|
||||
|
||||
@@ -3,43 +3,54 @@
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.train.mapper.TrainTeacherMapper">
|
||||
|
||||
|
||||
<resultMap type="TrainTeacher" id="TrainTeacherResult">
|
||||
<result property="tId" column="t_id" />
|
||||
<result property="tName" column="t_name" />
|
||||
<result property="tGender" column="t_gender" />
|
||||
<result property="tBirth" column="t_birth" />
|
||||
<result property="tDepartment" column="t_department" />
|
||||
<result property="tTitle" column="t_title" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="orgName" column="org_name" />
|
||||
<result property="jobTitle" column="job_title" />
|
||||
<result property="tPhone" column="t_phone" />
|
||||
<result property="tEmail" column="t_email" />
|
||||
<result property="createUser" column="create_user" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainTeacherVo">
|
||||
select t_id, t_name, t_gender, t_birth, t_department, t_title, t_phone, t_email, create_user, create_time, update_time, remark from train_teacher
|
||||
select t_id, t_name, t_gender, t_birth, user_id, org_name, job_title, t_phone, t_email, status, del_flag, create_by, create_time, update_by, update_time, remark from train_teacher
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainTeacherList" parameterType="TrainTeacher" resultMap="TrainTeacherResult">
|
||||
<include refid="selectTrainTeacherVo"/>
|
||||
<where>
|
||||
<where>
|
||||
and del_flag = '0'
|
||||
<if test="tName != null and tName != ''"> and t_name like concat('%', #{tName}, '%')</if>
|
||||
<if test="tGender != null and tGender != ''"> and t_gender = #{tGender}</if>
|
||||
<if test="tBirth != null "> and t_birth = #{tBirth}</if>
|
||||
<if test="tDepartment != null and tDepartment != ''"> and t_department = #{tDepartment}</if>
|
||||
<if test="tTitle != null and tTitle != ''"> and t_title = #{tTitle}</if>
|
||||
<if test="tPhone != null and tPhone != ''"> and t_phone = #{tPhone}</if>
|
||||
<if test="orgName != null and orgName != ''"> and org_name like concat('%', #{orgName}, '%')</if>
|
||||
<if test="jobTitle != null and jobTitle != ''"> and job_title like concat('%', #{jobTitle}, '%')</if>
|
||||
<if test="tPhone != null and tPhone != ''"> and t_phone like concat('%', #{tPhone}, '%')</if>
|
||||
<if test="tEmail != null and tEmail != ''"> and t_email = #{tEmail}</if>
|
||||
<if test="createUser != null and createUser != ''"> and create_user = #{createUser}</if>
|
||||
<if test="status != null"> and status = #{status}</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectTrainTeacherByTId" parameterType="String" resultMap="TrainTeacherResult">
|
||||
<include refid="selectTrainTeacherVo"/>
|
||||
where t_id = #{tId}
|
||||
where t_id = #{tId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="checkPhoneUnique" parameterType="String" resultMap="TrainTeacherResult">
|
||||
<include refid="selectTrainTeacherVo"/>
|
||||
where t_phone = #{tPhone} and del_flag = '0' limit 1
|
||||
</select>
|
||||
|
||||
<insert id="insertTrainTeacher" parameterType="TrainTeacher">
|
||||
@@ -49,12 +60,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="tName != null and tName != ''">t_name,</if>
|
||||
<if test="tGender != null">t_gender,</if>
|
||||
<if test="tBirth != null">t_birth,</if>
|
||||
<if test="tDepartment != null and tDepartment != ''">t_department,</if>
|
||||
<if test="tTitle != null">t_title,</if>
|
||||
<if test="tPhone != null">t_phone,</if>
|
||||
<if test="userId != null">user_id,</if>
|
||||
<if test="orgName != null and orgName != ''">org_name,</if>
|
||||
<if test="jobTitle != null and jobTitle != ''">job_title,</if>
|
||||
<if test="tPhone != null and tPhone != ''">t_phone,</if>
|
||||
<if test="tEmail != null">t_email,</if>
|
||||
<if test="createUser != null and createUser != ''">create_user,</if>
|
||||
<if test="status != null">status,</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag,</if>
|
||||
<if test="createBy != null and createBy != ''">create_by,</if>
|
||||
<if test="createTime != null">create_time,</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
</trim>
|
||||
@@ -63,12 +78,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="tName != null and tName != ''">#{tName},</if>
|
||||
<if test="tGender != null">#{tGender},</if>
|
||||
<if test="tBirth != null">#{tBirth},</if>
|
||||
<if test="tDepartment != null and tDepartment != ''">#{tDepartment},</if>
|
||||
<if test="tTitle != null">#{tTitle},</if>
|
||||
<if test="tPhone != null">#{tPhone},</if>
|
||||
<if test="userId != null">#{userId},</if>
|
||||
<if test="orgName != null and orgName != ''">#{orgName},</if>
|
||||
<if test="jobTitle != null and jobTitle != ''">#{jobTitle},</if>
|
||||
<if test="tPhone != null and tPhone != ''">#{tPhone},</if>
|
||||
<if test="tEmail != null">#{tEmail},</if>
|
||||
<if test="createUser != null and createUser != ''">#{createUser},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
<if test="createTime != null">#{createTime},</if>
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
</trim>
|
||||
@@ -80,26 +99,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="tName != null and tName != ''">t_name = #{tName},</if>
|
||||
<if test="tGender != null">t_gender = #{tGender},</if>
|
||||
<if test="tBirth != null">t_birth = #{tBirth},</if>
|
||||
<if test="tDepartment != null and tDepartment != ''">t_department = #{tDepartment},</if>
|
||||
<if test="tTitle != null">t_title = #{tTitle},</if>
|
||||
<if test="userId != null">user_id = #{userId},</if>
|
||||
<if test="orgName != null">org_name = #{orgName},</if>
|
||||
<if test="jobTitle != null">job_title = #{jobTitle},</if>
|
||||
<if test="tPhone != null">t_phone = #{tPhone},</if>
|
||||
<if test="tEmail != null">t_email = #{tEmail},</if>
|
||||
<if test="createUser != null and createUser != ''">create_user = #{createUser},</if>
|
||||
<if test="createTime != null">create_time = #{createTime},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
|
||||
<if test="updateTime != null">update_time = #{updateTime},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
</trim>
|
||||
where t_id = #{tId}
|
||||
where t_id = #{tId} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<delete id="deleteTrainTeacherByTId" parameterType="String">
|
||||
delete from train_teacher where t_id = #{tId}
|
||||
</delete>
|
||||
<!-- 逻辑删除:将 del_flag 置为 '2' -->
|
||||
<update id="deleteTrainTeacherByTId" parameterType="String">
|
||||
update train_teacher set del_flag = '2' where t_id = #{tId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteTrainTeacherByTIds" parameterType="String">
|
||||
delete from train_teacher where t_id in
|
||||
<!-- 批量逻辑删除:将 del_flag 置为 '2' -->
|
||||
<update id="deleteTrainTeacherByTIds" parameterType="String">
|
||||
update train_teacher set del_flag = '2' where t_id in
|
||||
<foreach item="tId" collection="array" open="(" separator="," close=")">
|
||||
#{tId}
|
||||
</foreach>
|
||||
</delete>
|
||||
</mapper>
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -29,16 +29,8 @@ export function addTeacher(data) {
|
||||
// 修改教师信息
|
||||
export function updateTeacher(data) {
|
||||
return request({
|
||||
url: '/train/teacher',
|
||||
url: '/train/teacher/' + (data.tId || ''),
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除教师信息
|
||||
export function delTeacher(tId) {
|
||||
return request({
|
||||
url: '/train/teacher/' + tId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="68px">
|
||||
<el-form-item label="教师姓名" prop="tName">
|
||||
<el-input
|
||||
v-model="queryParams.tName"
|
||||
@@ -9,34 +9,18 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别:男 / 女" prop="tGender">
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input
|
||||
v-model="queryParams.tGender"
|
||||
placeholder="请输入性别:男 / 女"
|
||||
v-model="queryParams.orgName"
|
||||
placeholder="请输入所属单位"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="tBirth">
|
||||
<el-date-picker clearable
|
||||
v-model="queryParams.tBirth"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择出生日期">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属院系" prop="tDepartment">
|
||||
<el-form-item label="职务职称" prop="jobTitle">
|
||||
<el-input
|
||||
v-model="queryParams.tDepartment"
|
||||
placeholder="请输入所属院系"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="职称:讲师 / 副教授 / 教授" prop="tTitle">
|
||||
<el-input
|
||||
v-model="queryParams.tTitle"
|
||||
placeholder="请输入职称:讲师 / 副教授 / 教授"
|
||||
v-model="queryParams.jobTitle"
|
||||
placeholder="请输入职务/职称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
@@ -49,21 +33,11 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="tEmail">
|
||||
<el-input
|
||||
v-model="queryParams.tEmail"
|
||||
placeholder="请输入邮箱"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建人 (管理员 / 教师)" prop="createUser">
|
||||
<el-input
|
||||
v-model="queryParams.createUser"
|
||||
placeholder="请输入创建人 (管理员 / 教师)"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="教师状态" clearable>
|
||||
<el-option label="正常" :value="1" />
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
@@ -74,81 +48,59 @@
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:teacher:add']"
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['train:teacher:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:teacher:edit']"
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['train:teacher:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['train:teacher:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['train:teacher:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="teacherList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="教师工号" align="center" prop="tId" />
|
||||
<el-table-column label="教师姓名" align="center" prop="tName" />
|
||||
<el-table-column label="性别:男 / 女" align="center" prop="tGender" />
|
||||
<el-table-column label="出生日期" align="center" prop="tBirth" width="180">
|
||||
<el-table-column label="性别" align="center" prop="tGender" width="80" />
|
||||
<el-table-column label="出生日期" align="center" prop="tBirth" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.tBirth, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属院系" align="center" prop="tDepartment" />
|
||||
<el-table-column label="职称:讲师 / 副教授 / 教授" align="center" prop="tTitle" />
|
||||
<el-table-column label="联系电话" align="center" prop="tPhone" />
|
||||
<el-table-column label="邮箱" align="center" prop="tEmail" />
|
||||
<el-table-column label="创建人 (管理员 / 教师)" align="center" prop="createUser" />
|
||||
<el-table-column label="备注" align="center" prop="remark" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<el-table-column label="所属单位" align="center" prop="orgName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="职务/职称" align="center" prop="jobTitle" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="联系电话" align="center" prop="tPhone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="tEmail" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="status" width="90">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
|
||||
{{ scope.row.status === 1 ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-hasPermi="['train:teacher:edit']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['train:teacher:edit']"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['train:teacher:remove']"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -162,39 +114,46 @@
|
||||
/>
|
||||
|
||||
<!-- 添加或修改教师信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-dialog :title="title" :visible.sync="open" width="550px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="90px">
|
||||
<el-form-item label="教师姓名" prop="tName">
|
||||
<el-input v-model="form.tName" placeholder="请输入教师姓名" />
|
||||
<el-input v-model="form.tName" placeholder="请输入教师姓名" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别:男 / 女" prop="tGender">
|
||||
<el-input v-model="form.tGender" placeholder="请输入性别:男 / 女" />
|
||||
<el-form-item label="性别" prop="tGender">
|
||||
<el-radio-group v-model="form.tGender">
|
||||
<el-radio label="男">男</el-radio>
|
||||
<el-radio label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="tBirth">
|
||||
<el-date-picker clearable
|
||||
<el-date-picker
|
||||
v-model="form.tBirth"
|
||||
clearable
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择出生日期">
|
||||
</el-date-picker>
|
||||
placeholder="请选择出生日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属院系" prop="tDepartment">
|
||||
<el-input v-model="form.tDepartment" placeholder="请输入所属院系" />
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input v-model="form.orgName" placeholder="请输入所属单位/部门" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职称:讲师 / 副教授 / 教授" prop="tTitle">
|
||||
<el-input v-model="form.tTitle" placeholder="请输入职称:讲师 / 副教授 / 教授" />
|
||||
<el-form-item label="职务/职称" prop="jobTitle">
|
||||
<el-input v-model="form.jobTitle" placeholder="请输入职务/职称" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="tPhone">
|
||||
<el-input v-model="form.tPhone" placeholder="请输入联系电话" />
|
||||
<el-input v-model="form.tPhone" placeholder="请输入11位手机号码" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="tEmail">
|
||||
<el-input v-model="form.tEmail" placeholder="请输入邮箱" />
|
||||
<el-input v-model="form.tEmail" placeholder="请输入电子邮箱" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="创建人 (管理员 / 教师)" prop="createUser">
|
||||
<el-input v-model="form.createUser" placeholder="请输入创建人 (管理员 / 教师)" />
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="1">正常</el-radio>
|
||||
<el-radio :label="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注信息" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -206,10 +165,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listTeacher, getTeacher, delTeacher, addTeacher, updateTeacher } from "@/api/train/teacher"
|
||||
import { listTeacher, getTeacher, addTeacher, updateTeacher } from '@/api/train/teacher'
|
||||
|
||||
export default {
|
||||
name: "Teacher",
|
||||
name: 'Teacher',
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
@@ -218,8 +177,6 @@ export default {
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
@@ -227,7 +184,7 @@ export default {
|
||||
// 教师信息表格数据
|
||||
teacherList: [],
|
||||
// 弹出层标题
|
||||
title: "",
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 查询参数
|
||||
@@ -235,27 +192,25 @@ export default {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tName: null,
|
||||
tGender: null,
|
||||
tBirth: null,
|
||||
tDepartment: null,
|
||||
tTitle: null,
|
||||
orgName: null,
|
||||
jobTitle: null,
|
||||
tPhone: null,
|
||||
tEmail: null,
|
||||
createUser: null,
|
||||
status: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
tName: [
|
||||
{ required: true, message: "教师姓名不能为空", trigger: "blur" }
|
||||
{ required: true, message: '教师姓名不能为空', trigger: 'blur' },
|
||||
{ max: 50, message: '教师姓名长度不能超过50个字符', trigger: 'blur' }
|
||||
],
|
||||
tDepartment: [
|
||||
{ required: true, message: "所属院系不能为空", trigger: "blur" }
|
||||
],
|
||||
createUser: [
|
||||
{ required: true, message: "创建人 (管理员 / 教师)不能为空", trigger: "blur" }
|
||||
tPhone: [
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号码', trigger: 'blur' }
|
||||
],
|
||||
tEmail: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -282,18 +237,16 @@ export default {
|
||||
this.form = {
|
||||
tId: null,
|
||||
tName: null,
|
||||
tGender: null,
|
||||
tGender: '男',
|
||||
tBirth: null,
|
||||
tDepartment: null,
|
||||
tTitle: null,
|
||||
orgName: null,
|
||||
jobTitle: null,
|
||||
tPhone: null,
|
||||
tEmail: null,
|
||||
createUser: null,
|
||||
createTime: null,
|
||||
updateTime: null,
|
||||
status: 1,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm("form")
|
||||
this.resetForm('form')
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
@@ -302,66 +255,49 @@ export default {
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm("queryForm")
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.tId)
|
||||
this.single = selection.length!==1
|
||||
this.multiple = !selection.length
|
||||
this.single = selection.length !== 1
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = "添加教师信息"
|
||||
this.title = '添加教师信息'
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const tId = row.tId || this.ids
|
||||
const tId = row.tId || this.ids[0]
|
||||
getTeacher(tId).then(response => {
|
||||
this.form = response.data
|
||||
this.open = true
|
||||
this.title = "修改教师信息"
|
||||
this.title = '修改教师信息'
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.tId != null) {
|
||||
updateTeacher(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addTeacher(this.form).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const tIds = row.tId || this.ids
|
||||
this.$modal.confirm('是否确认删除教师信息编号为"' + tIds + '"的数据项?').then(function() {
|
||||
return delTeacher(tIds)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess("删除成功")
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 导出按钮操作 */
|
||||
handleExport() {
|
||||
this.download('train/teacher/export', {
|
||||
...this.queryParams
|
||||
}, `teacher_${new Date().getTime()}.xlsx`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# ISSUE-01-01:师资主数据管理能力开发与验收报告
|
||||
|
||||
> **文档定位:** 本文档记录 RailwaySystem P0 阶段第一个正式 Issue(`ISSUE-01-01 师资主数据管理能力`)兼 Generator 正式 Pilot 的完整实施与验收结果。
|
||||
> **实施分支:** `feat/issue-01-01-teacher`
|
||||
> **基线 Commit:** `7fcaea29b8810546fd972ba0c9d366c71a357d63` (`Tag: issues-v1.0`)
|
||||
|
||||
---
|
||||
|
||||
## 1. Issue 目标
|
||||
|
||||
1. **业务目标:** 为排课选师资、计划主讲教师提供完备的教师档案检索、详情查看、建档与维护能力;
|
||||
2. **功能范围:** 教师姓名、单位、性别、出生日期、职务/职称、联系电话、邮箱维护;分页检索;手机号系统全局查重;RESTful 详情与更新;逻辑删除;
|
||||
3. **技术目标:** 完成 Generator V2 正式业务表 Pilot 验证,通过只读/隔离预览,比对并最小化修改现有实现,坚决落实逻辑删除与 API Contract V1.0.1 契约规范。
|
||||
|
||||
---
|
||||
|
||||
## 2. 原实现盘点 (Current Teacher Implementation Inventory)
|
||||
|
||||
在开发启动前,对系统已有教师模块进行了细致盘点:
|
||||
|
||||
| 资产层级 | 现有路径 / 标识 | 旧实现状态 | 与 API Contract V1.0.1 / Database V2 差异 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Entity** | `com.ruoyi.train.domain.TrainTeacher` | 包含 `tDepartment`, `tTitle`, `createUser` | 数据库 V2 已删除上述旧列,新增 `userId`, `orgName`, `jobTitle`, `status`, `delFlag`,旧实体无法匹配 V2 |
|
||||
| **Mapper XML** | `ruoyi-train/.../TrainTeacherMapper.xml` | 包含旧列查询;使用物理 `DELETE FROM` | 字段查询在 V2 会报错;物理删除破坏了系统的逻辑删除语义 |
|
||||
| **Mapper 接口** | `com.ruoyi.train.mapper.TrainTeacherMapper` | 标准 CRUD 接口 | 缺少手机号全局唯一性查重方法接口 |
|
||||
| **Service** | `TrainTeacherServiceImpl` | 简单直通 Mapper | 缺少手机号全局查重、40001/40401 错误码处理,主键策略未适配 varchar(32) |
|
||||
| **Controller** | `TrainTeacherController` | 权限使用 `system:teacher:*`;`PUT /train/teacher` | 权限违背契约;PUT 路径未遵循 `PUT /train/teacher/{tId}`;缺少 `pageSize <= 100` 校验 |
|
||||
| **前端 API** | `build_ui/src/api/train/teacher.js` | `updateTeacher` 为 `PUT /train/teacher` | 未对齐 RESTful `PUT /train/teacher/{tId}` |
|
||||
| **前端视图** | `build_ui/src/views/train/teacher/index.vue` | 表单使用院系、职称、创建人 | 未对齐单位 `orgName`、职务 `jobTitle`、状态 `status`;缺少手机号格式前端校验 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Generator Pilot 结果
|
||||
|
||||
依据授权范围,本轮仅针对 `train_teacher` 表开放 Generator Import 与 Preview:
|
||||
|
||||
1. **导入范围约束:** 仅且仅导入 `train_teacher`,其余业务表严禁导入;导入前 `gen_table` 为 0,导入后 `gen_table` 精确为 1 行;
|
||||
2. **表/列元数据审查:**
|
||||
- 候选查询及预览中,历史备份表 `bak_v1_*` 完全不可见;
|
||||
- `t_id`:`isPk = 1`, `isIncrement = 0`, `javaType = String`, `columnType = character varying(32)`(严格核验不是自增);
|
||||
- 数据类型映射验证:
|
||||
- `character varying` -> `String`
|
||||
- `character(1)` -> `String`
|
||||
- `timestamp` -> `Date`
|
||||
- `date` -> `Date`
|
||||
- `smallint` -> `Long/Integer`
|
||||
3. **隔离生成与三方 Diff:**
|
||||
- 全部 Generator 输出至隔离临时目录 `/tmp/railwaysystem-generator/issue-01-01/`,**严禁且未直接覆盖任何工程源码**;
|
||||
- **三方比对结论:**
|
||||
- Generator 生成的包名为 `com.ruoyi.system.*`,权限为 `system:teacher:*`,删除为物理 `DELETE`,表单包含迁移注释;
|
||||
- 现有项目包名为 `com.ruoyi.train.*`,契约权限要求为 `train:teacher:*`,删除要求为基于 `del_flag` 的逻辑删除;
|
||||
- 采取**最小化手工合并重构**,摒弃机械覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 4. 保留 / 修改 / 替换文件清单
|
||||
|
||||
| 文件路径 | 策略 | 变更重点说明 |
|
||||
| :--- | :---: | :--- |
|
||||
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java` | **MODIFY** | 移除旧字段 `tDepartment`, `tTitle`, `createUser`;新增 `userId`, `orgName`, `jobTitle`, `status`, `delFlag` 及对应 getter/setter |
|
||||
| `RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml` | **MODIFY** | 映射 V2 新列;所有查询增加 `del_flag = '0'` 过滤;删除改为 `UPDATE ... SET del_flag = '2'`;增加 `checkPhoneUnique` SQL |
|
||||
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java` | **MODIFY** | 增加 `checkPhoneUnique(String tPhone)` 方法 |
|
||||
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java` | **MODIFY** | 增加 `checkPhoneUnique(TrainTeacher trainTeacher)` 业务契约 |
|
||||
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java` | **MODIFY** | 落实核心业务逻辑:新增/修改手机号系统全局查重抛出 40001;教师不存在抛出 40401;UUID 32位主键生成;默认值与审计赋值;逻辑删除 |
|
||||
| `RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java` | **MODIFY** | 仅公开暴露 4 个 P0 Endpoint;权限统一为 `train:teacher:*`;对齐契约 RESTful 路径 `PUT /train/teacher/{tId}`;校验 `pageSize <= 100`、姓名必填与手机号格式;移除所有 Contract 外端点(DELETE/export) |
|
||||
| `build_ui/src/api/train/teacher.js` | **MODIFY** | `updateTeacher` 请求路径对齐为 `/train/teacher/{tId}`;移除未在契约中定义的 `delTeacher` |
|
||||
| `build_ui/src/views/train/teacher/index.vue` | **MODIFY** | 替换旧字段为 `orgName`, `jobTitle`, `status`;增加手机号正则校验;权限统一为 `train:teacher:*`;移除删除、导出按钮及未使用的处理方法;通过 eslint 零 warning/error 校验 |
|
||||
|
||||
---
|
||||
|
||||
## 5. API Contract 映射与落实
|
||||
|
||||
按 API Contract V1.0.1 冻结契约(`docs/api-v2/02_module_a_base_resource_api.md`),Teacher 模块正式公开的 HTTP API 严格收敛为 **4 个 P0 API**:
|
||||
|
||||
### 5.1 正式公开交付的 P0 API (4 个)
|
||||
|
||||
| 契约 API | HTTP Method | 路径 | 权限标识 | 核心实现与校验 |
|
||||
| :--- | :---: | :--- | :--- | :--- |
|
||||
| **分页查询教师列表** | `GET` | `/train/teacher/list` | `train:teacher:list` | 支持姓名、单位、职务、电话、状态多条件筛选;强制 `pageSize <= 100`;默认过滤 `del_flag = '0'` |
|
||||
| **获取教师详情** | `GET` | `/train/teacher/{tId}` | `train:teacher:query` | 教师 ID 存在性检查;若不存在返回 HTTP 200 + 业务状态码 `40401` (`TEACHER_NOT_FOUND`) |
|
||||
| **新增教师** | `POST` | `/train/teacher` | `train:teacher:add` | 姓名必填 (<=50字符);手机号格式正则校验;Service 层手机号重复预检查(重复抛出 `40001` `TEACHER_PHONE_DUPLICATED`);生成 32 位 UUID 主键 |
|
||||
| **修改教师** | `PUT` | `/train/teacher/{tId}` | `train:teacher:edit` | 路径参数与实体 ID 绑定;校验存在性 (不存在抛出 40401);Service 层手机号查重 (排除自身);更新审计时间与审计人 |
|
||||
|
||||
### 5.2 状态接口说明 (P1 范围)
|
||||
|
||||
- **启用/停用教师状态** (`PUT /train/teacher/{tId}/status`):属于冻结契约规划的 P1 接口。本 Issue (P0) 不提前公开该端点,教师状态可通过 P0 `PUT /train/teacher/{tId}` 进行维护。
|
||||
|
||||
### 5.3 Contract 外 HTTP Endpoint 治理
|
||||
|
||||
根据技术负责人审查意见,**HTTP API Contract V1.0.1 中不存在公开的 Teacher 删除与导出端点**:
|
||||
- `DELETE /train/teacher/{tIds}` 与 `POST /train/teacher/export` 已从 `TrainTeacherController` 中彻底删除映射,不再公开暴露任何 Contract 外 HTTP Endpoint;
|
||||
- 前端 `teacher.js` 与 `index.vue` 同步移除删除与导出调用及界面按钮,消除死代码。
|
||||
|
||||
---
|
||||
|
||||
## 6. 权限设计说明
|
||||
|
||||
1. **代码层完全落实:**
|
||||
- 后端全部采用 `@PreAuthorize("@ss.hasPermi('train:teacher:*')")`;
|
||||
- 前端操作按钮全部绑定 `v-hasPermi="['train:teacher:*']"`;
|
||||
- 彻底废除旧有的 `system:teacher:*`;
|
||||
2. **零数据库写权限:**
|
||||
- 严格遵循指令规则,本 Issue **绝对不向 `sys_menu` 插入或更新任何菜单与按钮数据**;
|
||||
- 菜单数据的入库统一留待 `ISSUE-11-01` 集中处理。
|
||||
|
||||
---
|
||||
|
||||
## 7. 手机号唯一性与持久层能力设计
|
||||
|
||||
1. **手机号唯一性实现现状与边界:**
|
||||
- **数据库现状:** 经只读系统目录核查,当前 Database V2 的 `train_teacher` 表仅包含主键索引 `train_teacher_pkey` 以及普通索引 `idx_teacher_user_id`、`idx_teacher_name`,**不存在 `UNIQUE(t_phone)` 约束或唯一索引**;
|
||||
- **实现机制:** 当前仅在 **Service层进行手机号重复预检查**;
|
||||
- **边界说明:** **并发强唯一性不由当前数据库约束保证**。本 Issue 严格遵守禁令,未修改 Database V2 结构增加约束;已登记为 **Database Change Review candidate** 候选议题;
|
||||
- **错误码合规性:** 业务查重异常返回的错误码 `40001`(`TEACHER_PHONE_DUPLICATED`)直接引自冻结契约 `docs/api-v2/10_permission_and_error_codes.md` 第 104 行,不存在自行发明。
|
||||
2. **底层持久化删除能力设计:**
|
||||
- **架构原则:** `HTTP Contract ≠ 内部 Persistence Capability`;
|
||||
- **逻辑删除能力保留在 Persistence 层**:Mapper 与 Service 层保留基于 `del_flag` 的软删除方法(`del_flag = '2'`),以支持系统内部业务或级联运维调度使用;
|
||||
- **一期 V1.0.1 未对外暴露 Teacher 删除 HTTP API**,Controller 层无公开删除映射。
|
||||
|
||||
---
|
||||
|
||||
## 8. 后端验证结果
|
||||
|
||||
通过独立的后端测试用例套件 (`TestTeacherBackendService`),全链路覆盖所有场景:
|
||||
|
||||
| 测试用例编号 | 场景描述 | 期望结果 | 实测结果 | 状态 |
|
||||
| :---: | :--- | :--- | :--- | :---: |
|
||||
| **TC-01** | 基线唯一历史教师查询 | 查出 1 行数据,字段正确映射 | 行数精确为 1,`orgName` 等字段完全一致 | **PASS** |
|
||||
| **TC-02** | AC-01: 正常新增教师 | 成功入库,UUID 32位主键,`del_flag='0'` | 影响行数 1,主键格式正确,查回一致 | **PASS** |
|
||||
| **TC-03** | AC-02: 手机号重复新增 | 抛出 40001 `TEACHER_PHONE_DUPLICATED` | 成功捕获 40001 业务异常,阻断新增 | **PASS** |
|
||||
| **TC-04** | 参数校验 (姓名为空 / 超长) | 拦截非法请求并提示对应错误信息 | 姓名空/超长均被严格拦截 | **PASS** |
|
||||
| **TC-05** | 查询与更新不存在的 ID | 查询返回空,更新抛出 40401 错误码 | 捕获 40401 `TEACHER_NOT_FOUND` | **PASS** |
|
||||
| **TC-06** | 修改已有教师资料 | 成功更新单位、职务、职称等字段 | 影响行数 1,再次查回字段与修改值完全一致 | **PASS** |
|
||||
| **TC-07** | 逻辑删除语义与物理恢复 | 软删除置 `del_flag='2'`,恢复基线 1 行 | 数据库物理验证通过,测试数据已自动复原 | **PASS** |
|
||||
|
||||
后端构建结果:
|
||||
```text
|
||||
mvn compile -DskipTests -f RuoYi-Vue/pom.xml
|
||||
[INFO] BUILD SUCCESS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 前端验证结果
|
||||
|
||||
1. **代码规范与 Lint 验证:**
|
||||
- 执行命令:`npx eslint --no-ignore src/api/train/teacher.js src/views/train/teacher/index.vue`
|
||||
- 验证结果:`0 problems (0 errors, 0 warnings)`;
|
||||
2. **UI 功能与服务验证:**
|
||||
- 前端页面仅保留新增、修改操作及搜索过滤,删除了删除与导出入口,界面整洁规范;
|
||||
- 表单严格执行姓名必填、手机号正则、邮箱格式等前端校验。
|
||||
|
||||
---
|
||||
|
||||
## 10. Database 影响与事实审计
|
||||
|
||||
1. **业务数据 DML 事实:**
|
||||
- **YES,执行过受控测试业务 DML。测试完成后已清理。**
|
||||
- 验收期间通过单元测试受控插入过一条测试教师记录(手机号 `13800000001`)用于 AC-01 与 AC-02 验证,随后在 TC-07 中彻底物理清理 (`DELETE`);
|
||||
- **当前只读核验:**
|
||||
- `SELECT COUNT(*) FROM train_teacher;` -> 精确为 **1**(恢复正式基线);
|
||||
- `SELECT COUNT(*) FROM train_teacher WHERE t_phone = '13800000001';` -> 精确为 **0**;
|
||||
- 测试生成的临时记录全部清零,未泄露或残留任何测试数据。
|
||||
2. **Database V2 结构影响:** 21 张正式业务表结构未发生任何 DDL 变动,保持基线一致;
|
||||
3. **Generator 基础设施兼容性处理:**
|
||||
- Generator Pilot 首次真实 Import 暴露了国产 Kingbase 数据库下 Generator 元数据列(`table_id` / `column_id`)自增兼容性问题;
|
||||
- 该问题已确认属于 **Generator Infrastructure Patch**,已转交独立基础设施补丁任务处理;
|
||||
- **本 Teacher 分支未包含该 DDL 脚本,未将基础设施改动混入业务 Commit**;
|
||||
4. **权限/字典表影响:** 未执行 `007_dicts.sql`,未修改 `sys_menu` 与 `sys_dict`。
|
||||
|
||||
---
|
||||
|
||||
## 11. 已知限制与后续演进
|
||||
|
||||
1. **菜单权限数据:** 页面按钮声明了 `train:teacher:*` 权限标识,由于未向 `sys_menu` 插入数据,在非 admin 超级管理员角色下需要待 `ISSUE-11-01` 菜单数据初始化后方可在生产环境分配角色权限;
|
||||
2. **公开 HTTP 接口收敛:** 一期 V1.0.1 严格对外暴露 4 个 P0 接口,删除与状态启停用独立端点留待后续版本根据需求统一规划;
|
||||
3. **并发强唯一性:** 当前手机号查重依赖 Service 层校验,若后续业务高并发场景需要强唯一性,由 Database Change Review 统一评估增加数据库约束。
|
||||
|
||||
---
|
||||
|
||||
## 12. Acceptance Criteria 逐项结果
|
||||
|
||||
| AC 编号 | Acceptance Criteria 描述 | 验证结果 |
|
||||
| :---: | :--- | :---: |
|
||||
| **AC-01** | Given 教师手机号 `13800000001` 不存在,When 提交新增教师请求,Then 成功插入 `train_teacher` 并返回 200。 | **PASS** |
|
||||
| **AC-02** | Given 教师手机号 `13800000001` 已存在,When 再次提交新增,Then 抛出错误码 40001 (`TEACHER_PHONE_DUPLICATED`),不产生新增记录。 | **PASS** |
|
||||
Reference in new issue
Block a user