合并发布:集成GitLab远端主数据扩展与教学资源基线
This commit is contained in:
commit
b93ac3fa71
43 files changed
+2583
-65
No files matched your search
+93
@@ -0,0 +1,93 @@
|
||||
package com.ruoyi.web.controller.train;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
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.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
import com.ruoyi.train.service.ITrainResourceService;
|
||||
|
||||
/**
|
||||
* 教学资源Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/train/resource")
|
||||
public class TrainResourceController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private ITrainResourceService trainResourceService;
|
||||
|
||||
/**
|
||||
* 查询教学资源列表
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:resource:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(TrainResource trainResource)
|
||||
{
|
||||
startPage();
|
||||
List<TrainResource> list = trainResourceService.selectTrainResourceList(trainResource);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取教学资源详细信息
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:resource:list')")
|
||||
@GetMapping(value = "/{resourceId}")
|
||||
public AjaxResult getInfo(@PathVariable("resourceId") String resourceId)
|
||||
{
|
||||
return success(trainResourceService.selectTrainResourceByResourceId(resourceId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教学资源
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:resource:edit')")
|
||||
@Log(title = "教学资源", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
public AjaxResult add(@Validated @RequestBody TrainResource trainResource)
|
||||
{
|
||||
trainResource.setCreateBy(SecurityUtils.getUsername());
|
||||
return toAjax(trainResourceService.insertTrainResource(trainResource));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教学资源 (RESTful DELETE /train/resource/{resourceIds})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:resource:edit')")
|
||||
@Log(title = "教学资源", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{resourceIds}")
|
||||
public AjaxResult remove(@PathVariable String[] resourceIds)
|
||||
{
|
||||
return toAjax(trainResourceService.deleteTrainResourceByResourceIds(resourceIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改教学资源 (RESTful PUT /train/resource/{resourceId})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:resource:edit')")
|
||||
@Log(title = "教学资源", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{resourceId}")
|
||||
public AjaxResult edit(@PathVariable("resourceId") String resourceId, @Validated @RequestBody TrainResource trainResource)
|
||||
{
|
||||
trainResource.setResourceId(resourceId);
|
||||
trainResource.setUpdateBy(SecurityUtils.getUsername());
|
||||
return toAjax(trainResourceService.updateTrainResource(trainResource));
|
||||
}
|
||||
}
|
||||
+33
-7
@@ -1,15 +1,12 @@
|
||||
package com.ruoyi.web.controller.train;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
@@ -18,6 +15,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.train.domain.dto.StudentCreateDTO;
|
||||
import com.ruoyi.train.domain.dto.StudentUpdateDTO;
|
||||
import com.ruoyi.train.domain.query.StudentQuery;
|
||||
import com.ruoyi.train.domain.vo.StudentAdminVO;
|
||||
import com.ruoyi.train.domain.vo.StudentDetailVO;
|
||||
@@ -30,6 +28,8 @@ import com.ruoyi.web.service.train.TrainAccountBindingAdminService;
|
||||
@RequestMapping({"/train/student", "/train/students"})
|
||||
public class TrainStudentController extends BaseController
|
||||
{
|
||||
private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$");
|
||||
|
||||
private final ITrainStudentService trainStudentService;
|
||||
private final TrainAccountBindingAdminService bindingAdminService;
|
||||
|
||||
@@ -76,6 +76,27 @@ public class TrainStudentController extends BaseController
|
||||
return success(trainStudentService.createStudent(dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改学员信息 (PUT /train/student/{sId})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:student:edit')")
|
||||
@Log(title = "学员主档", businessType = BusinessType.UPDATE,
|
||||
isSaveRequestData = false, isSaveResponseData = true)
|
||||
@PutMapping(value = "/{sId}")
|
||||
public AjaxResult edit(@PathVariable("sId") String sId, @Valid @RequestBody StudentUpdateDTO dto)
|
||||
{
|
||||
dto.setsId(sId);
|
||||
if (StringUtils.isNotEmpty(dto.getsName()) && dto.getsName().trim().length() > 50)
|
||||
{
|
||||
return AjaxResult.error("学员姓名长度不能超过50个字符");
|
||||
}
|
||||
if (StringUtils.isNotEmpty(dto.getsPhone()) && !isValidPhone(dto.getsPhone()))
|
||||
{
|
||||
return AjaxResult.error("学员手机号码格式不正确");
|
||||
}
|
||||
return success(trainStudentService.updateTrainStudent(dto));
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('train:student:remove')")
|
||||
@Log(title = "学员主档", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{sId}")
|
||||
@@ -83,4 +104,9 @@ public class TrainStudentController extends BaseController
|
||||
{
|
||||
return toAjax(trainStudentService.deleteTrainStudentBySId(sId));
|
||||
}
|
||||
|
||||
private boolean isValidPhone(String phone)
|
||||
{
|
||||
return PHONE_PATTERN.matcher(phone).matches();
|
||||
}
|
||||
}
|
||||
+12
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -67,6 +68,17 @@ public class TrainTrainingSiteController extends BaseController
|
||||
return toAjax(trainTrainingSiteService.insertTrainTrainingSite(trainTrainingSite));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除实训场地 (RESTful DELETE /train/training-site/{siteIds})
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('train:site:edit')")
|
||||
@Log(title = "实训场地", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{siteIds}")
|
||||
public AjaxResult remove(@PathVariable String[] siteIds)
|
||||
{
|
||||
return toAjax(trainTrainingSiteService.deleteTrainTrainingSiteBySiteIds(siteIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改实训场地 (RESTful PUT /train/training-site/{siteId})
|
||||
*/
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ public class SysLoginService
|
||||
public String login(String username, String password, String code, String uuid)
|
||||
{
|
||||
// 验证码校验
|
||||
//validateCaptcha(username, code, uuid);
|
||||
validateCaptcha(username, code, uuid);
|
||||
// 登录前置校验
|
||||
loginPreCheck(username, password);
|
||||
// 用户验证
|
||||
|
||||
@@ -46,6 +46,12 @@ public class TrainClassroomInfo extends BaseEntity
|
||||
/** 删除标志 (0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
private Integer multimedia;
|
||||
|
||||
private Integer projector;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
public void setClassRoomId(String classRoomId)
|
||||
{
|
||||
this.classRoomId = classRoomId;
|
||||
@@ -106,6 +112,30 @@ public class TrainClassroomInfo extends BaseEntity
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public Integer getMultimedia() {
|
||||
return multimedia;
|
||||
}
|
||||
|
||||
public void setMultimedia(Integer multimedia) {
|
||||
this.multimedia = multimedia;
|
||||
}
|
||||
|
||||
public Integer getProjector() {
|
||||
return projector;
|
||||
}
|
||||
|
||||
public void setProjector(Integer projector) {
|
||||
this.projector = projector;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
@@ -120,6 +150,9 @@ public class TrainClassroomInfo extends BaseEntity
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.append("multimedia", getMultimedia())
|
||||
.append("projector", getProjector())
|
||||
.append("seatArrange", getSeatArrange())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,10 @@ public class TrainCourse extends BaseEntity
|
||||
@Excel(name = "课程内容")
|
||||
private String courseContent;
|
||||
|
||||
/** 课程资源ID,多个资源以英文逗号分隔,对应 train_resource.resource_id */
|
||||
@Excel(name = "课程资源ID")
|
||||
private String resourceId;
|
||||
|
||||
/** 课程状态 (1:正常, 0:停用) */
|
||||
@Excel(name = "课程状态", readConverterExp = "1=正常,0=停用")
|
||||
private Integer status;
|
||||
@@ -180,6 +184,16 @@ public class TrainCourse extends BaseEntity
|
||||
return courseContent;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public String getResourceId()
|
||||
{
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status)
|
||||
{
|
||||
this.status = status;
|
||||
@@ -210,6 +224,7 @@ public class TrainCourse extends BaseEntity
|
||||
.append("cDuration", getcDuration())
|
||||
.append("teachingObjective", getTeachingObjective())
|
||||
.append("courseContent", getCourseContent())
|
||||
.append("resourceId", getResourceId())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.ruoyi.train.domain;
|
||||
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
/**
|
||||
* 教学资源对象 train_resource
|
||||
*
|
||||
* <p>统一教学资源:文档、视频、3D模型、VR资源、外部教学链接、其他。
|
||||
* 该表被 train_course_resource、train_study_duration 外键引用,删除一律走逻辑删除。</p>
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class TrainResource extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 资源唯一ID */
|
||||
private String resourceId;
|
||||
|
||||
/** 资源名称 */
|
||||
@NotBlank(message = "资源名称不能为空")
|
||||
@Size(max = 150, message = "资源名称长度不能超过150个字符")
|
||||
@Excel(name = "资源名称")
|
||||
private String resourceName;
|
||||
|
||||
/** 资源类型 (1:文档, 2:视频, 3:3D模型, 4:VR资源, 5:外部教学链接, 6:其他) */
|
||||
@NotNull(message = "资源类型不能为空")
|
||||
@Min(value = 1, message = "资源类型取值必须为1-6")
|
||||
@Max(value = 6, message = "资源类型取值必须为1-6")
|
||||
@Excel(name = "资源类型", readConverterExp = "1=文档,2=视频,3=3D模型,4=VR资源,5=外部教学链接,6=其他")
|
||||
private Integer resourceType;
|
||||
|
||||
/** 资源存储地址/在线链接 */
|
||||
@NotBlank(message = "资源地址不能为空")
|
||||
@Size(max = 500, message = "资源地址长度不能超过500个字符")
|
||||
@Excel(name = "资源地址")
|
||||
private String resourceUrl;
|
||||
|
||||
/** 资源描述/使用说明 */
|
||||
@Size(max = 500, message = "资源描述长度不能超过500个字符")
|
||||
@Excel(name = "资源描述")
|
||||
private String description;
|
||||
|
||||
/** 状态 (1:正常, 0:停用) */
|
||||
@Min(value = 0, message = "状态取值必须为0或1")
|
||||
@Max(value = 1, message = "状态取值必须为0或1")
|
||||
@Excel(name = "状态", readConverterExp = "0=停用,1=正常")
|
||||
private Integer status;
|
||||
|
||||
/** 删除标志 (0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
public void setResourceId(String resourceId)
|
||||
{
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public String getResourceId()
|
||||
{
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setResourceName(String resourceName)
|
||||
{
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
public String getResourceName()
|
||||
{
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
public void setResourceType(Integer resourceType)
|
||||
{
|
||||
this.resourceType = resourceType;
|
||||
}
|
||||
|
||||
public Integer getResourceType()
|
||||
{
|
||||
return resourceType;
|
||||
}
|
||||
|
||||
public void setResourceUrl(String resourceUrl)
|
||||
{
|
||||
this.resourceUrl = resourceUrl;
|
||||
}
|
||||
|
||||
public String getResourceUrl()
|
||||
{
|
||||
return resourceUrl;
|
||||
}
|
||||
|
||||
public void setDescription(String description)
|
||||
{
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setDelFlag(String delFlag)
|
||||
{
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getDelFlag()
|
||||
{
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
||||
.append("resourceId", getResourceId())
|
||||
.append("resourceName", getResourceName())
|
||||
.append("resourceType", getResourceType())
|
||||
.append("resourceUrl", getResourceUrl())
|
||||
.append("description", getDescription())
|
||||
.append("status", getStatus())
|
||||
.append("delFlag", getDelFlag())
|
||||
.append("createBy", getCreateBy())
|
||||
.append("createTime", getCreateTime())
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,10 @@ public class TrainStudent extends BaseEntity
|
||||
private Integer status;
|
||||
private String delFlag;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private Integer dutyLevel;
|
||||
|
||||
public String getsId()
|
||||
{
|
||||
return sId;
|
||||
@@ -194,6 +198,22 @@ public class TrainStudent extends BaseEntity
|
||||
this.delFlag = delFlag;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public Integer getDutyLevel() {
|
||||
return dutyLevel;
|
||||
}
|
||||
|
||||
public void setDutyLevel(Integer dutyLevel) {
|
||||
this.dutyLevel = dutyLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全日志表示:刻意不包含身份证密文、查重值或任何完整身份证信息。
|
||||
*/
|
||||
@@ -219,6 +239,8 @@ public class TrainStudent extends BaseEntity
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.append("dutyLevel", getDutyLevel())
|
||||
.append("avatar", getAvatar())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,15 @@ public class TrainTeacher extends BaseEntity
|
||||
/** 删除标志 (0:存在, 2:删除) */
|
||||
private String delFlag;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private String major;
|
||||
|
||||
private String courses;
|
||||
|
||||
private Date startTeaching;
|
||||
|
||||
|
||||
public void settId(String tId)
|
||||
{
|
||||
this.tId = tId;
|
||||
@@ -180,6 +189,38 @@ public class TrainTeacher extends BaseEntity
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public String getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public void setMajor(String major) {
|
||||
this.major = major;
|
||||
}
|
||||
|
||||
public String getCourses() {
|
||||
return courses;
|
||||
}
|
||||
|
||||
public void setCourses(String courses) {
|
||||
this.courses = courses;
|
||||
}
|
||||
|
||||
public Date getStartTeaching() {
|
||||
return startTeaching;
|
||||
}
|
||||
|
||||
public void setStartTeaching(Date startTeaching) {
|
||||
this.startTeaching = startTeaching;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
@@ -199,6 +240,10 @@ public class TrainTeacher extends BaseEntity
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.append("avatar", getAvatar())
|
||||
.append("major", getMajor())
|
||||
.append("courses", getCourses())
|
||||
.append("startTeaching", getStartTeaching())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ public class TrainTrainingSite extends BaseEntity
|
||||
/** 删除标志 (0代表存在 2代表删除) */
|
||||
private String delFlag;
|
||||
|
||||
private String major;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
public void setSiteId(String siteId)
|
||||
{
|
||||
this.siteId = siteId;
|
||||
@@ -120,6 +124,22 @@ public class TrainTrainingSite extends BaseEntity
|
||||
return delFlag;
|
||||
}
|
||||
|
||||
public String getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public void setMajor(String major) {
|
||||
this.major = major;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
|
||||
@@ -135,6 +155,8 @@ public class TrainTrainingSite extends BaseEntity
|
||||
.append("updateBy", getUpdateBy())
|
||||
.append("updateTime", getUpdateTime())
|
||||
.append("remark", getRemark())
|
||||
.append("major", getMajor())
|
||||
.append("seatArrange", getSeatArrange())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
+30
@@ -32,6 +32,12 @@ public class ClassroomCreateDTO implements Serializable
|
||||
/** 教室状态 (0: 正在使用, 1: 可用空闲, 2: 维护中) */
|
||||
private Integer roomStatus;
|
||||
|
||||
private Integer multimedia;
|
||||
|
||||
private Integer projector;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
/** 备注 */
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
@@ -85,4 +91,28 @@ public class ClassroomCreateDTO implements Serializable
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public Integer getMultimedia() {
|
||||
return multimedia;
|
||||
}
|
||||
|
||||
public void setMultimedia(Integer multimedia) {
|
||||
this.multimedia = multimedia;
|
||||
}
|
||||
|
||||
public Integer getProjector() {
|
||||
return projector;
|
||||
}
|
||||
|
||||
public void setProjector(Integer projector) {
|
||||
this.projector = projector;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,10 @@ public class SiteCreateDTO implements Serializable
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
private String major;
|
||||
|
||||
public String getSiteName()
|
||||
{
|
||||
return siteName;
|
||||
@@ -98,4 +102,20 @@ public class SiteCreateDTO implements Serializable
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
|
||||
public String getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public void setMajor(String major) {
|
||||
this.major = major;
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,9 @@ public class StudentCreateDTO implements Serializable
|
||||
@Size(max = 500, message = "备注长度不能超过500个字符")
|
||||
private String remark;
|
||||
|
||||
private Integer dutyLevel;
|
||||
private String avatar;
|
||||
|
||||
public String getsName() { return sName; }
|
||||
public void setsName(String sName) { this.sName = sName; }
|
||||
public String getsGender() { return sGender; }
|
||||
@@ -65,6 +68,22 @@ public class StudentCreateDTO implements Serializable
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String remark) { this.remark = remark; }
|
||||
|
||||
public Integer getDutyLevel() {
|
||||
return dutyLevel;
|
||||
}
|
||||
|
||||
public void setDutyLevel(Integer dutyLevel) {
|
||||
this.dutyLevel = dutyLevel;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.ruoyi.train.domain.dto;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 学员手工修改请求。
|
||||
*
|
||||
* <p>复用 {@link StudentCreateDTO} 的可编辑字段与校验规则,额外要求携带学员主键 sId。
|
||||
* 身份证号沿用「只写不读」策略:未提交时保留库内既有密文,不做清空。</p>
|
||||
*/
|
||||
public class StudentUpdateDTO extends StudentCreateDTO
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@NotBlank(message = "学员ID不能为空")
|
||||
@Size(max = 64, message = "学员ID长度不能超过64个字符")
|
||||
private String sId;
|
||||
|
||||
public String getsId()
|
||||
{
|
||||
return sId;
|
||||
}
|
||||
|
||||
public void setsId(String sId)
|
||||
{
|
||||
this.sId = sId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "StudentUpdateDTO{sId='" + sId + "', sName='" + getsName()
|
||||
+ "', idCardProvided=" + (getIdCard() != null) + "}";
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,12 @@ public class ClassroomDetailVO implements Serializable
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
private Integer multimedia;
|
||||
|
||||
private Integer projector;
|
||||
|
||||
public String getClassRoomId()
|
||||
{
|
||||
return classRoomId;
|
||||
@@ -119,4 +125,28 @@ public class ClassroomDetailVO implements Serializable
|
||||
{
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
|
||||
public Integer getMultimedia() {
|
||||
return multimedia;
|
||||
}
|
||||
|
||||
public void setMultimedia(Integer multimedia) {
|
||||
this.multimedia = multimedia;
|
||||
}
|
||||
|
||||
public Integer getProjector() {
|
||||
return projector;
|
||||
}
|
||||
|
||||
public void setProjector(Integer projector) {
|
||||
this.projector = projector;
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,8 @@ public class CourseDetailVO implements Serializable
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
private String resourceId;
|
||||
|
||||
/**
|
||||
* 已关联的教学资源列表
|
||||
* 注:当前 ISSUE-01-03 未实现真实 Course<->Resource 查询;
|
||||
@@ -91,6 +93,7 @@ public class CourseDetailVO implements Serializable
|
||||
vo.setCreateTime(course.getCreateTime());
|
||||
vo.setUpdateBy(course.getUpdateBy());
|
||||
vo.setUpdateTime(course.getUpdateTime());
|
||||
vo.setResourceId(course.getResourceId());
|
||||
// 资源绑定/解绑能力属于 P1,当前返回空列表保持响应结构稳定
|
||||
vo.setResources(Collections.emptyList());
|
||||
return vo;
|
||||
@@ -286,6 +289,14 @@ public class CourseDetailVO implements Serializable
|
||||
this.updateTime = updateTime;
|
||||
}
|
||||
|
||||
public String getResourceId() {
|
||||
return resourceId;
|
||||
}
|
||||
|
||||
public void setResourceId(String resourceId) {
|
||||
this.resourceId = resourceId;
|
||||
}
|
||||
|
||||
public List<Object> getResources()
|
||||
{
|
||||
return resources;
|
||||
|
||||
@@ -38,6 +38,10 @@ public class SiteDetailVO implements Serializable
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private String seatArrange;
|
||||
|
||||
private String major;
|
||||
|
||||
public String getSiteId()
|
||||
{
|
||||
return siteId;
|
||||
@@ -117,4 +121,20 @@ public class SiteDetailVO implements Serializable
|
||||
{
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getSeatArrange() {
|
||||
return seatArrange;
|
||||
}
|
||||
|
||||
public void setSeatArrange(String seatArrange) {
|
||||
this.seatArrange = seatArrange;
|
||||
}
|
||||
|
||||
public String getMajor() {
|
||||
return major;
|
||||
}
|
||||
|
||||
public void setMajor(String major) {
|
||||
this.major = major;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,10 @@ public class StudentDetailVO extends StudentItemVO
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
private Integer dutyLevel;
|
||||
|
||||
private String avatar;
|
||||
|
||||
public String getFaceRef() { return faceRef; }
|
||||
public void setFaceRef(String faceRef) { this.faceRef = faceRef; }
|
||||
public String getRemark() { return remark; }
|
||||
@@ -22,4 +26,20 @@ public class StudentDetailVO extends StudentItemVO
|
||||
public void setCreateBy(String createBy) { this.createBy = createBy; }
|
||||
public Date getCreateTime() { return createTime; }
|
||||
public void setCreateTime(Date createTime) { this.createTime = createTime; }
|
||||
|
||||
public Integer getDutyLevel() {
|
||||
return dutyLevel;
|
||||
}
|
||||
|
||||
public void setDutyLevel(Integer dutyLevel) {
|
||||
this.dutyLevel = dutyLevel;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,10 @@ public class StudentItemVO implements Serializable
|
||||
private Integer sourceType;
|
||||
private Integer status;
|
||||
|
||||
private String avatar;
|
||||
|
||||
private Integer dutyLevel;
|
||||
|
||||
public String getsId() { return sId; }
|
||||
public void setsId(String sId) { this.sId = sId; }
|
||||
public String getsName() { return sName; }
|
||||
@@ -44,4 +48,20 @@ public class StudentItemVO implements Serializable
|
||||
public void setSourceType(Integer sourceType) { this.sourceType = sourceType; }
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public Integer getDutyLevel() {
|
||||
return dutyLevel;
|
||||
}
|
||||
|
||||
public void setDutyLevel(Integer dutyLevel) {
|
||||
this.dutyLevel = dutyLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.train.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
|
||||
/**
|
||||
* 教学资源Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface TrainResourceMapper
|
||||
{
|
||||
/**
|
||||
* 查询教学资源
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 教学资源
|
||||
*/
|
||||
public TrainResource selectTrainResourceByResourceId(String resourceId);
|
||||
|
||||
/**
|
||||
* 查询教学资源列表
|
||||
*
|
||||
* @param trainResource 教学资源查询条件
|
||||
* @return 教学资源集合
|
||||
*/
|
||||
public List<TrainResource> selectTrainResourceList(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 新增教学资源
|
||||
*
|
||||
* @param trainResource 教学资源
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTrainResource(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 修改教学资源
|
||||
*
|
||||
* @param trainResource 教学资源
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTrainResource(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 删除教学资源(逻辑删除)
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainResourceByResourceId(String resourceId);
|
||||
|
||||
/**
|
||||
* 批量删除教学资源(逻辑删除)
|
||||
*
|
||||
* @param resourceIds 需要删除的数据主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainResourceByResourceIds(String[] resourceIds);
|
||||
}
|
||||
@@ -18,6 +18,14 @@ public interface TrainStudentMapper
|
||||
|
||||
int insertTrainStudent(TrainStudent trainStudent);
|
||||
|
||||
/**
|
||||
* 修改学员主档(仅更新非空字段,身份信息随身份证变更同步刷新)
|
||||
*
|
||||
* @param trainStudent 学员信息(必须携带 sId)
|
||||
* @return 影响行数
|
||||
*/
|
||||
int updateTrainStudent(TrainStudent trainStudent);
|
||||
|
||||
/**
|
||||
* 根据系统用户ID查询学员信息 (del_flag = '0')
|
||||
*
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.ruoyi.train.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
|
||||
/**
|
||||
* 教学资源Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface ITrainResourceService
|
||||
{
|
||||
/**
|
||||
* 查询教学资源
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 教学资源
|
||||
*/
|
||||
public TrainResource selectTrainResourceByResourceId(String resourceId);
|
||||
|
||||
/**
|
||||
* 查询教学资源列表
|
||||
*
|
||||
* @param trainResource 教学资源查询条件
|
||||
* @return 教学资源集合
|
||||
*/
|
||||
public List<TrainResource> selectTrainResourceList(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 新增教学资源
|
||||
*
|
||||
* @param trainResource 教学资源
|
||||
* @return 结果
|
||||
*/
|
||||
public int insertTrainResource(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 修改教学资源
|
||||
*
|
||||
* @param trainResource 教学资源
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateTrainResource(TrainResource trainResource);
|
||||
|
||||
/**
|
||||
* 批量删除教学资源
|
||||
*
|
||||
* @param resourceIds 需要删除的教学资源主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainResourceByResourceIds(String[] resourceIds);
|
||||
|
||||
/**
|
||||
* 删除教学资源信息
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteTrainResourceByResourceId(String resourceId);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.train.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.train.domain.dto.StudentCreateDTO;
|
||||
import com.ruoyi.train.domain.dto.StudentUpdateDTO;
|
||||
import com.ruoyi.train.domain.query.StudentQuery;
|
||||
import com.ruoyi.train.domain.vo.StudentDetailVO;
|
||||
import com.ruoyi.train.domain.vo.StudentItemVO;
|
||||
@@ -15,6 +16,14 @@ public interface ITrainStudentService
|
||||
|
||||
String createStudent(StudentCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 修改学员主档
|
||||
*
|
||||
* @param dto 学员修改请求(携带 sId)
|
||||
* @return 学员主档ID
|
||||
*/
|
||||
String updateTrainStudent(StudentUpdateDTO dto);
|
||||
|
||||
/**
|
||||
* 安全删除学员档案(ZERO-REFERENCE SAFE DELETE)
|
||||
*
|
||||
|
||||
+3
@@ -57,6 +57,9 @@ public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService
|
||||
vo.setRoomStatus(classroom.getRoomStatus());
|
||||
vo.setRemark(classroom.getRemark());
|
||||
vo.setCreateTime(classroom.getCreateTime());
|
||||
vo.setSeatArrange(classroom.getSeatArrange());
|
||||
vo.setMultimedia(classroom.getMultimedia());
|
||||
vo.setProjector(classroom.getProjector());
|
||||
|
||||
// 按 bind_classroom_id 查询绑定的电子班牌(0..N),映射为轻量摘要 VO,无绑定时返回空列表 []
|
||||
TrainElectronicBoard boardQuery = new TrainElectronicBoard();
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package com.ruoyi.train.service.impl;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.train.domain.TrainResource;
|
||||
import com.ruoyi.train.mapper.TrainResourceMapper;
|
||||
import com.ruoyi.train.service.ITrainResourceService;
|
||||
|
||||
/**
|
||||
* 教学资源Service业务层处理
|
||||
*
|
||||
* <p>资源表被 train_course_resource、train_study_duration 外键引用,
|
||||
* 因此删除统一使用逻辑删除(del_flag = '2'),禁止物理删除。</p>
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
public class TrainResourceServiceImpl implements ITrainResourceService
|
||||
{
|
||||
@Autowired
|
||||
private TrainResourceMapper trainResourceMapper;
|
||||
|
||||
/**
|
||||
* 查询教学资源
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 教学资源
|
||||
*/
|
||||
@Override
|
||||
public TrainResource selectTrainResourceByResourceId(String resourceId)
|
||||
{
|
||||
return trainResourceMapper.selectTrainResourceByResourceId(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询教学资源列表
|
||||
*
|
||||
* @param trainResource 教学资源查询条件
|
||||
* @return 教学资源集合
|
||||
*/
|
||||
@Override
|
||||
public List<TrainResource> selectTrainResourceList(TrainResource trainResource)
|
||||
{
|
||||
return trainResourceMapper.selectTrainResourceList(trainResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增教学资源
|
||||
*
|
||||
* @param trainResource 教学资源
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertTrainResource(TrainResource trainResource)
|
||||
{
|
||||
validateResourceType(trainResource.getResourceType());
|
||||
validateStatus(trainResource.getStatus());
|
||||
// 未指定主键时自动生成32位UUID,与 resource_id VARCHAR(32) 对齐
|
||||
if (StringUtils.isEmpty(trainResource.getResourceId()))
|
||||
{
|
||||
trainResource.setResourceId(IdUtils.fastSimpleUUID());
|
||||
}
|
||||
if (trainResource.getStatus() == null)
|
||||
{
|
||||
trainResource.setStatus(1); // 默认正常
|
||||
}
|
||||
trainResource.setDelFlag("0");
|
||||
trainResource.setCreateTime(DateUtils.getNowDate());
|
||||
return trainResourceMapper.insertTrainResource(trainResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改教学资源
|
||||
*
|
||||
* @param trainResource 教学资源(必须携带 resourceId)
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateTrainResource(TrainResource trainResource)
|
||||
{
|
||||
if (StringUtils.isEmpty(trainResource.getResourceId()))
|
||||
{
|
||||
throw new ServiceException("资源ID不能为空");
|
||||
}
|
||||
if (trainResource.getResourceType() != null)
|
||||
{
|
||||
validateResourceType(trainResource.getResourceType());
|
||||
}
|
||||
if (trainResource.getStatus() != null)
|
||||
{
|
||||
validateStatus(trainResource.getStatus());
|
||||
}
|
||||
trainResource.setUpdateTime(DateUtils.getNowDate());
|
||||
return trainResourceMapper.updateTrainResource(trainResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除教学资源
|
||||
*
|
||||
* @param resourceIds 需要删除的教学资源主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteTrainResourceByResourceIds(String[] resourceIds)
|
||||
{
|
||||
if (resourceIds == null || resourceIds.length == 0)
|
||||
{
|
||||
throw new ServiceException("请选择要删除的资源");
|
||||
}
|
||||
return trainResourceMapper.deleteTrainResourceByResourceIds(resourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除教学资源信息
|
||||
*
|
||||
* @param resourceId 教学资源主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteTrainResourceByResourceId(String resourceId)
|
||||
{
|
||||
if (StringUtils.isEmpty(resourceId))
|
||||
{
|
||||
throw new ServiceException("资源ID不能为空");
|
||||
}
|
||||
return trainResourceMapper.deleteTrainResourceByResourceId(resourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验资源类型取值:1文档 2视频 33D模型 4VR资源 5外部教学链接 6其他
|
||||
*/
|
||||
private void validateResourceType(Integer resourceType)
|
||||
{
|
||||
if (resourceType == null)
|
||||
{
|
||||
throw new ServiceException("资源类型不能为空");
|
||||
}
|
||||
if (resourceType < 1 || resourceType > 6)
|
||||
{
|
||||
throw new ServiceException("资源类型取值必须为1-6");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验状态取值:0停用 1正常
|
||||
*/
|
||||
private void validateStatus(Integer status)
|
||||
{
|
||||
if (status != null && status != 0 && status != 1)
|
||||
{
|
||||
throw new ServiceException("资源状态取值必须为0或1");
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -13,6 +13,7 @@ import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.train.constant.StudentErrorCode;
|
||||
import com.ruoyi.train.domain.TrainStudent;
|
||||
import com.ruoyi.train.domain.dto.StudentCreateDTO;
|
||||
import com.ruoyi.train.domain.dto.StudentUpdateDTO;
|
||||
import com.ruoyi.train.domain.query.StudentQuery;
|
||||
import com.ruoyi.train.domain.vo.StudentDetailVO;
|
||||
import com.ruoyi.train.domain.vo.StudentItemVO;
|
||||
@@ -99,6 +100,8 @@ public class TrainStudentServiceImpl implements ITrainStudentService
|
||||
student.setOrgName(trimToNull(dto.getOrgName()));
|
||||
student.setsPhone(trimToNull(dto.getsPhone()));
|
||||
student.setsEmail(trimToNull(dto.getsEmail()));
|
||||
student.setAvatar(trimToNull(dto.getAvatar()));
|
||||
student.setDutyLevel(dto.getDutyLevel());
|
||||
|
||||
if (normalizedIdCard != null)
|
||||
{
|
||||
@@ -144,6 +147,93 @@ public class TrainStudentServiceImpl implements ITrainStudentService
|
||||
return student.getsId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改学员主档信息。
|
||||
*
|
||||
* <p>与 createStudent 保持完全一致的安全语义:姓名必填、身份证格式校验、
|
||||
* 身份证以可逆密文 + 冻结查重哈希落库、末四位仅用于脱敏展示。差异点:</p>
|
||||
* <ul>
|
||||
* <li>身份证未提供时保留库内既有密文,避免误清空已归档的身份信息;</li>
|
||||
* <li>身份证变更时才做全局查重,哈希未变化视为本人,不做重复判定;</li>
|
||||
* <li>userId / sourceType / status / delFlag 不属于编辑范围,一律不覆盖。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param dto 学员修改请求(携带 sId)
|
||||
* @return 学员主档ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String updateTrainStudent(StudentUpdateDTO dto)
|
||||
{
|
||||
if (dto == null || StringUtils.isEmpty(dto.getsId()))
|
||||
{
|
||||
throw new ServiceException("学员ID不能为空");
|
||||
}
|
||||
validate(dto);
|
||||
|
||||
TrainStudent existing = trainStudentMapper.selectTrainStudentBySId(dto.getsId());
|
||||
if (existing == null)
|
||||
{
|
||||
throw new ServiceException(StudentErrorCode.STUDENT_NOT_FOUND_MESSAGE,
|
||||
StudentErrorCode.STUDENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
TrainStudent student = new TrainStudent();
|
||||
student.setsId(existing.getsId());
|
||||
student.setsName(dto.getsName().trim());
|
||||
student.setsGender(trimToNull(dto.getsGender()));
|
||||
student.setsBirth(dto.getsBirth());
|
||||
student.setOrgName(trimToNull(dto.getOrgName()));
|
||||
student.setsPhone(trimToNull(dto.getsPhone()));
|
||||
student.setsEmail(trimToNull(dto.getsEmail()));
|
||||
student.setAvatar(trimToNull(dto.getAvatar()));
|
||||
student.setFaceRef(trimToNull(dto.getFaceRef()));
|
||||
student.setDutyLevel(dto.getDutyLevel());
|
||||
student.setRemark(trimToNull(dto.getRemark()));
|
||||
|
||||
String normalizedIdCard = normalizeIdCard(dto.getIdCard());
|
||||
if (normalizedIdCard != null)
|
||||
{
|
||||
String hash = identityProtection.deterministicHash(normalizedIdCard);
|
||||
// 哈希未变化说明仍是同一人,只有真正变更身份证时才需要全局查重
|
||||
if (!hash.equals(existing.getIdCardHash())
|
||||
&& trainStudentMapper.selectOneByIdCardHash(hash) != null)
|
||||
{
|
||||
throw duplicateIdentity();
|
||||
}
|
||||
student.setIdCardCiphertext(identityProtection.encrypt(normalizedIdCard));
|
||||
student.setIdCardHash(hash);
|
||||
student.setIdCardLast4(normalizedIdCard.substring(normalizedIdCard.length() - 4));
|
||||
}
|
||||
else
|
||||
{
|
||||
student.setIdCardCiphertext(existing.getIdCardCiphertext());
|
||||
student.setIdCardHash(existing.getIdCardHash());
|
||||
student.setIdCardLast4(existing.getIdCardLast4());
|
||||
}
|
||||
|
||||
student.setUpdateTime(DateUtils.getNowDate());
|
||||
try
|
||||
{
|
||||
student.setUpdateBy(SecurityUtils.getUsername());
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
// 非 Web 单元测试无登录上下文;不使用任何请求数据作为审计用户兜底。
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
trainStudentMapper.updateTrainStudent(student);
|
||||
}
|
||||
catch (DuplicateKeyException e)
|
||||
{
|
||||
// 应用层预检之外,数据库唯一约束继续封堵并发重复身份证。
|
||||
throw duplicateIdentity();
|
||||
}
|
||||
return student.getsId();
|
||||
}
|
||||
|
||||
private StudentItemVO toItemVO(TrainStudent student)
|
||||
{
|
||||
StudentItemVO item = new StudentItemVO();
|
||||
@@ -164,6 +254,8 @@ public class TrainStudentServiceImpl implements ITrainStudentService
|
||||
target.setIdCardMasked(maskStoredIdentity(student));
|
||||
target.setSourceType(student.getSourceType());
|
||||
target.setStatus(student.getStatus());
|
||||
target.setAvatar(student.getAvatar());
|
||||
target.setDutyLevel(student.getDutyLevel());
|
||||
}
|
||||
|
||||
private String maskStoredIdentity(TrainStudent student)
|
||||
|
||||
@@ -16,19 +16,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="seatArrange" column="seat_arrange" />
|
||||
<result property="multimedia" column="multimedia" />
|
||||
<result property="projector" column="projector" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainClassroomInfoVo">
|
||||
select class_room_id, room_name, room_location, capacity, room_status, del_flag, create_by, create_time, update_by, update_time, remark from train_classroom_info
|
||||
select class_room_id, room_name, room_location, capacity, room_status, del_flag, create_by, create_time, update_by, update_time, remark, seat_arrange, multimedia, projector from train_classroom_info
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainClassroomInfoList" parameterType="TrainClassroomInfo" resultMap="TrainClassroomInfoResult">
|
||||
<include refid="selectTrainClassroomInfoVo"/>
|
||||
where del_flag = '0'
|
||||
<if test="roomName != null and roomName != ''"> and room_name like concat('%', #{roomName}, '%')</if>
|
||||
<if test="roomLocation != null and roomLocation != ''"> and room_location = #{roomLocation}</if>
|
||||
<if test="roomLocation != null and roomLocation != ''"> and room_location like concat('%', #{roomLocation}, '%')</if>
|
||||
<if test="capacity != null "> and capacity = #{capacity}</if>
|
||||
<if test="roomStatus != null "> and room_status = #{roomStatus}</if>
|
||||
<if test="multimedia != null "> and multimedia = #{multimedia}</if>
|
||||
<if test="projector != null "> and projector = #{projector}</if>
|
||||
<if test="createBy != null and createBy != ''"> and create_by = #{createBy}</if>
|
||||
order by create_time desc
|
||||
</select>
|
||||
@@ -61,6 +66,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="seatArrange != null">seat_arrange,</if>
|
||||
<if test="multimedia != null">multimedia,</if>
|
||||
<if test="projector != null">projector,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="classRoomId != null">#{classRoomId},</if>
|
||||
@@ -74,6 +82,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="seatArrange != null">#{seatArrange},</if>
|
||||
<if test="multimedia != null">#{multimedia},</if>
|
||||
<if test="projector != null">#{projector},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@@ -88,6 +99,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<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>
|
||||
<if test="seatArrange != null">seat_arrange = #{seatArrange},</if>
|
||||
<if test="multimedia != null">multimedia = #{multimedia},</if>
|
||||
<if test="projector != null">projector = #{projector},</if>
|
||||
</trim>
|
||||
where class_room_id = #{classRoomId}
|
||||
</update>
|
||||
|
||||
@@ -12,6 +12,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="cDuration" column="c_duration" />
|
||||
<result property="teachingObjective" column="teaching_objective" />
|
||||
<result property="courseContent" column="course_content" />
|
||||
<result property="resourceId" column="resource_id" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="createBy" column="create_by" />
|
||||
@@ -22,7 +23,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainCourseVo">
|
||||
select c_id, c_code, c_name, c_type, c_duration, teaching_objective, course_content, status, del_flag, create_by, create_time, update_by, update_time, remark from train_course
|
||||
select c_id, c_code, c_name, c_type, c_duration, teaching_objective, course_content, resource_id, status, del_flag, create_by, create_time, update_by, update_time, remark from train_course
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainCourseList" parameterType="TrainCourse" resultMap="TrainCourseResult">
|
||||
@@ -63,6 +64,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">c_duration,</if>
|
||||
<if test="teachingObjective != null and teachingObjective != ''">teaching_objective,</if>
|
||||
<if test="courseContent != null and courseContent != ''">course_content,</if>
|
||||
<if test="resourceId != null and resourceId != ''">resource_id,</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>
|
||||
@@ -79,6 +81,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">#{cDuration},</if>
|
||||
<if test="teachingObjective != null and teachingObjective != ''">#{teachingObjective},</if>
|
||||
<if test="courseContent != null and courseContent != ''">#{courseContent},</if>
|
||||
<if test="resourceId != null and resourceId != ''">#{resourceId},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
<if test="delFlag != null and delFlag != ''">#{delFlag},</if>
|
||||
<if test="createBy != null and createBy != ''">#{createBy},</if>
|
||||
@@ -98,6 +101,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="cDuration != null">c_duration = #{cDuration},</if>
|
||||
<if test="teachingObjective != null">teaching_objective = #{teachingObjective},</if>
|
||||
<if test="courseContent != null">course_content = #{courseContent},</if>
|
||||
<if test="resourceId != null">resource_id = #{resourceId},</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>
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.train.mapper.TrainResourceMapper">
|
||||
|
||||
<resultMap type="TrainResource" id="TrainResourceResult">
|
||||
<result property="resourceId" column="resource_id" />
|
||||
<result property="resourceName" column="resource_name" />
|
||||
<result property="resourceType" column="resource_type" />
|
||||
<result property="resourceUrl" column="resource_url" />
|
||||
<result property="description" column="description" />
|
||||
<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="selectTrainResourceVo">
|
||||
select resource_id, resource_name, resource_type, resource_url, description, status, del_flag, create_by, create_time, update_by, update_time, remark from train_resource
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainResourceList" parameterType="TrainResource" resultMap="TrainResourceResult">
|
||||
<include refid="selectTrainResourceVo"/>
|
||||
where del_flag = '0'
|
||||
<if test="resourceName != null and resourceName != ''"> and resource_name like concat('%', #{resourceName}, '%')</if>
|
||||
<if test="resourceType != null"> and resource_type = #{resourceType}</if>
|
||||
<if test="resourceUrl != null and resourceUrl != ''"> and resource_url like concat('%', #{resourceUrl}, '%')</if>
|
||||
<if test="description != null and description != ''"> and description like concat('%', #{description}, '%')</if>
|
||||
<if test="status != null"> and status = #{status}</if>
|
||||
<if test="createBy != null and createBy != ''"> and create_by = #{createBy}</if>
|
||||
<if test="params.beginTime != null and params.beginTime != ''"> and create_time >= #{params.beginTime}</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"> and create_time <= #{params.endTime}</if>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<select id="selectTrainResourceByResourceId" parameterType="String" resultMap="TrainResourceResult">
|
||||
<include refid="selectTrainResourceVo"/>
|
||||
where resource_id = #{resourceId} and del_flag = '0'
|
||||
</select>
|
||||
|
||||
<insert id="insertTrainResource" parameterType="TrainResource">
|
||||
insert into train_resource
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="resourceId != null">resource_id,</if>
|
||||
<if test="resourceName != null and resourceName != ''">resource_name,</if>
|
||||
<if test="resourceType != null">resource_type,</if>
|
||||
<if test="resourceUrl != null and resourceUrl != ''">resource_url,</if>
|
||||
<if test="description != null">description,</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>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="resourceId != null">#{resourceId},</if>
|
||||
<if test="resourceName != null and resourceName != ''">#{resourceName},</if>
|
||||
<if test="resourceType != null">#{resourceType},</if>
|
||||
<if test="resourceUrl != null and resourceUrl != ''">#{resourceUrl},</if>
|
||||
<if test="description != null">#{description},</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>
|
||||
</insert>
|
||||
|
||||
<update id="updateTrainResource" parameterType="TrainResource">
|
||||
update train_resource
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="resourceName != null and resourceName != ''">resource_name = #{resourceName},</if>
|
||||
<if test="resourceType != null">resource_type = #{resourceType},</if>
|
||||
<if test="resourceUrl != null and resourceUrl != ''">resource_url = #{resourceUrl},</if>
|
||||
<if test="description != null">description = #{description},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="delFlag != null and delFlag != ''">del_flag = #{delFlag},</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 resource_id = #{resourceId} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<update id="deleteTrainResourceByResourceId" parameterType="String">
|
||||
update train_resource set del_flag = '2' where resource_id = #{resourceId}
|
||||
</update>
|
||||
|
||||
<update id="deleteTrainResourceByResourceIds" parameterType="String">
|
||||
update train_resource set del_flag = '2' where resource_id in
|
||||
<foreach item="resourceId" collection="array" open="(" separator="," close=")">
|
||||
#{resourceId}
|
||||
</foreach>
|
||||
</update>
|
||||
</mapper>
|
||||
@@ -25,12 +25,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="avatar" column="avatar" />
|
||||
<result property="dutyLevel" column="duty_level" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainStudentDomain">
|
||||
select s_id, s_name, s_gender, s_birth, user_id, org_name, s_phone, s_email,
|
||||
id_card_ciphertext, id_card_hash, id_card_last4, face_ref, source_type,
|
||||
status, create_by, create_time, update_by, update_time, remark, del_flag
|
||||
status, create_by, create_time, update_by, update_time, remark, del_flag,
|
||||
avatar, duty_level
|
||||
from train_student
|
||||
</sql>
|
||||
|
||||
@@ -77,14 +80,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
insert into train_student (
|
||||
s_id, s_name, s_gender, s_birth, user_id, org_name, s_phone, s_email,
|
||||
id_card_ciphertext, id_card_hash, id_card_last4, face_ref, source_type,
|
||||
status, create_by, create_time, update_by, update_time, remark, del_flag
|
||||
status, create_by, create_time, update_by, update_time, remark, del_flag,
|
||||
avatar, duty_level
|
||||
) values (
|
||||
#{sId}, #{sName}, #{sGender}, #{sBirth}, #{userId}, #{orgName}, #{sPhone}, #{sEmail},
|
||||
#{idCardCiphertext}, #{idCardHash}, #{idCardLast4}, #{faceRef}, #{sourceType},
|
||||
#{status}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{remark}, #{delFlag}
|
||||
#{status}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{remark}, #{delFlag},
|
||||
#{avatar}, #{dutyLevel}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<!-- 修改学员主档:仅覆盖传入的非空字段,userId/sourceType/status/delFlag 不在编辑范围 -->
|
||||
<update id="updateTrainStudent" parameterType="TrainStudent">
|
||||
update train_student
|
||||
<trim prefix="SET" suffixOverrides=",">
|
||||
<if test="sName != null">s_name = #{sName},</if>
|
||||
<if test="sGender != null">s_gender = #{sGender},</if>
|
||||
<if test="sBirth != null">s_birth = #{sBirth},</if>
|
||||
<if test="orgName != null">org_name = #{orgName},</if>
|
||||
<if test="sPhone != null">s_phone = #{sPhone},</if>
|
||||
<if test="sEmail != null">s_email = #{sEmail},</if>
|
||||
<if test="idCardCiphertext != null">id_card_ciphertext = #{idCardCiphertext},</if>
|
||||
<if test="idCardHash != null">id_card_hash = #{idCardHash},</if>
|
||||
<if test="idCardLast4 != null">id_card_last4 = #{idCardLast4},</if>
|
||||
<if test="faceRef != null">face_ref = #{faceRef},</if>
|
||||
<if test="avatar != null">avatar = #{avatar},</if>
|
||||
<if test="dutyLevel != null">duty_level = #{dutyLevel},</if>
|
||||
<if test="remark != null">remark = #{remark},</if>
|
||||
update_time = #{updateTime},
|
||||
update_by = #{updateBy}
|
||||
</trim>
|
||||
where s_id = #{sId} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
<!-- 根据系统用户ID查询学员 (仅限未删除 del_flag = '0') -->
|
||||
<select id="selectTrainStudentByUserId" parameterType="Long" resultMap="TrainStudentResult">
|
||||
<include refid="selectTrainStudentDomain" />
|
||||
|
||||
@@ -21,10 +21,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="avatar" column="avatar" />
|
||||
<result property="major" column="major" />
|
||||
<result property="courses" column="courses" />
|
||||
<result property="startTeaching" column="start_teaching" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainTeacherVo">
|
||||
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
|
||||
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, avatar, major, courses, start_teaching from train_teacher
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainTeacherList" parameterType="TrainTeacher" resultMap="TrainTeacherResult">
|
||||
@@ -39,6 +43,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<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="status != null"> and status = #{status}</if>
|
||||
<if test="major != null and major != ''"> and major like concat('%', #{major}, '%')</if>
|
||||
<if test="courses != null and courses != ''"> and courses like concat('%', #{courses}, '%')</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
@@ -72,6 +78,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="avatar != null">avatar,</if>
|
||||
<if test="major != null">major,</if>
|
||||
<if test="courses != null">courses,</if>
|
||||
<if test="startTeaching != null">start_teaching,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="tId != null">#{tId},</if>
|
||||
@@ -90,6 +100,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="avatar != null">#{avatar},</if>
|
||||
<if test="major != null">#{major},</if>
|
||||
<if test="courses != null">#{courses},</if>
|
||||
<if test="startTeaching != null">#{startTeaching},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@@ -108,6 +122,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<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>
|
||||
<if test="avatar != null">avatar = #{avatar},</if>
|
||||
<if test="major != null">major = #{major},</if>
|
||||
<if test="courses != null">courses = #{courses},</if>
|
||||
<if test="startTeaching != null">start_teaching = #{startTeaching},</if>
|
||||
</trim>
|
||||
where t_id = #{tId} and del_flag = '0'
|
||||
</update>
|
||||
|
||||
@@ -17,20 +17,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="major" column="major" />
|
||||
<result property="seatArrange" column="seat_arrange" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectTrainTrainingSiteVo">
|
||||
select site_id, site_name, site_location, capacity, site_device, site_status, del_flag, create_by, create_time, update_by, update_time, remark from train_training_site
|
||||
select site_id, site_name, site_location, capacity, site_device, site_status, del_flag, create_by, create_time, update_by, update_time, remark, major, seat_arrange from train_training_site
|
||||
</sql>
|
||||
|
||||
<select id="selectTrainTrainingSiteList" parameterType="TrainTrainingSite" resultMap="TrainTrainingSiteResult">
|
||||
<include refid="selectTrainTrainingSiteVo"/>
|
||||
where del_flag = '0'
|
||||
<if test="siteName != null and siteName != ''"> and site_name like concat('%', #{siteName}, '%')</if>
|
||||
<if test="siteLocation != null and siteLocation != ''"> and site_location = #{siteLocation}</if>
|
||||
<if test="siteLocation != null and siteLocation != ''"> and site_location like concat('%', #{siteLocation}, '%')</if>
|
||||
<if test="capacity != null "> and capacity = #{capacity}</if>
|
||||
<if test="siteDevice != null and siteDevice != ''"> and site_device = #{siteDevice}</if>
|
||||
<if test="siteStatus != null "> and site_status = #{siteStatus}</if>
|
||||
<if test="major != null and major != ''"> and major like concat('%', #{major}, '%')</if>
|
||||
<if test="createBy != null and createBy != ''"> and create_by = #{createBy}</if>
|
||||
order by create_time desc
|
||||
</select>
|
||||
@@ -64,6 +67,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">update_by,</if>
|
||||
<if test="updateTime != null">update_time,</if>
|
||||
<if test="remark != null">remark,</if>
|
||||
<if test="major != null">major,</if>
|
||||
<if test="seatArrange != null">seat_arrange,</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="siteId != null">#{siteId},</if>
|
||||
@@ -78,6 +83,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="updateBy != null and updateBy != ''">#{updateBy},</if>
|
||||
<if test="updateTime != null">#{updateTime},</if>
|
||||
<if test="remark != null">#{remark},</if>
|
||||
<if test="major != null">#{major},</if>
|
||||
<if test="seatArrange != null">#{seatArrange},</if>
|
||||
</trim>
|
||||
</insert>
|
||||
|
||||
@@ -93,6 +100,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<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>
|
||||
<if test="major != null">major = #{major},</if>
|
||||
<if test="seatArrange != null">seat_arrange = #{seatArrange},</if>
|
||||
</trim>
|
||||
where site_id = #{siteId}
|
||||
</update>
|
||||
|
||||
@@ -96,7 +96,7 @@ export function updateUserPwd(oldPassword, newPassword) {
|
||||
return request({
|
||||
url: '/system/user/profile/updatePwd',
|
||||
method: 'put',
|
||||
params: data
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询教学资源列表
|
||||
export function listResource(query) {
|
||||
return request({
|
||||
url: '/train/resource/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询教学资源详细
|
||||
export function getResource(resourceId) {
|
||||
return request({
|
||||
url: '/train/resource/' + resourceId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增教学资源
|
||||
export function addResource(data) {
|
||||
return request({
|
||||
url: '/train/resource',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改教学资源
|
||||
export function updateResource(data) {
|
||||
return request({
|
||||
url: '/train/resource/' + data.resourceId,
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除教学资源
|
||||
export function delResource(resourceId) {
|
||||
return request({
|
||||
url: '/train/resource/' + resourceId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -26,6 +26,14 @@ export function addSite(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 删除实训场地
|
||||
export function delSite(ids) {
|
||||
return request({
|
||||
url: '/train/training-site/' + ids,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改实训场地
|
||||
export function updateSite(data) {
|
||||
return request({
|
||||
|
||||
@@ -26,6 +26,15 @@ export function addStudent(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 修改教师信息
|
||||
export function updateStudent(data) {
|
||||
return request({
|
||||
url: '/train/student/' + (data.sId || ''),
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询可关联学员的系统用户候选列表
|
||||
export function getStudentBindableUsers(keyword) {
|
||||
return request({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="68px">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="88px">
|
||||
<el-form-item label="教室名称" prop="roomName">
|
||||
<el-input
|
||||
v-model="queryParams.roomName"
|
||||
@@ -24,6 +24,26 @@
|
||||
<el-option label="维护中" :value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="具备多媒体" prop="multimedia">
|
||||
<el-select v-model="queryParams.multimedia" placeholder="具备多媒体" clearable>
|
||||
<el-option
|
||||
v-for="dict in yesNoOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="具备投影" prop="projector">
|
||||
<el-select v-model="queryParams.projector" placeholder="具备投影" clearable>
|
||||
<el-option
|
||||
v-for="dict in yesNoOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
@@ -71,7 +91,17 @@
|
||||
<el-tag v-else type="info">维护中</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="具备多媒体" align="center" prop="multimedia">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.train_yes_no" :value="scope.row.multimedia" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="具备投影" align="center" prop="projector">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.train_yes_no" :value="scope.row.projector" />
|
||||
</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="140">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@@ -120,6 +150,24 @@
|
||||
<el-radio :label="2">维护中</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="具备多媒体" prop="multimedia">
|
||||
<el-radio-group v-model="form.multimedia">
|
||||
<el-radio
|
||||
v-for="dict in yesNoOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="具备投影" prop="projector">
|
||||
<el-radio-group v-model="form.projector">
|
||||
<el-radio
|
||||
v-for="dict in yesNoOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" maxlength="500" />
|
||||
</el-form-item>
|
||||
@@ -137,6 +185,8 @@ import { listClassroom, getClassroom, addClassroom, updateClassroom, delClassroo
|
||||
|
||||
export default {
|
||||
name: 'Classroom',
|
||||
// 是否类字典(sys_yes_no),注入后可通过 dict.type.sys_yes_no 访问
|
||||
dicts: ['sys_yes_no', 'train_yes_no'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
@@ -164,7 +214,9 @@ export default {
|
||||
roomName: null,
|
||||
roomLocation: null,
|
||||
capacity: null,
|
||||
roomStatus: null
|
||||
roomStatus: null,
|
||||
multimedia: null,
|
||||
projector: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
@@ -191,10 +243,35 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/*
|
||||
* 是否(sys_yes_no)单选选项。
|
||||
* 数据库 multimedia / projector 为数值型(1=是,0=否),
|
||||
* 字典 dictValue 为 Y/N 字符串,此处统一转换为数值,保证回显与提交类型一致。
|
||||
*/
|
||||
yesNoOptions() {
|
||||
const list = (this.dict && this.dict.type && this.dict.type.train_yes_no) || []
|
||||
return list.map(item => ({
|
||||
label: item.label,
|
||||
//value: this.toYesNoValue(item.value)
|
||||
value: /^\d+$/.test(String(item.value)) ? Number(item.value) : item.value
|
||||
}))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 是否值归一化:Y->1,N->0,纯数字字符串转 Number */
|
||||
toYesNoValue(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return null
|
||||
}
|
||||
const text = String(value)
|
||||
if (text === 'Y' || text === 'y') return 1
|
||||
if (text === 'N' || text === 'n') return 0
|
||||
return /^\d+$/.test(text) ? Number(text) : value
|
||||
},
|
||||
/** 查询教室信息列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
@@ -217,6 +294,8 @@ export default {
|
||||
roomLocation: null,
|
||||
capacity: 50,
|
||||
roomStatus: 1,
|
||||
multimedia: 0,
|
||||
projector: 0,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm('form')
|
||||
@@ -249,6 +328,10 @@ export default {
|
||||
const classRoomId = row.classRoomId || this.ids
|
||||
getClassroom(classRoomId).then(response => {
|
||||
this.form = response.data
|
||||
console.log("multimedia==="+this.form.multimedia);
|
||||
// 归一化是否值,保证与单选选项(数值)类型一致,避免回显不选中
|
||||
//this.$set(this.form, 'multimedia', /^\d+$/.test(String(this.form.multimedia)) ? Number(this.form.multimedia) : this.form.multimedia)
|
||||
//this.$set(this.form, 'projector', /^\d+$/.test(String(this.form.projector)) ? Number(this.form.projector) : this.form.projector)
|
||||
this.open = true
|
||||
this.title = '修改教室信息'
|
||||
})
|
||||
|
||||
@@ -18,12 +18,19 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="课程类型" prop="cType">
|
||||
<el-input
|
||||
<el-select
|
||||
v-model="queryParams.cType"
|
||||
placeholder="请输入课程类型"
|
||||
placeholder="请选择课程类型"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in dict.type.train_course_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="课程状态" clearable>
|
||||
@@ -66,10 +73,14 @@
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="课程编码" align="center" prop="cCode" min-width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="课程名称" align="center" prop="cName" min-width="140" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="课程类型" align="center" prop="cType" width="120" />
|
||||
<el-table-column label="课程类型" align="center" prop="cType" width="120">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.train_course_type" :value="scope.row.cType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学时" align="center" prop="cDuration" width="80" />
|
||||
<el-table-column label="教学目标" align="center" prop="teachingObjective" min-width="150" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="课程内容" align="center" prop="courseContent" min-width="160" :show-overflow-tooltip="true" />
|
||||
<!-- <el-table-column label="教学目标" align="center" prop="teachingObjective" min-width="150" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="课程内容" align="center" prop="courseContent" min-width="160" :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' : 'info'">
|
||||
@@ -77,8 +88,8 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" min-width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
|
||||
<!-- <el-table-column label="备注" align="center" prop="remark" min-width="120" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />-->
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@@ -136,7 +147,14 @@
|
||||
<el-input v-model="form.cName" placeholder="请输入课程名称" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="课程类型" prop="cType">
|
||||
<el-input v-model="form.cType" placeholder="请输入课程类型 (如 理论课/实操课)" maxlength="30" />
|
||||
<el-select v-model="form.cType" placeholder="请选择课程类型" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in dict.type.train_course_type"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="课程学时" prop="cDuration">
|
||||
<el-input-number v-model="form.cDuration" :min="1" :max="9999" placeholder="请输入课程学时" style="width: 100%;" />
|
||||
@@ -147,6 +165,23 @@
|
||||
<el-form-item label="课程内容" prop="courseContent">
|
||||
<el-input v-model="form.courseContent" type="textarea" :rows="4" placeholder="请输入课程内容/摘要" />
|
||||
</el-form-item>
|
||||
<el-form-item label="课程资源" prop="resourceId">
|
||||
<el-select
|
||||
v-model="resourceIdList"
|
||||
multiple
|
||||
filterable
|
||||
collapse-tags
|
||||
placeholder="请选择课程资源 (可多选)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in resourceOptions"
|
||||
:key="item.resourceId"
|
||||
:label="item.resourceName"
|
||||
:value="String(item.resourceId)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio :label="1">正常</el-radio>
|
||||
@@ -167,9 +202,11 @@
|
||||
|
||||
<script>
|
||||
import { listCourse, getCourse, addCourse, updateCourse, delCourse } from '@/api/train/course'
|
||||
import { listResource, getResource } from '@/api/train/resource'
|
||||
|
||||
export default {
|
||||
name: 'Course',
|
||||
dicts: ['train_course_type'],
|
||||
data() {
|
||||
const validateDuration = (rule, value, callback) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
@@ -200,6 +237,10 @@ export default {
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 课程资源下拉备选项
|
||||
resourceOptions: [],
|
||||
// 已选课程资源ID集合 (对应 form.resourceId 逗号分隔字符串)
|
||||
resourceIdList: [],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
@@ -229,13 +270,52 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getResourceOptions()
|
||||
},
|
||||
methods: {
|
||||
/** 查询课程资源下拉备选项 (不做状态过滤,保证已选的停用资源也能正常回显) */
|
||||
getResourceOptions() {
|
||||
return listResource({ pageNum: 1, pageSize: 1000 }).then(response => {
|
||||
this.resourceOptions = response.rows || []
|
||||
return this.resourceOptions
|
||||
}).catch(() => {
|
||||
this.resourceOptions = this.resourceOptions || []
|
||||
return this.resourceOptions
|
||||
})
|
||||
},
|
||||
/** 课程资源ID字符串 -> 多选数组 (统一转成字符串,避免类型不一致导致无法勾选) */
|
||||
parseResourceIds(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return []
|
||||
}
|
||||
return String(value).split(',').map(item => String(item).trim()).filter(item => item !== '')
|
||||
},
|
||||
/** 保证已选资源在下拉选项中存在;element-ui 多选值若无对应 option 会不显示,故必须补齐后再回显 */
|
||||
ensureResourceOptions(ids) {
|
||||
const existingIds = this.resourceOptions.map(item => String(item.resourceId).trim())
|
||||
const missingIds = ids.filter(id => existingIds.indexOf(id) === -1)
|
||||
if (missingIds.length === 0) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.all(missingIds.map(id => {
|
||||
return getResource(id).then(response => {
|
||||
if (response.data) {
|
||||
this.resourceOptions.push(response.data)
|
||||
}
|
||||
}).catch(() => {})
|
||||
}))
|
||||
},
|
||||
/** 备选项就绪后再回显已选资源 */
|
||||
prepareResourceOptions(ids) {
|
||||
const load = this.resourceOptions.length > 0 ? Promise.resolve() : this.getResourceOptions()
|
||||
return Promise.resolve(load).then(() => this.ensureResourceOptions(ids))
|
||||
},
|
||||
/** 查询课程信息列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listCourse(this.queryParams).then(response => {
|
||||
this.courseList = response.rows
|
||||
//console.log(response.rows);
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
@@ -255,9 +335,11 @@ export default {
|
||||
cDuration: null,
|
||||
teachingObjective: null,
|
||||
courseContent: null,
|
||||
resourceId: null,
|
||||
status: 1,
|
||||
remark: null
|
||||
}
|
||||
this.resourceIdList = []
|
||||
this.resetForm('form')
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
@@ -278,24 +360,37 @@ export default {
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '添加课程'
|
||||
this.prepareResourceOptions([]).then(() => {
|
||||
this.open = true
|
||||
this.title = '添加课程'
|
||||
})
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const cId = row.cId || this.ids
|
||||
getCourse(cId).then(response => {
|
||||
//console.log(response.data);
|
||||
this.form = response.data
|
||||
if (this.form.status === undefined || this.form.status === null) {
|
||||
this.form.status = 1
|
||||
}
|
||||
this.open = true
|
||||
this.title = '修改课程'
|
||||
})
|
||||
const selectedIds = this.parseResourceIds(this.form.resourceId)
|
||||
// 必须先保证这些ID在下拉备选项中存在,否则多选不会被勾选/显示
|
||||
return this.prepareResourceOptions(selectedIds).then(() => {
|
||||
//this.resourceIdList = selectedIds
|
||||
//console.log("selectedIds===============");
|
||||
//console.log(selectedIds);
|
||||
this.$set(this,"resourceIdList",selectedIds);
|
||||
this.open = true
|
||||
this.title = '修改课程'
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
// 多选资源ID以逗号分隔写入 resource_id 字段
|
||||
this.form.resourceId = this.resourceIdList.length > 0 ? this.resourceIdList.join(',') : null
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.cId != null) {
|
||||
|
||||
@@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form v-show="showSearch" ref="queryForm" :model="queryParams" size="small" :inline="true" label-width="68px">
|
||||
<el-form-item label="资源名称" prop="resourceName">
|
||||
<el-input
|
||||
v-model="queryParams.resourceName"
|
||||
placeholder="请输入资源名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源类型" prop="resourceType">
|
||||
<el-select v-model="queryParams.resourceType" placeholder="资源类型" clearable>
|
||||
<el-option
|
||||
v-for="dict in resourceTypeOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<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>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:resource:edit']"
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-plus"
|
||||
size="mini"
|
||||
@click="handleAdd"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:resource:edit']"
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-edit"
|
||||
size="mini"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
v-hasPermi="['train:resource:edit']"
|
||||
type="danger"
|
||||
plain
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :show-search.sync="showSearch" @queryTable="getList" />
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="resourceList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="资源名称" align="center" prop="resourceName" min-width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="资源类型" align="center" prop="resourceType">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.train_resource_type" :value="scope.row.resourceType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="资源文件/地址" align="center" prop="resourceUrl" min-width="200" :show-overflow-tooltip="true">
|
||||
<template slot-scope="scope">
|
||||
<el-link
|
||||
v-if="scope.row.resourceType === 5"
|
||||
:href="scope.row.resourceUrl"
|
||||
type="primary"
|
||||
target="_blank"
|
||||
:underline="false"
|
||||
>{{ scope.row.resourceUrl }}</el-link>
|
||||
<el-link
|
||||
v-else
|
||||
:href="fileUrl(scope.row.resourceUrl)"
|
||||
type="primary"
|
||||
target="_blank"
|
||||
:underline="false"
|
||||
>{{ fileName(scope.row.resourceUrl) }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="资源描述" align="center" prop="description" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 1" type="success">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="180" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-hasPermi="['train:resource:edit']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['train:resource:edit']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total>0"
|
||||
:total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改教学资源对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="资源名称" prop="resourceName">
|
||||
<el-input v-model="form.resourceName" placeholder="请输入资源名称" maxlength="150" />
|
||||
</el-form-item>
|
||||
<el-form-item label="资源类型" prop="resourceType">
|
||||
<el-select v-model="form.resourceType" placeholder="请选择资源类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in resourceTypeOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExternalLink" label="外部链接" prop="resourceUrl">
|
||||
<el-input v-model="form.resourceUrl" placeholder="请输入外部教学链接,以 http(s):// 开头" maxlength="500" />
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="资源文件" prop="resourceUrl">
|
||||
<file-upload
|
||||
v-model="form.resourceUrl"
|
||||
:limit="1"
|
||||
:file-size="100"
|
||||
:file-type="fileTypes"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="资源描述" prop="description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" maxlength="500" placeholder="请输入资源描述/使用说明" />
|
||||
</el-form-item>
|
||||
<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" type="textarea" placeholder="请输入备注" maxlength="500" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listResource, getResource, addResource, updateResource, delResource } from '@/api/train/resource'
|
||||
|
||||
export default {
|
||||
name: 'TrainResource',
|
||||
dicts: ['train_resource_type'],
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: true,
|
||||
// 选中数组
|
||||
ids: [],
|
||||
// 非单个禁用
|
||||
single: true,
|
||||
// 非多个禁用
|
||||
multiple: true,
|
||||
// 显示搜索条件
|
||||
showSearch: true,
|
||||
// 总条数
|
||||
total: 0,
|
||||
// 教学资源表格数据
|
||||
resourceList: [],
|
||||
// 弹出层标题
|
||||
title: '',
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 允许上传的资源文件格式
|
||||
fileTypes: ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'txt', 'zip', 'rar', '7z', 'mp4', 'avi', 'mov', 'wmv', 'mp3', 'png', 'jpg', 'jpeg', 'gif', 'glb', 'gltf', 'obj', 'fbx'],
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
resourceName: null,
|
||||
resourceType: null,
|
||||
status: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
resourceName: [
|
||||
{ required: true, message: '资源名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
resourceType: [
|
||||
{ required: true, message: '资源类型不能为空', trigger: 'change' }
|
||||
],
|
||||
resourceUrl: [
|
||||
{ required: true, message: '资源文件或外部链接不能为空', trigger: ['blur', 'change'] }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 资源类型下拉项:字典值为字符串,数据表字段为整型,需转成数字
|
||||
resourceTypeOptions() {
|
||||
return (this.dict.type.train_resource_type || []).map(item => ({
|
||||
label: item.label,
|
||||
value: /^\d+$/.test(String(item.value)) ? Number(item.value) : item.value
|
||||
}))
|
||||
},
|
||||
// 资源类型为「外部教学链接」时不走上传,改为填写地址
|
||||
isExternalLink() {
|
||||
return Number(this.form.resourceType) === 5
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 切换资源类型时清空已选资源地址,避免类型与地址不匹配
|
||||
'form.resourceType'(newVal, oldVal) {
|
||||
if (oldVal !== undefined && newVal !== oldVal && this.open) {
|
||||
this.$set(this.form, 'resourceUrl', null)
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs['form']) {
|
||||
this.$refs['form'].clearValidate('resourceUrl')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
/** 查询教学资源列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listResource(this.queryParams).then(response => {
|
||||
this.resourceList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
this.reset()
|
||||
},
|
||||
// 表单重置
|
||||
reset() {
|
||||
this.form = {
|
||||
resourceId: null,
|
||||
resourceName: null,
|
||||
resourceType: 1,
|
||||
resourceUrl: null,
|
||||
description: null,
|
||||
status: 1,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm('form')
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm')
|
||||
this.handleQuery()
|
||||
},
|
||||
// 多选框选中数据
|
||||
handleSelectionChange(selection) {
|
||||
this.ids = selection.map(item => item.resourceId)
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
this.open = true
|
||||
this.title = '添加教学资源'
|
||||
},
|
||||
/** 修改按钮操作 */
|
||||
handleUpdate(row) {
|
||||
this.reset()
|
||||
const resourceId = row.resourceId || this.ids
|
||||
getResource(resourceId).then(response => {
|
||||
// 资源类型为整型,防止后端返回字符串导致下拉/单选回显失败
|
||||
this.form = {
|
||||
...response.data,
|
||||
resourceType: response.data.resourceType == null ? null : Number(response.data.resourceType),
|
||||
status: response.data.status == null ? 1 : Number(response.data.status)
|
||||
}
|
||||
this.open = true
|
||||
this.title = '修改教学资源'
|
||||
})
|
||||
},
|
||||
/** 提交按钮 */
|
||||
submitForm() {
|
||||
this.$refs['form'].validate(valid => {
|
||||
if (valid) {
|
||||
if (this.form.resourceId != null) {
|
||||
updateResource(this.form).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addResource(this.form).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.open = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.resourceId || this.ids
|
||||
if (!ids || (Array.isArray(ids) && ids.length === 0)) {
|
||||
this.$modal.msgWarning('请选择要删除的数据')
|
||||
return
|
||||
}
|
||||
this.$modal.confirm('是否确认删除资源编号为"' + ids + '"的数据项?').then(function() {
|
||||
return delResource(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
// 资源文件访问地址
|
||||
fileUrl(url) {
|
||||
if (!url) {
|
||||
return ''
|
||||
}
|
||||
if (/^(http|https):\/\//.test(url)) {
|
||||
return url
|
||||
}
|
||||
return process.env.VUE_APP_BASE_API + url
|
||||
},
|
||||
// 资源文件名
|
||||
fileName(url) {
|
||||
if (!url) {
|
||||
return ''
|
||||
}
|
||||
return url.substring(url.lastIndexOf('/') + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -23,6 +23,14 @@
|
||||
<el-option label="停用" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="适用专业" prop="major">
|
||||
<el-input
|
||||
v-model="queryParams.major"
|
||||
placeholder="请输入适用专业"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
@@ -63,6 +71,7 @@
|
||||
<el-tag type="info">{{ scope.row.capacity }} 人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="适用专业" align="center" prop="major" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="配套设备描述" align="center" prop="siteDevice" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="场地状态" align="center" prop="siteStatus">
|
||||
<template slot-scope="scope">
|
||||
@@ -70,7 +79,7 @@
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<!-- <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />-->
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@@ -80,6 +89,13 @@
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['train:site:edit']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -104,6 +120,9 @@
|
||||
<el-form-item label="最大容纳人数" prop="capacity">
|
||||
<el-input-number v-model="form.capacity" :min="1" :max="10000" placeholder="最大容纳人数" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="适用专业" prop="major">
|
||||
<el-input v-model="form.major" placeholder="请输入适用专业 (如 铁道供电技术)" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="场地配套设备" prop="siteDevice">
|
||||
<el-input v-model="form.siteDevice" type="textarea" placeholder="例如:VR头显12套、3D数字孪生演练台等" :rows="3" />
|
||||
</el-form-item>
|
||||
@@ -126,7 +145,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listSite, getSite, addSite, updateSite } from '@/api/train/site'
|
||||
import { listSite, getSite, addSite, updateSite, delSite } from '@/api/train/site'
|
||||
|
||||
export default {
|
||||
name: 'TrainingSite',
|
||||
@@ -157,7 +176,8 @@ export default {
|
||||
siteName: null,
|
||||
siteLocation: null,
|
||||
capacity: null,
|
||||
siteStatus: null
|
||||
siteStatus: null,
|
||||
major: null
|
||||
},
|
||||
// 表单参数
|
||||
form: {},
|
||||
@@ -211,6 +231,7 @@ export default {
|
||||
capacity: 30,
|
||||
siteDevice: null,
|
||||
siteStatus: 1,
|
||||
major: null,
|
||||
remark: null
|
||||
}
|
||||
this.resetForm('form')
|
||||
@@ -231,6 +252,20 @@ export default {
|
||||
this.single = selection.length !== 1
|
||||
this.multiple = !selection.length
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const ids = row.siteId || this.ids
|
||||
if (!ids || (Array.isArray(ids) && ids.length === 0)) {
|
||||
this.$modal.msgWarning('请选择要删除的数据')
|
||||
return
|
||||
}
|
||||
this.$modal.confirm('是否确认删除场地编号为"' + ids + '"的数据项?').then(function() {
|
||||
return delSite(ids)
|
||||
}).then(() => {
|
||||
this.getList()
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
}).catch(() => {})
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.reset()
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input
|
||||
<el-form-item label="所属部门" prop="orgName">
|
||||
<!-- <el-input
|
||||
v-model="queryParams.orgName"
|
||||
placeholder="请输入所属单位"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
/>-->
|
||||
<treeselect style="width: 210px;" v-model="queryParams.orgName" :options="deptOptions" :normalizer="normalizer" :show-count="true" placeholder="请选择归属部门" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="sPhone">
|
||||
<el-input
|
||||
@@ -59,6 +60,20 @@
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="studentList">
|
||||
<el-table-column label="头像" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
:src="avatarUrl(scope.row.avatar)"
|
||||
:preview-src-list="[avatarUrl(scope.row.avatar)]"
|
||||
fit="cover"
|
||||
class="student-avatar"
|
||||
>
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline" />
|
||||
</div>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学员姓名" align="center" prop="sName" min-width="110" />
|
||||
<el-table-column label="性别" align="center" prop="sGender" width="70">
|
||||
<template slot-scope="scope">
|
||||
@@ -71,6 +86,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属单位" align="center" prop="orgName" min-width="140" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="联系电话" align="center" prop="sPhone" width="130" />
|
||||
<el-table-column label="邮箱" align="center" prop="sEmail" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column label="身份证号" align="center" prop="idCardMasked" min-width="190" />
|
||||
@@ -98,7 +114,7 @@
|
||||
<el-tag v-else type="info" size="mini">未关联</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="210">
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-hasPermi="['train:student:query']"
|
||||
@@ -107,6 +123,13 @@
|
||||
icon="el-icon-view"
|
||||
@click="handleDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
v-hasPermi="['train:student:edit']"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)"
|
||||
>修改</el-button>
|
||||
<el-button
|
||||
v-if="!scope.row.userName && !scope.row.userId"
|
||||
v-hasPermi="['train:student:bindUser']"
|
||||
@@ -144,16 +167,27 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<el-dialog title="新增学员主档" :visible.sync="createOpen" width="560px" append-to-body @closed="resetCreate">
|
||||
<el-dialog :title="createTitle" :visible.sync="createOpen" width="560px" append-to-body @closed="resetCreate">
|
||||
<el-form ref="createForm" :model="createForm" :rules="rules" label-width="90px">
|
||||
<el-form-item label="学员头像" prop="avatar">
|
||||
<studentAvatar :user="{ avatar: createForm.avatar }" @success="handleAvatarSuccess" />
|
||||
</el-form-item>
|
||||
<el-form-item label="学员姓名" prop="sName">
|
||||
<el-input v-model="createForm.sName" placeholder="请输入学员姓名" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sGender">
|
||||
<el-radio-group v-model="createForm.sGender">
|
||||
<!-- <el-radio-group v-model="createForm.sGender">
|
||||
<el-radio label="男">男</el-radio>
|
||||
<el-radio label="女">女</el-radio>
|
||||
</el-radio-group>
|
||||
</el-radio-group>-->
|
||||
<el-select v-model="createForm.sGender" placeholder="请选择性别">
|
||||
<el-option
|
||||
v-for="dict in dict.type.sys_user_sex"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="出生日期" prop="sBirth">
|
||||
<el-date-picker
|
||||
@@ -165,7 +199,18 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input v-model="createForm.orgName" placeholder="请输入所属单位/部门" maxlength="100" />
|
||||
<!-- <el-input v-model="createForm.orgName" placeholder="请输入所属单位/部门" maxlength="100" />-->
|
||||
<treeselect v-model="createForm.orgName" :options="deptOptions" :normalizer="normalizer" :show-count="true" placeholder="请选择归属部门" />
|
||||
</el-form-item>
|
||||
<el-form-item label="岗位级别" prop="dutyLevel">
|
||||
<el-select v-model="createForm.dutyLevel" placeholder="请选择岗位级别" clearable style="width: 100%">
|
||||
<el-option
|
||||
v-for="dict in dutyLevelOptions"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="sPhone">
|
||||
<el-input v-model="createForm.sPhone" placeholder="请输入联系电话" maxlength="20" />
|
||||
@@ -197,6 +242,16 @@
|
||||
|
||||
<el-dialog title="学员主档详情" :visible.sync="detailOpen" width="560px" append-to-body>
|
||||
<el-form :model="detail" label-width="110px" class="student-detail">
|
||||
<el-form-item label="学员头像">
|
||||
<el-image
|
||||
v-if="detail.avatar"
|
||||
:src="avatarUrl(detail.avatar)"
|
||||
:preview-src-list="[avatarUrl(detail.avatar)]"
|
||||
fit="cover"
|
||||
class="student-avatar"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="学员姓名"><span>{{ detail.sName || '-' }}</span></el-form-item>
|
||||
<el-form-item label="系统编号"><span>{{ detail.sId || '-' }}</span></el-form-item>
|
||||
<el-form-item label="关联系统账号">
|
||||
@@ -204,6 +259,10 @@
|
||||
<el-tag v-else type="info" size="mini">未关联</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属单位"><span>{{ detail.orgName || '-' }}</span></el-form-item>
|
||||
<el-form-item label="岗位级别">
|
||||
<dict-tag v-if="detail.dutyLevel !== null && detail.dutyLevel !== undefined" :options="dict.type.train_duty_level" :value="detail.dutyLevel" />
|
||||
<span v-else>-</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话"><span>{{ detail.sPhone || '-' }}</span></el-form-item>
|
||||
<el-form-item label="邮箱"><span>{{ detail.sEmail || '-' }}</span></el-form-item>
|
||||
<el-form-item label="身份证号"><span>{{ detail.idCardMasked || '-' }}</span></el-form-item>
|
||||
@@ -275,10 +334,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addStudent, getStudent, listStudent, delStudent, getStudentBindableUsers, bindStudentUser, unbindStudentUser } from '@/api/train/student'
|
||||
|
||||
import request from '@/utils/request'
|
||||
import { addStudent, getStudent, listStudent, delStudent, getStudentBindableUsers, bindStudentUser, unbindStudentUser, updateStudent } from '@/api/train/student'
|
||||
import {deptTreeSelect} from "@/api/system/user";
|
||||
import Treeselect from "@riophae/vue-treeselect";
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
||||
import {listDept} from "@/api/system/dept";
|
||||
import studentAvatar from "./studentAvatar.vue";
|
||||
import {addTeacher, updateTeacher} from "@/api/train/teacher";
|
||||
export default {
|
||||
name: 'Student',
|
||||
components: { studentAvatar, Treeselect },
|
||||
// 岗位级别字典(train_duty_level),注入后可通过 dict.type.train_duty_level 访问
|
||||
dicts: ['train_duty_level', 'sys_user_sex'],
|
||||
data() {
|
||||
const validateIdCard = (rule, value, callback) => {
|
||||
if (!value || value.trim() === '') {
|
||||
@@ -298,6 +366,9 @@ export default {
|
||||
total: 0,
|
||||
studentList: [],
|
||||
createOpen: false,
|
||||
createTitle: '新增学员主档',
|
||||
// 弹窗是否为修改模式(true=修改,false=新增),用于复用同一弹窗
|
||||
isEdit: false,
|
||||
detailOpen: false,
|
||||
detail: {},
|
||||
// 账号关联弹窗
|
||||
@@ -316,7 +387,12 @@ export default {
|
||||
sPhone: null,
|
||||
status: null
|
||||
},
|
||||
// 部门树选项
|
||||
deptOptions: undefined,
|
||||
deptArr: {},
|
||||
createForm: {},
|
||||
// 头像上传组件用的学员对象(avatar 为入库的相对地址)
|
||||
user: { avatar: '' },
|
||||
rules: {
|
||||
sName: [
|
||||
{ required: true, message: '学员姓名不能为空', trigger: 'blur' },
|
||||
@@ -331,9 +407,27 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/*
|
||||
* 岗位级别下拉选项:取自字典 train_duty_level。
|
||||
* 后端 duty_level 为数值型,字典 dictValue 为字符串,
|
||||
* 此处将纯数字的字典值转为 Number,保证下拉回显时类型一致。
|
||||
*/
|
||||
dutyLevelOptions() {
|
||||
const list = (this.dict && this.dict.type && this.dict.type.train_duty_level) || []
|
||||
return list.map(item => ({
|
||||
label: item.label,
|
||||
value: /^\d+$/.test(String(item.value)) ? Number(item.value) : item.value
|
||||
}))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.resetCreate()
|
||||
this.mapDept()
|
||||
this.getList()
|
||||
this.getDeptTree()
|
||||
|
||||
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
@@ -341,10 +435,53 @@ export default {
|
||||
listStudent(this.queryParams).then(response => {
|
||||
this.studentList = response.rows
|
||||
this.total = response.total
|
||||
this.studentList.forEach(stud => {
|
||||
let studOrgName = this.formatOrgName(stud.orgName);
|
||||
//console.log(stud.orgName +"--"+ studOrgName);
|
||||
this.$set(stud, "orgName", studOrgName);
|
||||
});
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
},
|
||||
/** 查询部门下拉树结构 */
|
||||
getDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data;
|
||||
});
|
||||
},
|
||||
// 筛选节点
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
},
|
||||
// 节点单击事件
|
||||
handleNodeClick(data) {
|
||||
this.createForm.orgName = data.id;
|
||||
this.handleQuery();
|
||||
},
|
||||
// 数据格式化函数,把后端返回部门树形转成treeselect需要格式
|
||||
normalizer(node) {
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
children: node.children
|
||||
}
|
||||
},
|
||||
mapDept() {
|
||||
listDept().then(response => {
|
||||
let deptList = response.data || [];
|
||||
let deptArr = {};
|
||||
deptList.forEach(dept => {
|
||||
if (dept && dept.deptId !== undefined && dept.deptId !== null) {
|
||||
deptArr[dept.deptId] = dept.deptName;
|
||||
}
|
||||
});
|
||||
this.deptArr = deptArr;
|
||||
});
|
||||
},
|
||||
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
@@ -355,42 +492,140 @@ export default {
|
||||
},
|
||||
handleAdd() {
|
||||
this.resetCreate()
|
||||
this.isEdit = false
|
||||
this.createTitle = '新增学员主档'
|
||||
this.createOpen = true
|
||||
},
|
||||
/** 修改按钮:复用新增弹窗,回填学员信息 */
|
||||
handleUpdate(row) {
|
||||
this.resetCreate()
|
||||
this.isEdit = true
|
||||
this.createTitle = '修改学员主档'
|
||||
// 身份证号为脱敏字段,不回显;留空表示不修改
|
||||
this.createForm = {
|
||||
sId: row.sId,
|
||||
avatar: row.avatar || null,
|
||||
sName: row.sName || null,
|
||||
sGender: row.sGender || '男',
|
||||
sBirth: row.sBirth || null,
|
||||
orgName: this.toDeptValue(row.orgName),
|
||||
dutyLevel: row.dutyLevel !== undefined && row.dutyLevel !== null ? this.toDutyLevelValue(row.dutyLevel) : null,
|
||||
sPhone: row.sPhone || null,
|
||||
sEmail: row.sEmail || null,
|
||||
idCard: null,
|
||||
faceRef: row.faceRef || null,
|
||||
remark: row.remark || null
|
||||
}
|
||||
// 同步头像组件预览
|
||||
this.user = { avatar: this.createForm.avatar || '' }
|
||||
this.createOpen = true
|
||||
},
|
||||
/** 岗位级别值归一化:数字字符串转 Number,保证与下拉选项 value 类型一致 */
|
||||
toDutyLevelValue(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return null
|
||||
}
|
||||
return /^\d+$/.test(String(value)) ? Number(value) : value
|
||||
},
|
||||
/** 部门值归一化:数字字符串转 Number,保证与部门树选项 id 类型一致 */
|
||||
toDeptValue(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return null
|
||||
}
|
||||
return /^\d+$/.test(String(value)) ? Number(value) : value
|
||||
},
|
||||
resetCreate() {
|
||||
this.createForm = {
|
||||
avatar: null,
|
||||
sName: null,
|
||||
sGender: '男',
|
||||
sBirth: null,
|
||||
orgName: null,
|
||||
dutyLevel: null,
|
||||
sPhone: null,
|
||||
sEmail: null,
|
||||
idCard: null,
|
||||
faceRef: null,
|
||||
remark: null
|
||||
}
|
||||
// 重置头像组件预览
|
||||
this.user = { avatar: '' }
|
||||
this.$set(this.createForm, "avatar", null);
|
||||
// 还原为新增模式
|
||||
this.isEdit = false
|
||||
this.createTitle = '新增学员主档'
|
||||
if (this.$refs.createForm) {
|
||||
this.resetForm('createForm')
|
||||
}
|
||||
},
|
||||
// 接收子组件上传成功抛出的头像地址:写入新增表单,随其他学员信息一并提交入库
|
||||
handleAvatarSuccess(imgUrl) {
|
||||
if (!imgUrl) {
|
||||
return
|
||||
}
|
||||
this.$set(this.createForm, 'avatar', imgUrl)
|
||||
// 校验通过后清除该项的校验提示
|
||||
if (this.$refs.createForm) {
|
||||
this.$refs.createForm.clearValidate('avatar')
|
||||
}
|
||||
},
|
||||
// 头像地址解析:补全 baseApi,空值使用默认头像
|
||||
avatarUrl(avatar) {
|
||||
if (!avatar) {
|
||||
return require('@/assets/images/profile.jpg')
|
||||
}
|
||||
if (avatar.indexOf('http') === 0 || avatar.indexOf('data:') === 0) {
|
||||
return avatar
|
||||
}
|
||||
return process.env.VUE_APP_BASE_API + avatar
|
||||
},
|
||||
submitCreate() {
|
||||
this.$refs.createForm.validate(valid => {
|
||||
/*this.$refs.createForm.validate(valid => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.submitting = true
|
||||
addStudent(this.createForm).then(() => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
// 头像地址由子组件上传后写入 createForm.avatar,与其他学员信息一并提交
|
||||
const payload = Object.assign({}, this.createForm, {
|
||||
avatar: this.createForm.avatar || (this.user && this.user.avatar) || null
|
||||
})
|
||||
// 修改模式调用 PUT /train/student,新增模式调用 POST /train/student
|
||||
const submitRequest = this.isEdit
|
||||
? request({ url: '/train/student', method: 'put', data: payload })
|
||||
: addStudent(payload)
|
||||
submitRequest.then(() => {
|
||||
this.$modal.msgSuccess(this.isEdit ? '修改成功' : '新增成功')
|
||||
this.createOpen = false
|
||||
this.resetCreate();
|
||||
this.getList()
|
||||
}).finally(() => {
|
||||
this.submitting = false
|
||||
})
|
||||
})*/
|
||||
|
||||
this.$refs.createForm.validate(valid => {
|
||||
if (valid) {
|
||||
if (this.createForm.sId != null) {
|
||||
updateStudent(this.createForm).then(response => {
|
||||
this.$modal.msgSuccess('修改成功')
|
||||
this.createOpen = false
|
||||
this.getList()
|
||||
})
|
||||
} else {
|
||||
addStudent(this.createForm).then(response => {
|
||||
this.$modal.msgSuccess('新增成功')
|
||||
this.createOpen = false
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDetail(row) {
|
||||
getStudent(row.sId).then(response => {
|
||||
this.detail = response.data || {}
|
||||
//console.log(this.deptArr);
|
||||
this.$set(this.detail, "orgName", this.formatOrgName(this.detail.orgName));
|
||||
this.detailOpen = true
|
||||
})
|
||||
},
|
||||
@@ -467,6 +702,17 @@ export default {
|
||||
}
|
||||
return labels[sourceType] || '-'
|
||||
},
|
||||
// 所属单位:orgName 为数字或 deptArr 中存在映射时取 deptArr 对应值,否则显示 orgName 本身
|
||||
formatOrgName(orgName) {
|
||||
if (orgName === null || orgName === undefined || orgName === '') return '-'
|
||||
const map = this.deptArr || {}
|
||||
const mapped = map[orgName]
|
||||
const isNumber = /^\d+$/.test(String(orgName))
|
||||
if (isNumber || mapped !== undefined) {
|
||||
return (mapped !== undefined && mapped !== null && mapped !== '') ? mapped : orgName
|
||||
}
|
||||
return orgName
|
||||
},
|
||||
formatGender(gender) {
|
||||
if (gender === '0' || gender === 0 || gender === '男') return '男'
|
||||
if (gender === '1' || gender === 1 || gender === '女') return '女'
|
||||
@@ -476,8 +722,26 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.student-detail ::v-deep .el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.student-avatar .image-slot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #c0c4cc;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="user-info-head" @click="editCropper()"><img :src="options.img" title="点击上传头像" class="img-circle img-lg" @error="handleAvatarError"></div>
|
||||
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
|
||||
<el-row>
|
||||
<el-col :xs="24" :md="12" :style="{height: '350px'}">
|
||||
<vue-cropper
|
||||
v-if="visible"
|
||||
ref="cropper"
|
||||
:img="options.img"
|
||||
:info="true"
|
||||
:auto-crop="options.autoCrop"
|
||||
:auto-crop-width="options.autoCropWidth"
|
||||
:auto-crop-height="options.autoCropHeight"
|
||||
:fixed-box="options.fixedBox"
|
||||
:output-type="options.outputType"
|
||||
@realTime="realTime"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :xs="24" :md="12" :style="{height: '350px'}">
|
||||
<div class="avatar-upload-preview">
|
||||
<img :src="previews.url" :style="previews.img">
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<br>
|
||||
<el-row>
|
||||
<el-col :lg="2" :sm="3" :xs="3">
|
||||
<el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
|
||||
<el-button size="small">
|
||||
选择
|
||||
<i class="el-icon-upload el-icon--right" />
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 2}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-plus" size="small" @click="changeScale(1)" />
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-minus" size="small" @click="changeScale(-1)" />
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-refresh-left" size="small" @click="rotateLeft()" />
|
||||
</el-col>
|
||||
<el-col :lg="{span: 1, offset: 1}" :sm="2" :xs="2">
|
||||
<el-button icon="el-icon-refresh-right" size="small" @click="rotateRight()" />
|
||||
</el-col>
|
||||
<el-col :lg="{span: 2, offset: 6}" :sm="2" :xs="2">
|
||||
<el-button type="primary" size="small" @click="uploadImg()">提 交</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { VueCropper } from 'vue-cropper'
|
||||
import request from '@/utils/request'
|
||||
import { debounce } from '@/utils'
|
||||
|
||||
// 默认头像
|
||||
const DEFAULT_AVATAR = require('@/assets/images/profile.jpg')
|
||||
|
||||
export default {
|
||||
components: { VueCropper },
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
// 是否显示cropper
|
||||
visible: false,
|
||||
// 弹出层标题
|
||||
title: '修改头像',
|
||||
options: {
|
||||
img: DEFAULT_AVATAR, // 裁剪图片的地址
|
||||
autoCrop: true, // 是否默认生成截图框
|
||||
autoCropWidth: 200, // 默认生成截图框宽度
|
||||
autoCropHeight: 200, // 默认生成截图框高度
|
||||
fixedBox: true, // 固定截图框大小 不允许改变
|
||||
outputType: 'png' // 默认生成截图为PNG格式
|
||||
},
|
||||
previews: {},
|
||||
resizeHandler: null,
|
||||
// 当前生效的头像地址(本地上传成功后优先于此值,避免被关闭弹窗重置)
|
||||
currentAvatar: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 跟随传入学员的头像地址变化
|
||||
'user.avatar': {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.currentAvatar = val || ''
|
||||
this.options.img = this.resolveAvatar(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 头像地址解析:相对路径补全 baseApi,空值使用默认头像
|
||||
// withStamp=true 时追加时间戳,强制浏览器重新加载,避免缓存导致图片不刷新
|
||||
resolveAvatar(avatar, withStamp) {
|
||||
if (!avatar) {
|
||||
return DEFAULT_AVATAR
|
||||
}
|
||||
if (avatar.indexOf('http') === 0 || avatar.indexOf('data:') === 0) {
|
||||
return avatar
|
||||
}
|
||||
let url = process.env.VUE_APP_BASE_API + avatar
|
||||
if (withStamp) {
|
||||
url += (url.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().getTime()
|
||||
}
|
||||
return url
|
||||
},
|
||||
handleAvatarError() {
|
||||
this.options.img = DEFAULT_AVATAR
|
||||
},
|
||||
// 编辑头像
|
||||
editCropper() {
|
||||
this.open = true
|
||||
},
|
||||
// 打开弹出层结束时的回调
|
||||
modalOpened() {
|
||||
this.visible = true
|
||||
if (!this.resizeHandler) {
|
||||
this.resizeHandler = debounce(() => {
|
||||
this.refresh()
|
||||
}, 100)
|
||||
}
|
||||
window.addEventListener('resize', this.resizeHandler)
|
||||
},
|
||||
// 刷新组件
|
||||
refresh() {
|
||||
this.$refs.cropper.refresh()
|
||||
},
|
||||
// 覆盖默认的上传行为
|
||||
requestUpload() {
|
||||
},
|
||||
// 向左旋转
|
||||
rotateLeft() {
|
||||
this.$refs.cropper.rotateLeft()
|
||||
},
|
||||
// 向右旋转
|
||||
rotateRight() {
|
||||
this.$refs.cropper.rotateRight()
|
||||
},
|
||||
// 图片缩放
|
||||
changeScale(num) {
|
||||
num = num || 1
|
||||
this.$refs.cropper.changeScale(num)
|
||||
},
|
||||
// 上传预处理
|
||||
beforeUpload(file) {
|
||||
if (file.type.indexOf('image/') === -1) {
|
||||
this.$modal.msgError('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。')
|
||||
} else {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(file)
|
||||
reader.onload = () => {
|
||||
this.options.img = reader.result
|
||||
}
|
||||
}
|
||||
},
|
||||
// 上传图片:走通用上传 /common/upload,只存文件不修改 sys_user.avatar
|
||||
uploadImg() {
|
||||
this.$refs.cropper.getCropBlob(data => {
|
||||
const formData = new FormData()
|
||||
// 使用带文件名的 File,保证后端能识别后缀
|
||||
const uploadFile = new File([data], 'avatar.png', { type: 'image/png' })
|
||||
formData.append('file', uploadFile)
|
||||
request({
|
||||
url: '/common/upload',
|
||||
method: 'post',
|
||||
data: formData,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}).then(response => {
|
||||
// 入库用相对路径,如 /profile/upload/2026/09/28/xxx.png
|
||||
const imgUrl = response.fileName || response.url
|
||||
// 记录当前生效头像,防止弹窗关闭时被旧值覆盖
|
||||
this.currentAvatar = imgUrl
|
||||
// 追加时间戳强制刷新,避免浏览器缓存
|
||||
this.options.img = this.resolveAvatar(imgUrl, true)
|
||||
// 先抛给父组件更新 user.avatar,再关闭弹窗
|
||||
this.$emit('success', imgUrl)
|
||||
this.$modal.msgSuccess('上传成功')
|
||||
this.open = false
|
||||
this.visible = false
|
||||
})
|
||||
})
|
||||
},
|
||||
// 实时预览
|
||||
realTime(data) {
|
||||
this.previews = data
|
||||
},
|
||||
// 关闭窗口:优先使用本地已上传的头像,其次取父组件传入的 user.avatar
|
||||
closeDialog() {
|
||||
this.options.img = this.resolveAvatar(this.currentAvatar || (this.user && this.user.avatar))
|
||||
this.visible = false
|
||||
window.removeEventListener('resize', this.resizeHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.user-info-head {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.user-info-head:hover:after {
|
||||
content: '+';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
color: #eee;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
cursor: pointer;
|
||||
line-height: 110px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
@@ -10,12 +10,13 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input
|
||||
<!-- <el-input
|
||||
v-model="queryParams.orgName"
|
||||
placeholder="请输入所属单位"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
/>-->
|
||||
<treeselect style="width: 210px;" v-model="queryParams.orgName" :options="deptOptions" :normalizer="normalizer" :show-count="true" placeholder="请选择归属部门" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务职称" prop="jobTitle">
|
||||
<el-input
|
||||
@@ -25,14 +26,30 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="tPhone">
|
||||
<el-form-item label="专业方向" prop="major">
|
||||
<el-input
|
||||
v-model="queryParams.major"
|
||||
placeholder="请输入专业方向"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="主讲课程" prop="courses">
|
||||
<el-input
|
||||
v-model="queryParams.courses"
|
||||
placeholder="请输入主讲课程"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- <el-form-item label="联系电话" prop="tPhone">
|
||||
<el-input
|
||||
v-model="queryParams.tPhone"
|
||||
placeholder="请输入联系电话"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form-item>-->
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="教师状态" clearable>
|
||||
<el-option label="正常" :value="1" />
|
||||
@@ -72,21 +89,37 @@
|
||||
|
||||
<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" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-image
|
||||
:src="avatarUrl(scope.row.avatar)"
|
||||
:preview-src-list="[avatarUrl(scope.row.avatar)]"
|
||||
fit="cover"
|
||||
class="teacher-avatar"
|
||||
>
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline" />
|
||||
</div>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="教师姓名" align="center" prop="tName" min-width="120" />
|
||||
<el-table-column label="性别" align="center" prop="tGender" width="80">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatGender(scope.row.tGender) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出生日期" align="center" prop="tBirth" width="120">
|
||||
<!-- <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>-->
|
||||
<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="major" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="主讲课程" align="center" prop="courses" :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'">
|
||||
@@ -106,7 +139,7 @@
|
||||
<el-tag v-else type="info" size="mini">未关联</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<!-- <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="210">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
@@ -154,8 +187,11 @@
|
||||
/>
|
||||
|
||||
<!-- 添加或修改教师信息对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="550px" append-to-body>
|
||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="90px">
|
||||
<el-form-item label="教师头像" prop="avatar">
|
||||
<studentAvatar :user="user" @success="handleAvatarSuccess" />
|
||||
</el-form-item>
|
||||
<el-form-item label="教师姓名" prop="tName">
|
||||
<el-input v-model="form.tName" placeholder="请输入教师姓名" maxlength="50" show-word-limit />
|
||||
</el-form-item>
|
||||
@@ -175,11 +211,27 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属单位" prop="orgName">
|
||||
<el-input v-model="form.orgName" placeholder="请输入所属单位/部门" maxlength="100" />
|
||||
<!-- <el-input v-model="form.orgName" placeholder="请输入所属单位/部门" maxlength="100" />-->
|
||||
<treeselect v-model="form.orgName" :options="deptOptions" :normalizer="normalizer" :show-count="true" placeholder="请选择所属单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务/职称" prop="jobTitle">
|
||||
<el-input v-model="form.jobTitle" placeholder="请输入职务/职称" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业方向" prop="major">
|
||||
<el-input v-model="form.major" placeholder="请输入专业方向" maxlength="100" />
|
||||
</el-form-item>
|
||||
<el-form-item label="主讲课程" prop="courses">
|
||||
<el-input v-model="form.courses" placeholder="请输入主讲课程,多门课程可用逗号分隔" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="从教时间" prop="startTeaching">
|
||||
<el-date-picker
|
||||
v-model="form.startTeaching"
|
||||
clearable
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="请选择从教时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="tPhone">
|
||||
<el-input v-model="form.tPhone" placeholder="请输入11位手机号码" maxlength="20" />
|
||||
</el-form-item>
|
||||
@@ -266,9 +318,15 @@
|
||||
|
||||
<script>
|
||||
import { listTeacher, getTeacher, delTeacher, addTeacher, updateTeacher, getTeacherBindableUsers, bindTeacherUser, unbindTeacherUser } from '@/api/train/teacher'
|
||||
|
||||
import {deptTreeSelect} from "@/api/system/user";
|
||||
import Treeselect from "@riophae/vue-treeselect";
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
|
||||
import {listDept} from "@/api/system/dept";
|
||||
// 复用学员头像上传组件(通用:props.user 传入 {avatar},上传成功后抛出 success 事件)
|
||||
import studentAvatar from '@/views/train/student/studentAvatar'
|
||||
export default {
|
||||
name: 'Teacher',
|
||||
components: { Treeselect, studentAvatar },
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
@@ -305,7 +363,11 @@ export default {
|
||||
tPhone: null,
|
||||
status: null
|
||||
},
|
||||
// 表单参数
|
||||
// 部门树选项
|
||||
deptOptions: undefined,
|
||||
deptArr: {},
|
||||
// 头像上传组件用的教师对象(avatar 为入库的相对地址)
|
||||
user: { avatar: '' },
|
||||
form: {},
|
||||
// 表单校验
|
||||
rules: {
|
||||
@@ -318,12 +380,20 @@ export default {
|
||||
],
|
||||
tEmail: [
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
|
||||
],
|
||||
major: [
|
||||
{ max: 100, message: '专业方向长度不能超过100个字符', trigger: 'blur' }
|
||||
],
|
||||
courses: [
|
||||
{ max: 200, message: '主讲课程长度不能超过200个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.mapDept()
|
||||
this.getList()
|
||||
this.getDeptTree()
|
||||
},
|
||||
methods: {
|
||||
/** 查询教师信息列表 */
|
||||
@@ -332,9 +402,52 @@ export default {
|
||||
listTeacher(this.queryParams).then(response => {
|
||||
this.teacherList = response.rows
|
||||
this.total = response.total
|
||||
this.teacherList.forEach(stud => {
|
||||
let studOrgName = this.formatOrgName(stud.orgName);
|
||||
//console.log(stud.orgName +"--"+ studOrgName);
|
||||
this.$set(stud, "orgName", studOrgName);
|
||||
});
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
/** 查询部门下拉树结构 */
|
||||
getDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data;
|
||||
});
|
||||
},
|
||||
// 筛选节点
|
||||
filterNode(value, data) {
|
||||
if (!value) return true;
|
||||
return data.label.indexOf(value) !== -1;
|
||||
},
|
||||
// 节点单击事件
|
||||
handleNodeClick(data) {
|
||||
this.createForm.orgName = data.id;
|
||||
this.handleQuery();
|
||||
},
|
||||
// 数据格式化函数,把后端返回部门树形转成treeselect需要格式
|
||||
normalizer(node) {
|
||||
return {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
children: node.children
|
||||
}
|
||||
},
|
||||
mapDept() {
|
||||
listDept().then(response => {
|
||||
let deptList = response.data || [];
|
||||
let deptArr = {};
|
||||
deptList.forEach(dept => {
|
||||
if (dept && dept.deptId !== undefined && dept.deptId !== null) {
|
||||
deptArr[dept.deptId] = dept.deptName;
|
||||
}
|
||||
});
|
||||
this.deptArr = deptArr;
|
||||
});
|
||||
},
|
||||
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false
|
||||
@@ -344,18 +457,46 @@ export default {
|
||||
reset() {
|
||||
this.form = {
|
||||
tId: null,
|
||||
avatar: null,
|
||||
tName: null,
|
||||
tGender: '男',
|
||||
tBirth: null,
|
||||
orgName: null,
|
||||
jobTitle: null,
|
||||
major: null,
|
||||
courses: null,
|
||||
startTeaching: null,
|
||||
tPhone: null,
|
||||
tEmail: null,
|
||||
status: 1,
|
||||
remark: null
|
||||
}
|
||||
// 重置头像组件预览
|
||||
this.user = { avatar: '' }
|
||||
this.resetForm('form')
|
||||
},
|
||||
/** 头像上传成功回调:相对地址写入表单,随教师信息一并提交 */
|
||||
handleAvatarSuccess(imgUrl) {
|
||||
if (!imgUrl) {
|
||||
return
|
||||
}
|
||||
this.$set(this.form, 'avatar', imgUrl)
|
||||
// 同步更新头像组件的预览
|
||||
this.user = Object.assign({}, this.user, { avatar: imgUrl })
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate('avatar')
|
||||
}
|
||||
},
|
||||
/** 头像地址解析:补全 baseApi,空值使用默认头像 */
|
||||
avatarUrl(avatar) {
|
||||
if (!avatar) {
|
||||
return require('@/assets/images/profile.jpg')
|
||||
}
|
||||
if (avatar.indexOf('http') === 0 || avatar.indexOf('data:') === 0) {
|
||||
return avatar
|
||||
}
|
||||
return process.env.VUE_APP_BASE_API + avatar
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
@@ -383,6 +524,8 @@ export default {
|
||||
const tId = row.tId || this.ids[0]
|
||||
getTeacher(tId).then(response => {
|
||||
this.form = response.data
|
||||
// 回填头像预览
|
||||
this.user = { avatar: (this.form && this.form.avatar) || '' }
|
||||
this.open = true
|
||||
this.title = '修改教师信息'
|
||||
})
|
||||
@@ -472,6 +615,17 @@ export default {
|
||||
this.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
},
|
||||
// 所属单位:orgName 为数字或 deptArr 中存在映射时取 deptArr 对应值,否则显示 orgName 本身
|
||||
formatOrgName(orgName) {
|
||||
if (orgName === null || orgName === undefined || orgName === '') return '-'
|
||||
const map = this.deptArr || {}
|
||||
const mapped = map[orgName]
|
||||
const isNumber = /^\d+$/.test(String(orgName))
|
||||
if (isNumber || mapped !== undefined) {
|
||||
return (mapped !== undefined && mapped !== null && mapped !== '') ? mapped : orgName
|
||||
}
|
||||
return orgName
|
||||
},
|
||||
formatGender(gender) {
|
||||
if (gender === '0' || gender === 0 || gender === '男') return '男'
|
||||
if (gender === '1' || gender === 1 || gender === '女') return '女'
|
||||
@@ -480,3 +634,23 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.teacher-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.teacher-avatar .image-slot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #c0c4cc;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user