心理健康2.0
This commit is contained in:
1 parent
067957256d
commit
3b492479b5
63 files changed
+10278
-642
No files matched your search
@@ -93,6 +93,12 @@
|
||||
<artifactId>ruoyi-xg</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 系统模块 - 用于访问用户信息 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package org.dromara.stgl.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.stgl.domain.StglAnnualReviewAudit;
|
||||
import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo;
|
||||
import org.dromara.stgl.service.IStglAnnualReviewAuditService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团年审审批
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/stgl/annualReviewAudit")
|
||||
public class StglAnnualReviewAuditController extends BaseController {
|
||||
|
||||
private final IStglAnnualReviewAuditService stglAnnualReviewAuditService;
|
||||
|
||||
/**
|
||||
* 查询社团年审审批详细
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<StglAnnualReviewAudit> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(stglAnnualReviewAuditService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:query")
|
||||
@GetMapping("/byAnnualReview/{annualReviewId}")
|
||||
public R<List<StglAnnualReviewAudit>> getByAnnualReviewId(@NotNull(message = "年审记录ID不能为空")
|
||||
@PathVariable Long annualReviewId) {
|
||||
return R.ok(stglAnnualReviewAuditService.queryByAnnualReviewId(annualReviewId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:query")
|
||||
@GetMapping("/pending/{approverId}")
|
||||
public R<List<StglAnnualReviewAudit>> getPendingByApproverId(@NotNull(message = "审批人ID不能为空")
|
||||
@PathVariable Long approverId) {
|
||||
return R.ok(stglAnnualReviewAuditService.queryPendingByApproverId(approverId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团年审审批
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:add")
|
||||
@Log(title = "社团年审审批", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody StglAnnualReviewAudit stglAnnualReviewAudit) {
|
||||
return toAjax(stglAnnualReviewAuditService.insertByBo(stglAnnualReviewAudit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团年审审批
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:edit")
|
||||
@Log(title = "社团年审审批", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody StglAnnualReviewAudit stglAnnualReviewAudit) {
|
||||
return toAjax(stglAnnualReviewAuditService.updateByBo(stglAnnualReviewAudit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除社团年审审批
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:remove")
|
||||
@Log(title = "社团年审审批", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(stglAnnualReviewAuditService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批年审记录
|
||||
*/
|
||||
@SaCheckPermission("stgl:annualReviewAudit:approve")
|
||||
@Log(title = "社团年审审批", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/approve")
|
||||
public R<Void> approve(@Validated @RequestBody StglAnnualReviewApprovalBo approvalBo) {
|
||||
return toAjax(stglAnnualReviewAuditService.approveAnnualReview(approvalBo));
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package org.dromara.stgl.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.stgl.domain.vo.StglAuditorVo;
|
||||
import org.dromara.stgl.domain.bo.StglAuditorBo;
|
||||
import org.dromara.stgl.service.IStglAuditorService;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/stgl/auditor")
|
||||
public class StglAuditorController extends BaseController {
|
||||
|
||||
private final IStglAuditorService stglAuditorService;
|
||||
private final ISysUserService sysUserService;
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<StglAuditorVo> list(StglAuditorBo bo, PageQuery pageQuery) {
|
||||
return stglAuditorService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出社团管理审核人配置列表
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:export")
|
||||
@Log(title = "社团管理审核人配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(StglAuditorBo bo, HttpServletResponse response) {
|
||||
List<StglAuditorVo> list = stglAuditorService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "社团管理审核人配置", StglAuditorVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取社团管理审核人配置详细信息
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<StglAuditorVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(stglAuditorService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团管理审核人配置
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:add")
|
||||
@Log(title = "社团管理审核人配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody StglAuditorBo bo) {
|
||||
return toAjax(stglAuditorService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团管理审核人配置
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:edit")
|
||||
@Log(title = "社团管理审核人配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody StglAuditorBo bo) {
|
||||
return toAjax(stglAuditorService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除社团管理审核人配置
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:remove")
|
||||
@Log(title = "社团管理审核人配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(stglAuditorService.deleteWithValidByIds(List.of(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加审核人
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:add")
|
||||
@Log(title = "批量添加审核人", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/batchAdd")
|
||||
public R<Void> batchAdd(@RequestBody BatchAddRequest request) {
|
||||
return toAjax(stglAuditorService.batchAddAuditors(request.getUserIds(), request.getRemark()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的审核人列表
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:list")
|
||||
@GetMapping("/myConfig")
|
||||
public TableDataInfo<StglAuditorVo> myConfigList(StglAuditorBo bo, PageQuery pageQuery) {
|
||||
return stglAuditorService.queryMyConfigList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:edit")
|
||||
@Log(title = "启用审核人", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/enable")
|
||||
public R<Void> enable(@RequestBody List<Long> ids) {
|
||||
return toAjax(stglAuditorService.enableAuditors(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:edit")
|
||||
@Log(title = "停用审核人", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/disable")
|
||||
public R<Void> disable(@RequestBody List<Long> ids) {
|
||||
return toAjax(stglAuditorService.disableAuditors(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
*/
|
||||
@SaCheckPermission("stgl:auditor:list")
|
||||
@GetMapping("/users/{deptId}")
|
||||
public R<List<SysUserVo>> listUserByDeptId(@PathVariable String deptId) {
|
||||
List<SysUserVo> users = sysUserService.selectUserListByDept(Long.valueOf(deptId));
|
||||
return R.ok(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加请求对象
|
||||
*/
|
||||
public static class BatchAddRequest {
|
||||
private List<Long> userIds;
|
||||
private String remark;
|
||||
|
||||
public List<Long> getUserIds() {
|
||||
return userIds;
|
||||
}
|
||||
|
||||
public void setUserIds(List<Long> userIds) {
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package org.dromara.stgl.domain;
|
||||
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 社团年审审批对象 stgl_annual_review_audit
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("stgl_annual_review_audit")
|
||||
public class StglAnnualReviewAudit extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
private Long annualReviewId;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/college_approve/youth_league_approve)
|
||||
*/
|
||||
private String nodeCode;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/学院审批/校团委审批)
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2,3)
|
||||
*/
|
||||
private BigDecimal nodeOrder;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
@TableField("audit_status")
|
||||
private String nodeStatus;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
private Long approverId;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
private String approverName;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
private String approveResult;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
@TableField("audit_opinion")
|
||||
private String approveOpinion;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
@TableField("audit_time")
|
||||
private Date approveTime;
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package org.dromara.stgl.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置对象 stgl_auditor
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("stgl_auditor")
|
||||
public class StglAuditor extends TenantEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
private String configByName;
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.dromara.stgl.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 社团管理用户和部门完整信息
|
||||
* 用于批量添加审核人时获取完整的用户数据
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
public class StglUserWithDeptInfo {
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 用户账号 */
|
||||
private String userName;
|
||||
|
||||
/** 用户昵称 */
|
||||
private String nickName;
|
||||
|
||||
/** 部门ID */
|
||||
private Long deptId;
|
||||
|
||||
/** 部门名称 */
|
||||
private String deptName;
|
||||
|
||||
/** 手机号码 */
|
||||
private String phonenumber;
|
||||
|
||||
/** 用户邮箱 */
|
||||
private String email;
|
||||
|
||||
/** 帐号状态(1正常 0停用) */
|
||||
private String status;
|
||||
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.dromara.stgl.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团年审审批请求业务对象
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
public class StglAnnualReviewApprovalBo {
|
||||
|
||||
/**
|
||||
* 审批记录ID
|
||||
*/
|
||||
@NotNull(message = "审批记录ID不能为空")
|
||||
private Long auditId;
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
@NotBlank(message = "审批结果不能为空")
|
||||
private String approveResult;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
private String approveOpinion;
|
||||
|
||||
/**
|
||||
* 下一步审核人列表
|
||||
*/
|
||||
private List<NextAuditor> nextAuditors;
|
||||
|
||||
/**
|
||||
* 下一步审核人信息
|
||||
*/
|
||||
@Data
|
||||
public static class NextAuditor {
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
private String nickName;
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package org.dromara.stgl.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.stgl.domain.StglAuditor;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置业务对象 stgl_auditor
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = StglAuditor.class, reverseConvertGenerate = false)
|
||||
public class StglAuditorBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
@NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@NotBlank(message = "用户昵称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@NotNull(message = "部门ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@NotBlank(message = "部门名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
@NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
private String configByName;
|
||||
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 搜索值
|
||||
*/
|
||||
private String searchValue;
|
||||
|
||||
/**
|
||||
* 创建时间范围
|
||||
*/
|
||||
private List<Date> createTimeRange;
|
||||
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
package org.dromara.stgl.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.stgl.domain.StglAuditor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置视图对象 stgl_auditor
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = StglAuditor.class)
|
||||
public class StglAuditorVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ExcelProperty(value = "主键ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@ExcelProperty(value = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
@ExcelProperty(value = "用户姓名")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户昵称")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@ExcelProperty(value = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@ExcelProperty(value = "部门名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@ExcelProperty(value = "联系电话")
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
@ExcelProperty(value = "邮箱地址")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_normal_disable")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
@ExcelProperty(value = "备注说明")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
@ExcelProperty(value = "配置人")
|
||||
private String configByName;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private Long createDept;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ExcelProperty(value = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者ID
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ExcelProperty(value = "更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package org.dromara.stgl.mapper;
|
||||
|
||||
import org.dromara.stgl.domain.StglAnnualReviewAudit;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团年审审批Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface StglAnnualReviewAuditMapper extends BaseMapperPlus<StglAnnualReviewAudit, StglAnnualReviewAudit> {
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
*
|
||||
* @param annualReviewId 年审记录ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> selectByAnnualReviewId(Long annualReviewId);
|
||||
|
||||
/**
|
||||
* 根据年审记录ID和节点代码查询审批记录
|
||||
*
|
||||
* @param annualReviewId 年审记录ID
|
||||
* @param nodeCode 节点代码
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> selectByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode);
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*
|
||||
* @param approverId 审批人ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> selectPendingByApproverId(Long approverId);
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.dromara.stgl.mapper;
|
||||
|
||||
import org.dromara.stgl.domain.StglAuditor;
|
||||
import org.dromara.stgl.domain.vo.StglAuditorVo;
|
||||
import org.dromara.stgl.domain.StglUserWithDeptInfo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface StglAuditorMapper extends BaseMapperPlus<StglAuditor, StglAuditorVo> {
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置
|
||||
*
|
||||
* @param id 社团管理审核人配置主键
|
||||
* @return 社团管理审核人配置
|
||||
*/
|
||||
StglAuditorVo selectStglAuditorVoById(Long id);
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*
|
||||
* @param stglAuditor 社团管理审核人配置
|
||||
* @return 社团管理审核人配置集合
|
||||
*/
|
||||
List<StglAuditorVo> selectStglAuditorVoList(StglAuditor stglAuditor);
|
||||
|
||||
/**
|
||||
* 根据用户ID检查是否已存在审核人配置
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 记录数
|
||||
*/
|
||||
int checkUserExists(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
*
|
||||
* @param ids 审核人ID列表
|
||||
* @return 影响行数
|
||||
*/
|
||||
int enableAuditorsByIds(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
*
|
||||
* @param ids 审核人ID列表
|
||||
* @return 影响行数
|
||||
*/
|
||||
int disableAuditorsByIds(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 查询完整的用户信息(包含部门信息)用于批量添加
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @return 用户和部门完整信息列表
|
||||
*/
|
||||
List<StglUserWithDeptInfo> selectUsersWithDeptInfo(@Param("userIds") List<Long> userIds);
|
||||
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package org.dromara.stgl.service;
|
||||
|
||||
import org.dromara.stgl.domain.StglAnnualReviewAudit;
|
||||
import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团年审审批Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface IStglAnnualReviewAuditService {
|
||||
|
||||
/**
|
||||
* 查询社团年审审批
|
||||
*
|
||||
* @param id 社团年审审批主键
|
||||
* @return 社团年审审批
|
||||
*/
|
||||
StglAnnualReviewAudit queryById(Long id);
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
*
|
||||
* @param annualReviewId 年审记录ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> queryByAnnualReviewId(Long annualReviewId);
|
||||
|
||||
/**
|
||||
* 根据年审记录ID和节点代码查询审批记录
|
||||
*
|
||||
* @param annualReviewId 年审记录ID
|
||||
* @param nodeCode 节点代码
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> queryByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode);
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*
|
||||
* @param approverId 审批人ID
|
||||
* @return 审批记录列表
|
||||
*/
|
||||
List<StglAnnualReviewAudit> queryPendingByApproverId(Long approverId);
|
||||
|
||||
/**
|
||||
* 新增社团年审审批
|
||||
*
|
||||
* @param stglAnnualReviewAudit 社团年审审批
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean insertByBo(StglAnnualReviewAudit stglAnnualReviewAudit);
|
||||
|
||||
/**
|
||||
* 修改社团年审审批
|
||||
*
|
||||
* @param stglAnnualReviewAudit 社团年审审批
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean updateByBo(StglAnnualReviewAudit stglAnnualReviewAudit);
|
||||
|
||||
/**
|
||||
* 校验并批量删除社团年审审批信息
|
||||
*
|
||||
* @param ids 需要删除的社团年审审批主键集合
|
||||
* @param isValid 是否校验,true-删除前校验,false-不校验
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 创建指导老师审批记录
|
||||
*
|
||||
* @param annualReviewId 年审记录ID
|
||||
* @param advisorIds 指导老师ID列表
|
||||
* @param advisorNames 指导老师姓名列表
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean createAdvisorApprovalRecords(Long annualReviewId, List<Long> advisorIds, List<String> advisorNames);
|
||||
|
||||
/**
|
||||
* 审批年审记录
|
||||
*
|
||||
* @param auditId 审批记录ID
|
||||
* @param approveResult 审批结果
|
||||
* @param approveOpinion 审批意见
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean approveAnnualReview(Long auditId, String approveResult, String approveOpinion);
|
||||
|
||||
/**
|
||||
* 审批年审记录(支持动态审核人选择)
|
||||
*
|
||||
* @param approvalBo 审批请求对象
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean approveAnnualReview(StglAnnualReviewApprovalBo approvalBo);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.dromara.stgl.service;
|
||||
|
||||
import org.dromara.stgl.domain.vo.StglAuditorVo;
|
||||
import org.dromara.stgl.domain.bo.StglAuditorBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface IStglAuditorService {
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 社团管理审核人配置
|
||||
*/
|
||||
StglAuditorVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 社团管理审核人配置集合
|
||||
*/
|
||||
TableDataInfo<StglAuditorVo> queryPageList(StglAuditorBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 社团管理审核人配置集合
|
||||
*/
|
||||
List<StglAuditorVo> queryList(StglAuditorBo bo);
|
||||
|
||||
/**
|
||||
* 新增社团管理审核人配置
|
||||
*
|
||||
* @param bo 社团管理审核人配置
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean insertByBo(StglAuditorBo bo);
|
||||
|
||||
/**
|
||||
* 修改社团管理审核人配置
|
||||
*
|
||||
* @param bo 社团管理审核人配置
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean updateByBo(StglAuditorBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除社团管理审核人配置信息
|
||||
*
|
||||
* @param ids 需要删除的主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量添加审核人
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean batchAddAuditors(List<Long> userIds, String remark);
|
||||
|
||||
/**
|
||||
* 查询我配置的审核人列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 审核人列表
|
||||
*/
|
||||
TableDataInfo<StglAuditorVo> queryMyConfigList(StglAuditorBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
*
|
||||
* @param ids 审核人ID列表
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean enableAuditors(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
*
|
||||
* @param ids 审核人ID列表
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean disableAuditors(List<Long> ids);
|
||||
|
||||
}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
package org.dromara.stgl.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.stgl.domain.StglAnnualReviewAudit;
|
||||
import org.dromara.stgl.domain.StglClubAnnualReview;
|
||||
import org.dromara.stgl.domain.bo.StglAnnualReviewApprovalBo;
|
||||
import org.dromara.stgl.mapper.StglAnnualReviewAuditMapper;
|
||||
import org.dromara.stgl.mapper.StglClubAnnualReviewMapper;
|
||||
import org.dromara.stgl.service.IStglAnnualReviewAuditService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 社团年审审批Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class StglAnnualReviewAuditServiceImpl implements IStglAnnualReviewAuditService {
|
||||
|
||||
private final StglAnnualReviewAuditMapper baseMapper;
|
||||
private final StglClubAnnualReviewMapper annualReviewMapper;
|
||||
|
||||
/**
|
||||
* 查询社团年审审批
|
||||
*/
|
||||
@Override
|
||||
public StglAnnualReviewAudit queryById(Long id) {
|
||||
return baseMapper.selectById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglAnnualReviewAudit> queryByAnnualReviewId(Long annualReviewId) {
|
||||
return baseMapper.selectByAnnualReviewId(annualReviewId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据年审记录ID和节点代码查询审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglAnnualReviewAudit> queryByAnnualReviewIdAndNodeCode(Long annualReviewId, String nodeCode) {
|
||||
return baseMapper.selectByAnnualReviewIdAndNodeCode(annualReviewId, nodeCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
*/
|
||||
@Override
|
||||
public List<StglAnnualReviewAudit> queryPendingByApproverId(Long approverId) {
|
||||
return baseMapper.selectPendingByApproverId(approverId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团年审审批
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(StglAnnualReviewAudit bo) {
|
||||
StglAnnualReviewAudit add = BeanUtil.toBean(bo, StglAnnualReviewAudit.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团年审审批
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(StglAnnualReviewAudit bo) {
|
||||
StglAnnualReviewAudit update = BeanUtil.toBean(bo, StglAnnualReviewAudit.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(StglAnnualReviewAudit entity) {
|
||||
// TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除社团年审审批
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指导老师审批记录
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean createAdvisorApprovalRecords(Long annualReviewId, List<Long> advisorIds, List<String> advisorNames) {
|
||||
if (ObjectUtil.isEmpty(advisorIds) || ObjectUtil.isEmpty(advisorNames) || advisorIds.size() != advisorNames.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除已存在的指导老师审批记录
|
||||
LambdaQueryWrapper<StglAnnualReviewAudit> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(StglAnnualReviewAudit::getAnnualReviewId, annualReviewId)
|
||||
.eq(StglAnnualReviewAudit::getNodeCode, "advisor_approve");
|
||||
baseMapper.delete(wrapper);
|
||||
|
||||
// 创建新的指导老师审批记录
|
||||
for (int i = 0; i < advisorIds.size(); i++) {
|
||||
StglAnnualReviewAudit audit = new StglAnnualReviewAudit();
|
||||
audit.setAnnualReviewId(annualReviewId);
|
||||
audit.setNodeCode("advisor_approve");
|
||||
audit.setNodeName("指导老师审批");
|
||||
|
||||
// 设置节点顺序:单个指导老师为1.0,多个指导老师为1.1, 1.2, 1.3...
|
||||
if (advisorIds.size() == 1) {
|
||||
audit.setNodeOrder(new BigDecimal("1.0"));
|
||||
} else {
|
||||
audit.setNodeOrder(new BigDecimal("1." + (i + 1)));
|
||||
}
|
||||
|
||||
// 设置节点状态:第一个为pending,其他为waiting
|
||||
audit.setNodeStatus(i == 0 ? "pending" : "waiting");
|
||||
audit.setApproverId(advisorIds.get(i));
|
||||
audit.setApproverName(advisorNames.get(i));
|
||||
|
||||
baseMapper.insert(audit);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批年审记录
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean approveAnnualReview(Long auditId, String approveResult, String approveOpinion) {
|
||||
// 1. 获取当前审批记录
|
||||
StglAnnualReviewAudit currentAudit = baseMapper.selectById(auditId);
|
||||
if (currentAudit == null || !"pending".equals(currentAudit.getNodeStatus())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 更新当前审批记录
|
||||
currentAudit.setNodeStatus("approved");
|
||||
currentAudit.setApproveResult(approveResult);
|
||||
currentAudit.setApproveOpinion(approveOpinion);
|
||||
currentAudit.setApproveTime(new Date());
|
||||
baseMapper.updateById(currentAudit);
|
||||
|
||||
// 3. 如果审批不通过,直接更新年审记录状态为4(审核不通过)
|
||||
if ("不通过".equals(approveResult)) {
|
||||
StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId());
|
||||
if (annualReview != null) {
|
||||
annualReview.setStatus("4");
|
||||
annualReviewMapper.updateById(annualReview);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 4. 如果审批通过,检查是否有下一个待审批的同环节审批人
|
||||
List<StglAnnualReviewAudit> samePhaseAudits = baseMapper.selectByAnnualReviewIdAndNodeCode(
|
||||
currentAudit.getAnnualReviewId(), currentAudit.getNodeCode());
|
||||
|
||||
// 查找下一个waiting状态的审批记录
|
||||
StglAnnualReviewAudit nextAudit = null;
|
||||
for (StglAnnualReviewAudit audit : samePhaseAudits) {
|
||||
if ("waiting".equals(audit.getNodeStatus()) &&
|
||||
audit.getNodeOrder().compareTo(currentAudit.getNodeOrder()) > 0) {
|
||||
if (nextAudit == null || audit.getNodeOrder().compareTo(nextAudit.getNodeOrder()) < 0) {
|
||||
nextAudit = audit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 如果有下一个审批人,将其状态改为pending
|
||||
if (nextAudit != null) {
|
||||
nextAudit.setNodeStatus("pending");
|
||||
baseMapper.updateById(nextAudit);
|
||||
} else {
|
||||
// 6. 如果同环节所有审批人都审批通过,检查是否是第一个审批节点,决定是否更新状态为2(审核中)
|
||||
boolean allApproved = samePhaseAudits.stream()
|
||||
.allMatch(audit -> "approved".equals(audit.getNodeStatus()) && "通过".equals(audit.getApproveResult()));
|
||||
|
||||
if (allApproved) {
|
||||
// 只有第一个审批节点(node_order=1.1)通过时才设置为2-审核中
|
||||
if (isFirstApprovalNode(currentAudit)) {
|
||||
StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId());
|
||||
if (annualReview != null) {
|
||||
annualReview.setStatus("2");
|
||||
annualReviewMapper.updateById(annualReview);
|
||||
log.info("第一个审批节点通过,年审记录状态更新为2-审核中,年审记录ID: {}", annualReview.getId());
|
||||
}
|
||||
} else {
|
||||
log.info("非第一个审批节点通过,保持当前状态,当前节点: {}, node_order: {}",
|
||||
currentAudit.getNodeName(), currentAudit.getNodeOrder());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批年审记录(支持动态审核人选择)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean approveAnnualReview(StglAnnualReviewApprovalBo approvalBo) {
|
||||
// 调用原有方法进行基础审批
|
||||
Boolean result = approveAnnualReview(approvalBo.getAuditId(),
|
||||
approvalBo.getApproveResult(),
|
||||
approvalBo.getApproveOpinion());
|
||||
|
||||
// 如果审批通过且有指定下一步审核人,创建新的审批记录
|
||||
if (result && "通过".equals(approvalBo.getApproveResult()) &&
|
||||
approvalBo.getNextAuditors() != null && !approvalBo.getNextAuditors().isEmpty()) {
|
||||
|
||||
// 获取当前审批记录
|
||||
StglAnnualReviewAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId());
|
||||
if (currentAudit != null) {
|
||||
// 创建下一步审核人的审批记录
|
||||
createNextStepAuditRecords(currentAudit.getAnnualReviewId(), approvalBo.getNextAuditors());
|
||||
}
|
||||
} else if (result && "通过".equals(approvalBo.getApproveResult()) &&
|
||||
(approvalBo.getNextAuditors() == null || approvalBo.getNextAuditors().isEmpty())) {
|
||||
|
||||
// 如果审批通过且没有下一步审核人,检查是否是校团委最后节点,决定是否结束流程
|
||||
StglAnnualReviewAudit currentAudit = baseMapper.selectById(approvalBo.getAuditId());
|
||||
if (currentAudit != null && isSchoolCommitteeLastNode(currentAudit)) {
|
||||
// 校团委最后节点审批通过,流程结束,将年审记录状态设置为3(审核通过)
|
||||
StglClubAnnualReview annualReview = annualReviewMapper.selectById(currentAudit.getAnnualReviewId());
|
||||
if (annualReview != null) {
|
||||
annualReview.setStatus("3");
|
||||
annualReviewMapper.updateById(annualReview);
|
||||
log.info("校团委最后节点审批通过,年审流程结束,年审记录ID: {}, 状态更新为: 3", annualReview.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建下一步审批记录
|
||||
*/
|
||||
private void createNextStepAuditRecords(Long annualReviewId, List<StglAnnualReviewApprovalBo.NextAuditor> nextAuditors) {
|
||||
// 查询已有的审批记录,确定下一个审批层级
|
||||
List<StglAnnualReviewAudit> existingAudits = baseMapper.selectByAnnualReviewId(annualReviewId);
|
||||
|
||||
// 确定下一个审批层级
|
||||
int nextLevel = determineNextApprovalLevel(existingAudits);
|
||||
|
||||
for (int i = 0; i < nextAuditors.size(); i++) {
|
||||
StglAnnualReviewApprovalBo.NextAuditor auditor = nextAuditors.get(i);
|
||||
|
||||
StglAnnualReviewAudit nextAudit = new StglAnnualReviewAudit();
|
||||
nextAudit.setAnnualReviewId(annualReviewId);
|
||||
nextAudit.setNodeCode(getNodeCodeByLevel(nextLevel));
|
||||
nextAudit.setNodeName(getNodeNameByLevel(nextLevel));
|
||||
|
||||
// 根据层级和顺序设置node_order
|
||||
if (nextAuditors.size() == 1 && nextLevel == 3) {
|
||||
// 校团委审批单个审核人为3.0
|
||||
nextAudit.setNodeOrder(new BigDecimal(nextLevel + ".0"));
|
||||
} else {
|
||||
// 其他情况按 层级.序号 格式设置
|
||||
nextAudit.setNodeOrder(new BigDecimal(nextLevel + "." + (i + 1)));
|
||||
}
|
||||
|
||||
nextAudit.setApproverId(auditor.getUserId());
|
||||
nextAudit.setApproverName(auditor.getNickName() != null ? auditor.getNickName() : auditor.getUserName());
|
||||
nextAudit.setNodeStatus(i == 0 ? "pending" : "waiting"); // 第一个为pending,其余为waiting
|
||||
nextAudit.setCreateTime(new Date());
|
||||
|
||||
baseMapper.insert(nextAudit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定下一个审批层级
|
||||
*/
|
||||
private int determineNextApprovalLevel(List<StglAnnualReviewAudit> existingAudits) {
|
||||
int maxLevel = 1; // 默认从指导老师审批层级开始
|
||||
|
||||
for (StglAnnualReviewAudit audit : existingAudits) {
|
||||
BigDecimal nodeOrder = audit.getNodeOrder();
|
||||
if (nodeOrder != null) {
|
||||
int level = nodeOrder.intValue(); // 获取整数部分作为层级
|
||||
if (level > maxLevel) {
|
||||
maxLevel = level;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxLevel + 1; // 下一个层级
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据层级获取节点代码
|
||||
*/
|
||||
private String getNodeCodeByLevel(int level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return "advisor_approve";
|
||||
case 2:
|
||||
return "college_approve";
|
||||
case 3:
|
||||
return "youth_league_approve";
|
||||
default:
|
||||
return "college_approve"; // 默认为二级学院审批
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据层级获取节点名称
|
||||
*/
|
||||
private String getNodeNameByLevel(int level) {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return "指导老师审批";
|
||||
case 2:
|
||||
return "二级学院审批";
|
||||
case 3:
|
||||
return "校团委审批";
|
||||
default:
|
||||
return "二级学院审批"; // 默认为二级学院审批
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为第一个审批节点(node_order=1.1)
|
||||
*/
|
||||
private boolean isFirstApprovalNode(StglAnnualReviewAudit currentAudit) {
|
||||
// 检查node_order是否为1.1
|
||||
return currentAudit != null &&
|
||||
currentAudit.getNodeOrder() != null &&
|
||||
currentAudit.getNodeOrder().compareTo(new BigDecimal("1.1")) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前是否是校团委审批的最后一个节点
|
||||
*/
|
||||
private boolean isSchoolCommitteeLastNode(StglAnnualReviewAudit currentAudit) {
|
||||
if (currentAudit == null || currentAudit.getNodeOrder() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String currentNodeName = currentAudit.getNodeName();
|
||||
BigDecimal currentNodeOrder = currentAudit.getNodeOrder();
|
||||
|
||||
// 检查是否是校团委审批节点且node_order是3.X格式
|
||||
boolean isSchoolCommitteeNode = "校团委审批".equals(currentNodeName);
|
||||
boolean isNodeOrder3X = currentNodeOrder.compareTo(new BigDecimal("3")) >= 0 &&
|
||||
currentNodeOrder.compareTo(new BigDecimal("4")) < 0;
|
||||
|
||||
if (!isSchoolCommitteeNode || !isNodeOrder3X) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取同一环节的所有节点
|
||||
List<StglAnnualReviewAudit> samePhaseNodes = baseMapper.selectByAnnualReviewIdAndNodeCode(
|
||||
currentAudit.getAnnualReviewId(), currentAudit.getNodeCode());
|
||||
|
||||
if (samePhaseNodes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找出同一环节中最大的nodeOrder
|
||||
BigDecimal maxNodeOrder = samePhaseNodes.stream()
|
||||
.map(StglAnnualReviewAudit::getNodeOrder)
|
||||
.filter(order -> order != null)
|
||||
.max(BigDecimal::compareTo)
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 当前节点的nodeOrder等于同环节最大nodeOrder时,表示是最后一个节点
|
||||
boolean isLastNode = currentNodeOrder.compareTo(maxNodeOrder) == 0;
|
||||
|
||||
log.info("校团委最后节点判断: 节点名称={}, nodeOrder={}, 是否校团委={}, 是否3.X={}, 是否最后节点={}",
|
||||
currentNodeName, currentNodeOrder, isSchoolCommitteeNode, isNodeOrder3X, isLastNode);
|
||||
|
||||
return isLastNode;
|
||||
}
|
||||
}
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
package org.dromara.stgl.service.impl;
|
||||
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.dromara.stgl.domain.bo.StglAuditorBo;
|
||||
import org.dromara.stgl.domain.vo.StglAuditorVo;
|
||||
import org.dromara.stgl.domain.StglAuditor;
|
||||
import org.dromara.stgl.domain.StglUserWithDeptInfo;
|
||||
import org.dromara.stgl.mapper.StglAuditorMapper;
|
||||
import org.dromara.stgl.service.IStglAuditorService;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 社团管理审核人配置Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class StglAuditorServiceImpl implements IStglAuditorService {
|
||||
|
||||
private final StglAuditorMapper baseMapper;
|
||||
private final ISysUserService sysUserService;
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置
|
||||
*/
|
||||
@Override
|
||||
public StglAuditorVo queryById(Long id) {
|
||||
StglAuditor entity = baseMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return MapstructUtils.convert(entity, StglAuditorVo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<StglAuditorVo> queryPageList(StglAuditorBo bo, PageQuery pageQuery) {
|
||||
// 直接使用查询条件构建器
|
||||
LambdaQueryWrapper<StglAuditor> lqw = buildQueryWrapper(bo);
|
||||
Page<StglAuditor> entityPage = baseMapper.selectPage(pageQuery.build(), lqw);
|
||||
|
||||
// 转换为 VO 对象
|
||||
List<StglAuditorVo> voList = new ArrayList<>();
|
||||
for (StglAuditor entity : entityPage.getRecords()) {
|
||||
StglAuditorVo vo = MapstructUtils.convert(entity, StglAuditorVo.class);
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
// 构建分页结果
|
||||
Page<StglAuditorVo> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
|
||||
voPage.setRecords(voList);
|
||||
|
||||
return TableDataInfo.build(voPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<StglAuditorVo> queryList(StglAuditorBo bo) {
|
||||
LambdaQueryWrapper<StglAuditor> lqw = buildQueryWrapper(bo);
|
||||
List<StglAuditor> entityList = baseMapper.selectList(lqw);
|
||||
|
||||
// 转换为 VO 对象
|
||||
List<StglAuditorVo> voList = new ArrayList<>();
|
||||
for (StglAuditor entity : entityList) {
|
||||
StglAuditorVo vo = MapstructUtils.convert(entity, StglAuditorVo.class);
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
return voList;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<StglAuditor> buildQueryWrapper(StglAuditorBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<StglAuditor> lqw = Wrappers.<StglAuditor>lambdaQuery();
|
||||
lqw.eq(bo.getUserId() != null, StglAuditor::getUserId, bo.getUserId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getUserName()), StglAuditor::getUserName, bo.getUserName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getNickName()), StglAuditor::getNickName, bo.getNickName());
|
||||
lqw.eq(bo.getDeptId() != null, StglAuditor::getDeptId, bo.getDeptId());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), StglAuditor::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getConfigBy() != null, StglAuditor::getConfigBy, bo.getConfigBy());
|
||||
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
|
||||
StglAuditor::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
|
||||
lqw.orderByDesc(StglAuditor::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团管理审核人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(StglAuditorBo bo) {
|
||||
StglAuditor add = MapstructUtils.convert(bo, StglAuditor.class);
|
||||
validEntityBeforeSave(add);
|
||||
|
||||
// 设置配置人信息
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
add.setConfigBy(currentUserId);
|
||||
|
||||
// 获取当前用户信息设置配置人姓名
|
||||
try {
|
||||
SysUserVo currentUser = sysUserService.selectUserById(currentUserId);
|
||||
if (currentUser != null) {
|
||||
add.setConfigByName(currentUser.getUserName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果获取失败,使用默认值
|
||||
add.setConfigByName("系统管理员");
|
||||
}
|
||||
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团管理审核人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(StglAuditorBo bo) {
|
||||
StglAuditor update = MapstructUtils.convert(bo, StglAuditor.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(StglAuditor entity) {
|
||||
if (entity.getUserId() != null) {
|
||||
// 检查用户是否已存在(新增时或修改用户ID时)
|
||||
if (entity.getId() == null) {
|
||||
// 新增时检查
|
||||
int count = baseMapper.checkUserExists(entity.getUserId());
|
||||
if (count > 0) {
|
||||
throw new ServiceException("该用户已配置为审核人,不能重复添加");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除社团管理审核人配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids) {
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加审核人
|
||||
*/
|
||||
@Override
|
||||
public Boolean batchAddAuditors(List<Long> userIds, String remark) {
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
throw new ServiceException("用户ID列表不能为空");
|
||||
}
|
||||
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
String currentUserName = "系统管理员";
|
||||
|
||||
// 获取当前用户信息
|
||||
try {
|
||||
SysUserVo currentUser = sysUserService.selectUserById(currentUserId);
|
||||
if (currentUser != null) {
|
||||
currentUserName = currentUser.getUserName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 使用默认值
|
||||
}
|
||||
|
||||
// 1. 获取完整的用户信息(包含部门信息)
|
||||
List<StglUserWithDeptInfo> users = baseMapper.selectUsersWithDeptInfo(userIds);
|
||||
if (users.isEmpty()) {
|
||||
throw new ServiceException("没有找到有效的用户信息");
|
||||
}
|
||||
|
||||
// 2. 过滤已存在的审核人
|
||||
List<Long> existingUserIds = baseMapper.selectList(Wrappers.<StglAuditor>lambdaQuery()
|
||||
.in(StglAuditor::getUserId, userIds))
|
||||
.stream().map(StglAuditor::getUserId).collect(Collectors.toList());
|
||||
|
||||
// 3. 构建新增审核人列表
|
||||
List<StglAuditor> auditorList = new ArrayList<>();
|
||||
for (StglUserWithDeptInfo user : users) {
|
||||
if (!existingUserIds.contains(user.getUserId())) {
|
||||
StglAuditor auditor = new StglAuditor();
|
||||
auditor.setUserId(user.getUserId());
|
||||
auditor.setUserName(user.getUserName());
|
||||
auditor.setNickName(user.getNickName());
|
||||
auditor.setDeptId(user.getDeptId());
|
||||
auditor.setDeptName(user.getDeptName() != null ? user.getDeptName() : "");
|
||||
auditor.setPhoneNumber(user.getPhonenumber());
|
||||
auditor.setEmail(user.getEmail());
|
||||
auditor.setStatus("1"); // 默认正常状态
|
||||
auditor.setRemark(remark);
|
||||
auditor.setConfigBy(currentUserId);
|
||||
auditor.setConfigByName(currentUserName);
|
||||
auditorList.add(auditor);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量保存
|
||||
if (auditorList.isEmpty()) {
|
||||
throw new ServiceException("所有用户都已存在,无需重复添加");
|
||||
}
|
||||
|
||||
return baseMapper.insertBatch(auditorList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的审核人列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<StglAuditorVo> queryMyConfigList(StglAuditorBo bo, PageQuery pageQuery) {
|
||||
bo.setConfigBy(LoginHelper.getUserId());
|
||||
return queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
*/
|
||||
@Override
|
||||
public Boolean enableAuditors(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.enableAuditorsByIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
*/
|
||||
@Override
|
||||
public Boolean disableAuditors(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.disableAuditorsByIds(ids) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
+77
-1
@@ -24,8 +24,10 @@ import org.dromara.stgl.mapper.StglClubAnnualReviewActivityMapper;
|
||||
import org.dromara.stgl.service.IStglClubAnnualReviewService;
|
||||
import org.dromara.stgl.service.IStglActivityService;
|
||||
import org.dromara.stgl.service.IStglClubService;
|
||||
import org.dromara.stgl.service.IStglAnnualReviewAuditService;
|
||||
import org.dromara.stgl.domain.bo.StglActivityBo;
|
||||
import org.dromara.stgl.domain.vo.StglClubVo;
|
||||
import org.dromara.stgl.domain.StglClub;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -60,6 +62,7 @@ public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewSer
|
||||
private final StglClubAnnualReviewActivityMapper activityMapper;
|
||||
private final IStglActivityService stglActivityService;
|
||||
private final IStglClubService stglClubService;
|
||||
private final IStglAnnualReviewAuditService auditService;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
@@ -539,9 +542,82 @@ public class StglClubAnnualReviewServiceImpl implements IStglClubAnnualReviewSer
|
||||
throw new RuntimeException("只能提交草稿状态的年审记录");
|
||||
}
|
||||
|
||||
// 获取社团信息,提取指导老师信息
|
||||
StglClubVo club = stglClubService.queryById(entity.getClubId());
|
||||
if (club == null) {
|
||||
throw new RuntimeException("社团信息不存在");
|
||||
}
|
||||
|
||||
// 提取指导老师信息
|
||||
List<Long> advisorIds = new ArrayList<>();
|
||||
List<String> advisorNames = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 优先从JSON字段读取指导老师数据
|
||||
if (StringUtils.isNotBlank(club.getAdvisorsJson())) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Map<String, Object>> advisors = mapper.readValue(club.getAdvisorsJson(),
|
||||
new com.fasterxml.jackson.core.type.TypeReference<List<Map<String, Object>>>() {});
|
||||
|
||||
for (Map<String, Object> advisor : advisors) {
|
||||
// 提取用户ID和姓名
|
||||
Object userIdObj = null;
|
||||
String advisorName = null;
|
||||
|
||||
// 尝试直接从advisor对象获取userId和name(兼容旧格式)
|
||||
if (advisor.containsKey("userId")) {
|
||||
userIdObj = advisor.get("userId");
|
||||
advisorName = (String) advisor.get("name");
|
||||
}
|
||||
// 尝试从teacher子对象获取userId和name(新格式)
|
||||
else if (advisor.containsKey("teacher")) {
|
||||
Object teacherObj = advisor.get("teacher");
|
||||
if (teacherObj instanceof Map) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> teacher = (Map<String, Object>) teacherObj;
|
||||
userIdObj = teacher.get("userId");
|
||||
advisorName = (String) teacher.get("nickName");
|
||||
if (StringUtils.isBlank(advisorName)) {
|
||||
advisorName = (String) teacher.get("userName");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (userIdObj != null && StringUtils.isNotBlank(advisorName)) {
|
||||
Long userId = Long.valueOf(userIdObj.toString());
|
||||
advisorIds.add(userId);
|
||||
advisorNames.add(advisorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有JSON数据,尝试从单一字段获取
|
||||
if (advisorIds.isEmpty() && StringUtils.isNotBlank(club.getAdvisorName())) {
|
||||
// 这种情况下无法获取用户ID,暂时跳过审批流程
|
||||
log.warn("社团 {} 的指导老师信息不完整,无法创建审批记录", club.getClubName());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解析指导老师信息失败", e);
|
||||
throw new RuntimeException("解析指导老师信息失败");
|
||||
}
|
||||
|
||||
// 更新年审记录状态
|
||||
entity.setStatus("1");
|
||||
entity.setReviewTime(new Date());
|
||||
return baseMapper.updateById(entity) > 0;
|
||||
boolean updateResult = baseMapper.updateById(entity) > 0;
|
||||
|
||||
// 如果有指导老师信息,创建审批记录
|
||||
if (!advisorIds.isEmpty() && updateResult) {
|
||||
try {
|
||||
auditService.createAdvisorApprovalRecords(id, advisorIds, advisorNames);
|
||||
} catch (Exception e) {
|
||||
log.error("创建审批记录失败", e);
|
||||
throw new RuntimeException("创建审批记录失败");
|
||||
}
|
||||
}
|
||||
|
||||
return updateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?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="org.dromara.stgl.mapper.StglAnnualReviewAuditMapper">
|
||||
|
||||
<resultMap type="org.dromara.stgl.domain.StglAnnualReviewAudit" id="StglAnnualReviewAuditResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="tenantId" column="tenant_id" />
|
||||
<result property="createDept" column="create_dept" />
|
||||
<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="deleted" column="deleted" />
|
||||
<result property="annualReviewId" column="annual_review_id" />
|
||||
<result property="nodeCode" column="node_code" />
|
||||
<result property="nodeName" column="node_name" />
|
||||
<result property="nodeOrder" column="node_order" />
|
||||
<result property="nodeStatus" column="audit_status" />
|
||||
<result property="approverId" column="approver_id" />
|
||||
<result property="approverName" column="approver_name" />
|
||||
<result property="approveResult" column="approve_result" />
|
||||
<result property="approveOpinion" column="audit_opinion" />
|
||||
<result property="approveTime" column="audit_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectStglAnnualReviewAuditVo">
|
||||
select id, tenant_id, create_dept, create_by, create_time, update_by, update_time, deleted,
|
||||
annual_review_id, node_code, node_name, node_order, audit_status,
|
||||
approver_id, approver_name, approve_result, audit_opinion, audit_time
|
||||
from stgl_annual_review_audit
|
||||
</sql>
|
||||
|
||||
<!-- 根据年审记录ID查询审批记录 -->
|
||||
<select id="selectByAnnualReviewId" parameterType="Long" resultMap="StglAnnualReviewAuditResult">
|
||||
<include refid="selectStglAnnualReviewAuditVo"/>
|
||||
where annual_review_id = #{annualReviewId} and deleted = 0
|
||||
order by node_order
|
||||
</select>
|
||||
|
||||
<!-- 根据年审记录ID和节点代码查询审批记录 -->
|
||||
<select id="selectByAnnualReviewIdAndNodeCode" resultMap="StglAnnualReviewAuditResult">
|
||||
<include refid="selectStglAnnualReviewAuditVo"/>
|
||||
where annual_review_id = #{annualReviewId} and node_code = #{nodeCode} and deleted = 0
|
||||
order by node_order
|
||||
</select>
|
||||
|
||||
<!-- 根据审批人ID查询待审批记录 -->
|
||||
<select id="selectPendingByApproverId" parameterType="Long" resultMap="StglAnnualReviewAuditResult">
|
||||
<include refid="selectStglAnnualReviewAuditVo"/>
|
||||
where approver_id = #{approverId} and audit_status = 'pending' and deleted = 0
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
<?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="org.dromara.stgl.mapper.StglAuditorMapper">
|
||||
|
||||
<resultMap type="org.dromara.stgl.domain.vo.StglAuditorVo" id="StglAuditorResult">
|
||||
<result property="id" column="id" />
|
||||
<result property="userId" column="user_id" />
|
||||
<result property="userName" column="user_name" />
|
||||
<result property="nickName" column="nick_name" />
|
||||
<result property="deptId" column="dept_id" />
|
||||
<result property="deptName" column="dept_name" />
|
||||
<result property="phoneNumber" column="phone_number" />
|
||||
<result property="email" column="email" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="configBy" column="config_by" />
|
||||
<result property="configByName" column="config_by_name" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectStglAuditorVo">
|
||||
SELECT
|
||||
sa.id,
|
||||
sa.user_id,
|
||||
sa.user_name,
|
||||
sa.nick_name,
|
||||
sa.dept_id,
|
||||
sa.dept_name,
|
||||
sa.phone_number,
|
||||
sa.email,
|
||||
sa.status,
|
||||
sa.remark,
|
||||
sa.config_by,
|
||||
sa.config_by_name,
|
||||
sa.create_by,
|
||||
sa.create_time,
|
||||
sa.update_by,
|
||||
sa.update_time
|
||||
FROM stgl_auditor sa
|
||||
</sql>
|
||||
|
||||
<select id="selectStglAuditorVoList" parameterType="org.dromara.stgl.domain.StglAuditor" resultMap="StglAuditorResult">
|
||||
<include refid="selectStglAuditorVo"/>
|
||||
<where>
|
||||
sa.deleted = 0
|
||||
<if test="userId != null">
|
||||
AND sa.user_id = #{userId}
|
||||
</if>
|
||||
<if test="userName != null and userName != ''">
|
||||
AND sa.user_name LIKE CONCAT('%', #{userName}, '%')
|
||||
</if>
|
||||
<if test="nickName != null and nickName != ''">
|
||||
AND sa.nick_name LIKE CONCAT('%', #{nickName}, '%')
|
||||
</if>
|
||||
<if test="deptId != null">
|
||||
AND sa.dept_id = #{deptId}
|
||||
</if>
|
||||
<if test="status != null and status != ''">
|
||||
AND sa.status = #{status}
|
||||
</if>
|
||||
<if test="configBy != null">
|
||||
AND sa.config_by = #{configBy}
|
||||
</if>
|
||||
<if test="params.beginCreateTime != null and params.beginCreateTime != ''">
|
||||
AND sa.create_time >= #{params.beginCreateTime}
|
||||
</if>
|
||||
<if test="params.endCreateTime != null and params.endCreateTime != ''">
|
||||
AND sa.create_time <= #{params.endCreateTime}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY sa.create_time DESC
|
||||
</select>
|
||||
|
||||
<select id="selectStglAuditorVoById" parameterType="Long" resultMap="StglAuditorResult">
|
||||
<include refid="selectStglAuditorVo"/>
|
||||
<where>
|
||||
sa.id = #{id} AND sa.deleted = 0
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<select id="checkUserExists" parameterType="Long" resultType="int">
|
||||
SELECT COUNT(1) FROM stgl_auditor
|
||||
WHERE user_id = #{userId} AND deleted = 0
|
||||
</select>
|
||||
|
||||
<update id="enableAuditorsByIds">
|
||||
UPDATE stgl_auditor
|
||||
SET status = '1', update_time = NOW()
|
||||
WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<update id="disableAuditorsByIds">
|
||||
UPDATE stgl_auditor
|
||||
SET status = '0', update_time = NOW()
|
||||
WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 查询完整的用户信息(包含部门信息)用于批量添加 -->
|
||||
<select id="selectUsersWithDeptInfo" resultType="org.dromara.stgl.domain.StglUserWithDeptInfo">
|
||||
select
|
||||
u.user_id as userId,
|
||||
u.user_name as userName,
|
||||
u.nick_name as nickName,
|
||||
u.dept_id as deptId,
|
||||
d.dept_name as deptName,
|
||||
u.phonenumber as phonenumber,
|
||||
u.email as email,
|
||||
u.status as status
|
||||
from sys_user u
|
||||
left join sys_dept d on u.dept_id = d.dept_id
|
||||
where u.status = '0'
|
||||
and u.user_id in
|
||||
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
|
||||
#{userId}
|
||||
</foreach>
|
||||
order by u.dept_id, u.user_name
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+85
-49
@@ -6,6 +6,7 @@ import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo;
|
||||
import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo;
|
||||
import org.dromara.xljk.domain.vo.TimeSlotDetailVo;
|
||||
import org.dromara.xljk.domain.vo.ScheduleWithStatusVo;
|
||||
import org.dromara.xljk.service.IXljkConsultationAppointmentService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@@ -161,52 +162,10 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
|
||||
// 从请求体中获取参数
|
||||
String status = requestBody.get("status").toString();
|
||||
Date actualVisitTime = null;
|
||||
Integer communicationDuration = null;
|
||||
String consultationContent = "";
|
||||
String counselorSuggestion = "";
|
||||
|
||||
// 处理actualVisitTime
|
||||
if (requestBody.get("actualVisitTime") != null) {
|
||||
try {
|
||||
String actualVisitTimeStr = requestBody.get("actualVisitTime").toString();
|
||||
log.info("解析实际到访时间字符串: {}", actualVisitTimeStr);
|
||||
|
||||
// 尝试多种日期格式
|
||||
SimpleDateFormat[] dateFormats = {
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"),
|
||||
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"),
|
||||
new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"),
|
||||
new SimpleDateFormat("yyyy-MM-dd")
|
||||
};
|
||||
|
||||
boolean parsed = false;
|
||||
for (SimpleDateFormat sdf : dateFormats) {
|
||||
try {
|
||||
actualVisitTime = sdf.parse(actualVisitTimeStr);
|
||||
parsed = true;
|
||||
log.info("成功解析实际到访时间: {}, 使用格式: {}", actualVisitTime, sdf.toPattern());
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
// 继续尝试下一个格式
|
||||
}
|
||||
}
|
||||
|
||||
if (!parsed) {
|
||||
log.error("无法解析实际到访时间: {}", actualVisitTimeStr);
|
||||
// 使用当前时间作为默认值
|
||||
actualVisitTime = new Date();
|
||||
log.info("使用当前时间作为默认值: {}", actualVisitTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("解析actualVisitTime失败", e);
|
||||
// 使用当前时间作为默认值
|
||||
actualVisitTime = new Date();
|
||||
log.info("使用当前时间作为默认值: {}", actualVisitTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理communicationDuration
|
||||
if (requestBody.get("communicationDuration") != null) {
|
||||
try {
|
||||
@@ -227,8 +186,47 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
counselorSuggestion = requestBody.get("counselorSuggestion").toString();
|
||||
}
|
||||
|
||||
boolean result = service.recordConsultation(id, actualVisitTime, communicationDuration,
|
||||
consultationContent, counselorSuggestion, status);
|
||||
// 处理心理病史相关字段
|
||||
Integer psychologicalHistory = null;
|
||||
String psychologicalHistoryDetail = "";
|
||||
String basicSituation = "";
|
||||
String majorLifeEvents = "";
|
||||
Integer needReferral = null;
|
||||
|
||||
if (requestBody.get("psychologicalHistory") != null) {
|
||||
try {
|
||||
psychologicalHistory = Integer.parseInt(requestBody.get("psychologicalHistory").toString());
|
||||
} catch (Exception e) {
|
||||
log.error("解析psychologicalHistory失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (requestBody.get("psychologicalHistoryDetail") != null) {
|
||||
psychologicalHistoryDetail = requestBody.get("psychologicalHistoryDetail").toString();
|
||||
}
|
||||
|
||||
if (requestBody.get("basicSituation") != null) {
|
||||
basicSituation = requestBody.get("basicSituation").toString();
|
||||
}
|
||||
|
||||
if (requestBody.get("majorLifeEvents") != null) {
|
||||
majorLifeEvents = requestBody.get("majorLifeEvents").toString();
|
||||
}
|
||||
|
||||
if (requestBody.get("needReferral") != null) {
|
||||
try {
|
||||
needReferral = Integer.parseInt(requestBody.get("needReferral").toString());
|
||||
} catch (Exception e) {
|
||||
log.error("解析needReferral失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("解析的心理病史相关字段: psychologicalHistory={}, psychologicalHistoryDetail={}, basicSituation={}, majorLifeEvents={}, needReferral={}",
|
||||
psychologicalHistory, psychologicalHistoryDetail, basicSituation, majorLifeEvents, needReferral);
|
||||
|
||||
boolean result = service.recordConsultation(id, communicationDuration,
|
||||
consultationContent, counselorSuggestion, status,
|
||||
psychologicalHistory, psychologicalHistoryDetail, basicSituation, majorLifeEvents, needReferral);
|
||||
if (result) {
|
||||
return R.ok(true);
|
||||
} else {
|
||||
@@ -284,7 +282,7 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
XSSFSheet sheet = workbook.createSheet("预约记录");
|
||||
|
||||
// 表头
|
||||
String[] headers = {"序号", "学生姓名", "学号", "院系", "专业", "班级", "咨询原因", "咨询师", "预约日期", "预约时段", "状态", "咨询结果", "实际到访时间", "沟通时长", "咨询内容", "咨询师建议"};
|
||||
String[] headers = {"序号", "学生姓名", "学号", "院系", "专业", "班级", "咨询主题", "咨询原因", "咨询师", "预约日期", "预约时段", "状态", "咨询结果", "沟通时长", "心理病史", "心理病史详情", "基本情况", "重大生活事件", "本次咨询过程", "是否需要转介", "本学期到访次数"};
|
||||
XSSFRow headerRow = sheet.createRow(0);
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
headerRow.createCell(i).setCellValue(headers[i]);
|
||||
@@ -303,6 +301,7 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
row.createCell(col++).setCellValue(vo.getCollegeName());
|
||||
row.createCell(col++).setCellValue(vo.getMajorName());
|
||||
row.createCell(col++).setCellValue(vo.getClassName());
|
||||
row.createCell(col++).setCellValue(vo.getConsultTheme() != null ? vo.getConsultTheme() : ""); // 咨询主题
|
||||
row.createCell(col++).setCellValue(vo.getReason());
|
||||
row.createCell(col++).setCellValue(vo.getCounselorName());
|
||||
row.createCell(col++).setCellValue(vo.getAppointmentDate() != null ? dateFormat.format(vo.getAppointmentDate()) : "");
|
||||
@@ -315,15 +314,43 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
case 1: status = "已到访"; break;
|
||||
case 2: status = "已取消"; break;
|
||||
case 3: status = "未到访"; break;
|
||||
case 4: status = "待审核"; break;
|
||||
case 5: status = "已驳回"; break;
|
||||
default: status = "未知状态";
|
||||
}
|
||||
}
|
||||
row.createCell(col++).setCellValue(status);
|
||||
row.createCell(col++).setCellValue(status); // 咨询结果
|
||||
row.createCell(col++).setCellValue(vo.getActualVisitTime() != null ? dateTimeFormat.format(vo.getActualVisitTime()) : "");
|
||||
row.createCell(col++).setCellValue(vo.getCommunicationDuration() != null ? vo.getCommunicationDuration().toString() : "");
|
||||
row.createCell(col++).setCellValue(vo.getConsultationContent());
|
||||
row.createCell(col++).setCellValue(vo.getCounselorSuggestion());
|
||||
row.createCell(col++).setCellValue(vo.getCommunicationDuration() != null ? vo.getCommunicationDuration().toString() + "分钟" : "");
|
||||
|
||||
// 心理病史
|
||||
String psychologicalHistory = "";
|
||||
if (vo.getPsychologicalHistory() != null) {
|
||||
psychologicalHistory = vo.getPsychologicalHistory() == 2 ? "是" : "否";
|
||||
}
|
||||
row.createCell(col++).setCellValue(psychologicalHistory);
|
||||
|
||||
// 心理病史详情
|
||||
row.createCell(col++).setCellValue(vo.getPsychologicalHistoryDetail() != null ? vo.getPsychologicalHistoryDetail() : "");
|
||||
|
||||
// 基本情况
|
||||
row.createCell(col++).setCellValue(vo.getBasicSituation() != null ? vo.getBasicSituation() : "");
|
||||
|
||||
// 重大生活事件
|
||||
row.createCell(col++).setCellValue(vo.getMajorLifeEvents() != null ? vo.getMajorLifeEvents() : "");
|
||||
|
||||
// 本次咨询过程(原咨询内容)
|
||||
row.createCell(col++).setCellValue(vo.getConsultationContent() != null ? vo.getConsultationContent() : "");
|
||||
|
||||
// 是否需要转介
|
||||
String needReferral = "";
|
||||
if (vo.getNeedReferral() != null) {
|
||||
needReferral = vo.getNeedReferral() == 2 ? "是" : "否";
|
||||
}
|
||||
row.createCell(col++).setCellValue(needReferral);
|
||||
|
||||
// 本学期到访次数
|
||||
row.createCell(col++).setCellValue(vo.getSemesterVisitCount() != null ? vo.getSemesterVisitCount().toString() + "次" : "0次");
|
||||
}
|
||||
|
||||
// 写入字节流
|
||||
@@ -350,6 +377,15 @@ public class XljkConsultationAppointmentController extends BaseController {
|
||||
.body(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日历视图的排班数据(带预约状态)
|
||||
*/
|
||||
@GetMapping("/calendar-schedules")
|
||||
public R<List<ScheduleWithStatusVo>> getCalendarSchedules(@RequestParam String startDate, @RequestParam String endDate) {
|
||||
List<ScheduleWithStatusVo> schedules = service.getCalendarSchedules(startDate, endDate);
|
||||
return R.ok(schedules);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义CSV字段中的特殊字符
|
||||
*/
|
||||
|
||||
+46
-3
@@ -14,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.dromara.xljk.domain.vo.XljkConsultationScheduleSummaryVo;
|
||||
|
||||
@RestController
|
||||
@@ -57,10 +58,14 @@ public class XljkConsultationScheduleController {
|
||||
batchBo.setCounselorId(Long.valueOf(requestMap.get("counselorId").toString()));
|
||||
}
|
||||
|
||||
if (requestMap.containsKey("office")) {
|
||||
batchBo.setOffice(requestMap.get("office").toString());
|
||||
}
|
||||
|
||||
batchBo.setStartDate(requestMap.get("startDate").toString());
|
||||
batchBo.setEndDate(requestMap.get("endDate").toString());
|
||||
|
||||
// 设置上午时段
|
||||
// 设置中午时段(字段名保持morning以兼容前端)
|
||||
if (requestMap.containsKey("morningEnabled")) {
|
||||
batchBo.setMorningEnabled(Boolean.valueOf(requestMap.get("morningEnabled").toString()));
|
||||
}
|
||||
@@ -73,6 +78,26 @@ public class XljkConsultationScheduleController {
|
||||
batchBo.setMorningEndTime(requestMap.get("morningEndTime").toString());
|
||||
}
|
||||
|
||||
// 设置中午时段(新版本)
|
||||
if (requestMap.containsKey("noonEnabled")) {
|
||||
batchBo.setNoonEnabled(Boolean.valueOf(requestMap.get("noonEnabled").toString()));
|
||||
}
|
||||
|
||||
if (requestMap.containsKey("noonTimeSlots")) {
|
||||
List<Map<String, Object>> noonSlots = (List<Map<String, Object>>) requestMap.get("noonTimeSlots");
|
||||
if (noonSlots != null) {
|
||||
List<XljkConsultationScheduleBatchBo.TimeSlot> timeSlots = noonSlots.stream()
|
||||
.map(slot -> {
|
||||
XljkConsultationScheduleBatchBo.TimeSlot timeSlot = new XljkConsultationScheduleBatchBo.TimeSlot();
|
||||
timeSlot.setStartTime(slot.get("startTime").toString());
|
||||
timeSlot.setEndTime(slot.get("endTime").toString());
|
||||
return timeSlot;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
batchBo.setNoonTimeSlots(timeSlots);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置下午时段
|
||||
if (requestMap.containsKey("afternoonEnabled")) {
|
||||
batchBo.setAfternoonEnabled(Boolean.valueOf(requestMap.get("afternoonEnabled").toString()));
|
||||
@@ -86,6 +111,21 @@ public class XljkConsultationScheduleController {
|
||||
batchBo.setAfternoonEndTime(requestMap.get("afternoonEndTime").toString());
|
||||
}
|
||||
|
||||
if (requestMap.containsKey("afternoonTimeSlots")) {
|
||||
List<Map<String, Object>> afternoonSlots = (List<Map<String, Object>>) requestMap.get("afternoonTimeSlots");
|
||||
if (afternoonSlots != null) {
|
||||
List<XljkConsultationScheduleBatchBo.TimeSlot> timeSlots = afternoonSlots.stream()
|
||||
.map(slot -> {
|
||||
XljkConsultationScheduleBatchBo.TimeSlot timeSlot = new XljkConsultationScheduleBatchBo.TimeSlot();
|
||||
timeSlot.setStartTime(slot.get("startTime").toString());
|
||||
timeSlot.setEndTime(slot.get("endTime").toString());
|
||||
return timeSlot;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
batchBo.setAfternoonTimeSlots(timeSlots);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置其他字段
|
||||
if (requestMap.containsKey("status")) {
|
||||
batchBo.setStatus(requestMap.get("status").toString());
|
||||
@@ -166,10 +206,13 @@ public class XljkConsultationScheduleController {
|
||||
*/
|
||||
@GetMapping("/options/counselors")
|
||||
public R<List<Map<String, Object>>> getCounselorOptions() {
|
||||
String sql = "SELECT u.user_id as id, u.nick_name as name FROM sys_user u " +
|
||||
String sql = "SELECT u.user_id as id, u.nick_name as name, c.office as office " +
|
||||
"FROM sys_user u " +
|
||||
"JOIN sys_user_role ur ON u.user_id = ur.user_id " +
|
||||
"JOIN sys_role r ON ur.role_id = r.role_id " +
|
||||
"WHERE r.role_key = 'xljk_consultant' AND u.del_flag = '0'";
|
||||
"LEFT JOIN xljk_consulter c ON u.user_id = c.user_id AND c.deleted = 0 " +
|
||||
"WHERE r.role_key = 'xljk_consultant' AND u.del_flag = '0' " +
|
||||
"ORDER BY u.nick_name";
|
||||
|
||||
List<Map<String, Object>> counselorOptions = jdbcTemplate.queryForList(sql);
|
||||
return R.ok(counselorOptions);
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package org.dromara.xljk.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.excel.utils.ExcelUtil;
|
||||
import org.dromara.xljk.domain.vo.XljkConsulterVo;
|
||||
import org.dromara.xljk.domain.bo.XljkConsulterBo;
|
||||
import org.dromara.xljk.service.IXljkConsulterService;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/xljk/consulter")
|
||||
public class XljkConsulterController extends BaseController {
|
||||
|
||||
private final IXljkConsulterService xljkConsulterService;
|
||||
private final ISysUserService sysUserService;
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<XljkConsulterVo> list(XljkConsulterBo bo, PageQuery pageQuery) {
|
||||
return xljkConsulterService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出心理咨询师配置列表
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:export")
|
||||
@Log(title = "心理咨询师配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(XljkConsulterBo bo, HttpServletResponse response) {
|
||||
List<XljkConsulterVo> list = xljkConsulterService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "心理咨询师配置", XljkConsulterVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取心理咨询师配置详细信息
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<XljkConsulterVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable Long id) {
|
||||
return R.ok(xljkConsulterService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增心理咨询师配置
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:add")
|
||||
@Log(title = "心理咨询师配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody XljkConsulterBo bo) {
|
||||
return toAjax(xljkConsulterService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改心理咨询师配置
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:edit")
|
||||
@Log(title = "心理咨询师配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XljkConsulterBo bo) {
|
||||
return toAjax(xljkConsulterService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除心理咨询师配置
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:remove")
|
||||
@Log(title = "心理咨询师配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(xljkConsulterService.deleteWithValidByIds(List.of(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加咨询师
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:add")
|
||||
@Log(title = "批量添加咨询师", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/batchAdd")
|
||||
public R<Void> batchAdd(@RequestBody BatchAddRequest request) {
|
||||
return toAjax(xljkConsulterService.batchAddConsulters(request.getUserIds(), request.getRemark()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的咨询师列表
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:list")
|
||||
@GetMapping("/myConfig")
|
||||
public TableDataInfo<XljkConsulterVo> myConfigList(XljkConsulterBo bo, PageQuery pageQuery) {
|
||||
return xljkConsulterService.queryMyConfigList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:edit")
|
||||
@Log(title = "启用咨询师", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/enable")
|
||||
public R<Void> enable(@RequestBody List<Long> ids) {
|
||||
return toAjax(xljkConsulterService.enableConsulters(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:edit")
|
||||
@Log(title = "停用咨询师", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/disable")
|
||||
public R<Void> disable(@RequestBody List<Long> ids) {
|
||||
return toAjax(xljkConsulterService.disableConsulters(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
*/
|
||||
@SaCheckPermission("xljk:consulter:list")
|
||||
@GetMapping("/users/{deptId}")
|
||||
public R<List<SysUserVo>> listUserByDeptId(@PathVariable String deptId) {
|
||||
List<SysUserVo> users = sysUserService.selectUserListByDept(Long.valueOf(deptId));
|
||||
return R.ok(users);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加请求对象
|
||||
*/
|
||||
public static class BatchAddRequest {
|
||||
private List<Long> userIds;
|
||||
private String remark;
|
||||
|
||||
public List<Long> getUserIds() {
|
||||
return userIds;
|
||||
}
|
||||
|
||||
public void setUserIds(List<Long> userIds) {
|
||||
this.userIds = userIds;
|
||||
}
|
||||
|
||||
public String getRemark() {
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark) {
|
||||
this.remark = remark;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+13
-1
@@ -3,6 +3,7 @@ package org.dromara.xljk.domain;
|
||||
import java.util.Date;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
@Data
|
||||
public class XljkConsultationAppointment implements Serializable {
|
||||
@@ -21,9 +22,20 @@ public class XljkConsultationAppointment implements Serializable {
|
||||
private String appointmentTime;
|
||||
private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00"
|
||||
private String reason;
|
||||
|
||||
@TableField("consult_theme")
|
||||
private String consultTheme; // 咨询主题
|
||||
|
||||
private String status;
|
||||
private Date actualVisitTime;
|
||||
private Integer communicationDuration;
|
||||
private String consultationContent;
|
||||
private String counselorSuggestion;
|
||||
|
||||
// 新增心理健康相关字段
|
||||
private String studentName; // 学生姓名(冗余字段)
|
||||
private Integer psychologicalHistory; // 心理病史(1-否,2-是)
|
||||
private String psychologicalHistoryDetail; // 心理病史详情
|
||||
private String basicSituation; // 基本情况
|
||||
private String majorLifeEvents; // 重大生活事件
|
||||
private Integer needReferral; // 是否需要转介(1-不需要,2-需要)
|
||||
}
|
||||
+2
-1
@@ -15,8 +15,9 @@ public class XljkConsultationSchedule implements Serializable {
|
||||
private Date updateTime;
|
||||
private Integer deleted;
|
||||
private Long counselorId;
|
||||
private String office; // 办公室
|
||||
private Date scheduleDate;
|
||||
private String timeSlot; // 0上午 1下午
|
||||
private String timeSlot; // 0中午 1下午
|
||||
private String startTime; // 开始时间 HH:mm 格式
|
||||
private String endTime; // 结束时间 HH:mm 格式
|
||||
private String status; // 0正常 1停用
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.dromara.xljk.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置对象 xljk_consulter
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("xljk_consulter")
|
||||
public class XljkConsulter extends TenantEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
private String office;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
private String configByName;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer deleted;
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package org.dromara.xljk.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 心理咨询用户和部门完整信息
|
||||
* 用于批量添加咨询师时获取完整的用户数据
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
public class XljkUserWithDeptInfo {
|
||||
|
||||
/** 用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 用户账号 */
|
||||
private String userName;
|
||||
|
||||
/** 用户昵称 */
|
||||
private String nickName;
|
||||
|
||||
/** 部门ID */
|
||||
private Long deptId;
|
||||
|
||||
/** 部门名称 */
|
||||
private String deptName;
|
||||
|
||||
/** 手机号码 */
|
||||
private String phonenumber;
|
||||
|
||||
/** 用户邮箱 */
|
||||
private String email;
|
||||
|
||||
/** 帐号状态(1正常 0停用) */
|
||||
private String status;
|
||||
|
||||
}
|
||||
+8
-1
@@ -22,12 +22,19 @@ public class XljkConsultationAppointmentBo {
|
||||
private String appointmentTime;
|
||||
private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00"
|
||||
private String reason;
|
||||
private String consultTheme; // 咨询主题
|
||||
private String status;
|
||||
private Date actualVisitTime;
|
||||
private Integer communicationDuration;
|
||||
private String consultationContent;
|
||||
private String counselorSuggestion;
|
||||
|
||||
// 新增心理健康相关字段
|
||||
private Integer psychologicalHistory; // 心理病史(1-否,2-是)
|
||||
private String psychologicalHistoryDetail; // 心理病史详情
|
||||
private String basicSituation; // 基本情况
|
||||
private String majorLifeEvents; // 重大生活事件
|
||||
private Integer needReferral; // 是否需要转介(1-不需要,2-需要)
|
||||
|
||||
// 查询参数字段
|
||||
private String studentName;
|
||||
private String studentNo;
|
||||
|
||||
+35
-5
@@ -2,6 +2,7 @@ package org.dromara.xljk.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量创建预约时段的业务对象
|
||||
@@ -13,6 +14,11 @@ public class XljkConsultationScheduleBatchBo {
|
||||
*/
|
||||
private Long counselorId;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
private String office;
|
||||
|
||||
/**
|
||||
* 开始日期
|
||||
*/
|
||||
@@ -24,35 +30,50 @@ public class XljkConsultationScheduleBatchBo {
|
||||
private String endDate;
|
||||
|
||||
/**
|
||||
* 是否启用上午时段
|
||||
* 是否启用中午时段(字段名保持morningEnabled以兼容前端)
|
||||
*/
|
||||
private Boolean morningEnabled;
|
||||
|
||||
/**
|
||||
* 上午开始时间 (HH:mm)
|
||||
* 中午开始时间 (HH:mm)(字段名保持morningStartTime以兼容前端)
|
||||
*/
|
||||
private String morningStartTime;
|
||||
|
||||
/**
|
||||
* 上午结束时间 (HH:mm)
|
||||
* 中午结束时间 (HH:mm)(字段名保持morningEndTime以兼容前端)
|
||||
*/
|
||||
private String morningEndTime;
|
||||
|
||||
/**
|
||||
* 是否启用中午时段
|
||||
*/
|
||||
private Boolean noonEnabled;
|
||||
|
||||
/**
|
||||
* 中午时段列表
|
||||
*/
|
||||
private List<TimeSlot> noonTimeSlots;
|
||||
|
||||
/**
|
||||
* 是否启用下午时段
|
||||
*/
|
||||
private Boolean afternoonEnabled;
|
||||
|
||||
/**
|
||||
* 下午开始时间 (HH:mm)
|
||||
* 下午开始时间 (HH:mm)(兼容旧版本)
|
||||
*/
|
||||
private String afternoonStartTime;
|
||||
|
||||
/**
|
||||
* 下午结束时间 (HH:mm)
|
||||
* 下午结束时间 (HH:mm)(兼容旧版本)
|
||||
*/
|
||||
private String afternoonEndTime;
|
||||
|
||||
/**
|
||||
* 下午时段列表
|
||||
*/
|
||||
private List<TimeSlot> afternoonTimeSlots;
|
||||
|
||||
/**
|
||||
* 状态 (0正常 1停用)
|
||||
*/
|
||||
@@ -67,4 +88,13 @@ public class XljkConsultationScheduleBatchBo {
|
||||
* 批次ID,用于标识同一批次创建的预约时段
|
||||
*/
|
||||
private String batchId;
|
||||
|
||||
/**
|
||||
* 时段内部类
|
||||
*/
|
||||
@Data
|
||||
public static class TimeSlot {
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -14,8 +14,9 @@ public class XljkConsultationScheduleBo {
|
||||
private Date updateTime;
|
||||
private Integer deleted;
|
||||
private Long counselorId;
|
||||
private String office; // 办公室
|
||||
private Date scheduleDate;
|
||||
private String timeSlot; // 0上午 1下午
|
||||
private String timeSlot; // 0中午 1下午
|
||||
private String startTime; // 开始时间 HH:mm 格式
|
||||
private String endTime; // 结束时间 HH:mm 格式
|
||||
private String status;
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package org.dromara.xljk.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.xljk.domain.XljkConsulter;
|
||||
|
||||
import jakarta.validation.constraints.*;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置业务对象 xljk_consulter
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = XljkConsulter.class, reverseConvertGenerate = false)
|
||||
public class XljkConsulterBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
@NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@NotBlank(message = "用户昵称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@NotNull(message = "部门ID不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@NotBlank(message = "部门名称不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
private String office;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
@NotBlank(message = "状态不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
private String configByName;
|
||||
|
||||
/**
|
||||
* 分页大小
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 搜索值
|
||||
*/
|
||||
private String searchValue;
|
||||
|
||||
/**
|
||||
* 创建时间范围
|
||||
*/
|
||||
private List<Date> createTimeRange;
|
||||
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package org.dromara.xljk.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 带预约状态的排班数据视图对象
|
||||
*/
|
||||
@Data
|
||||
public class ScheduleWithStatusVo {
|
||||
|
||||
/**
|
||||
* 排班ID
|
||||
*/
|
||||
private Long scheduleId;
|
||||
|
||||
/**
|
||||
* 咨询师ID
|
||||
*/
|
||||
private Long counselorId;
|
||||
|
||||
/**
|
||||
* 咨询师姓名
|
||||
*/
|
||||
private String counselorName;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private String date;
|
||||
|
||||
/**
|
||||
* 时段类型(0中午 1下午 2晚上)
|
||||
*/
|
||||
private String timeSlot;
|
||||
|
||||
/**
|
||||
* 开始时间(HH:mm)
|
||||
*/
|
||||
private String startTime;
|
||||
|
||||
/**
|
||||
* 结束时间(HH:mm)
|
||||
*/
|
||||
private String endTime;
|
||||
|
||||
/**
|
||||
* 时段显示文本(如:中午 08:30-12:00)
|
||||
*/
|
||||
private String timeDisplay;
|
||||
|
||||
/**
|
||||
* 是否已预约满
|
||||
*/
|
||||
private Boolean isFullyBooked;
|
||||
|
||||
/**
|
||||
* 是否已过期
|
||||
*/
|
||||
private Boolean isExpired;
|
||||
|
||||
/**
|
||||
* 可预约数量
|
||||
*/
|
||||
private Integer availableSlots;
|
||||
|
||||
/**
|
||||
* 总预约数量
|
||||
*/
|
||||
private Integer totalSlots;
|
||||
|
||||
// 以下是基础字段,用于避免MyBatis映射错误
|
||||
private Long createBy; // 创建者
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime; // 创建时间
|
||||
private Long updateBy; // 更新者
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime; // 更新时间
|
||||
}
|
||||
+11
-3
@@ -30,14 +30,12 @@ public class XljkConsultationAppointmentVo {
|
||||
private String appointmentTime;
|
||||
private String appointmentDetailTime; // 具体预约时间段,如"08:30-09:00"
|
||||
private String reason;
|
||||
private String consultTheme; // 咨询主题
|
||||
/**
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date actualVisitTime;
|
||||
|
||||
private Integer communicationDuration;
|
||||
private String consultationContent;
|
||||
private String counselorSuggestion;
|
||||
@@ -51,4 +49,14 @@ public class XljkConsultationAppointmentVo {
|
||||
// 新增院系和专业字段
|
||||
private String collegeName; // 院系名称
|
||||
private String majorName; // 专业名称
|
||||
|
||||
// 新增心理健康相关字段
|
||||
private Integer psychologicalHistory; // 心理病史(1-否,2-是)
|
||||
private String psychologicalHistoryDetail; // 心理病史详情
|
||||
private String basicSituation; // 基本情况
|
||||
private String majorLifeEvents; // 重大生活事件
|
||||
private Integer needReferral; // 是否需要转介(1-不需要,2-需要)
|
||||
|
||||
// 本学期到访次数(计算字段,不存储在数据库)
|
||||
private Integer semesterVisitCount;
|
||||
}
|
||||
+3
-2
@@ -18,11 +18,12 @@ public class XljkConsultationScheduleSummaryVo {
|
||||
private Integer deleted;
|
||||
private Long counselorId;
|
||||
private String counselorName; // 咨询师姓名,用于前端显示
|
||||
private String office; // 办公室,用于前端显示
|
||||
|
||||
// 汇总信息
|
||||
private String dateRange; // 日期范围,如"2025-07-01 至 2025-07-10"
|
||||
private Boolean morningEnabled; // 是否启用上午时段
|
||||
private String morningTime; // 上午时间,如"08:30-12:00"
|
||||
private Boolean morningEnabled; // 是否启用中午时段(字段名保持不变以兼容前端)
|
||||
private String morningTime; // 中午时间,如"08:30-12:00"
|
||||
private Boolean afternoonEnabled; // 是否启用下午时段
|
||||
private String afternoonTime; // 下午时间,如"14:30-17:00"
|
||||
private String status; // 状态
|
||||
|
||||
+2
-1
@@ -15,8 +15,9 @@ public class XljkConsultationScheduleVo {
|
||||
private Integer deleted;
|
||||
private Long counselorId;
|
||||
private String counselorName; // 咨询师姓名,用于前端显示
|
||||
private String office; // 办公室
|
||||
private Date scheduleDate;
|
||||
private String timeSlot; // 0上午 1下午
|
||||
private String timeSlot; // 0中午 1下午
|
||||
private String startTime; // 开始时间 HH:mm 格式
|
||||
private String endTime; // 结束时间 HH:mm 格式
|
||||
private String status;
|
||||
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package org.dromara.xljk.domain.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.xljk.domain.XljkConsulter;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置视图对象 xljk_consulter
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = XljkConsulter.class)
|
||||
public class XljkConsulterVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ExcelProperty(value = "主键ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@ExcelProperty(value = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
@ExcelProperty(value = "用户姓名")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户昵称")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
@ExcelProperty(value = "部门ID")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
@ExcelProperty(value = "部门名称")
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
@ExcelProperty(value = "联系电话")
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
@ExcelProperty(value = "办公室")
|
||||
private String office;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
@ExcelProperty(value = "邮箱地址")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_normal_disable")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
@ExcelProperty(value = "备注说明")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
private Long configBy;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
@ExcelProperty(value = "配置人")
|
||||
private String configByName;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*/
|
||||
private Long createDept;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ExcelProperty(value = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者ID
|
||||
*/
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ExcelProperty(value = "更新时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.dromara.xljk.mapper;
|
||||
|
||||
import org.dromara.xljk.domain.XljkConsulter;
|
||||
import org.dromara.xljk.domain.vo.XljkConsulterVo;
|
||||
import org.dromara.xljk.domain.XljkUserWithDeptInfo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface XljkConsulterMapper extends BaseMapperPlus<XljkConsulter, XljkConsulterVo> {
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置
|
||||
*
|
||||
* @param id 心理咨询师配置主键
|
||||
* @return 心理咨询师配置
|
||||
*/
|
||||
XljkConsulterVo selectXljkConsulterVoById(Long id);
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*
|
||||
* @param xljkConsulter 心理咨询师配置
|
||||
* @return 心理咨询师配置集合
|
||||
*/
|
||||
List<XljkConsulterVo> selectXljkConsulterVoList(XljkConsulter xljkConsulter);
|
||||
|
||||
/**
|
||||
* 根据用户ID检查是否已存在咨询师配置
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 记录数
|
||||
*/
|
||||
int checkUserExists(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
*
|
||||
* @param ids 咨询师ID列表
|
||||
* @return 影响行数
|
||||
*/
|
||||
int enableConsultersByIds(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
*
|
||||
* @param ids 咨询师ID列表
|
||||
* @return 影响行数
|
||||
*/
|
||||
int disableConsultersByIds(@Param("ids") List<Long> ids);
|
||||
|
||||
/**
|
||||
* 查询完整的用户信息(包含部门信息)用于批量添加
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @return 用户和部门完整信息列表
|
||||
*/
|
||||
List<XljkUserWithDeptInfo> selectUsersWithDeptInfo(@Param("userIds") List<Long> userIds);
|
||||
|
||||
}
|
||||
+19
-4
@@ -4,6 +4,7 @@ import org.dromara.xljk.domain.XljkConsultationAppointment;
|
||||
import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo;
|
||||
import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo;
|
||||
import org.dromara.xljk.domain.vo.TimeSlotDetailVo;
|
||||
import org.dromara.xljk.domain.vo.ScheduleWithStatusVo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import java.util.List;
|
||||
@@ -43,7 +44,7 @@ public interface IXljkConsultationAppointmentService {
|
||||
/**
|
||||
* 获取可用的详细时段列表(30分钟一个时段)
|
||||
* @param date 日期
|
||||
* @param timeSlot 时段类型(0上午 1下午)
|
||||
* @param timeSlot 时段类型(0中午 1下午)
|
||||
* @return 详细时段列表
|
||||
*/
|
||||
List<TimeSlotDetailVo> getAvailableDetailSlots(Date date, String timeSlot);
|
||||
@@ -65,15 +66,21 @@ public interface IXljkConsultationAppointmentService {
|
||||
/**
|
||||
* 记录咨询结果
|
||||
* @param id 预约ID
|
||||
* @param actualVisitTime 实际到访时间
|
||||
* @param communicationDuration 沟通时长
|
||||
* @param consultationContent 咨询内容
|
||||
* @param counselorSuggestion 咨询师建议
|
||||
* @param status 状态(1已完成/3未到访)
|
||||
* @param psychologicalHistory 心理病史(1-否,2-是)
|
||||
* @param psychologicalHistoryDetail 心理病史详情
|
||||
* @param basicSituation 基本情况
|
||||
* @param majorLifeEvents 重大生活事件
|
||||
* @param needReferral 是否需要转介(1-不需要,2-需要)
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean recordConsultation(Long id, Date actualVisitTime, Integer communicationDuration,
|
||||
String consultationContent, String counselorSuggestion, String status);
|
||||
boolean recordConsultation(Long id, Integer communicationDuration,
|
||||
String consultationContent, String counselorSuggestion, String status,
|
||||
Integer psychologicalHistory, String psychologicalHistoryDetail,
|
||||
String basicSituation, String majorLifeEvents, Integer needReferral);
|
||||
|
||||
/**
|
||||
* 获取院系选项
|
||||
@@ -95,4 +102,12 @@ public interface IXljkConsultationAppointmentService {
|
||||
* @return 班级选项列表
|
||||
*/
|
||||
List<Map<String, Object>> getClassOptions(String collegeName, String majorName);
|
||||
|
||||
/**
|
||||
* 获取日历视图的排班数据(带预约状态)
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 排班数据列表
|
||||
*/
|
||||
List<ScheduleWithStatusVo> getCalendarSchedules(String startDate, String endDate);
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package org.dromara.xljk.service;
|
||||
|
||||
import org.dromara.xljk.domain.vo.XljkConsulterVo;
|
||||
import org.dromara.xljk.domain.bo.XljkConsulterBo;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
public interface IXljkConsulterService {
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 心理咨询师配置
|
||||
*/
|
||||
XljkConsulterVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 心理咨询师配置集合
|
||||
*/
|
||||
TableDataInfo<XljkConsulterVo> queryPageList(XljkConsulterBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 心理咨询师配置集合
|
||||
*/
|
||||
List<XljkConsulterVo> queryList(XljkConsulterBo bo);
|
||||
|
||||
/**
|
||||
* 新增心理咨询师配置
|
||||
*
|
||||
* @param bo 心理咨询师配置
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean insertByBo(XljkConsulterBo bo);
|
||||
|
||||
/**
|
||||
* 修改心理咨询师配置
|
||||
*
|
||||
* @param bo 心理咨询师配置
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean updateByBo(XljkConsulterBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除心理咨询师配置信息
|
||||
*
|
||||
* @param ids 需要删除的主键集合
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量添加咨询师
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean batchAddConsulters(List<Long> userIds, String remark);
|
||||
|
||||
/**
|
||||
* 查询我配置的咨询师列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 咨询师列表
|
||||
*/
|
||||
TableDataInfo<XljkConsulterVo> queryMyConfigList(XljkConsulterBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
*
|
||||
* @param ids 咨询师ID列表
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean enableConsulters(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
*
|
||||
* @param ids 咨询师ID列表
|
||||
* @return 结果
|
||||
*/
|
||||
Boolean disableConsulters(List<Long> ids);
|
||||
|
||||
}
|
||||
+393
-67
@@ -10,6 +10,7 @@ import org.dromara.xljk.domain.XljkConsultationSchedule;
|
||||
import org.dromara.xljk.domain.bo.XljkConsultationAppointmentBo;
|
||||
import org.dromara.xljk.domain.vo.XljkConsultationAppointmentVo;
|
||||
import org.dromara.xljk.domain.vo.TimeSlotDetailVo;
|
||||
import org.dromara.xljk.domain.vo.ScheduleWithStatusVo;
|
||||
import org.dromara.xljk.mapper.XljkConsultationAppointmentMapper;
|
||||
import org.dromara.xljk.mapper.XljkConsultationScheduleMapper;
|
||||
import org.dromara.xljk.service.IXljkConsultationAppointmentService;
|
||||
@@ -38,6 +39,8 @@ import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -71,9 +74,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
BeanUtils.copyProperties(entity, vo);
|
||||
|
||||
// 记录关键字段
|
||||
log.info("复制后的VO对象关键字段:status={}, actualVisitTime={}, communicationDuration={}, consultationContent长度={}, counselorSuggestion长度={}",
|
||||
log.info("复制后的VO对象关键字段:status={}, communicationDuration={}, consultationContent长度={}, counselorSuggestion长度={}",
|
||||
vo.getStatus(),
|
||||
vo.getActualVisitTime(),
|
||||
vo.getCommunicationDuration(),
|
||||
vo.getConsultationContent() != null ? vo.getConsultationContent().length() : 0,
|
||||
vo.getCounselorSuggestion() != null ? vo.getCounselorSuggestion().length() : 0);
|
||||
@@ -128,23 +130,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
|
||||
// 再次检查关键字段
|
||||
if (vo.getStatus() != null && vo.getStatus() == 1) {
|
||||
log.info("已到访状态,检查咨询结果相关字段:actualVisitTime={}, communicationDuration={}",
|
||||
vo.getActualVisitTime(), vo.getCommunicationDuration());
|
||||
|
||||
// 如果是已到访状态但实际到访时间为空,尝试从数据库直接查询
|
||||
if (vo.getActualVisitTime() == null) {
|
||||
log.warn("已到访状态但实际到访时间为空,尝试从数据库直接查询");
|
||||
try {
|
||||
String timeSql = "SELECT actual_visit_time FROM xljk_consultation_appointment WHERE id = " + id;
|
||||
Date actualVisitTime = jdbcTemplate.queryForObject(timeSql, Date.class);
|
||||
if (actualVisitTime != null) {
|
||||
log.info("从数据库查询到实际到访时间: {}", actualVisitTime);
|
||||
vo.setActualVisitTime(actualVisitTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("查询实际到访时间失败", e);
|
||||
}
|
||||
}
|
||||
log.info("已到访状态,检查咨询结果相关字段:communicationDuration={}",
|
||||
vo.getCommunicationDuration());
|
||||
}
|
||||
|
||||
log.info("返回预约记录详情: {}", vo);
|
||||
@@ -238,6 +225,9 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
return resultList.stream().map(row -> {
|
||||
XljkConsultationAppointmentVo vo = new XljkConsultationAppointmentVo();
|
||||
|
||||
// 获取学生ID用于计算本学期到访次数
|
||||
Long studentId = row.get("student_id") != null ? ((Number) row.get("student_id")).longValue() : null;
|
||||
|
||||
// 设置基本字段
|
||||
vo.setId(((Number) row.get("id")).longValue());
|
||||
vo.setTenantId((String) row.get("tenant_id"));
|
||||
@@ -291,11 +281,44 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
vo.setStatus(0); // 默认为待咨询
|
||||
}
|
||||
|
||||
vo.setActualVisitTime(convertToDate(row.get("actual_visit_time")));
|
||||
vo.setCommunicationDuration(row.get("communication_duration") != null ? ((Number) row.get("communication_duration")).intValue() : null);
|
||||
vo.setConsultationContent((String) row.get("consultation_content"));
|
||||
vo.setCounselorSuggestion((String) row.get("counselor_suggestion"));
|
||||
|
||||
// 设置咨询主题字段
|
||||
vo.setConsultTheme((String) row.get("consult_theme"));
|
||||
|
||||
// 设置心理健康相关字段
|
||||
Object psychologicalHistoryObj = row.get("psychological_history");
|
||||
if (psychologicalHistoryObj != null) {
|
||||
if (psychologicalHistoryObj instanceof Number) {
|
||||
vo.setPsychologicalHistory(((Number) psychologicalHistoryObj).intValue());
|
||||
} else if (psychologicalHistoryObj instanceof String) {
|
||||
try {
|
||||
vo.setPsychologicalHistory(Integer.parseInt((String) psychologicalHistoryObj));
|
||||
} catch (NumberFormatException e) {
|
||||
vo.setPsychologicalHistory(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vo.setPsychologicalHistoryDetail((String) row.get("psychological_history_detail"));
|
||||
vo.setBasicSituation((String) row.get("basic_situation"));
|
||||
vo.setMajorLifeEvents((String) row.get("major_life_events"));
|
||||
|
||||
Object needReferralObj = row.get("need_referral");
|
||||
if (needReferralObj != null) {
|
||||
if (needReferralObj instanceof Number) {
|
||||
vo.setNeedReferral(((Number) needReferralObj).intValue());
|
||||
} else if (needReferralObj instanceof String) {
|
||||
try {
|
||||
vo.setNeedReferral(Integer.parseInt((String) needReferralObj));
|
||||
} catch (NumberFormatException e) {
|
||||
vo.setNeedReferral(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置关联字段
|
||||
vo.setStudentName((String) row.get("student_name"));
|
||||
vo.setStudentNo((String) row.get("student_no"));
|
||||
@@ -304,9 +327,68 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
vo.setCollegeName((String) row.get("college_name"));
|
||||
vo.setMajorName((String) row.get("major_name"));
|
||||
|
||||
// 计算本学期到访次数
|
||||
if (studentId != null) {
|
||||
vo.setSemesterVisitCount(calculateSemesterVisitCount(studentId));
|
||||
} else {
|
||||
vo.setSemesterVisitCount(0);
|
||||
}
|
||||
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算学生本学期到访次数
|
||||
* 上学期:3月1日-8月31日
|
||||
* 下学期:9月1日-2月29日
|
||||
* @param studentId 学生ID
|
||||
* @return 本学期到访次数
|
||||
*/
|
||||
private int calculateSemesterVisitCount(Long studentId) {
|
||||
try {
|
||||
// 获取当前日期
|
||||
LocalDate now = LocalDate.now();
|
||||
String startDate, endDate;
|
||||
|
||||
// 判断当前处于哪个学期
|
||||
if (now.getMonthValue() >= 3 && now.getMonthValue() <= 8) {
|
||||
// 上学期:3月1日-8月31日
|
||||
startDate = now.getYear() + "-03-01";
|
||||
endDate = now.getYear() + "-08-31";
|
||||
} else if (now.getMonthValue() >= 9) {
|
||||
// 下学期:9月1日-次年2月29日(当年9月到次年2月)
|
||||
startDate = now.getYear() + "-09-01";
|
||||
// 计算次年2月的最后一天
|
||||
int nextYear = now.getYear() + 1;
|
||||
LocalDate nextFeb = LocalDate.of(nextYear, Month.FEBRUARY, 1);
|
||||
LocalDate lastDayOfFeb = nextFeb.withDayOfMonth(nextFeb.lengthOfMonth());
|
||||
endDate = lastDayOfFeb.toString();
|
||||
} else {
|
||||
// 1月、2月属于上一学年的下学期
|
||||
int prevYear = now.getYear() - 1;
|
||||
startDate = prevYear + "-09-01";
|
||||
// 当年2月的最后一天
|
||||
LocalDate currentFeb = LocalDate.of(now.getYear(), Month.FEBRUARY, 1);
|
||||
LocalDate lastDayOfFeb = currentFeb.withDayOfMonth(currentFeb.lengthOfMonth());
|
||||
endDate = lastDayOfFeb.toString();
|
||||
}
|
||||
|
||||
// 查询该学生在当前学期内状态为已到访(status=1)的记录数
|
||||
String sql = "SELECT COUNT(*) FROM xljk_consultation_appointment " +
|
||||
"WHERE student_id = ? AND status = 1 AND deleted = 0 " +
|
||||
"AND appointment_date >= ? AND appointment_date <= ?";
|
||||
|
||||
Integer count = jdbcTemplate.queryForObject(sql, Integer.class, studentId, startDate, endDate);
|
||||
|
||||
log.debug("学生ID: {}, 学期时间段: {} - {}, 到访次数: {}", studentId, startDate, endDate, count);
|
||||
|
||||
return count != null ? count : 0;
|
||||
} catch (Exception e) {
|
||||
log.error("计算学生本学期到访次数失败,学生ID: {}", studentId, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insert(XljkConsultationAppointmentBo bo) {
|
||||
@@ -341,7 +423,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
LambdaQueryWrapper<XljkConsultationAppointment> appointmentQueryWrapper = new LambdaQueryWrapper<>();
|
||||
appointmentQueryWrapper.ge(XljkConsultationAppointment::getAppointmentDate, startDate)
|
||||
.le(XljkConsultationAppointment::getAppointmentDate, endDate)
|
||||
.in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1")); // 待咨询或已完成的
|
||||
.in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的
|
||||
|
||||
List<XljkConsultationAppointment> appointments = mapper.selectList(appointmentQueryWrapper);
|
||||
|
||||
@@ -365,9 +447,10 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
String dateStr = new SimpleDateFormat("yyyy-MM-dd").format(schedule.getScheduleDate());
|
||||
String key = dateStr + "_" + schedule.getId();
|
||||
|
||||
// 检查该时段的可用详细时段
|
||||
// 检查该时段的可用详细时段(用于时间判断,但总容量固定为1)
|
||||
List<TimeSlotDetailVo> detailSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime());
|
||||
int totalDetailSlots = detailSlots.size();
|
||||
int totalDetailSlots = detailSlots.size(); // 保留用于时间过期判断
|
||||
int totalSlots = 1; // 每个时段只能预约1个人
|
||||
int bookedCount = 0;
|
||||
boolean isExpired = false;
|
||||
|
||||
@@ -408,15 +491,11 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
isExpired = false;
|
||||
}
|
||||
|
||||
// 检查已预约的详细时段
|
||||
// 检查已预约数量(按整个时段计算,不再按详细时段计算)
|
||||
if (bookedDetailSlots.containsKey(key)) {
|
||||
List<String> bookedTimes = bookedDetailSlots.get(key);
|
||||
for (TimeSlotDetailVo slot : detailSlots) {
|
||||
String detailTime = slot.getStartTime() + "-" + slot.getEndTime();
|
||||
if (bookedTimes.contains(detailTime)) {
|
||||
bookedCount++;
|
||||
}
|
||||
}
|
||||
// 只要有任何预约,就算已预约1个(因为每个时段只能预约1个人)
|
||||
bookedCount = bookedTimes.isEmpty() ? 0 : 1;
|
||||
}
|
||||
|
||||
// 创建时段信息
|
||||
@@ -427,8 +506,8 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
slot.put("timeSlot", schedule.getTimeSlot());
|
||||
slot.put("startTime", schedule.getStartTime());
|
||||
slot.put("endTime", schedule.getEndTime());
|
||||
slot.put("allBooked", bookedCount >= totalDetailSlots); // 是否全部约满
|
||||
slot.put("availableCount", totalDetailSlots - bookedCount); // 可用数量
|
||||
slot.put("allBooked", bookedCount >= totalSlots); // 是否全部约满
|
||||
slot.put("availableCount", totalSlots - bookedCount); // 可用数量
|
||||
slot.put("isExpired", isExpired); // 是否已过期
|
||||
|
||||
// 查询咨询师姓名
|
||||
@@ -463,21 +542,34 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
.eq(XljkConsultationSchedule::getTimeSlot, timeSlot)
|
||||
.eq(XljkConsultationSchedule::getStatus, "0"); // 0表示正常可用
|
||||
|
||||
XljkConsultationSchedule schedule = scheduleMapper.selectOne(scheduleQueryWrapper);
|
||||
// 修改:使用selectList()而不是selectOne(),因为可能有多条记录
|
||||
List<XljkConsultationSchedule> schedules = scheduleMapper.selectList(scheduleQueryWrapper);
|
||||
|
||||
if (schedule == null) {
|
||||
if (schedules == null || schedules.isEmpty()) {
|
||||
// 该日期没有排班
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 2. 生成30分钟时段列表
|
||||
List<TimeSlotDetailVo> timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime());
|
||||
// 2. 合并所有排班的时段,生成完整的30分钟时段列表
|
||||
List<TimeSlotDetailVo> allTimeSlots = new ArrayList<>();
|
||||
Set<String> uniqueTimeSlots = new LinkedHashSet<>(); // 用于去重,保持顺序
|
||||
|
||||
// 3. 查询已预约的时段
|
||||
for (XljkConsultationSchedule schedule : schedules) {
|
||||
List<TimeSlotDetailVo> timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime());
|
||||
for (TimeSlotDetailVo slot : timeSlots) {
|
||||
String slotKey = slot.getStartTime() + "-" + slot.getEndTime();
|
||||
if (uniqueTimeSlots.add(slotKey)) {
|
||||
allTimeSlots.add(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查询已预约的时段(查询所有相关排班的预约)
|
||||
List<Long> scheduleIds = schedules.stream().map(XljkConsultationSchedule::getId).collect(Collectors.toList());
|
||||
LambdaQueryWrapper<XljkConsultationAppointment> appointmentQueryWrapper = new LambdaQueryWrapper<>();
|
||||
appointmentQueryWrapper.eq(XljkConsultationAppointment::getAppointmentDate, date)
|
||||
.eq(XljkConsultationAppointment::getScheduleId, schedule.getId()) // 按排班ID过滤,确保只查询当前时段的预约
|
||||
.in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1")); // 待咨询或已完成的
|
||||
.in(XljkConsultationAppointment::getScheduleId, scheduleIds) // 按排班ID列表过滤
|
||||
.in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的
|
||||
|
||||
List<XljkConsultationAppointment> appointments = mapper.selectList(appointmentQueryWrapper);
|
||||
|
||||
@@ -491,7 +583,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
|
||||
for (TimeSlotDetailVo slot : timeSlots) {
|
||||
for (TimeSlotDetailVo slot : allTimeSlots) {
|
||||
String detailTime = slot.getStartTime() + "-" + slot.getEndTime();
|
||||
|
||||
// 检查是否已预约
|
||||
@@ -532,7 +624,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
}
|
||||
}
|
||||
|
||||
return timeSlots;
|
||||
return allTimeSlots;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -582,7 +674,7 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation
|
||||
XljkConsultationAppointment entity = new XljkConsultationAppointment();
|
||||
BeanUtils.copyProperties(bo, entity);
|
||||
entity.setStudentId(studentId); // 使用验证后的学生ID
|
||||
entity.setStatus("0"); // 0表示待咨询
|
||||
entity.setStatus("4"); // 4表示待审核
|
||||
entity.setCreateTime(new Date()); // 设置创建时间
|
||||
return mapper.insert(entity) > 0;
|
||||
}
|
||||
@@ -645,10 +737,6 @@ if (statusObj != null) {
|
||||
vo.setScheduleId(((Number) row.get("schedule_id")).longValue());
|
||||
}
|
||||
|
||||
if (row.get("actual_visit_time") != null) {
|
||||
vo.setActualVisitTime(convertToDate(row.get("actual_visit_time")));
|
||||
}
|
||||
|
||||
if (row.get("communication_duration") != null) {
|
||||
vo.setCommunicationDuration(((Number) row.get("communication_duration")).intValue());
|
||||
}
|
||||
@@ -691,9 +779,11 @@ if (statusObj != null) {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean recordConsultation(Long id, Date actualVisitTime, Integer communicationDuration,
|
||||
String consultationContent, String counselorSuggestion, String status) {
|
||||
log.info("记录咨询结果,ID: {}, 状态: {}, 实际到访时间: {}", id, status, actualVisitTime);
|
||||
public boolean recordConsultation(Long id, Integer communicationDuration,
|
||||
String consultationContent, String counselorSuggestion, String status,
|
||||
Integer psychologicalHistory, String psychologicalHistoryDetail,
|
||||
String basicSituation, String majorLifeEvents, Integer needReferral) {
|
||||
log.info("记录咨询结果,ID: {}, 状态: {}, 心理病史: {}, 转介: {}", id, status, psychologicalHistory, needReferral);
|
||||
|
||||
XljkConsultationAppointment entity = mapper.selectById(id);
|
||||
if (entity == null) {
|
||||
@@ -717,29 +807,42 @@ if (statusObj != null) {
|
||||
|
||||
// 只有在已到访状态(1)时,才设置咨询结果相关字段
|
||||
if (statusInt == 1) {
|
||||
if (actualVisitTime == null) {
|
||||
log.warn("实际到访时间为空,使用当前时间");
|
||||
actualVisitTime = new Date();
|
||||
}
|
||||
|
||||
entity.setActualVisitTime(actualVisitTime);
|
||||
entity.setCommunicationDuration(communicationDuration != null ? communicationDuration : 30);
|
||||
entity.setConsultationContent(consultationContent);
|
||||
entity.setCounselorSuggestion(counselorSuggestion);
|
||||
|
||||
log.info("设置咨询结果相关字段:实际到访时间={}, 沟通时长={}, 咨询内容长度={}, 咨询师建议长度={}",
|
||||
actualVisitTime,
|
||||
// 设置心理病史相关字段
|
||||
entity.setPsychologicalHistory(psychologicalHistory);
|
||||
entity.setPsychologicalHistoryDetail(psychologicalHistoryDetail);
|
||||
entity.setBasicSituation(basicSituation);
|
||||
entity.setMajorLifeEvents(majorLifeEvents);
|
||||
entity.setNeedReferral(needReferral);
|
||||
|
||||
log.info("设置咨询结果相关字段:沟通时长={}, 咨询内容长度={}, 咨询师建议长度={}",
|
||||
communicationDuration,
|
||||
consultationContent != null ? consultationContent.length() : 0,
|
||||
counselorSuggestion != null ? counselorSuggestion.length() : 0);
|
||||
|
||||
log.info("设置心理病史相关字段:心理病史={}, 心理病史详情长度={}, 基本情况长度={}, 重大生活事件长度={}, 转介={}",
|
||||
psychologicalHistory,
|
||||
psychologicalHistoryDetail != null ? psychologicalHistoryDetail.length() : 0,
|
||||
basicSituation != null ? basicSituation.length() : 0,
|
||||
majorLifeEvents != null ? majorLifeEvents.length() : 0,
|
||||
needReferral);
|
||||
} else {
|
||||
// 未到访状态,清空咨询结果相关字段
|
||||
log.info("非已到访状态,清空咨询结果相关字段");
|
||||
entity.setActualVisitTime(null);
|
||||
entity.setCommunicationDuration(0);
|
||||
entity.setConsultationContent("");
|
||||
entity.setCounselorSuggestion("");
|
||||
|
||||
// 清空心理病史相关字段
|
||||
entity.setPsychologicalHistory(null);
|
||||
entity.setPsychologicalHistoryDetail("");
|
||||
entity.setBasicSituation("");
|
||||
entity.setMajorLifeEvents("");
|
||||
entity.setNeedReferral(null);
|
||||
|
||||
// 如果是未到访状态(3),创建未到访记录
|
||||
if (statusInt == 3) {
|
||||
log.info("记录未到访,创建未到访记录");
|
||||
@@ -1323,10 +1426,19 @@ if (statusObj != null) {
|
||||
public TableDataInfo<XljkConsultationAppointmentVo> selectPageList(XljkConsultationAppointmentBo bo, PageQuery pageQuery) {
|
||||
log.info("分页查询预约记录列表,页码: {}, 每页数量: {}", pageQuery.getPageNum(), pageQuery.getPageSize());
|
||||
|
||||
// 将变量定义移到try块外,确保catch块可以访问
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// 构建查询条件
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("SELECT a.*, s.name AS student_name, s.student_no, ");
|
||||
sqlBuilder.append("SELECT a.id, a.tenant_id, a.student_id, a.counselor_id, a.schedule_id, ");
|
||||
sqlBuilder.append("a.appointment_date, a.appointment_time, a.appointment_detail_time, ");
|
||||
sqlBuilder.append("a.reason, a.status, a.communication_duration, a.consultation_content, ");
|
||||
sqlBuilder.append("a.counselor_suggestion, a.psychological_history, a.psychological_history_detail, ");
|
||||
sqlBuilder.append("a.basic_situation, a.major_life_events, a.need_referral, ");
|
||||
sqlBuilder.append("a.create_by, a.create_time, a.update_by, a.update_time, ");
|
||||
sqlBuilder.append("s.name AS student_name, s.student_no, ");
|
||||
sqlBuilder.append("s.college_name, s.major_name, s.class_name, ");
|
||||
sqlBuilder.append("u.nick_name AS counselor_name ");
|
||||
sqlBuilder.append("FROM xljk_consultation_appointment a ");
|
||||
@@ -1334,8 +1446,6 @@ if (statusObj != null) {
|
||||
sqlBuilder.append("LEFT JOIN sys_user u ON a.counselor_id = u.user_id ");
|
||||
sqlBuilder.append("WHERE a.deleted = 0 ");
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
// 添加查询条件
|
||||
if (bo.getStudentName() != null && !bo.getStudentName().isEmpty()) {
|
||||
sqlBuilder.append("AND s.name LIKE ? ");
|
||||
@@ -1457,10 +1567,6 @@ if (statusObj != null) {
|
||||
vo.setScheduleId(((Number) row.get("schedule_id")).longValue());
|
||||
}
|
||||
|
||||
if (row.get("actual_visit_time") != null) {
|
||||
vo.setActualVisitTime(convertToDate(row.get("actual_visit_time")));
|
||||
}
|
||||
|
||||
if (row.get("communication_duration") != null) {
|
||||
vo.setCommunicationDuration(((Number) row.get("communication_duration")).intValue());
|
||||
}
|
||||
@@ -1468,6 +1574,37 @@ if (statusObj != null) {
|
||||
vo.setConsultationContent((String) row.get("consultation_content"));
|
||||
vo.setCounselorSuggestion((String) row.get("counselor_suggestion"));
|
||||
|
||||
// 新增字段映射
|
||||
Object psychologicalHistoryObj = row.get("psychological_history");
|
||||
if (psychologicalHistoryObj != null) {
|
||||
if (psychologicalHistoryObj instanceof Number) {
|
||||
vo.setPsychologicalHistory(((Number) psychologicalHistoryObj).intValue());
|
||||
} else if (psychologicalHistoryObj instanceof String) {
|
||||
try {
|
||||
vo.setPsychologicalHistory(Integer.parseInt((String) psychologicalHistoryObj));
|
||||
} catch (NumberFormatException e) {
|
||||
vo.setPsychologicalHistory(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vo.setPsychologicalHistoryDetail((String) row.get("psychological_history_detail"));
|
||||
vo.setBasicSituation((String) row.get("basic_situation"));
|
||||
vo.setMajorLifeEvents((String) row.get("major_life_events"));
|
||||
|
||||
Object needReferralObj = row.get("need_referral");
|
||||
if (needReferralObj != null) {
|
||||
if (needReferralObj instanceof Number) {
|
||||
vo.setNeedReferral(((Number) needReferralObj).intValue());
|
||||
} else if (needReferralObj instanceof String) {
|
||||
try {
|
||||
vo.setNeedReferral(Integer.parseInt((String) needReferralObj));
|
||||
} catch (NumberFormatException e) {
|
||||
vo.setNeedReferral(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 其他通用字段
|
||||
if (row.get("create_by") != null) {
|
||||
vo.setCreateBy(((Number) row.get("create_by")).longValue());
|
||||
@@ -1485,6 +1622,13 @@ if (statusObj != null) {
|
||||
vo.setUpdateTime(convertToDate(row.get("update_time")));
|
||||
}
|
||||
|
||||
// 计算本学期到访次数
|
||||
if (vo.getStudentId() != null) {
|
||||
vo.setSemesterVisitCount(calculateSemesterVisitCount(vo.getStudentId()));
|
||||
} else {
|
||||
vo.setSemesterVisitCount(0);
|
||||
}
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
@@ -1496,12 +1640,194 @@ if (statusObj != null) {
|
||||
return tableDataInfo;
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询预约记录列表异常", e);
|
||||
log.error("SQL: {}", sqlBuilder.toString());
|
||||
log.error("参数: {}", params);
|
||||
TableDataInfo<XljkConsultationAppointmentVo> emptyResult = new TableDataInfo<>();
|
||||
emptyResult.setRows(new ArrayList<>());
|
||||
emptyResult.setTotal(0);
|
||||
emptyResult.setCode(200);
|
||||
emptyResult.setMsg("查询失败");
|
||||
emptyResult.setMsg("查询失败: " + e.getMessage());
|
||||
return emptyResult;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ScheduleWithStatusVo> getCalendarSchedules(String startDate, String endDate) {
|
||||
try {
|
||||
// 1. 查询指定日期范围内的所有排班数据
|
||||
LambdaQueryWrapper<XljkConsultationSchedule> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.ge(XljkConsultationSchedule::getScheduleDate, startDate)
|
||||
.le(XljkConsultationSchedule::getScheduleDate, endDate)
|
||||
.eq(XljkConsultationSchedule::getStatus, "0") // 0表示正常可用
|
||||
.orderByAsc(XljkConsultationSchedule::getScheduleDate)
|
||||
.orderByAsc(XljkConsultationSchedule::getTimeSlot)
|
||||
.orderByAsc(XljkConsultationSchedule::getStartTime);
|
||||
|
||||
List<XljkConsultationSchedule> schedules = scheduleMapper.selectList(queryWrapper);
|
||||
|
||||
if (schedules.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 2. 查询该日期范围内的所有预约记录
|
||||
List<Long> scheduleIds = schedules.stream().map(XljkConsultationSchedule::getId).collect(Collectors.toList());
|
||||
LambdaQueryWrapper<XljkConsultationAppointment> appointmentQueryWrapper = new LambdaQueryWrapper<>();
|
||||
appointmentQueryWrapper.in(XljkConsultationAppointment::getScheduleId, scheduleIds)
|
||||
.in(XljkConsultationAppointment::getStatus, Arrays.asList("0", "1", "4")); // 待咨询、已完成或待审核的
|
||||
|
||||
List<XljkConsultationAppointment> appointments = mapper.selectList(appointmentQueryWrapper);
|
||||
|
||||
// 3. 构建排班ID到预约记录的映射
|
||||
Map<Long, List<XljkConsultationAppointment>> appointmentMap = appointments.stream()
|
||||
.collect(Collectors.groupingBy(XljkConsultationAppointment::getScheduleId));
|
||||
|
||||
// 4. 获取咨询师信息映射
|
||||
Map<Long, String> counselorMap = getCounselorNameMap();
|
||||
|
||||
// 5. 获取当前时间
|
||||
Date now = new Date();
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
|
||||
|
||||
// 6. 构建结果列表
|
||||
List<ScheduleWithStatusVo> result = new ArrayList<>();
|
||||
|
||||
for (XljkConsultationSchedule schedule : schedules) {
|
||||
ScheduleWithStatusVo vo = new ScheduleWithStatusVo();
|
||||
|
||||
// 基本信息
|
||||
vo.setScheduleId(schedule.getId());
|
||||
vo.setCounselorId(schedule.getCounselorId());
|
||||
vo.setCounselorName(counselorMap.get(schedule.getCounselorId()));
|
||||
vo.setDate(dateFormat.format(schedule.getScheduleDate()));
|
||||
vo.setTimeSlot(schedule.getTimeSlot());
|
||||
vo.setStartTime(schedule.getStartTime());
|
||||
vo.setEndTime(schedule.getEndTime());
|
||||
|
||||
// 构建时段显示文本
|
||||
String timeSlotText = getTimeSlotText(schedule.getTimeSlot());
|
||||
vo.setTimeDisplay(timeSlotText + " " + schedule.getStartTime() + "-" + schedule.getEndTime());
|
||||
|
||||
// 计算预约状态
|
||||
List<XljkConsultationAppointment> scheduleAppointments = appointmentMap.get(schedule.getId());
|
||||
int bookedCount = scheduleAppointments != null ? scheduleAppointments.size() : 0;
|
||||
|
||||
// 每个时段只能预约1个人(无论时段长度)
|
||||
// 生成30分钟时段用于详细时段显示,但总预约数量固定为1
|
||||
List<TimeSlotDetailVo> timeSlots = TimeSlotUtils.generateTimeSlots(schedule.getStartTime(), schedule.getEndTime());
|
||||
int totalSlots = 1; // 每个时段只能预约1个人
|
||||
|
||||
vo.setTotalSlots(totalSlots);
|
||||
vo.setAvailableSlots(Math.max(0, totalSlots - bookedCount));
|
||||
vo.setIsFullyBooked(bookedCount >= totalSlots);
|
||||
|
||||
// 检查是否已过期
|
||||
boolean isExpired = false;
|
||||
try {
|
||||
Date scheduleDate = schedule.getScheduleDate();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(scheduleDate);
|
||||
|
||||
// 设置具体的结束时间
|
||||
String[] endTimeParts = schedule.getEndTime().split(":");
|
||||
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(endTimeParts[0]));
|
||||
calendar.set(Calendar.MINUTE, Integer.parseInt(endTimeParts[1]));
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
|
||||
Date scheduleEndTime = calendar.getTime();
|
||||
isExpired = scheduleEndTime.before(now);
|
||||
} catch (Exception e) {
|
||||
log.warn("检查时段过期状态失败: " + schedule.getId(), e);
|
||||
}
|
||||
|
||||
vo.setIsExpired(isExpired);
|
||||
|
||||
// 设置基础字段
|
||||
vo.setCreateBy(schedule.getCreateBy());
|
||||
vo.setCreateTime(schedule.getCreateTime());
|
||||
vo.setUpdateBy(schedule.getUpdateBy());
|
||||
vo.setUpdateTime(schedule.getUpdateTime());
|
||||
|
||||
result.add(vo);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取日历排班数据失败", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时段类型文本
|
||||
*/
|
||||
private String getTimeSlotText(String timeSlot) {
|
||||
switch (timeSlot) {
|
||||
case "0": return "中午";
|
||||
case "1": return "下午";
|
||||
case "2": return "晚上";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取咨询师名称映射
|
||||
*/
|
||||
private Map<Long, String> getCounselorNameMap() {
|
||||
try {
|
||||
Map<Long, String> counselorMap = new HashMap<>();
|
||||
|
||||
// 1. 查询所有已创建排班的咨询师ID
|
||||
LambdaQueryWrapper<XljkConsultationSchedule> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.select(XljkConsultationSchedule::getCounselorId)
|
||||
.eq(XljkConsultationSchedule::getStatus, "0") // 只查询正常状态的
|
||||
.groupBy(XljkConsultationSchedule::getCounselorId); // 去重
|
||||
|
||||
List<XljkConsultationSchedule> schedules = scheduleMapper.selectList(queryWrapper);
|
||||
if (schedules.isEmpty()) {
|
||||
return counselorMap;
|
||||
}
|
||||
|
||||
// 2. 提取咨询师ID列表
|
||||
List<Long> counselorIds = schedules.stream()
|
||||
.map(XljkConsultationSchedule::getCounselorId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (counselorIds.isEmpty()) {
|
||||
return counselorMap;
|
||||
}
|
||||
|
||||
// 3. 批量查询咨询师信息
|
||||
List<SysUserVo> counselors = userService.selectUserByIds(counselorIds, null);
|
||||
|
||||
// 4. 构建ID到姓名的映射
|
||||
counselorMap = counselors.stream()
|
||||
.filter(user -> user.getUserId() != null)
|
||||
.collect(Collectors.toMap(
|
||||
SysUserVo::getUserId,
|
||||
user -> {
|
||||
// 优先使用昵称,如果没有昵称则使用用户名
|
||||
String name = user.getNickName();
|
||||
if (StringUtils.isBlank(name)) {
|
||||
name = user.getUserName();
|
||||
}
|
||||
if (StringUtils.isBlank(name)) {
|
||||
name = "未知咨询师";
|
||||
}
|
||||
return name;
|
||||
},
|
||||
(existing, replacement) -> existing // 如果ID重复,保留第一个
|
||||
));
|
||||
|
||||
log.info("成功加载咨询师信息映射,共{}个咨询师", counselorMap.size());
|
||||
return counselorMap;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取咨询师信息失败", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
-28
@@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -26,6 +27,7 @@ import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class XljkConsultationScheduleServiceImpl implements IXljkConsultationScheduleService {
|
||||
@Autowired
|
||||
@@ -43,11 +45,16 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
// 查询咨询师姓名
|
||||
if (entity.getCounselorId() != null) {
|
||||
String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?";
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, entity.getCounselorId());
|
||||
if (!result.isEmpty()) {
|
||||
vo.setCounselorName((String) result.get(0).get("nick_name"));
|
||||
try {
|
||||
String counselorName = jdbcTemplate.queryForObject(sql, String.class, entity.getCounselorId());
|
||||
vo.setCounselorName(counselorName);
|
||||
} catch (Exception e) {
|
||||
log.error("查询咨询师姓名失败,counselorId: {}", entity.getCounselorId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// office字段直接从排班记录中获取
|
||||
vo.setOffice(entity.getOffice());
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -65,11 +72,15 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
// 查询咨询师姓名
|
||||
if (entity.getCounselorId() != null) {
|
||||
String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?";
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, entity.getCounselorId());
|
||||
if (!result.isEmpty()) {
|
||||
vo.setCounselorName((String) result.get(0).get("nick_name"));
|
||||
try {
|
||||
String counselorName = jdbcTemplate.queryForObject(sql, String.class, entity.getCounselorId());
|
||||
vo.setCounselorName(counselorName);
|
||||
} catch (Exception e) {
|
||||
log.error("查询咨询师姓名失败,counselorId: {}", entity.getCounselorId(), e);
|
||||
}
|
||||
}
|
||||
// office字段直接从排班记录中获取
|
||||
vo.setOffice(entity.getOffice());
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
@@ -87,6 +98,11 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
queryWrapper.eq(XljkConsultationSchedule::getCounselorId, bo.getCounselorId());
|
||||
}
|
||||
|
||||
// 添加办公室筛选条件
|
||||
if (bo.getOffice() != null && !bo.getOffice().isEmpty()) {
|
||||
queryWrapper.like(XljkConsultationSchedule::getOffice, bo.getOffice());
|
||||
}
|
||||
|
||||
// 按创建时间倒序查询所有记录
|
||||
queryWrapper.orderByDesc(XljkConsultationSchedule::getCreateTime);
|
||||
|
||||
@@ -144,12 +160,17 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
// 查询咨询师姓名
|
||||
if (batchGroup.getCounselorId() != null) {
|
||||
String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?";
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, batchGroup.getCounselorId());
|
||||
if (!result.isEmpty()) {
|
||||
summary.setCounselorName((String) result.get(0).get("nick_name"));
|
||||
try {
|
||||
String counselorName = jdbcTemplate.queryForObject(sql, String.class, batchGroup.getCounselorId());
|
||||
summary.setCounselorName(counselorName);
|
||||
} catch (Exception e) {
|
||||
log.error("查询咨询师姓名失败,counselorId: {}", batchGroup.getCounselorId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// office字段直接从排班记录中获取
|
||||
summary.setOffice(batchGroup.getOffice());
|
||||
|
||||
// 确保创建时间不为空
|
||||
if (summary.getCreateTime() == null) {
|
||||
summary.setCreateTime(new Date());
|
||||
@@ -226,12 +247,17 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
// 查询咨询师姓名
|
||||
if (firstSchedule.getCounselorId() != null) {
|
||||
String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?";
|
||||
List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, firstSchedule.getCounselorId());
|
||||
if (!result.isEmpty()) {
|
||||
summary.setCounselorName((String) result.get(0).get("nick_name"));
|
||||
try {
|
||||
String counselorName = jdbcTemplate.queryForObject(sql, String.class, firstSchedule.getCounselorId());
|
||||
summary.setCounselorName(counselorName);
|
||||
} catch (Exception e) {
|
||||
log.error("查询咨询师姓名失败,counselorId: {}", firstSchedule.getCounselorId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// office字段直接从排班记录中获取
|
||||
summary.setOffice(firstSchedule.getOffice());
|
||||
|
||||
summaryList.add(summary);
|
||||
}
|
||||
}
|
||||
@@ -283,9 +309,12 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检查是否至少启用了一个时段
|
||||
if ((batchBo.getMorningEnabled() == null || !batchBo.getMorningEnabled()) &&
|
||||
(batchBo.getAfternoonEnabled() == null || !batchBo.getAfternoonEnabled())) {
|
||||
// 检查是否至少启用了一个时段(兼容新旧版本)
|
||||
boolean hasMorning = (batchBo.getMorningEnabled() != null && batchBo.getMorningEnabled());
|
||||
boolean hasNoon = (batchBo.getNoonEnabled() != null && batchBo.getNoonEnabled());
|
||||
boolean hasAfternoon = (batchBo.getAfternoonEnabled() != null && batchBo.getAfternoonEnabled());
|
||||
|
||||
if (!hasMorning && !hasNoon && !hasAfternoon) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -321,12 +350,13 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
|
||||
// 如果是周末且设置了跳过周末,则跳过
|
||||
if (!(isWeekend && Boolean.TRUE.equals(batchBo.getSkipWeekends()))) {
|
||||
// 创建上午时段
|
||||
// 创建中午时段(字段名保持morning以兼容前端)
|
||||
if (Boolean.TRUE.equals(batchBo.getMorningEnabled())) {
|
||||
XljkConsultationSchedule morningSchedule = new XljkConsultationSchedule();
|
||||
morningSchedule.setCounselorId(batchBo.getCounselorId());
|
||||
morningSchedule.setOffice(batchBo.getOffice());
|
||||
morningSchedule.setScheduleDate(currentDate);
|
||||
morningSchedule.setTimeSlot("0"); // 上午
|
||||
morningSchedule.setTimeSlot("0"); // 中午
|
||||
morningSchedule.setStartTime(batchBo.getMorningStartTime());
|
||||
morningSchedule.setEndTime(batchBo.getMorningEndTime());
|
||||
morningSchedule.setStatus(batchBo.getStatus());
|
||||
@@ -335,18 +365,54 @@ public class XljkConsultationScheduleServiceImpl implements IXljkConsultationSch
|
||||
schedules.add(morningSchedule);
|
||||
}
|
||||
|
||||
// 创建下午时段
|
||||
// 创建中午时段(新版本)
|
||||
if (Boolean.TRUE.equals(batchBo.getNoonEnabled()) && batchBo.getNoonTimeSlots() != null) {
|
||||
for (XljkConsultationScheduleBatchBo.TimeSlot timeSlot : batchBo.getNoonTimeSlots()) {
|
||||
XljkConsultationSchedule noonSchedule = new XljkConsultationSchedule();
|
||||
noonSchedule.setCounselorId(batchBo.getCounselorId());
|
||||
noonSchedule.setOffice(batchBo.getOffice());
|
||||
noonSchedule.setScheduleDate(currentDate);
|
||||
noonSchedule.setTimeSlot("0"); // 中午时段
|
||||
noonSchedule.setStartTime(timeSlot.getStartTime());
|
||||
noonSchedule.setEndTime(timeSlot.getEndTime());
|
||||
noonSchedule.setStatus(batchBo.getStatus());
|
||||
noonSchedule.setBatchId(batchId); // 设置批次ID
|
||||
noonSchedule.setCreateTime(now); // 设置创建时间
|
||||
schedules.add(noonSchedule);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建下午时段(兼容新旧版本)
|
||||
if (Boolean.TRUE.equals(batchBo.getAfternoonEnabled())) {
|
||||
XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule();
|
||||
afternoonSchedule.setCounselorId(batchBo.getCounselorId());
|
||||
afternoonSchedule.setScheduleDate(currentDate);
|
||||
afternoonSchedule.setTimeSlot("1"); // 下午
|
||||
afternoonSchedule.setStartTime(batchBo.getAfternoonStartTime());
|
||||
afternoonSchedule.setEndTime(batchBo.getAfternoonEndTime());
|
||||
afternoonSchedule.setStatus(batchBo.getStatus());
|
||||
afternoonSchedule.setBatchId(batchId); // 设置批次ID
|
||||
afternoonSchedule.setCreateTime(now); // 设置创建时间
|
||||
schedules.add(afternoonSchedule);
|
||||
if (batchBo.getAfternoonTimeSlots() != null && !batchBo.getAfternoonTimeSlots().isEmpty()) {
|
||||
// 新版本:支持多时段
|
||||
for (XljkConsultationScheduleBatchBo.TimeSlot timeSlot : batchBo.getAfternoonTimeSlots()) {
|
||||
XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule();
|
||||
afternoonSchedule.setCounselorId(batchBo.getCounselorId());
|
||||
afternoonSchedule.setOffice(batchBo.getOffice());
|
||||
afternoonSchedule.setScheduleDate(currentDate);
|
||||
afternoonSchedule.setTimeSlot("1"); // 下午
|
||||
afternoonSchedule.setStartTime(timeSlot.getStartTime());
|
||||
afternoonSchedule.setEndTime(timeSlot.getEndTime());
|
||||
afternoonSchedule.setStatus(batchBo.getStatus());
|
||||
afternoonSchedule.setBatchId(batchId); // 设置批次ID
|
||||
afternoonSchedule.setCreateTime(now); // 设置创建时间
|
||||
schedules.add(afternoonSchedule);
|
||||
}
|
||||
} else if (batchBo.getAfternoonStartTime() != null && batchBo.getAfternoonEndTime() != null) {
|
||||
// 旧版本:单时段
|
||||
XljkConsultationSchedule afternoonSchedule = new XljkConsultationSchedule();
|
||||
afternoonSchedule.setCounselorId(batchBo.getCounselorId());
|
||||
afternoonSchedule.setOffice(batchBo.getOffice());
|
||||
afternoonSchedule.setScheduleDate(currentDate);
|
||||
afternoonSchedule.setTimeSlot("1"); // 下午
|
||||
afternoonSchedule.setStartTime(batchBo.getAfternoonStartTime());
|
||||
afternoonSchedule.setEndTime(batchBo.getAfternoonEndTime());
|
||||
afternoonSchedule.setStatus(batchBo.getStatus());
|
||||
afternoonSchedule.setBatchId(batchId); // 设置批次ID
|
||||
afternoonSchedule.setCreateTime(now); // 设置创建时间
|
||||
schedules.add(afternoonSchedule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
package org.dromara.xljk.service.impl;
|
||||
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.dromara.xljk.domain.bo.XljkConsulterBo;
|
||||
import org.dromara.xljk.domain.vo.XljkConsulterVo;
|
||||
import org.dromara.xljk.domain.XljkConsulter;
|
||||
import org.dromara.xljk.domain.XljkUserWithDeptInfo;
|
||||
import org.dromara.xljk.mapper.XljkConsulterMapper;
|
||||
import org.dromara.xljk.service.IXljkConsulterService;
|
||||
import org.dromara.system.service.ISysUserService;
|
||||
import org.dromara.system.service.ISysRoleService;
|
||||
import org.dromara.system.domain.vo.SysUserVo;
|
||||
import org.dromara.system.domain.vo.SysRoleVo;
|
||||
import org.dromara.system.domain.SysRole;
|
||||
import org.dromara.system.domain.SysUserRole;
|
||||
import org.dromara.system.domain.bo.SysRoleBo;
|
||||
import org.dromara.system.mapper.SysUserRoleMapper;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 心理咨询师配置Service业务层处理
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2024-01-01
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class XljkConsulterServiceImpl implements IXljkConsulterService {
|
||||
|
||||
private final XljkConsulterMapper baseMapper;
|
||||
private final ISysUserService sysUserService;
|
||||
private final ISysRoleService sysRoleService;
|
||||
private final SysUserRoleMapper sysUserRoleMapper;
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置
|
||||
*/
|
||||
@Override
|
||||
public XljkConsulterVo queryById(Long id) {
|
||||
XljkConsulter entity = baseMapper.selectById(id);
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return MapstructUtils.convert(entity, XljkConsulterVo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XljkConsulterVo> queryPageList(XljkConsulterBo bo, PageQuery pageQuery) {
|
||||
// 直接使用查询条件构建器
|
||||
LambdaQueryWrapper<XljkConsulter> lqw = buildQueryWrapper(bo);
|
||||
Page<XljkConsulter> entityPage = baseMapper.selectPage(pageQuery.build(), lqw);
|
||||
|
||||
// 转换为 VO 对象
|
||||
List<XljkConsulterVo> voList = new ArrayList<>();
|
||||
for (XljkConsulter entity : entityPage.getRecords()) {
|
||||
XljkConsulterVo vo = MapstructUtils.convert(entity, XljkConsulterVo.class);
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
// 构建分页结果
|
||||
Page<XljkConsulterVo> voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
|
||||
voPage.setRecords(voList);
|
||||
|
||||
return TableDataInfo.build(voPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<XljkConsulterVo> queryList(XljkConsulterBo bo) {
|
||||
LambdaQueryWrapper<XljkConsulter> lqw = buildQueryWrapper(bo);
|
||||
List<XljkConsulter> entityList = baseMapper.selectList(lqw);
|
||||
|
||||
// 转换为 VO 对象
|
||||
List<XljkConsulterVo> voList = new ArrayList<>();
|
||||
for (XljkConsulter entity : entityList) {
|
||||
XljkConsulterVo vo = MapstructUtils.convert(entity, XljkConsulterVo.class);
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
return voList;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<XljkConsulter> buildQueryWrapper(XljkConsulterBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<XljkConsulter> lqw = Wrappers.<XljkConsulter>lambdaQuery();
|
||||
lqw.eq(bo.getUserId() != null, XljkConsulter::getUserId, bo.getUserId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getUserName()), XljkConsulter::getUserName, bo.getUserName());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getNickName()), XljkConsulter::getNickName, bo.getNickName());
|
||||
lqw.eq(bo.getDeptId() != null, XljkConsulter::getDeptId, bo.getDeptId());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XljkConsulter::getStatus, bo.getStatus());
|
||||
lqw.eq(bo.getConfigBy() != null, XljkConsulter::getConfigBy, bo.getConfigBy());
|
||||
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
|
||||
XljkConsulter::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
|
||||
lqw.orderByDesc(XljkConsulter::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增心理咨询师配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(XljkConsulterBo bo) {
|
||||
XljkConsulter add = MapstructUtils.convert(bo, XljkConsulter.class);
|
||||
validEntityBeforeSave(add);
|
||||
|
||||
// 设置配置人信息
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
add.setConfigBy(currentUserId);
|
||||
|
||||
// 获取当前用户信息设置配置人姓名
|
||||
try {
|
||||
SysUserVo currentUser = sysUserService.selectUserById(currentUserId);
|
||||
if (currentUser != null) {
|
||||
add.setConfigByName(currentUser.getUserName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果获取失败,使用默认值
|
||||
add.setConfigByName("系统管理员");
|
||||
}
|
||||
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改心理咨询师配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(XljkConsulterBo bo) {
|
||||
XljkConsulter update = MapstructUtils.convert(bo, XljkConsulter.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(XljkConsulter entity) {
|
||||
if (entity.getUserId() != null) {
|
||||
// 检查用户是否已存在(新增时或修改用户ID时)
|
||||
if (entity.getId() == null) {
|
||||
// 新增时检查
|
||||
int count = baseMapper.checkUserExists(entity.getUserId());
|
||||
if (count > 0) {
|
||||
throw new ServiceException("该用户已配置为咨询师,不能重复添加");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除心理咨询师配置
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids) {
|
||||
return baseMapper.deleteBatchIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加咨询师
|
||||
*/
|
||||
@Override
|
||||
public Boolean batchAddConsulters(List<Long> userIds, String remark) {
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
throw new ServiceException("用户ID列表不能为空");
|
||||
}
|
||||
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
String currentUserName = "系统管理员";
|
||||
|
||||
// 获取当前用户信息
|
||||
try {
|
||||
SysUserVo currentUser = sysUserService.selectUserById(currentUserId);
|
||||
if (currentUser != null) {
|
||||
currentUserName = currentUser.getUserName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 使用默认值
|
||||
}
|
||||
|
||||
// 1. 获取完整的用户信息(包含部门信息)
|
||||
List<XljkUserWithDeptInfo> users = baseMapper.selectUsersWithDeptInfo(userIds);
|
||||
if (users.isEmpty()) {
|
||||
throw new ServiceException("没有找到有效的用户信息");
|
||||
}
|
||||
|
||||
// 2. 过滤已存在的咨询师
|
||||
List<Long> existingUserIds = baseMapper.selectList(Wrappers.<XljkConsulter>lambdaQuery()
|
||||
.in(XljkConsulter::getUserId, userIds))
|
||||
.stream().map(XljkConsulter::getUserId).collect(Collectors.toList());
|
||||
|
||||
// 3. 构建新增咨询师列表
|
||||
List<XljkConsulter> consulterList = new ArrayList<>();
|
||||
for (XljkUserWithDeptInfo user : users) {
|
||||
if (!existingUserIds.contains(user.getUserId())) {
|
||||
XljkConsulter consulter = new XljkConsulter();
|
||||
consulter.setUserId(user.getUserId());
|
||||
consulter.setUserName(user.getUserName());
|
||||
consulter.setNickName(user.getNickName());
|
||||
consulter.setDeptId(user.getDeptId());
|
||||
consulter.setDeptName(user.getDeptName() != null ? user.getDeptName() : "");
|
||||
consulter.setPhoneNumber(user.getPhonenumber());
|
||||
consulter.setEmail(user.getEmail());
|
||||
consulter.setStatus("1"); // 默认正常状态
|
||||
consulter.setRemark(remark);
|
||||
consulter.setConfigBy(currentUserId);
|
||||
consulter.setConfigByName(currentUserName);
|
||||
consulterList.add(consulter);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量保存
|
||||
if (consulterList.isEmpty()) {
|
||||
throw new ServiceException("所有用户都已存在,无需重复添加");
|
||||
}
|
||||
|
||||
boolean result = baseMapper.insertBatch(consulterList);
|
||||
|
||||
// 5. 为新添加的咨询师自动分配 xljk_consultant 角色
|
||||
if (result) {
|
||||
assignConsultantRole(consulterList);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为咨询师分配 xljk_consultant 角色
|
||||
*/
|
||||
private void assignConsultantRole(List<XljkConsulter> consulterList) {
|
||||
try {
|
||||
// 1. 根据角色键查询角色信息
|
||||
SysRoleBo roleBo = new SysRoleBo();
|
||||
roleBo.setRoleKey("xljk_consultant");
|
||||
List<SysRoleVo> roleList = sysRoleService.selectRoleList(roleBo);
|
||||
|
||||
if (roleList == null || roleList.isEmpty()) {
|
||||
throw new ServiceException("未找到角色键为 'xljk_consultant' 的角色,请联系管理员创建该角色");
|
||||
}
|
||||
|
||||
Long roleId = roleList.get(0).getRoleId();
|
||||
|
||||
// 2. 为每个用户分配角色
|
||||
List<SysUserRole> userRoleList = new ArrayList<>();
|
||||
for (XljkConsulter consulter : consulterList) {
|
||||
// 检查用户是否已有该角色
|
||||
Long count = sysUserRoleMapper.selectCount(
|
||||
Wrappers.<SysUserRole>lambdaQuery()
|
||||
.eq(SysUserRole::getUserId, consulter.getUserId())
|
||||
.eq(SysUserRole::getRoleId, roleId)
|
||||
);
|
||||
|
||||
// 如果用户还没有该角色,则添加
|
||||
if (count == 0) {
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(consulter.getUserId());
|
||||
userRole.setRoleId(roleId);
|
||||
userRoleList.add(userRole);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 批量插入用户角色关联
|
||||
if (!userRoleList.isEmpty()) {
|
||||
sysUserRoleMapper.insertBatch(userRoleList);
|
||||
System.out.println("成功为 " + userRoleList.size() + " 个用户分配了 'xljk_consultant' 角色");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 记录错误但不影响主流程
|
||||
System.err.println("为咨询师分配角色失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的咨询师列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XljkConsulterVo> queryMyConfigList(XljkConsulterBo bo, PageQuery pageQuery) {
|
||||
bo.setConfigBy(LoginHelper.getUserId());
|
||||
return queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
*/
|
||||
@Override
|
||||
public Boolean enableConsulters(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.enableConsultersByIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
*/
|
||||
@Override
|
||||
public Boolean disableConsulters(List<Long> ids) {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return baseMapper.disableConsultersByIds(ids) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?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="org.dromara.xljk.mapper.XljkConsulterMapper">
|
||||
|
||||
<resultMap type="org.dromara.xljk.domain.vo.XljkConsulterVo" id="XljkConsulterVoResult">
|
||||
<result property="id" column="id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
<result property="nickName" column="nick_name"/>
|
||||
<result property="deptId" column="dept_id"/>
|
||||
<result property="deptName" column="dept_name"/>
|
||||
<result property="phoneNumber" column="phone_number"/>
|
||||
<result property="office" column="office"/>
|
||||
<result property="email" column="email"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="configBy" column="config_by"/>
|
||||
<result property="configByName" column="config_by_name"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="createDept" column="create_dept"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectXljkConsulterVo">
|
||||
select id, user_id, user_name, nick_name, dept_id, dept_name, phone_number, office, email, status, remark, config_by, config_by_name, tenant_id, create_dept, create_by, create_time, update_by, update_time from xljk_consulter
|
||||
</sql>
|
||||
|
||||
<select id="selectXljkConsulterVoById" parameterType="Long" resultMap="XljkConsulterVoResult">
|
||||
<include refid="selectXljkConsulterVo"/>
|
||||
where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="selectXljkConsulterVoList" parameterType="org.dromara.xljk.domain.XljkConsulter" resultMap="XljkConsulterVoResult">
|
||||
<include refid="selectXljkConsulterVo"/>
|
||||
<where>
|
||||
<if test="userId != null">and user_id = #{userId}</if>
|
||||
<if test="userName != null and userName != ''">and user_name like concat('%', #{userName}, '%')</if>
|
||||
<if test="nickName != null and nickName != ''">and nick_name like concat('%', #{nickName}, '%')</if>
|
||||
<if test="deptId != null">and dept_id = #{deptId}</if>
|
||||
<if test="status != null and status != ''">and status = #{status}</if>
|
||||
<if test="configBy != null">and config_by = #{configBy}</if>
|
||||
<if test="params.beginCreateTime != null and params.beginCreateTime != '' and params.endCreateTime != null and params.endCreateTime != ''">
|
||||
and create_time between #{params.beginCreateTime} and #{params.endCreateTime}
|
||||
</if>
|
||||
</where>
|
||||
order by create_time desc
|
||||
</select>
|
||||
|
||||
<!-- 检查用户是否已存在 -->
|
||||
<select id="checkUserExists" resultType="int">
|
||||
SELECT COUNT(1) FROM xljk_consulter WHERE user_id = #{userId} AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 批量启用咨询师 -->
|
||||
<update id="enableConsultersByIds">
|
||||
UPDATE xljk_consulter SET status = '1' WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 批量停用咨询师 -->
|
||||
<update id="disableConsultersByIds">
|
||||
UPDATE xljk_consulter SET status = '0' WHERE id IN
|
||||
<foreach collection="ids" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND deleted = 0
|
||||
</update>
|
||||
|
||||
<!-- 查询完整的用户信息(包含部门信息)用于批量添加 -->
|
||||
<select id="selectUsersWithDeptInfo" resultType="org.dromara.xljk.domain.XljkUserWithDeptInfo">
|
||||
SELECT
|
||||
u.user_id as userId,
|
||||
u.user_name as userName,
|
||||
u.nick_name as nickName,
|
||||
u.dept_id as deptId,
|
||||
d.dept_name as deptName,
|
||||
u.phonenumber as phonenumber,
|
||||
u.email as email,
|
||||
u.status as status
|
||||
FROM sys_user u
|
||||
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
|
||||
WHERE u.user_id IN
|
||||
<foreach collection="userIds" item="userId" open="(" separator="," close=")">
|
||||
#{userId}
|
||||
</foreach>
|
||||
AND u.del_flag = '0'
|
||||
AND u.status = '0'
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -33,6 +33,19 @@ export const getAvailableDetailSlots = (date, timeSlot) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取日历视图的排班数据(带预约状态)- 直接从xljk_consultation_schedule表查询
|
||||
* @param {string} startDate 开始日期 YYYY-MM-DD
|
||||
* @param {string} endDate 结束日期 YYYY-MM-DD
|
||||
*/
|
||||
export const getCalendarSchedules = (startDate, endDate) => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/calendar-schedules',
|
||||
method: 'GET',
|
||||
params: { startDate, endDate }
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 预约记录管理 API =====
|
||||
|
||||
/**
|
||||
@@ -171,6 +184,16 @@ export const getClassOptions = (collegeName, majorName) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取咨询主题选项(字典数据)
|
||||
*/
|
||||
export const getConsultThemeOptions = () => {
|
||||
return request({
|
||||
url: '/system/dict/data/type/xljk_consult_theme',
|
||||
method: 'GET'
|
||||
});
|
||||
};
|
||||
|
||||
// ===== 移动端专用辅助方法 =====
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
<view class="filter-section">
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
v-for="(tab, index) in filterTabs"
|
||||
v-for="(tab, index) in visibleFilterTabs"
|
||||
:key="index"
|
||||
class="filter-tab"
|
||||
:class="{ active: activeFilterIndex === index }"
|
||||
@click="switchFilter(index)"
|
||||
:class="{ active: getOriginalTabIndex(index) === activeFilterIndex }"
|
||||
@click="switchFilter(getOriginalTabIndex(index))"
|
||||
>
|
||||
<text class="tab-text">{{ tab.label }}</text>
|
||||
<!-- 只有待咨询标签显示圆形标记 -->
|
||||
@@ -71,12 +71,15 @@
|
||||
></u-avatar>
|
||||
</view>
|
||||
<view class="counselor-info">
|
||||
<text class="counselor-name">{{ appointment.counselorName || '未知咨询师' }}</text>
|
||||
<view class="counselor-name-row">
|
||||
<text class="counselor-name">{{ appointment.counselorName || '未知咨询师' }}</text>
|
||||
<text class="appointment-office">{{ appointment.office || '暂无办公室信息' }}</text>
|
||||
</view>
|
||||
<text class="appointment-reason">{{ appointment.reason || '心理咨询' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<view v-if="appointment.status === '0'" class="inline-action">
|
||||
<view v-if="appointment.status === '0' || appointment.status === '4'" class="inline-action">
|
||||
<button
|
||||
class="cancel-button-inline"
|
||||
@click.stop="handleCancelClick(appointment)"
|
||||
@@ -113,7 +116,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMyAppointments, cancelAppointment } from '@/api/xljk'
|
||||
import { getMyAppointments, cancelAppointment, getCounselorOptions } from '@/api/xljk'
|
||||
|
||||
export default {
|
||||
name: 'MyAppointments',
|
||||
@@ -124,8 +127,10 @@ export default {
|
||||
{ label: '全部', value: 'all', count: 0 },
|
||||
{ label: '待咨询', value: '0', count: 0 },
|
||||
{ label: '已完成', value: '1', count: 0 },
|
||||
{ label: '已取消', value: '2', count: 0 },
|
||||
{ label: '未到访', value: '3', count: 0 }
|
||||
{ label: '已取消', value: '2', count: 0, hidden: true }, // 隐藏已取消标签
|
||||
{ label: '未到访', value: '3', count: 0 },
|
||||
{ label: '待审核', value: '4', count: 0, hidden: true }, // 隐藏待审核标签
|
||||
{ label: '已驳回', value: '5', count: 0 }
|
||||
],
|
||||
activeFilterIndex: 0,
|
||||
|
||||
@@ -140,6 +145,12 @@ export default {
|
||||
cancelingAppointmentId: null // 正在取消的预约ID
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取可见的筛选标签(过滤掉隐藏的标签)
|
||||
visibleFilterTabs() {
|
||||
return this.filterTabs.filter(tab => !tab.hidden)
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 初始化时重置取消相关状态
|
||||
this.resetCancelState()
|
||||
@@ -152,6 +163,12 @@ export default {
|
||||
this.loadAppointments()
|
||||
},
|
||||
methods: {
|
||||
// 获取可见标签在原始数组中的索引
|
||||
getOriginalTabIndex(visibleIndex) {
|
||||
const visibleTab = this.visibleFilterTabs[visibleIndex]
|
||||
return this.filterTabs.findIndex(tab => tab.value === visibleTab.value)
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
@@ -203,14 +220,38 @@ export default {
|
||||
response.data.forEach((item, index) => {
|
||||
console.log(`🔍 [我的预约] 数据${index}: ID=${item.appointmentId}, status=${item.status} (${typeof item.status})`)
|
||||
})
|
||||
|
||||
// 获取咨询师信息用于补充办公室字段
|
||||
let counselors = []
|
||||
try {
|
||||
const counselorRes = await getCounselorOptions()
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data
|
||||
console.log('🔍 [我的预约] 获取到咨询师列表:', counselors)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败:', error)
|
||||
}
|
||||
|
||||
// 转换API数据格式
|
||||
const appointments = response.data.map(item => {
|
||||
// 补充办公室信息
|
||||
let office = item.office || '暂无办公室信息'
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find(c => c.id === item.counselorId)
|
||||
if (counselor && counselor.office) {
|
||||
office = counselor.office
|
||||
console.log('🔍 [我的预约] 补充咨询师办公室信息:', { counselorId: item.counselorId, office: counselor.office })
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.id, // 修复:使用 id 而不是 appointmentId
|
||||
appointmentDate: item.appointmentDate,
|
||||
appointmentTime: item.appointmentDetailTime || item.appointmentTime,
|
||||
counselorName: item.counselorName,
|
||||
counselorAvatar: '',
|
||||
office: office, // 使用补充后的办公室信息
|
||||
reason: item.reason,
|
||||
status: String(item.status), // 转换为字符串,便于筛选比较
|
||||
originalData: item
|
||||
@@ -249,14 +290,18 @@ export default {
|
||||
const status1Count = this.allAppointments.filter(item => item.status === '1').length
|
||||
const status2Count = this.allAppointments.filter(item => item.status === '2').length
|
||||
const status3Count = this.allAppointments.filter(item => item.status === '3').length
|
||||
const status4Count = this.allAppointments.filter(item => item.status === '4').length
|
||||
const status5Count = this.allAppointments.filter(item => item.status === '5').length
|
||||
|
||||
console.log('🔍 [我的预约] 状态统计: 待咨询(0)=', status0Count, ', 已完成(1)=', status1Count, ', 已取消(2)=', status2Count, ', 未到访(3)=', status3Count)
|
||||
console.log('🔍 [我的预约] 状态统计: 待咨询(0)=', status0Count, ', 已完成(1)=', status1Count, ', 已取消(2)=', status2Count, ', 未到访(3)=', status3Count, ', 待审核(4)=', status4Count, ', 已驳回(5)=', status5Count)
|
||||
|
||||
this.filterTabs[0].count = this.allAppointments.length // 全部
|
||||
this.filterTabs[1].count = status0Count // 待咨询
|
||||
this.filterTabs[2].count = status1Count // 已完成
|
||||
this.filterTabs[3].count = status2Count // 已取消
|
||||
this.filterTabs[4].count = status3Count // 未到访
|
||||
this.filterTabs[5].count = status4Count // 待审核
|
||||
this.filterTabs[6].count = status5Count // 已驳回
|
||||
},
|
||||
|
||||
// 切换筛选器
|
||||
@@ -288,6 +333,8 @@ export default {
|
||||
'1': '已完成',
|
||||
'2': '已取消',
|
||||
'3': '未到访',
|
||||
'4': '待审核',
|
||||
'5': '已驳回',
|
||||
// 兼容英文状态值
|
||||
'pending': '待咨询',
|
||||
'completed': '已完成',
|
||||
@@ -304,6 +351,8 @@ export default {
|
||||
'1': 'success',
|
||||
'2': 'primary', // 已取消状态使用蓝色
|
||||
'3': 'info', // 未到访状态使用灰色
|
||||
'4': 'info', // 待审核状态使用灰色
|
||||
'5': 'error', // 已驳回状态使用红色
|
||||
// 兼容英文状态值
|
||||
'pending': 'warning',
|
||||
'completed': 'success',
|
||||
@@ -321,7 +370,9 @@ export default {
|
||||
'0': '暂无待咨询预约',
|
||||
'1': '暂无已完成预约',
|
||||
'2': '暂无已取消预约',
|
||||
'3': '暂无未到访预约'
|
||||
'3': '暂无未到访预约',
|
||||
'4': '暂无待审核预约',
|
||||
'5': '暂无已驳回预约'
|
||||
}
|
||||
return textMap[currentFilter.value] || '暂无数据'
|
||||
},
|
||||
@@ -355,9 +406,9 @@ export default {
|
||||
cancelAppointment(appointment) {
|
||||
console.log('点击取消预约按钮,预约信息:', appointment)
|
||||
|
||||
if (appointment.status !== '0') {
|
||||
if (appointment.status !== '0' && appointment.status !== '4') {
|
||||
uni.showToast({
|
||||
title: '只能取消待咨询的预约',
|
||||
title: '只能取消待咨询或待审核的预约',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
@@ -670,12 +721,26 @@ export default {
|
||||
.counselor-info {
|
||||
flex: 1;
|
||||
|
||||
.counselor-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
.counselor-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6rpx;
|
||||
|
||||
.counselor-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.appointment-office {
|
||||
font-size: 24rpx;
|
||||
color: #1890ff;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.appointment-reason {
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
|
||||
<!-- 分组时间段列表 -->
|
||||
<view v-else-if="timeSlots.length > 0" class="grouped-time-slots">
|
||||
<!-- 上午时段组 -->
|
||||
<!-- 中午时段组 -->
|
||||
<view v-if="groupedTimeSlots.morning.length > 0" class="time-group">
|
||||
<view class="group-header">
|
||||
<text class="group-title">上午时段</text>
|
||||
<text class="group-title">中午时段</text>
|
||||
</view>
|
||||
<view class="slots-grid">
|
||||
<view
|
||||
@@ -133,6 +133,30 @@
|
||||
<text class="detail-label">咨询师:</text>
|
||||
<text class="detail-value">{{ selectedBookingSlot && selectedBookingSlot.counselorName }}</text>
|
||||
</view>
|
||||
<view class="booking-detail-item">
|
||||
<text class="detail-label">办公室:</text>
|
||||
<text class="detail-value">{{ selectedBookingSlot && selectedBookingSlot.office || '暂无信息' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 咨询主题选择 -->
|
||||
<view class="booking-detail-item">
|
||||
<text class="detail-label required">咨询主题:</text>
|
||||
</view>
|
||||
<view class="theme-select-container">
|
||||
<u-picker
|
||||
:show="showThemePicker"
|
||||
:columns="themeColumns"
|
||||
@confirm="onThemeConfirm"
|
||||
@cancel="showThemePicker = false"
|
||||
keyName="dictLabel"
|
||||
></u-picker>
|
||||
<view class="theme-select-input" @click="showThemePicker = true">
|
||||
<text class="theme-select-text" :class="{ placeholder: !selectedTheme }">
|
||||
{{ selectedTheme ? selectedTheme.dictLabel : '请选择咨询主题' }}
|
||||
</text>
|
||||
<u-icon name="arrow-down" size="14" color="#c0c4cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 咨询原因输入框 -->
|
||||
<view class="booking-detail-item reason-item">
|
||||
@@ -158,7 +182,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAvailableTimeSlots, getAvailableDetailSlots, submitAppointment } from '@/api/xljk'
|
||||
import { getAvailableTimeSlots, getAvailableDetailSlots, getCalendarSchedules, submitAppointment, getConsultThemeOptions, getCounselorOptions } from '@/api/xljk'
|
||||
|
||||
export default {
|
||||
name: 'XljkReservation',
|
||||
@@ -182,7 +206,13 @@ export default {
|
||||
showBookingModal: false,
|
||||
selectedBookingSlot: null,
|
||||
bookingLoading: false,
|
||||
consultationReason: ''
|
||||
consultationReason: '',
|
||||
|
||||
// 咨询主题相关
|
||||
showThemePicker: false,
|
||||
themeOptions: [],
|
||||
themeColumns: [[]],
|
||||
selectedTheme: null
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
@@ -190,6 +220,7 @@ export default {
|
||||
console.log('🔍 [预约页面] 开始初始化')
|
||||
this.initWeekDates()
|
||||
this.selectTodayAsDefault()
|
||||
this.loadConsultThemeOptions()
|
||||
console.log('🔍 [预约页面] 初始化完成')
|
||||
} catch (error) {
|
||||
console.error('🔍 [预约页面] 初始化失败:', error)
|
||||
@@ -286,88 +317,104 @@ export default {
|
||||
return `${date.getMonth() + 1}月${date.getDate()}日`
|
||||
},
|
||||
|
||||
// 加载时间段数据 - 优化为加载详细时段
|
||||
// 加载时间段数据 - 直接从xljk_consultation_schedule表获取
|
||||
async loadTimeSlots(date) {
|
||||
this.loadingTimeSlots = true
|
||||
this.timeSlots = []
|
||||
this.groupedTimeSlots = { morning: [], afternoon: [] }
|
||||
|
||||
try {
|
||||
// 1. 先获取大时段列表 - 增加参数验证
|
||||
// 参数验证
|
||||
if (!date) {
|
||||
throw new Error('日期参数无效')
|
||||
}
|
||||
|
||||
console.log('🔍 开始获取大时段数据,日期:', date)
|
||||
const bigSlotsResponse = await getAvailableTimeSlots(date, date)
|
||||
console.log('🔍 大时段API返回:', bigSlotsResponse)
|
||||
console.log('🔍 开始获取排班数据,日期:', date)
|
||||
|
||||
// 直接调用calendar-schedules接口获取排班数据(跟web端一样)
|
||||
const response = await getCalendarSchedules(date, date)
|
||||
console.log('🔍 排班数据API返回:', response)
|
||||
|
||||
// 验证API响应结构
|
||||
if (!bigSlotsResponse || typeof bigSlotsResponse !== 'object') {
|
||||
if (!response || typeof response !== 'object') {
|
||||
throw new Error('API响应格式异常')
|
||||
}
|
||||
|
||||
if (bigSlotsResponse.code === 200 && bigSlotsResponse.data && bigSlotsResponse.data.length > 0) {
|
||||
// 2. 并行获取所有详细时段 - 增加数据安全性检查
|
||||
const detailPromises = bigSlotsResponse.data
|
||||
.filter(bigSlot => bigSlot && bigSlot.timeSlot) // 过滤无效数据
|
||||
.map(bigSlot =>
|
||||
getAvailableDetailSlots(date, bigSlot.timeSlot)
|
||||
.then(response => ({
|
||||
bigSlot,
|
||||
detailSlots: response && response.data ? response.data : []
|
||||
}))
|
||||
.catch(error => {
|
||||
console.error(`获取时段 ${bigSlot.timeSlot} 的详细数据失败:`, error)
|
||||
return { bigSlot, detailSlots: [] }
|
||||
})
|
||||
)
|
||||
if (response.code === 200 && response.data && Array.isArray(response.data)) {
|
||||
// 如果排班数据缺少办公室信息,获取咨询师信息来补充
|
||||
let counselors = []
|
||||
const hasMissingOfficeInfo = response.data.some(schedule =>
|
||||
(!schedule.office || schedule.office.trim() === '') && schedule.counselorId
|
||||
)
|
||||
|
||||
const allDetailResults = await Promise.all(detailPromises)
|
||||
console.log('🔍 详细时段API返回:', allDetailResults)
|
||||
if (hasMissingOfficeInfo) {
|
||||
try {
|
||||
console.log('🔍 检测到缺少办公室信息,开始获取咨询师详细信息...')
|
||||
const counselorRes = await getCounselorOptions()
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data
|
||||
console.log('🔍 获取到咨询师列表:', counselors)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 扁平化并增强数据 - 增加数据安全性检查
|
||||
const allDetailSlots = []
|
||||
allDetailResults.forEach(({ bigSlot, detailSlots }) => {
|
||||
// 确保 bigSlot 和 detailSlots 都是有效的
|
||||
if (!bigSlot || !Array.isArray(detailSlots)) {
|
||||
console.warn('无效的时段数据:', { bigSlot, detailSlots })
|
||||
// 处理排班数据,转换为移动端需要的格式
|
||||
const scheduleSlots = []
|
||||
|
||||
response.data.forEach(schedule => {
|
||||
// 确保必要的字段存在
|
||||
if (!schedule || !schedule.startTime || !schedule.endTime) {
|
||||
console.warn('无效的排班数据:', schedule)
|
||||
return
|
||||
}
|
||||
|
||||
detailSlots.forEach(detail => {
|
||||
// 确保必要的字段存在
|
||||
if (!detail || !detail.timeSlot) {
|
||||
console.warn('无效的详细时段数据:', detail)
|
||||
return
|
||||
try {
|
||||
// 构建时间范围显示
|
||||
const timeRange = `${schedule.startTime}-${schedule.endTime}`
|
||||
|
||||
// 判断是否可预约
|
||||
const available = !schedule.isFullyBooked && !schedule.isExpired
|
||||
|
||||
// 补充办公室信息
|
||||
let office = schedule.office || '暂无信息'
|
||||
if ((!schedule.office || schedule.office.trim() === '') && schedule.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find(c => c.id === schedule.counselorId)
|
||||
if (counselor && counselor.office) {
|
||||
office = counselor.office
|
||||
console.log('🔍 补充排班办公室信息:', { scheduleId: schedule.scheduleId, counselorId: schedule.counselorId, office: counselor.office })
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
allDetailSlots.push({
|
||||
id: `${bigSlot.scheduleId || 'unknown'}_${detail.timeSlot}`,
|
||||
timeRange: detail.timeSlot,
|
||||
available: !detail.isBooked && !detail.isExpired,
|
||||
isBooked: Boolean(detail.isBooked),
|
||||
isExpired: Boolean(detail.isExpired),
|
||||
counselorName: bigSlot.counselorName || '未知咨询师',
|
||||
counselorId: bigSlot.counselorId || null,
|
||||
counselorAvatar: '',
|
||||
dateText: this.selectedDateText || '',
|
||||
originalData: {
|
||||
...detail,
|
||||
parentScheduleId: bigSlot.scheduleId || null,
|
||||
counselorId: bigSlot.counselorId || null,
|
||||
timeSlot: bigSlot.timeSlot || null
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('处理时段数据时出错:', err, { bigSlot, detail })
|
||||
}
|
||||
})
|
||||
scheduleSlots.push({
|
||||
id: `schedule_${schedule.scheduleId}`,
|
||||
timeRange: timeRange,
|
||||
available: available,
|
||||
isBooked: Boolean(schedule.isFullyBooked),
|
||||
isExpired: Boolean(schedule.isExpired),
|
||||
counselorName: schedule.counselorName || '未知咨询师',
|
||||
counselorId: schedule.counselorId || null,
|
||||
counselorAvatar: '',
|
||||
office: office, // 使用补充后的办公室信息
|
||||
dateText: this.selectedDateText || '',
|
||||
originalData: {
|
||||
scheduleId: schedule.scheduleId,
|
||||
counselorId: schedule.counselorId,
|
||||
timeSlot: schedule.timeSlot,
|
||||
startTime: schedule.startTime,
|
||||
endTime: schedule.endTime,
|
||||
available: available,
|
||||
office: office // 在原始数据中也保存补充后的办公室信息
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('处理排班数据时出错:', err, schedule)
|
||||
}
|
||||
})
|
||||
|
||||
// 4. 按时间排序 - 增加安全性检查
|
||||
allDetailSlots.sort((a, b) => {
|
||||
// 按时间排序
|
||||
scheduleSlots.sort((a, b) => {
|
||||
try {
|
||||
// 安全地获取时间用于排序
|
||||
const timeA = (a.timeRange && typeof a.timeRange === 'string') ? a.timeRange.split('-')[0] : '00:00'
|
||||
@@ -375,27 +422,27 @@ export default {
|
||||
return timeA.localeCompare(timeB)
|
||||
} catch (err) {
|
||||
console.error('时间排序出错:', err, { a, b })
|
||||
return 0 // 如果排序出错,保持原顺序
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
// 5. 按上午下午分组
|
||||
this.groupAndSetTimeSlots(allDetailSlots)
|
||||
// 按上午下午分组
|
||||
this.groupAndSetTimeSlots(scheduleSlots)
|
||||
|
||||
console.log('🔍 最终处理的详细时段数据:', {
|
||||
total: allDetailSlots.length,
|
||||
console.log('🔍 最终处理的排班时段数据:', {
|
||||
total: scheduleSlots.length,
|
||||
morning: this.groupedTimeSlots.morning.length,
|
||||
afternoon: this.groupedTimeSlots.afternoon.length
|
||||
})
|
||||
|
||||
} else {
|
||||
// 如果没有大时段数据
|
||||
// 如果没有排班数据
|
||||
this.timeSlots = []
|
||||
this.groupedTimeSlots = { morning: [], afternoon: [] }
|
||||
// 移除Toast提示,避免在没有排班时显示不必要的消息
|
||||
console.log('🔍 当天无排班数据')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载详细时段失败:', error)
|
||||
console.error('加载排班时段失败:', error)
|
||||
|
||||
// 网络错误时显示友好提示
|
||||
uni.showToast({
|
||||
@@ -412,7 +459,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// 按上午下午分组时段 - 增加安全性检查
|
||||
// 按时段分组 - 基于timeSlot字段分组(0-中午时段,1-下午时段)
|
||||
groupAndSetTimeSlots(detailSlots) {
|
||||
try {
|
||||
// 确保输入是有效数组
|
||||
@@ -423,35 +470,29 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
// 按上午下午分组 - 增加安全性检查
|
||||
// 基于timeSlot字段分组 - 增加安全性检查
|
||||
const morningSlots = detailSlots.filter(slot => {
|
||||
try {
|
||||
if (!slot || !slot.timeRange || typeof slot.timeRange !== 'string') {
|
||||
if (!slot || !slot.originalData || slot.originalData.timeSlot === undefined) {
|
||||
console.warn('缺少timeSlot数据的时段:', slot)
|
||||
return false
|
||||
}
|
||||
const timeParts = slot.timeRange.split(':')
|
||||
if (timeParts.length === 0) {
|
||||
return false
|
||||
}
|
||||
const hour = parseInt(timeParts[0])
|
||||
return !isNaN(hour) && hour < 12
|
||||
// timeSlot为0的是中午时段
|
||||
return slot.originalData.timeSlot === 0 || slot.originalData.timeSlot === '0'
|
||||
} catch (err) {
|
||||
console.warn('处理上午时段时出错:', err, slot)
|
||||
console.warn('处理中午时段时出错:', err, slot)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const afternoonSlots = detailSlots.filter(slot => {
|
||||
try {
|
||||
if (!slot || !slot.timeRange || typeof slot.timeRange !== 'string') {
|
||||
if (!slot || !slot.originalData || slot.originalData.timeSlot === undefined) {
|
||||
console.warn('缺少timeSlot数据的时段:', slot)
|
||||
return false
|
||||
}
|
||||
const timeParts = slot.timeRange.split(':')
|
||||
if (timeParts.length === 0) {
|
||||
return false
|
||||
}
|
||||
const hour = parseInt(timeParts[0])
|
||||
return !isNaN(hour) && hour >= 12
|
||||
// timeSlot为1的是下午时段
|
||||
return slot.originalData.timeSlot === 1 || slot.originalData.timeSlot === '1'
|
||||
} catch (err) {
|
||||
console.warn('处理下午时段时出错:', err, slot)
|
||||
return false
|
||||
@@ -459,13 +500,15 @@ export default {
|
||||
})
|
||||
|
||||
this.groupedTimeSlots = {
|
||||
morning: morningSlots,
|
||||
morning: morningSlots, // 实际上是中午时段
|
||||
afternoon: afternoonSlots
|
||||
}
|
||||
|
||||
// 保持原有的timeSlots格式兼容现有代码
|
||||
this.timeSlots = [...morningSlots, ...afternoonSlots]
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('分组时段时出错:', error)
|
||||
// 发生错误时设置为空数组
|
||||
@@ -479,6 +522,39 @@ export default {
|
||||
this.loadTimeSlots(this.selectedDate)
|
||||
},
|
||||
|
||||
// 加载咨询主题选项
|
||||
async loadConsultThemeOptions() {
|
||||
try {
|
||||
const response = await getConsultThemeOptions()
|
||||
console.log('🔍 咨询主题字典数据:', response)
|
||||
|
||||
if (response.code === 200 && response.data && Array.isArray(response.data)) {
|
||||
this.themeOptions = response.data // 直接使用所有字典数据
|
||||
this.themeColumns = [this.themeOptions]
|
||||
console.log('🔍 处理后的咨询主题选项:', this.themeOptions)
|
||||
} else {
|
||||
console.warn('咨询主题字典数据格式异常:', response)
|
||||
this.themeOptions = []
|
||||
this.themeColumns = [[]]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载咨询主题选项失败:', error)
|
||||
this.themeOptions = []
|
||||
this.themeColumns = [[]]
|
||||
// 不显示错误提示,避免影响用户体验
|
||||
}
|
||||
},
|
||||
|
||||
// 确认选择咨询主题
|
||||
onThemeConfirm(value) {
|
||||
console.log('🔍 选择的咨询主题:', value)
|
||||
if (value && value.value && value.value.length > 0) {
|
||||
this.selectedTheme = value.value[0]
|
||||
console.log('🔍 设置选中的咨询主题:', this.selectedTheme)
|
||||
}
|
||||
this.showThemePicker = false
|
||||
},
|
||||
|
||||
// 处理时间段点击
|
||||
handleTimeSlotClick(slot) {
|
||||
console.log('🔍 点击时间段:', slot)
|
||||
@@ -500,6 +576,7 @@ export default {
|
||||
}
|
||||
|
||||
this.selectedBookingSlot = slot
|
||||
this.selectedTheme = null // 重置咨询主题选择
|
||||
this.showBookingModal = true
|
||||
console.log('🔍 设置弹窗显示状态:', this.showBookingModal)
|
||||
console.log('🔍 选中的预约时段数据:', this.selectedBookingSlot)
|
||||
@@ -509,6 +586,15 @@ export default {
|
||||
async confirmBooking() {
|
||||
if (this.bookingLoading) return
|
||||
|
||||
// 验证咨询主题
|
||||
if (!this.selectedTheme || !this.selectedTheme.dictValue) {
|
||||
uni.showToast({
|
||||
title: '请选择咨询主题',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证咨询原因
|
||||
if (!this.consultationReason || this.consultationReason.trim() === '') {
|
||||
uni.showToast({
|
||||
@@ -567,12 +653,13 @@ export default {
|
||||
// 构造预约数据
|
||||
const appointmentData = {
|
||||
studentId: studentId,
|
||||
scheduleId: this.selectedBookingSlot.originalData.parentScheduleId,
|
||||
scheduleId: this.selectedBookingSlot.originalData.scheduleId,
|
||||
counselorId: this.selectedBookingSlot.originalData.counselorId,
|
||||
appointmentDate: this.selectedDate,
|
||||
appointmentTime: this.selectedBookingSlot.originalData.timeSlot,
|
||||
appointmentDetailTime: this.selectedBookingSlot.timeRange,
|
||||
reason: this.consultationReason.trim() // 使用用户输入的咨询原因
|
||||
reason: this.consultationReason.trim(), // 使用用户输入的咨询原因
|
||||
consultTheme: this.selectedTheme.dictValue // 咨询主题字典值
|
||||
}
|
||||
|
||||
// 调用真实API提交预约
|
||||
@@ -587,6 +674,7 @@ export default {
|
||||
this.showBookingModal = false
|
||||
this.selectedBookingSlot = null
|
||||
this.consultationReason = '' // 重置咨询原因
|
||||
this.selectedTheme = null // 重置咨询主题
|
||||
|
||||
// 刷新时间段数据
|
||||
this.refreshTimeSlots()
|
||||
@@ -625,6 +713,7 @@ export default {
|
||||
this.showBookingModal = false
|
||||
this.selectedBookingSlot = null
|
||||
this.consultationReason = '' // 重置咨询原因
|
||||
this.selectedTheme = null // 重置咨询主题
|
||||
},
|
||||
|
||||
|
||||
@@ -981,11 +1070,36 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.theme-select-container {
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.theme-select-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16rpx 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
background-color: #fafafa;
|
||||
min-height: 60rpx;
|
||||
|
||||
.theme-select-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
|
||||
&.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reason-input-container {
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 4rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import request from '@/utils/request'
|
||||
import { AxiosPromise } from 'axios'
|
||||
import { StglAuditorVO, StglAuditorForm, StglAuditorQuery } from './types'
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listStglAuditor = (query?: StglAuditorQuery): AxiosPromise<StglAuditorVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询社团管理审核人配置详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStglAuditor = (id: string | number): AxiosPromise<StglAuditorVO> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增社团管理审核人配置
|
||||
* @param data
|
||||
*/
|
||||
export const addStglAuditor = (data: StglAuditorForm) => {
|
||||
return request({
|
||||
url: '/stgl/auditor',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改社团管理审核人配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateStglAuditor = (data: StglAuditorForm) => {
|
||||
return request({
|
||||
url: '/stgl/auditor',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除社团管理审核人配置
|
||||
* @param id
|
||||
*/
|
||||
export const delStglAuditor = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出社团管理审核人配置
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const exportStglAuditor = (query?: StglAuditorQuery) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加审核人
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
*/
|
||||
export const batchAddAuditors = (userIds: number[], remark?: string) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/batchAdd',
|
||||
method: 'post',
|
||||
data: {
|
||||
userIds,
|
||||
remark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的审核人列表
|
||||
* @param query
|
||||
*/
|
||||
export const listMyConfigAuditors = (query?: StglAuditorQuery): AxiosPromise<StglAuditorVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/auditor/myConfig',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用审核人
|
||||
* @param ids
|
||||
*/
|
||||
export const enableAuditors = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/enable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用审核人
|
||||
* @param ids
|
||||
*/
|
||||
export const disableAuditors = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/disable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
* @param deptId
|
||||
*/
|
||||
export const listUserByDeptId = (deptId: string | number) => {
|
||||
return request({
|
||||
url: '/stgl/auditor/users/' + deptId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { PageQuery } from '@/api/types'
|
||||
|
||||
export interface StglAuditorVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface StglAuditorForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber?: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName?: string;
|
||||
}
|
||||
|
||||
export interface StglAuditorQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
'params[beginCreateTime]'?: string;
|
||||
'params[endCreateTime]'?: string;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { StglAnnualReviewAuditVO, StglAnnualReviewAuditForm, StglAnnualReviewAuditQuery } from './type';
|
||||
|
||||
/**
|
||||
* 查询社团年审审批列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const listStglAnnualReviewAudit = (query?: StglAnnualReviewAuditQuery): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询社团年审审批详细
|
||||
* @param id
|
||||
*/
|
||||
export const getStglAnnualReviewAudit = (id: string | number): AxiosPromise<StglAnnualReviewAuditVO> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据年审记录ID查询审批记录
|
||||
* @param annualReviewId
|
||||
*/
|
||||
export const getAnnualReviewAuditsByReviewId = (annualReviewId: string | number): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/byAnnualReview/' + annualReviewId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据审批人ID查询待审批记录
|
||||
* @param approverId
|
||||
*/
|
||||
export const getPendingAuditsByApproverId = (approverId: string | number): AxiosPromise<StglAnnualReviewAuditVO[]> => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/pending/' + approverId,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增社团年审审批
|
||||
* @param data
|
||||
*/
|
||||
export const addStglAnnualReviewAudit = (data: StglAnnualReviewAuditForm) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改社团年审审批
|
||||
* @param data
|
||||
*/
|
||||
export const updateStglAnnualReviewAudit = (data: StglAnnualReviewAuditForm) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除社团年审审批
|
||||
* @param id
|
||||
*/
|
||||
export const delStglAnnualReviewAudit = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 审批年审记录
|
||||
* @param auditId 审批记录ID
|
||||
* @param approveResult 审批结果
|
||||
* @param approveOpinion 审批意见
|
||||
* @param nextAuditors 下一步审核人列表(可选)
|
||||
*/
|
||||
export const approveAnnualReview = (
|
||||
auditId: string | number,
|
||||
approveResult: string,
|
||||
approveOpinion: string,
|
||||
nextAuditors?: Array<{userId: string | number; userName: string; nickName: string}>
|
||||
) => {
|
||||
return request({
|
||||
url: '/stgl/annualReviewAudit/approve',
|
||||
method: 'post',
|
||||
data: {
|
||||
auditId,
|
||||
approveResult,
|
||||
approveOpinion,
|
||||
nextAuditors: nextAuditors || []
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,182 @@
|
||||
export interface StglAnnualReviewAuditVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/college_approve/youth_league_approve)
|
||||
*/
|
||||
nodeCode: string;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/学院审批/校团委审批)
|
||||
*/
|
||||
nodeName: string;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2,3)
|
||||
*/
|
||||
nodeOrder: number;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
nodeStatus: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId: string | number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approverName: string;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
approveResult: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approveOpinion: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approveTime: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: string | number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: string | number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface StglAnnualReviewAuditForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId?: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码(advisor_approve/college_approve/youth_league_approve)
|
||||
*/
|
||||
nodeCode?: string;
|
||||
|
||||
/**
|
||||
* 节点名称(指导老师审批/学院审批/校团委审批)
|
||||
*/
|
||||
nodeName?: string;
|
||||
|
||||
/**
|
||||
* 节点顺序(1.1,1.2,2,3)
|
||||
*/
|
||||
nodeOrder?: number;
|
||||
|
||||
/**
|
||||
* 节点状态(pending/waiting/approved)
|
||||
*/
|
||||
nodeStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId?: string | number;
|
||||
|
||||
/**
|
||||
* 审批人姓名
|
||||
*/
|
||||
approverName?: string;
|
||||
|
||||
/**
|
||||
* 审批结果(通过/不通过)
|
||||
*/
|
||||
approveResult?: string;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
*/
|
||||
approveOpinion?: string;
|
||||
|
||||
/**
|
||||
* 审批时间
|
||||
*/
|
||||
approveTime?: string;
|
||||
}
|
||||
|
||||
export interface StglAnnualReviewAuditQuery extends PageQuery {
|
||||
/**
|
||||
* 年审记录ID
|
||||
*/
|
||||
annualReviewId?: string | number;
|
||||
|
||||
/**
|
||||
* 节点代码
|
||||
*/
|
||||
nodeCode?: string;
|
||||
|
||||
/**
|
||||
* 节点状态
|
||||
*/
|
||||
nodeStatus?: string;
|
||||
|
||||
/**
|
||||
* 审批人ID
|
||||
*/
|
||||
approverId?: string | number;
|
||||
|
||||
/**
|
||||
* 审批结果
|
||||
*/
|
||||
approveResult?: string;
|
||||
|
||||
/**
|
||||
* 审批时间范围
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批节点状态选项
|
||||
*/
|
||||
export const AUDIT_NODE_STATUS_OPTIONS = [
|
||||
{ label: '待审批', value: 'pending' },
|
||||
{ label: '等待中', value: 'waiting' },
|
||||
{ label: '已审批', value: 'approved' }
|
||||
];
|
||||
|
||||
/**
|
||||
* 审批结果选项
|
||||
*/
|
||||
export const APPROVE_RESULT_OPTIONS = [
|
||||
{ label: '通过', value: '通过' },
|
||||
{ label: '不通过', value: '不通过' }
|
||||
];
|
||||
@@ -4,18 +4,23 @@ import {
|
||||
XljkConsultationScheduleVO,
|
||||
XljkConsultationScheduleForm,
|
||||
XljkConsultationScheduleQuery,
|
||||
XljkConsultationScheduleSummaryVO,
|
||||
XljkConsultationAppointmentVO,
|
||||
XljkConsultationAppointmentForm,
|
||||
XljkConsultationAppointmentQuery,
|
||||
AvailableTimeSlot,
|
||||
TimeSlotDetail,
|
||||
CounselorOption
|
||||
CounselorOption,
|
||||
ScheduleWithStatus,
|
||||
XljkConsulterVO,
|
||||
XljkConsulterForm,
|
||||
XljkConsulterQuery
|
||||
} from './types';
|
||||
|
||||
// ===== 预约时段管理 API =====
|
||||
|
||||
// 查询预约时段列表
|
||||
export const listXljkSchedule = (query: XljkConsultationScheduleQuery): AxiosPromise<XljkConsultationScheduleVO[]> => {
|
||||
export const listXljkSchedule = (query: XljkConsultationScheduleQuery): AxiosPromise<XljkConsultationScheduleSummaryVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consultation/schedule/list',
|
||||
method: 'get',
|
||||
@@ -102,6 +107,15 @@ export const getXljkAvailableTimeSlots = (startDate: string, endDate: string): A
|
||||
});
|
||||
};
|
||||
|
||||
// 获取日历视图的排班数据(带预约状态)
|
||||
export const getXljkCalendarSchedules = (startDate: string, endDate: string): AxiosPromise<ScheduleWithStatus[]> => {
|
||||
return request({
|
||||
url: '/xljk/consultation/appointment/calendar-schedules',
|
||||
method: 'get',
|
||||
params: { startDate, endDate }
|
||||
});
|
||||
};
|
||||
|
||||
// 获取可用的详细时段列表(30分钟一个时段)
|
||||
export const getXljkAvailableDetailSlots = (date: string, timeSlot: string): AxiosPromise<TimeSlotDetail[]> => {
|
||||
return request({
|
||||
@@ -213,3 +227,140 @@ export const getXljkConsultationClassOptions = (collegeName?: string, majorName?
|
||||
params: { collegeName, majorName }
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
|
||||
export const listXljkConsulter = (query?: XljkConsulterQuery): AxiosPromise<XljkConsulterVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询心理咨询师配置详细
|
||||
* @param id
|
||||
*/
|
||||
export const getXljkConsulter = (id: string | number): AxiosPromise<XljkConsulterVO> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增心理咨询师配置
|
||||
* @param data
|
||||
*/
|
||||
export const addXljkConsulter = (data: XljkConsulterForm) => {
|
||||
return request({
|
||||
url: '/xljk/consulter',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改心理咨询师配置
|
||||
* @param data
|
||||
*/
|
||||
export const updateXljkConsulter = (data: XljkConsulterForm) => {
|
||||
return request({
|
||||
url: '/xljk/consulter',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除心理咨询师配置
|
||||
* @param id
|
||||
*/
|
||||
export const delXljkConsulter = (id: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出心理咨询师配置
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const exportXljkConsulter = (query?: XljkConsulterQuery) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/export',
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加咨询师
|
||||
* @param userIds 用户ID列表
|
||||
* @param remark 备注
|
||||
*/
|
||||
export const batchAddConsulters = (userIds: number[], remark?: string) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/batchAdd',
|
||||
method: 'post',
|
||||
data: {
|
||||
userIds,
|
||||
remark
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我配置的咨询师列表
|
||||
* @param query
|
||||
*/
|
||||
export const listMyConfigConsulters = (query?: XljkConsulterQuery): AxiosPromise<XljkConsulterVO[]> => {
|
||||
return request({
|
||||
url: '/xljk/consulter/myConfig',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量启用咨询师
|
||||
* @param ids
|
||||
*/
|
||||
export const enableConsulters = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/enable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量停用咨询师
|
||||
* @param ids
|
||||
*/
|
||||
export const disableConsulters = (ids: number[]) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/disable',
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据部门ID获取用户列表
|
||||
* @param deptId
|
||||
*/
|
||||
export const listUserByDeptId = (deptId: string | number) => {
|
||||
return request({
|
||||
url: '/xljk/consulter/users/' + deptId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { PageQuery } from '@/api/types'
|
||||
|
||||
export interface XljkConsultationScheduleVO {
|
||||
/**
|
||||
* 主键ID
|
||||
@@ -14,6 +16,11 @@ export interface XljkConsultationScheduleVO {
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
@@ -71,6 +78,11 @@ export interface XljkConsultationScheduleForm extends BaseEntity {
|
||||
*/
|
||||
counselorId?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
@@ -103,6 +115,11 @@ export interface XljkConsultationScheduleQuery extends PageQuery {
|
||||
*/
|
||||
counselorId?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期开始
|
||||
*/
|
||||
@@ -130,6 +147,10 @@ export interface XljkConsultationScheduleQuery extends PageQuery {
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@@ -163,13 +184,13 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
*/
|
||||
reason?: string;
|
||||
/**
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访)
|
||||
* 咨询主题
|
||||
*/
|
||||
status?: number;
|
||||
consultTheme?: string;
|
||||
/**
|
||||
* 实际到访时间
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访 4待审核 5已驳回)
|
||||
*/
|
||||
actualVisitTime?: any;
|
||||
status?: number | string;
|
||||
/**
|
||||
* 沟通时长(分钟)
|
||||
*/
|
||||
@@ -186,6 +207,26 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
/**
|
||||
* 心理病史(0-是,1-否)
|
||||
*/
|
||||
psychologicalHistory?: number;
|
||||
/**
|
||||
* 心理病史详情
|
||||
*/
|
||||
psychologicalHistoryDetail?: string;
|
||||
/**
|
||||
* 基本情况
|
||||
*/
|
||||
basicSituation?: string;
|
||||
/**
|
||||
* 重大生活事件
|
||||
*/
|
||||
majorLifeEvents?: string;
|
||||
/**
|
||||
* 是否需要转介(0-是,1-否)
|
||||
*/
|
||||
needReferral?: number;
|
||||
/**
|
||||
* 班级
|
||||
*/
|
||||
@@ -194,6 +235,10 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 咨询师姓名
|
||||
*/
|
||||
counselorName?: string;
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
/**
|
||||
* 院系
|
||||
*/
|
||||
@@ -202,6 +247,10 @@ export interface XljkConsultationAppointmentVO extends BaseEntity {
|
||||
* 专业
|
||||
*/
|
||||
majorName?: string;
|
||||
/**
|
||||
* 本学期到访次数
|
||||
*/
|
||||
semesterVisitCount?: number;
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentForm {
|
||||
@@ -242,13 +291,13 @@ export interface XljkConsultationAppointmentForm {
|
||||
*/
|
||||
reason?: string;
|
||||
/**
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访)
|
||||
* 咨询主题
|
||||
*/
|
||||
status?: number;
|
||||
consultTheme?: string;
|
||||
/**
|
||||
* 实际到访时间
|
||||
* 状态(0待咨询 1已完成 2已取消 3未到访 4待审核 5已驳回)
|
||||
*/
|
||||
actualVisitTime?: any;
|
||||
status?: number | string;
|
||||
/**
|
||||
* 沟通时长(分钟)
|
||||
*/
|
||||
@@ -261,6 +310,30 @@ export interface XljkConsultationAppointmentForm {
|
||||
* 咨询师建议
|
||||
*/
|
||||
counselorSuggestion?: string;
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
studentName?: string;
|
||||
/**
|
||||
* 心理病史(0-是,1-否)
|
||||
*/
|
||||
psychologicalHistory?: number;
|
||||
/**
|
||||
* 心理病史详情
|
||||
*/
|
||||
psychologicalHistoryDetail?: string;
|
||||
/**
|
||||
* 基本情况
|
||||
*/
|
||||
basicSituation?: string;
|
||||
/**
|
||||
* 重大生活事件
|
||||
*/
|
||||
majorLifeEvents?: string;
|
||||
/**
|
||||
* 是否需要转介(0-是,1-否)
|
||||
*/
|
||||
needReferral?: number;
|
||||
}
|
||||
|
||||
export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
@@ -289,6 +362,11 @@ export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
*/
|
||||
majorName?: string;
|
||||
|
||||
/**
|
||||
* 班级
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 咨询师ID
|
||||
*/
|
||||
@@ -312,7 +390,11 @@ export interface XljkConsultationAppointmentQuery extends PageQuery {
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
params?: {
|
||||
beginAppointmentDate?: string;
|
||||
endAppointmentDate?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AvailableTimeSlot {
|
||||
@@ -331,6 +413,11 @@ export interface AvailableTimeSlot {
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 日期
|
||||
*/
|
||||
@@ -419,6 +506,11 @@ export interface CounselorOption {
|
||||
* 咨询师姓名
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office: string;
|
||||
}
|
||||
|
||||
// 预约时段汇总视图对象
|
||||
@@ -433,6 +525,7 @@ export interface XljkConsultationScheduleSummaryVO {
|
||||
deleted: number;
|
||||
counselorId: number;
|
||||
counselorName: string;
|
||||
office: string;
|
||||
dateRange: string;
|
||||
morningEnabled: boolean;
|
||||
morningTime: string;
|
||||
@@ -602,3 +695,291 @@ export interface NoShowVO {
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带预约状态的排班数据
|
||||
*/
|
||||
export interface ScheduleWithStatus {
|
||||
/**
|
||||
* 排班ID
|
||||
*/
|
||||
scheduleId: number;
|
||||
|
||||
/**
|
||||
* 咨询师ID
|
||||
*/
|
||||
counselorId: number;
|
||||
|
||||
/**
|
||||
* 咨询师姓名
|
||||
*/
|
||||
counselorName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 排班日期
|
||||
*/
|
||||
date: string;
|
||||
|
||||
/**
|
||||
* 时段类型(0上午 1中午 2下午)
|
||||
*/
|
||||
timeSlot: string;
|
||||
|
||||
/**
|
||||
* 开始时间(HH:mm)
|
||||
*/
|
||||
startTime: string;
|
||||
|
||||
/**
|
||||
* 结束时间(HH:mm)
|
||||
*/
|
||||
endTime: string;
|
||||
|
||||
/**
|
||||
* 时段显示文本(如:上午 08:30-12:00)
|
||||
*/
|
||||
timeDisplay: string;
|
||||
|
||||
/**
|
||||
* 是否已预约满
|
||||
*/
|
||||
isFullyBooked: boolean;
|
||||
|
||||
/**
|
||||
* 是否已过期
|
||||
*/
|
||||
isExpired: boolean;
|
||||
|
||||
/**
|
||||
* 可预约数量
|
||||
*/
|
||||
availableSlots: number;
|
||||
|
||||
/**
|
||||
* 总预约数量
|
||||
*/
|
||||
totalSlots: number;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy?: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime?: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy?: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterVO {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName: string;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
createBy: number;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
createTime: string;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
updateBy: number;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterForm {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
deptName?: string;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
phoneNumber?: string;
|
||||
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 备注说明
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 配置人姓名
|
||||
*/
|
||||
configByName?: string;
|
||||
}
|
||||
|
||||
export interface XljkConsulterQuery extends PageQuery {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
userId?: number;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
userName?: string;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
nickName?: string;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
deptId?: number;
|
||||
|
||||
/**
|
||||
* 状态(1正常 0停用)
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 配置人ID
|
||||
*/
|
||||
configBy?: number;
|
||||
|
||||
/**
|
||||
* 办公室
|
||||
*/
|
||||
office?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
'params[beginCreateTime]'?: string;
|
||||
'params[endCreateTime]'?: string;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,350 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="年审审批"
|
||||
v-model="visible"
|
||||
width="600px"
|
||||
append-to-body
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="社团名称:">
|
||||
<span>{{ auditInfo?.clubName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="年审年度:">
|
||||
<span>{{ auditInfo?.reviewYear || '加载中...' }}年</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批节点:">
|
||||
<span>{{ auditRecord?.nodeName || '加载中...' }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="审批结果:" prop="approveResult">
|
||||
<el-radio-group v-model="form.approveResult" @change="handleApproveResultChange">
|
||||
<el-radio value="通过">通过</el-radio>
|
||||
<el-radio value="不通过">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
|
||||
<el-form-item label="审批意见:" prop="approveOpinion">
|
||||
<el-input
|
||||
v-model="form.approveOpinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请填写审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批流程选择对话框 -->
|
||||
<ApprovalProcessDialog
|
||||
v-model:visible="showApprovalProcessDialog"
|
||||
:next-node-name="getNextNodeName()"
|
||||
@confirm="handleApprovalProcessConfirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { approveAnnualReview, getStglAnnualReviewAudit, getAnnualReviewAuditsByReviewId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { getStglClubAnnualReview } from '@/api/stgl/club/annualReview';
|
||||
import ApprovalProcessDialog from './auditorSelector/ApprovalProcessDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<ElFormInstance>();
|
||||
|
||||
const form = ref({
|
||||
auditId: '',
|
||||
approveResult: '',
|
||||
approveOpinion: ''
|
||||
});
|
||||
|
||||
const auditInfo = ref<any>(null);
|
||||
const auditRecord = ref<any>(null);
|
||||
const allAuditRecords = ref<any[]>([]);
|
||||
const showApprovalProcessDialog = ref(false);
|
||||
|
||||
const rules = reactive<ElFormRules>({
|
||||
approveResult: [
|
||||
{ required: true, message: '请选择审批结果', trigger: 'change' }
|
||||
],
|
||||
approveOpinion: [
|
||||
{ required: true, message: '请填写审批意见', trigger: 'blur' },
|
||||
{ max: 500, message: '审批意见不能超过500个字符', trigger: 'blur' }
|
||||
]
|
||||
});
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (auditId: string | number, annualReviewId: string | number) => {
|
||||
visible.value = true;
|
||||
form.value.auditId = auditId.toString();
|
||||
|
||||
// 获取年审记录信息用于显示
|
||||
try {
|
||||
const res = await getStglClubAnnualReview(annualReviewId);
|
||||
auditInfo.value = res.data;
|
||||
} catch (error) {
|
||||
console.error('获取年审记录信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取审批记录信息用于显示节点名称
|
||||
try {
|
||||
const auditRes = await getStglAnnualReviewAudit(auditId);
|
||||
auditRecord.value = auditRes.data;
|
||||
} catch (error) {
|
||||
console.error('获取审批记录信息失败:', error);
|
||||
}
|
||||
|
||||
// 获取当前年审的所有审批记录,用于判断是否是本环节最后一个节点
|
||||
try {
|
||||
const allAuditsRes = await getAnnualReviewAuditsByReviewId(annualReviewId);
|
||||
allAuditRecords.value = allAuditsRes.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取所有审批记录失败:', error);
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
resetForm();
|
||||
};
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
form.value.approveResult = '';
|
||||
form.value.approveOpinion = '';
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
/** 审批结果变化处理 */
|
||||
const handleApproveResultChange = (value: string) => {
|
||||
// 审批结果变化时的处理逻辑(如需要)
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
auditInfo.value = null;
|
||||
auditRecord.value = null;
|
||||
allAuditRecords.value = [];
|
||||
};
|
||||
|
||||
/** 提交审批 */
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) return;
|
||||
|
||||
// 如果是校团委审批的最后一个节点,直接结束流程,不弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isSchoolCommitteeLastNode()) {
|
||||
console.log('校团委最后节点,直接结束流程');
|
||||
await submitApproval();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果审批通过,且当前节点是本环节的最后一个节点,弹出审批流程选择对话框
|
||||
if (form.value.approveResult === '通过' && isLastNodeInCurrentPhase()) {
|
||||
showApprovalProcessDialog.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接提交审批(审批不通过或不是本环节最后一个节点的情况)
|
||||
await submitApproval();
|
||||
};
|
||||
|
||||
/** 判断当前节点是否是本环节的最后一个节点 */
|
||||
const isLastNodeInCurrentPhase = () => {
|
||||
if (!auditRecord.value || !allAuditRecords.value.length) {
|
||||
console.log('判断是否是最后节点: 数据不足', {
|
||||
hasAuditRecord: !!auditRecord.value,
|
||||
allAuditRecordsLength: allAuditRecords.value.length
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeCode = auditRecord.value.nodeCode;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
console.log('当前审批记录详情:', {
|
||||
currentNodeCode,
|
||||
currentNodeOrder,
|
||||
currentNodeOrderType: typeof auditRecord.value.nodeOrder,
|
||||
currentNodeOrderRaw: auditRecord.value.nodeOrder,
|
||||
auditRecordKeys: Object.keys(auditRecord.value)
|
||||
});
|
||||
|
||||
// 获取同一环节的所有节点
|
||||
const samePhaseNodes = allAuditRecords.value.filter(record =>
|
||||
record.nodeCode === currentNodeCode
|
||||
);
|
||||
|
||||
console.log('所有审批记录:', allAuditRecords.value.map(record => ({
|
||||
id: record.id,
|
||||
nodeCode: record.nodeCode,
|
||||
nodeName: record.nodeName,
|
||||
nodeOrder: record.nodeOrder,
|
||||
nodeOrderType: typeof record.nodeOrder,
|
||||
approverName: record.approverName,
|
||||
nodeStatus: record.nodeStatus
|
||||
})));
|
||||
|
||||
console.log('同环节节点:', samePhaseNodes.map(n => ({
|
||||
nodeOrder: n.nodeOrder,
|
||||
nodeOrderType: typeof n.nodeOrder,
|
||||
approverName: n.approverName,
|
||||
nodeStatus: n.nodeStatus
|
||||
})));
|
||||
|
||||
if (samePhaseNodes.length === 0) {
|
||||
console.log('警告: 没有找到同环节的节点');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 找出同一环节中最大的nodeOrder,使用Number转换确保数值比较
|
||||
const nodeOrders = samePhaseNodes.map(node => Number(node.nodeOrder));
|
||||
const maxNodeOrder = Math.max(...nodeOrders);
|
||||
|
||||
console.log('判断是否是最后节点:', {
|
||||
currentNodeCode,
|
||||
currentNodeOrder,
|
||||
maxNodeOrder,
|
||||
nodeOrders,
|
||||
isLast: currentNodeOrder === maxNodeOrder,
|
||||
comparison: `${currentNodeOrder} === ${maxNodeOrder}`
|
||||
});
|
||||
|
||||
// 当前节点的nodeOrder等于同环节最大nodeOrder时,表示是最后一个节点
|
||||
return currentNodeOrder === maxNodeOrder;
|
||||
};
|
||||
|
||||
/** 判断当前是否是校团委审批的最后一个节点 */
|
||||
const isSchoolCommitteeLastNode = () => {
|
||||
if (!auditRecord.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentNodeName = auditRecord.value.nodeName;
|
||||
const currentNodeOrder = Number(auditRecord.value.nodeOrder);
|
||||
|
||||
// 检查是否是校团委审批节点且node_order是3.X格式
|
||||
const isSchoolCommitteeNode = currentNodeName === '校团委审批';
|
||||
const isNodeOrder3X = currentNodeOrder >= 3 && currentNodeOrder < 4;
|
||||
|
||||
console.log('校团委最后节点判断:', {
|
||||
currentNodeName,
|
||||
currentNodeOrder,
|
||||
isSchoolCommitteeNode,
|
||||
isNodeOrder3X,
|
||||
isLastInPhase: isLastNodeInCurrentPhase()
|
||||
});
|
||||
|
||||
// 校团委审批 + node_order=3.X + 是本环节最后一个节点
|
||||
return isSchoolCommitteeNode && isNodeOrder3X && isLastNodeInCurrentPhase();
|
||||
};
|
||||
|
||||
/** 获取下一步节点名称 */
|
||||
const getNextNodeName = () => {
|
||||
// 根据当前节点确定下一步节点
|
||||
const currentNodeName = auditRecord.value?.nodeName;
|
||||
|
||||
if (currentNodeName === '指导老师审批') {
|
||||
return '二级学院审批';
|
||||
} else if (currentNodeName === '二级学院审批') {
|
||||
return '校团委审批';
|
||||
} else if (currentNodeName === '校团委审批') {
|
||||
return '完成审批';
|
||||
}
|
||||
|
||||
// 默认返回二级学院审批
|
||||
return '二级学院审批';
|
||||
};
|
||||
|
||||
/** 实际提交审批 */
|
||||
const submitApproval = async (approvalProcessData?: any) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 判断是否是校团委最后节点
|
||||
const isSchoolCommitteeFinalNode = isSchoolCommitteeLastNode();
|
||||
|
||||
// 准备审批数据
|
||||
const approveData = {
|
||||
auditId: form.value.auditId,
|
||||
approveResult: form.value.approveResult,
|
||||
approveOpinion: form.value.approveOpinion,
|
||||
// 校团委最后节点时不设置下一步审核人,表示流程结束
|
||||
// 其他情况:只有审批通过且是本环节最后一个节点时,才包含下一步审核人信息
|
||||
nextAuditors: isSchoolCommitteeFinalNode ? [] :
|
||||
(form.value.approveResult === '通过' && isLastNodeInCurrentPhase()) ?
|
||||
(approvalProcessData?.auditors || []).map((auditor: any) => ({
|
||||
userId: auditor.userId,
|
||||
userName: auditor.userName,
|
||||
nickName: auditor.nickName
|
||||
})) : []
|
||||
};
|
||||
|
||||
console.log('提交审批数据:', {
|
||||
审批结果: approveData.approveResult,
|
||||
是否最后节点: isLastNodeInCurrentPhase(),
|
||||
是否校团委最后节点: isSchoolCommitteeFinalNode,
|
||||
下一步审核人数量: approveData.nextAuditors.length,
|
||||
流程状态: isSchoolCommitteeFinalNode ? '流程结束' : '继续流转'
|
||||
});
|
||||
|
||||
await approveAnnualReview(
|
||||
approveData.auditId,
|
||||
approveData.approveResult,
|
||||
approveData.approveOpinion,
|
||||
approveData.nextAuditors
|
||||
);
|
||||
|
||||
if (isSchoolCommitteeFinalNode) {
|
||||
proxy?.$modal.msgSuccess('审批成功,年审流程已完成!');
|
||||
} else {
|
||||
proxy?.$modal.msgSuccess('审批成功');
|
||||
}
|
||||
|
||||
handleClose();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('审批失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 审批流程确认处理 */
|
||||
const handleApprovalProcessConfirm = async (approvalProcessData: any) => {
|
||||
console.log('审批流程确认,数据:', approvalProcessData);
|
||||
await submitApproval(approvalProcessData);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="选择审批流程"
|
||||
v-model="visible"
|
||||
width="70%"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="approval-dialog-content">
|
||||
<el-alert
|
||||
:title="`请选择${props.nextNodeName}审核人`"
|
||||
description="社团年审审批提交后将按照您选择的审批人员顺序进行审批,请仔细选择审核人员。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<!-- 下一步审批节点提示 -->
|
||||
<div class="next-node-info">
|
||||
<div class="node-label">
|
||||
<el-icon><Document /></el-icon>
|
||||
下一步审批节点:{{ props.nextNodeName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前已选择的审核人显示 -->
|
||||
<div class="current-auditors" v-if="currentAuditors.length > 0">
|
||||
<div class="section-title">
|
||||
<el-icon><User /></el-icon>
|
||||
当前已选择的审核人 ({{ currentAuditors.length }} 人)
|
||||
</div>
|
||||
<div class="auditor-tags">
|
||||
<el-tag
|
||||
v-for="auditor in currentAuditors"
|
||||
:key="auditor.userId"
|
||||
type="info"
|
||||
style="margin: 2px 5px 2px 0;"
|
||||
>
|
||||
{{ auditor.nickName }}({{ auditor.userName }})
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核人选择区域 -->
|
||||
<div class="auditor-selection-section">
|
||||
<div class="custom-process-config">
|
||||
<el-form-item label="选择审核人" required>
|
||||
<div class="auditor-selector">
|
||||
<div class="selector-header">
|
||||
<el-button type="primary" icon="Plus" @click="openAuditorSelector">添加审核人</el-button>
|
||||
<el-text v-if="selectedAuditors.length > 0" type="info" size="small">
|
||||
已选择 {{ selectedAuditors.length }} 个审核人,将按添加顺序进行审批
|
||||
</el-text>
|
||||
</div>
|
||||
|
||||
<!-- 已选择的审核人列表 -->
|
||||
<div v-if="selectedAuditors.length > 0" class="selected-auditors">
|
||||
<div class="auditor-flow">
|
||||
<div v-for="(auditor, index) in selectedAuditors" :key="auditor.userId" class="auditor-node">
|
||||
<div class="node-content">
|
||||
<span class="node-index">{{ index + 1 }}</span>
|
||||
<div class="auditor-info">
|
||||
<div class="auditor-name">{{ auditor.nickName || auditor.userName }}</div>
|
||||
<div class="auditor-dept">{{ auditor.deptName || '未知部门' }}</div>
|
||||
</div>
|
||||
<el-button type="danger" icon="Delete" size="small" circle @click="removeAuditor(auditor)" />
|
||||
</div>
|
||||
<div v-if="index < selectedAuditors.length - 1" class="flow-arrow">
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流程预览说明 -->
|
||||
<div class="flow-description">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
<template #title>
|
||||
<span>审批流程预览:{{ props.nextNodeName }}将按上述顺序进行逐级审批,每个节点审批通过后自动流转到下一节点</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<div v-else class="empty-state">
|
||||
<el-empty description="暂未选择审核人">
|
||||
<el-button type="primary" @click="openAuditorSelector">选择审核人</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
:loading="submitting"
|
||||
:disabled="selectedAuditors.length === 0"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确认提交 ({{ selectedAuditors.length }} 个审核人)
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核人选择器 -->
|
||||
<AuditorSelector
|
||||
ref="auditorSelectorRef"
|
||||
@confirm="handleAuditorsSelected"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { Check, User, UserFilled, Document, Plus, ArrowRight, Delete } from '@element-plus/icons-vue';
|
||||
import AuditorSelector from './index.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
currentAuditors?: any[]; // 当前已选择的审核人
|
||||
nextNodeName?: string; // 下一步审批节点名称
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'confirm', approvalData: any): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
currentAuditors: () => [],
|
||||
nextNodeName: '二级学院审批'
|
||||
});
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const visible = ref(props.visible);
|
||||
const submitting = ref(false);
|
||||
const selectedAuditors = ref<any[]>([]);
|
||||
const currentAuditors = ref<any[]>([]);
|
||||
const auditorSelectorRef = ref<InstanceType<typeof AuditorSelector>>();
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
// 初始化当前审核人和选择的审核人
|
||||
currentAuditors.value = [...(props.currentAuditors || [])];
|
||||
selectedAuditors.value = [...(props.currentAuditors || [])];
|
||||
}
|
||||
});
|
||||
|
||||
// 监听内部visible变化
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:visible', newVal);
|
||||
});
|
||||
|
||||
// 打开审核人选择器
|
||||
const openAuditorSelector = () => {
|
||||
auditorSelectorRef.value?.open(selectedAuditors.value);
|
||||
};
|
||||
|
||||
// 审核人选择确认
|
||||
const handleAuditorsSelected = (auditors: any[]) => {
|
||||
selectedAuditors.value = [...auditors];
|
||||
};
|
||||
|
||||
// 移除审核人
|
||||
const removeAuditor = (auditor: any) => {
|
||||
const index = selectedAuditors.value.findIndex(item => item.userId === auditor.userId);
|
||||
if (index > -1) {
|
||||
selectedAuditors.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
ElMessage.info('已取消提交');
|
||||
};
|
||||
|
||||
// 确认提交
|
||||
const handleConfirm = async () => {
|
||||
try {
|
||||
if (selectedAuditors.value.length === 0) {
|
||||
ElMessage.warning('请至少选择一个审核人');
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
|
||||
// 构建审批流程数据
|
||||
const approvalData = {
|
||||
auditorIds: selectedAuditors.value.map(auditor => auditor.userId),
|
||||
processName: '年审审批流程',
|
||||
auditors: selectedAuditors.value
|
||||
};
|
||||
|
||||
console.log('确认审批流程,数据:', approvalData);
|
||||
|
||||
// 触发确认事件
|
||||
emit('confirm', approvalData);
|
||||
|
||||
visible.value = false;
|
||||
ElMessage.success('审批流程配置成功');
|
||||
} catch (error) {
|
||||
console.error('确认审批流程失败:', error);
|
||||
ElMessage.error('确认审批流程失败');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({
|
||||
open: (preSelectedAuditors?: any[]) => {
|
||||
visible.value = true;
|
||||
currentAuditors.value = [...(preSelectedAuditors || [])];
|
||||
selectedAuditors.value = [...(preSelectedAuditors || [])];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.approval-dialog-content {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.next-node-info {
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
background-color: #f0f9ff;
|
||||
border: 1px solid #d1ecf1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.node-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
color: #0c63e4;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.current-auditors {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.auditor-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.auditor-selection-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.custom-process-config {
|
||||
padding: 24px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.auditor-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.selected-auditors {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.auditor-flow {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auditor-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
background: white;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.node-index {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.auditor-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.auditor-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.auditor-dept {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.flow-description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,396 @@
|
||||
<template>
|
||||
<div class="auditor-selector">
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="选择审核人"
|
||||
width="900px"
|
||||
append-to-body
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="auditor-select-content">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-section" style="margin-bottom: 20px !important;">
|
||||
<el-input v-model="searchKeyword" placeholder="搜索审核人姓名或部门" clearable @input="handleSearch" style="width: 300px">
|
||||
<template #prefix>
|
||||
<el-icon><Search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 审核人表格 -->
|
||||
<el-table
|
||||
ref="auditorTableRef"
|
||||
:data="tableData"
|
||||
@selection-change="handleSelectionChange"
|
||||
max-height="400"
|
||||
empty-text="暂无可用审核人"
|
||||
style="margin-top: 20px !important;"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :selectable="isSelectable" header-align="center" align="center" />
|
||||
<el-table-column label="序号" type="index" width="60" header-align="center" align="center" />
|
||||
<el-table-column label="姓名" prop="nickName" width="130" header-align="center" align="center" />
|
||||
<el-table-column label="部门" prop="deptName" header-align="center" align="center" />
|
||||
<el-table-column label="联系电话" prop="phoneNumber" width="130" header-align="center" />
|
||||
<el-table-column label="邮箱" prop="email" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="状态" prop="status" width="80" header-align="center" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.status === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div
|
||||
class="dialog-footer"
|
||||
style="display: flex !important; justify-content: space-between !important; align-items: center !important; width: 100% !important; margin: 0 !important;"
|
||||
>
|
||||
<span class="selected-count-text" style="color: #909399; font-size: 12px;"> 已选择 {{ selectedUsers.length }} 个审核人 </span>
|
||||
<div class="footer-buttons" style="display: flex !important; gap: 12px !important;">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm" :disabled="selectedUsers.length === 0"> 确定添加 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, nextTick, computed, getCurrentInstance } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage, ElMessageBox, ElTable } from 'element-plus';
|
||||
import { Search } from '@element-plus/icons-vue';
|
||||
import { listStglAuditor } from '@/api/stgl/auditor';
|
||||
import { listDept } from '@/api/system/dept';
|
||||
import { StglAuditorVO, StglAuditorQuery } from '@/api/stgl/auditor/types';
|
||||
import { DeptVO } from '@/api/system/dept/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_normal_disable } = proxy?.useDict('sys_normal_disable') || {};
|
||||
const auditorTableRef = ref();
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [users: StglAuditorVO[]];
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const userList = ref<StglAuditorVO[]>([]);
|
||||
const filteredUserList = ref<StglAuditorVO[]>([]);
|
||||
const selectedUsers = ref<StglAuditorVO[]>([]);
|
||||
const total = ref(0);
|
||||
const deptOptions = ref<any[]>([]);
|
||||
const searchKeyword = ref('');
|
||||
|
||||
// 计算表格数据
|
||||
const tableData = computed(() => {
|
||||
// 确保返回的数组总是有效的
|
||||
if (searchKeyword.value && filteredUserList.value) {
|
||||
return filteredUserList.value;
|
||||
}
|
||||
return userList.value || [];
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<StglAuditorQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 500, // 增加页面大小以显示所有审核人
|
||||
nickName: '',
|
||||
userName: '',
|
||||
deptId: undefined,
|
||||
status: '1' // 只查询正常状态的审核人
|
||||
});
|
||||
|
||||
// 初始查询参数的备份,用于重置
|
||||
const defaultQuery: StglAuditorQuery = {
|
||||
pageNum: 1,
|
||||
pageSize: 500, // 增加页面大小以显示所有审核人
|
||||
nickName: '',
|
||||
userName: '',
|
||||
deptId: undefined,
|
||||
status: '1'
|
||||
};
|
||||
|
||||
/** 打开选择器 */
|
||||
const open = (preSelected: StglAuditorVO[] = []) => {
|
||||
visible.value = true;
|
||||
selectedUsers.value = [...preSelected];
|
||||
// 重置搜索状态
|
||||
searchKeyword.value = '';
|
||||
filteredUserList.value = [];
|
||||
getDeptTree();
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 获取部门树 */
|
||||
const getDeptTree = async () => {
|
||||
try {
|
||||
const response = await listDept();
|
||||
deptOptions.value = handleTree(response.data, 'id', 'parentId');
|
||||
} catch (error) {
|
||||
console.error('获取部门树失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取审核人列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listStglAuditor(queryParams.value);
|
||||
userList.value = response.rows;
|
||||
filteredUserList.value = response.rows;
|
||||
total.value = response.total;
|
||||
|
||||
// 如果有搜索关键词,执行搜索
|
||||
if (searchKeyword.value) {
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
// 设置表格选中状态
|
||||
nextTick(() => {
|
||||
setTableSelection();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取审核人列表失败:', error);
|
||||
userList.value = [];
|
||||
filteredUserList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 设置表格选中状态 */
|
||||
const setTableSelection = () => {
|
||||
if (!auditorTableRef.value) return;
|
||||
|
||||
tableData.value.forEach(user => {
|
||||
const isSelected = selectedUsers.value.some(selected =>
|
||||
selected.userId === user.userId
|
||||
);
|
||||
auditorTableRef.value.toggleRowSelection(user, isSelected);
|
||||
});
|
||||
};
|
||||
|
||||
/** 判断行是否可选 */
|
||||
const isSelectable = (row: StglAuditorVO) => {
|
||||
return row.status === '1'; // 只有正常状态的审核人可选
|
||||
};
|
||||
|
||||
/** 表格选择变化 */
|
||||
const handleSelectionChange = (selection: StglAuditorVO[]) => {
|
||||
// 处理当前显示数据的选择
|
||||
const currentPageUserIds = tableData.value.map(user => user.userId);
|
||||
|
||||
// 移除当前页原有的选择
|
||||
selectedUsers.value = selectedUsers.value.filter(user =>
|
||||
!currentPageUserIds.includes(user.userId)
|
||||
);
|
||||
|
||||
// 添加当前页新的选择
|
||||
selectedUsers.value.push(...selection);
|
||||
};
|
||||
|
||||
/** 移除选中审核人 */
|
||||
const removeUser = (user: StglAuditorVO) => {
|
||||
selectedUsers.value = selectedUsers.value.filter(selected =>
|
||||
selected.userId !== user.userId
|
||||
);
|
||||
|
||||
// 如果当前页包含该用户,需要取消表格选中
|
||||
if (auditorTableRef.value) {
|
||||
const currentUser = tableData.value.find(u => u.userId === user.userId);
|
||||
if (currentUser) {
|
||||
auditorTableRef.value.toggleRowSelection(currentUser, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 清空所有选择 */
|
||||
const clearSelected = () => {
|
||||
selectedUsers.value = [];
|
||||
if (auditorTableRef.value) {
|
||||
auditorTableRef.value.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索处理 */
|
||||
const handleSearch = () => {
|
||||
if (!searchKeyword.value) {
|
||||
filteredUserList.value = userList.value;
|
||||
return;
|
||||
}
|
||||
|
||||
const keyword = searchKeyword.value.toLowerCase();
|
||||
filteredUserList.value = userList.value.filter(
|
||||
(user) =>
|
||||
(user.nickName && user.nickName.toLowerCase().includes(keyword)) ||
|
||||
(user.userName && user.userName.toLowerCase().includes(keyword)) ||
|
||||
(user.deptName && user.deptName.toLowerCase().includes(keyword))
|
||||
);
|
||||
};
|
||||
|
||||
/** 搜索 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置搜索 */
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams.value, defaultQuery);
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
selectedUsers.value = [];
|
||||
userList.value = [];
|
||||
filteredUserList.value = [];
|
||||
searchKeyword.value = '';
|
||||
Object.assign(queryParams.value, defaultQuery);
|
||||
};
|
||||
|
||||
/** 确认选择 */
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedUsers.value);
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
/** 处理树形数据 */
|
||||
const handleTree = (data: any[], id: string, parentId: string, children = 'children') => {
|
||||
const config = { id, parentId, children };
|
||||
const childrenListMap: any = {};
|
||||
const nodeIds: any = {};
|
||||
const tree = [];
|
||||
|
||||
for (const d of data) {
|
||||
const parentIdValue = d[config.parentId];
|
||||
if (childrenListMap[parentIdValue] == null) {
|
||||
childrenListMap[parentIdValue] = [];
|
||||
}
|
||||
nodeIds[d[config.id]] = d;
|
||||
childrenListMap[parentIdValue].push(d);
|
||||
}
|
||||
|
||||
for (const d of data) {
|
||||
const parentIdValue = d[config.parentId];
|
||||
if (nodeIds[parentIdValue] == null) {
|
||||
tree.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of tree) {
|
||||
adaptToChildrenArray(t);
|
||||
}
|
||||
|
||||
function adaptToChildrenArray(o: any) {
|
||||
if (childrenListMap[o[config.id]] !== null) {
|
||||
o[config.children] = childrenListMap[o[config.id]];
|
||||
}
|
||||
if (o[config.children]) {
|
||||
for (const c of o[config.children]) {
|
||||
adaptToChildrenArray(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tree.map(item => ({
|
||||
id: item.id,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map((child: any) => convertToTreeNode(child)) : []
|
||||
}));
|
||||
};
|
||||
|
||||
const convertToTreeNode = (item: any): any => ({
|
||||
id: item.id,
|
||||
label: item.deptName,
|
||||
children: item.children ? item.children.map((child: any) => convertToTreeNode(child)) : []
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.auditor-selector {
|
||||
.auditor-select-content {
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-top: 20px !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
:deep(.el-table) {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Element Plus Dialog Footer 样式覆盖 - 使用最高优先级的选择器 */
|
||||
.auditor-selector :deep(.el-dialog__footer) {
|
||||
padding: 15px 20px !important;
|
||||
text-align: left !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.auditor-selector :deep(.el-dialog__footer .dialog-footer) {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
/* 确保按钮容器样式正确 */
|
||||
.auditor-selector .dialog-footer .footer-buttons {
|
||||
display: flex !important;
|
||||
gap: 12px !important;
|
||||
margin-left: auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
/* 确保文本不会撑开容器 */
|
||||
.auditor-selector .dialog-footer .selected-count-text {
|
||||
flex-shrink: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 确保搜索框和表格间距的全局样式 */
|
||||
.auditor-selector .auditor-select-content .search-section {
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
.auditor-selector .auditor-select-content .el-table {
|
||||
margin-top: 20px !important;
|
||||
}
|
||||
</style>
|
||||
@@ -124,8 +124,27 @@
|
||||
<!-- 指导老师意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">指导老师意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.advisorOpinion || '暂无意见' }}
|
||||
<div v-if="advisorAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in advisorAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -133,8 +152,27 @@
|
||||
<!-- 二级学院意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">二级学院意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.collegeOpinion || '暂无意见' }}
|
||||
<div v-if="collegeAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in collegeAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -142,8 +180,27 @@
|
||||
<!-- 校团委意见 -->
|
||||
<div class="opinion-section">
|
||||
<div class="opinion-title">校团委意见</div>
|
||||
<div class="opinion-content">
|
||||
{{ annualReview.youthLeagueOpinion || '暂无意见' }}
|
||||
<div v-if="youthLeagueAudits.length > 0" class="audit-records">
|
||||
<div v-for="audit in youthLeagueAudits" :key="audit.id" class="audit-record">
|
||||
<div class="audit-header">
|
||||
<span class="approver-name">{{ audit.approverName }}</span>
|
||||
<span class="audit-status" :class="getAuditStatusClass(audit.nodeStatus)">
|
||||
{{ getAuditStatusText(audit.nodeStatus) }}
|
||||
</span>
|
||||
<span v-if="audit.approveTime" class="audit-time">
|
||||
{{ parseTime(audit.approveTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="audit.approveOpinion" class="audit-opinion">
|
||||
{{ audit.approveOpinion }}
|
||||
</div>
|
||||
<div v-else class="audit-opinion pending">
|
||||
待审批
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="opinion-content">
|
||||
暂无审批记录
|
||||
</div>
|
||||
<div class="opinion-signature">签名(公章)</div>
|
||||
</div>
|
||||
@@ -158,7 +215,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getStglClubAnnualReview, getAnnualReviewActivities } from '@/api/stgl/club/annualReview';
|
||||
import { getAnnualReviewAuditsByReviewId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { StglClubAnnualReviewVO } from '@/api/stgl/club/annualReview/type';
|
||||
import { StglAnnualReviewAuditVO } from '@/api/stgl/club/annualReviewAudit/type';
|
||||
|
||||
// 扩展的年审VO接口(包含JSON字段)
|
||||
interface ExtendedAnnualReviewVO extends StglClubAnnualReviewVO {
|
||||
@@ -180,6 +239,8 @@ const annualReview = ref<ExtendedAnnualReviewVO>();
|
||||
const memberList = ref<any[]>([]);
|
||||
// 活动列表
|
||||
const activityList = ref<any[]>([]);
|
||||
// 审批记录列表
|
||||
const auditList = ref<StglAnnualReviewAuditVO[]>([]);
|
||||
|
||||
// 年份范围计算
|
||||
const yearRange = computed(() => {
|
||||
@@ -208,6 +269,41 @@ const memberCountText = computed(() => {
|
||||
return `${annualReview.value?.memberCount || 0}人`;
|
||||
});
|
||||
|
||||
// 指导老师审批记录
|
||||
const advisorAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'advisor_approve');
|
||||
});
|
||||
|
||||
// 二级学院审批记录
|
||||
const collegeAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'college_approve');
|
||||
});
|
||||
|
||||
// 校团委审批记录
|
||||
const youthLeagueAudits = computed(() => {
|
||||
return auditList.value.filter(audit => audit.nodeCode === 'youth_league_approve');
|
||||
});
|
||||
|
||||
/** 获取审批状态样式类 */
|
||||
const getAuditStatusClass = (status: string) => {
|
||||
const statusClasses: { [key: string]: string } = {
|
||||
'pending': 'status-pending',
|
||||
'waiting': 'status-waiting',
|
||||
'approved': 'status-approved'
|
||||
};
|
||||
return statusClasses[status] || '';
|
||||
};
|
||||
|
||||
/** 获取审批状态文本 */
|
||||
const getAuditStatusText = (status: string) => {
|
||||
const statusTexts: { [key: string]: string } = {
|
||||
'pending': '待审批',
|
||||
'waiting': '等待中',
|
||||
'approved': '已审批'
|
||||
};
|
||||
return statusTexts[status] || status;
|
||||
};
|
||||
|
||||
/** 格式化活动时间显示 */
|
||||
const formatActivityTime = (startTime: string, endTime?: string): string => {
|
||||
if (!startTime) return '';
|
||||
@@ -302,6 +398,19 @@ const getDetail = async () => {
|
||||
memberList.value = [];
|
||||
}
|
||||
|
||||
// 获取审批记录
|
||||
try {
|
||||
const auditRes = await getAnnualReviewAuditsByReviewId(props.annualReviewId);
|
||||
if (auditRes && auditRes.data && Array.isArray(auditRes.data)) {
|
||||
auditList.value = auditRes.data;
|
||||
} else {
|
||||
auditList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取审批记录失败:', error);
|
||||
auditList.value = [];
|
||||
}
|
||||
|
||||
// 从stgl_club_annual_review_activity表中获取年审记录对应的活动数据
|
||||
try {
|
||||
const activitiesRes = await getAnnualReviewActivities(props.annualReviewId);
|
||||
@@ -524,4 +633,79 @@ watch(
|
||||
line-height: 1.4;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 审批记录样式 */
|
||||
.audit-records {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.audit-record {
|
||||
margin-bottom: 15px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.audit-record:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.audit-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.approver-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.audit-status {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fdf6ec;
|
||||
color: #e6a23c;
|
||||
border: 1px solid #f5dab1;
|
||||
}
|
||||
|
||||
.status-waiting {
|
||||
background: #f4f4f5;
|
||||
color: #909399;
|
||||
border: 1px solid #d3d4d6;
|
||||
}
|
||||
|
||||
.status-approved {
|
||||
background: #f0f9ff;
|
||||
color: #67c23a;
|
||||
border: 1px solid #b3d8ff;
|
||||
}
|
||||
|
||||
.audit-time {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.audit-opinion {
|
||||
line-height: 1.6;
|
||||
color: #606266;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.audit-opinion.pending {
|
||||
color: #909399;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
@@ -459,9 +459,24 @@ const loadClubInfo = async (clubId: string | number) => {
|
||||
try {
|
||||
const advisors = JSON.parse(clubData.advisorsJson);
|
||||
if (Array.isArray(advisors) && advisors.length > 0) {
|
||||
const firstAdvisor = advisors[0];
|
||||
advisorName = firstAdvisor.name || firstAdvisor.teacher?.nickName || '';
|
||||
advisorDept = firstAdvisor.dept || firstAdvisor.teacher?.deptName || '';
|
||||
// 处理多个指导老师的情况
|
||||
const advisorNames = [];
|
||||
const advisorDepts = [];
|
||||
|
||||
advisors.forEach(advisor => {
|
||||
const name = advisor.name || advisor.teacher?.nickName || '';
|
||||
const dept = advisor.dept || advisor.teacher?.deptName || '';
|
||||
|
||||
if (name) {
|
||||
advisorNames.push(name);
|
||||
}
|
||||
if (dept && !advisorDepts.includes(dept)) {
|
||||
advisorDepts.push(dept);
|
||||
}
|
||||
});
|
||||
|
||||
advisorName = advisorNames.join('、');
|
||||
advisorDept = advisorDepts.join('、');
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('解析指导老师JSON数据失败:', error);
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="annualReviewList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<el-table v-loading="loading" border :data="annualReviewList" :key="tableKey" @selection-change="handleSelectionChange" @row-dblclick="handleView">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="社团名称" align="center" prop="clubName" min-width="150" />
|
||||
<el-table-column label="成立日期" align="center" prop="establishDate" width="120">
|
||||
@@ -80,11 +80,11 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status) as any">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status) as any">{{ getStatusText(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="年审时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
@@ -104,7 +104,10 @@
|
||||
<el-tooltip content="撤回" placement="top" v-if="scope.row.status === '1'">
|
||||
<el-button link type="warning" icon="RefreshLeft" @click="handleWithdraw(scope.row)" v-hasPermi="['stgl:clubAnnualReview:withdraw']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0'">
|
||||
<el-tooltip content="审批" placement="top" v-if="(scope.row.status === '1' || scope.row.status === '2') && canApprove(scope.row)">
|
||||
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['stgl:annualReviewAudit:approve']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="scope.row.status === '0' || scope.row.status === '4'">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:clubAnnualReview:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@@ -129,10 +132,15 @@
|
||||
<el-dialog title="年审记录详情" v-model="detailDialog.visible" width="1000px" append-to-body>
|
||||
<annual-review-detail :annual-review-id="detailDialog.annualReviewId" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审批对话框 -->
|
||||
<approve-dialog ref="approveDialogRef" @success="handleApproveSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="StglClubAnnualReview" lang="ts">
|
||||
import { ref, reactive, toRefs, nextTick, onMounted, getCurrentInstance } from 'vue';
|
||||
import type { ComponentInternalInstance } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import {
|
||||
listStglClubAnnualReview,
|
||||
@@ -143,11 +151,16 @@ import {
|
||||
exportStglClubAnnualReview
|
||||
} from '@/api/stgl/club/annualReview';
|
||||
import { StglClubAnnualReviewVO, StglClubAnnualReviewQuery, ANNUAL_REVIEW_STATUS_OPTIONS } from '@/api/stgl/club/annualReview/type';
|
||||
import { getPendingAuditsByApproverId } from '@/api/stgl/club/annualReviewAudit';
|
||||
import { StglAnnualReviewAuditVO } from '@/api/stgl/club/annualReviewAudit/type';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import AnnualReviewForm from './form.vue';
|
||||
import AnnualReviewDetail from './detail.vue';
|
||||
import ApproveDialog from './approveDialog.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const route = useRoute();
|
||||
const userStore = useUserStore();
|
||||
const { stgl_club_type } = toRefs(proxy?.useDict('stgl_club_type') as any);
|
||||
|
||||
const annualReviewList = ref<StglClubAnnualReviewVO[]>([]);
|
||||
@@ -157,9 +170,16 @@ const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const tableKey = ref(0); // 用于强制重新渲染表格
|
||||
|
||||
// 审批权限相关状态
|
||||
const pendingAudits = ref<StglAnnualReviewAuditVO[]>([]);
|
||||
const pendingAuditsLoading = ref(false);
|
||||
const pendingAuditsCache = ref<Map<number, boolean>>(new Map());
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const annualReviewFormRef = ref<InstanceType<typeof AnnualReviewForm>>();
|
||||
const approveDialogRef = ref<InstanceType<typeof ApproveDialog>>();
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = ref(ANNUAL_REVIEW_STATUS_OPTIONS);
|
||||
@@ -214,10 +234,36 @@ const { queryParams, dialog, detailDialog } = toRefs(data);
|
||||
/** 查询社团年审记录列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
const res = await listStglClubAnnualReview(queryParams.value);
|
||||
annualReviewList.value = res.rows;
|
||||
total.value = res.total;
|
||||
loading.value = false;
|
||||
try {
|
||||
// 并行加载年审记录和待审批记录
|
||||
const [annualReviewRes] = await Promise.all([
|
||||
listStglClubAnnualReview(queryParams.value),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
|
||||
// 确保数据更新
|
||||
if (annualReviewRes && annualReviewRes.rows) {
|
||||
annualReviewList.value = annualReviewRes.rows;
|
||||
total.value = annualReviewRes.total;
|
||||
} else {
|
||||
console.warn('获取的年审记录数据格式异常:', annualReviewRes);
|
||||
annualReviewList.value = [];
|
||||
total.value = 0;
|
||||
}
|
||||
|
||||
// 强制触发 Vue 的响应式更新
|
||||
nextTick(() => {
|
||||
console.log('年审记录列表已更新,记录数:', annualReviewList.value.length);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
proxy?.$modal.msgError('加载数据失败');
|
||||
// 重置数据
|
||||
annualReviewList.value = [];
|
||||
total.value = 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
@@ -279,20 +325,51 @@ const handleView = (row: StglClubAnnualReviewVO) => {
|
||||
const handleSubmit = async (row: StglClubAnnualReviewVO) => {
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认提交年审记录"${row.clubName}"?`);
|
||||
|
||||
// 调用提交接口
|
||||
await submitStglClubAnnualReview(row.id);
|
||||
await getList();
|
||||
|
||||
// API调用成功后,立即更新本地状态,避免被getList覆盖
|
||||
await updateRowStatus(row.id, '1');
|
||||
|
||||
proxy?.$modal.msgSuccess('提交成功');
|
||||
} catch {}
|
||||
|
||||
// 延迟重新获取数据,确保状态已经在界面上显示
|
||||
setTimeout(async () => {
|
||||
await getList();
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
// 提交失败时才需要恢复状态,因为我们现在是先调用API再更新状态
|
||||
}
|
||||
};
|
||||
|
||||
/** 撤回按钮操作 */
|
||||
const handleWithdraw = async (row: StglClubAnnualReviewVO) => {
|
||||
try {
|
||||
await proxy?.$modal.confirm(`是否确认撤回年审记录"${row.clubName}"?`);
|
||||
|
||||
// 调用撤回接口
|
||||
await withdrawStglClubAnnualReview(row.id);
|
||||
await getList();
|
||||
|
||||
// API调用成功后,立即更新本地状态,避免被getList覆盖
|
||||
await updateRowStatus(row.id, '0');
|
||||
|
||||
proxy?.$modal.msgSuccess('撤回成功');
|
||||
} catch {}
|
||||
|
||||
// 延迟重新获取数据,确保状态已经在界面上显示
|
||||
setTimeout(async () => {
|
||||
await Promise.all([
|
||||
getList(),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
}, 100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('撤回失败:', error);
|
||||
// 撤回失败时才需要恢复状态,因为我们现在是先调用API再更新状态
|
||||
}
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
@@ -322,6 +399,30 @@ const handleFormSuccess = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 更新行状态的工具函数 */
|
||||
const updateRowStatus = async (rowId: string | number, newStatus: string) => {
|
||||
const rowIndex = annualReviewList.value.findIndex(item => item.id === rowId);
|
||||
if (rowIndex !== -1) {
|
||||
// 创建新对象确保Vue能检测到变化
|
||||
const updatedRow = {
|
||||
...annualReviewList.value[rowIndex],
|
||||
status: newStatus
|
||||
};
|
||||
// 使用splice替换整个对象,触发响应式更新
|
||||
annualReviewList.value.splice(rowIndex, 1, updatedRow);
|
||||
|
||||
// 强制触发DOM更新和表格重新渲染
|
||||
tableKey.value++;
|
||||
await nextTick();
|
||||
|
||||
console.log(`状态已同步更新: ${updatedRow.status}`);
|
||||
console.log('当前数组状态:', annualReviewList.value.map(item => ({ id: item.id, status: item.status, clubName: item.clubName })));
|
||||
|
||||
return updatedRow;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/** 获取状态标签类型 */
|
||||
const getStatusTagType = (status: string) => {
|
||||
const tagTypes: { [key: string]: string } = {
|
||||
@@ -336,8 +437,114 @@ const getStatusTagType = (status: string) => {
|
||||
|
||||
/** 获取状态文本 */
|
||||
const getStatusText = (status: string) => {
|
||||
// 本地状态映射,确保显示正确
|
||||
const statusMap: { [key: string]: string } = {
|
||||
'0': '草稿',
|
||||
'1': '已提交',
|
||||
'2': '审核中',
|
||||
'3': '审核通过',
|
||||
'4': '审核不通过'
|
||||
};
|
||||
|
||||
// 优先使用本地映射
|
||||
if (statusMap[status]) {
|
||||
const result = statusMap[status];
|
||||
console.log(`getStatusText: 状态=${status}, 返回文本="${result}"`);
|
||||
return result;
|
||||
}
|
||||
|
||||
// fallback到原来的逻辑
|
||||
const option = statusOptions.value.find(item => item.value === status);
|
||||
return option?.label || status;
|
||||
const result = option?.label || status;
|
||||
console.log(`getStatusText: 状态=${status}, fallback返回="${result}"`);
|
||||
return result;
|
||||
};
|
||||
|
||||
/** 获取当前用户的待审批记录 */
|
||||
const loadPendingAudits = async () => {
|
||||
if (!userStore.userId || pendingAuditsLoading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
pendingAuditsLoading.value = true;
|
||||
const response = await getPendingAuditsByApproverId(userStore.userId);
|
||||
|
||||
// 处理响应数据,确保 data 是数组
|
||||
// 后端返回的是 R<List<StglAnnualReviewAudit>>,R.data 可能为 null 或 []
|
||||
let data = [];
|
||||
if (response && response.data !== undefined) {
|
||||
// 如果response.data存在但为null,使用空数组
|
||||
data = response.data || [];
|
||||
} else if (response && response.rows !== undefined) {
|
||||
// 有些API返回格式可能是分页格式
|
||||
data = response.rows || [];
|
||||
}
|
||||
|
||||
pendingAudits.value = data;
|
||||
|
||||
// 更新缓存
|
||||
pendingAuditsCache.value.clear();
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((audit: StglAnnualReviewAuditVO) => {
|
||||
if (audit.annualReviewId) {
|
||||
pendingAuditsCache.value.set(Number(audit.annualReviewId), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取待审批记录失败:', error);
|
||||
// 重置数据
|
||||
pendingAudits.value = [];
|
||||
pendingAuditsCache.value.clear();
|
||||
// 不显示错误提示,避免影响页面正常使用
|
||||
} finally {
|
||||
pendingAuditsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 判断当前用户是否可以审批该年审记录 */
|
||||
const canApprove = (row: StglClubAnnualReviewVO) => {
|
||||
// 检查当前用户是否登录
|
||||
if (!userStore.userId) {
|
||||
console.log('canApprove: 用户未登录');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查年审记录状态,支持"已提交"和"审核中"状态
|
||||
if (row.status !== '1' && row.status !== '2') {
|
||||
console.log(`canApprove: 状态不符合 ${row.status}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查缓存中是否有该年审记录的审批权限
|
||||
const hasPermission = pendingAuditsCache.value.has(Number(row.id));
|
||||
console.log(`canApprove: 年审ID=${row.id}, 当前用户=${userStore.userId}, 缓存权限=${hasPermission}, 缓存内容=`, Array.from(pendingAuditsCache.value.keys()));
|
||||
return hasPermission;
|
||||
};
|
||||
|
||||
/** 审批按钮操作 */
|
||||
const handleApprove = async (row: StglClubAnnualReviewVO) => {
|
||||
// 从待审批记录中找到对应的审批ID
|
||||
const audit = pendingAudits.value.find(audit =>
|
||||
Number(audit.annualReviewId) === Number(row.id)
|
||||
);
|
||||
|
||||
if (!audit) {
|
||||
proxy?.$modal.msgError('未找到对应的审批记录');
|
||||
return;
|
||||
}
|
||||
|
||||
approveDialogRef.value?.open(audit.id, row.id);
|
||||
};
|
||||
|
||||
/** 审批成功回调 */
|
||||
const handleApproveSuccess = async () => {
|
||||
// 审批完成后,需要同时刷新年审记录和待审批记录
|
||||
await Promise.all([
|
||||
getList(),
|
||||
loadPendingAudits()
|
||||
]);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1278,9 +1278,9 @@ watch(
|
||||
() => form.value.advisors,
|
||||
(advisors: AdvisorVO[]) => {
|
||||
// 防止递归更新
|
||||
if (syncingAdvisors.value) return;
|
||||
if (syncLock.value) return;
|
||||
|
||||
syncingAdvisors.value = true;
|
||||
syncLock.value = true;
|
||||
nextTick(() => {
|
||||
try {
|
||||
// 为了兼容旧版本,同步第一个指导老师的信息到旧字段
|
||||
@@ -1297,7 +1297,7 @@ watch(
|
||||
form.value.advisorEmail = undefined;
|
||||
}
|
||||
} finally {
|
||||
syncingAdvisors.value = false;
|
||||
syncLock.value = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -33,21 +33,21 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="院系" prop="collegeName">
|
||||
<el-select v-model="queryParams.collegeName" placeholder="请选择院系" clearable style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option v-for="college in collegeOptions" :key="college.name" :label="college.name" :value="college.name" />
|
||||
<el-option v-for="college in collegeOptions" :key="college.value" :label="college.label" :value="college.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业" prop="majorName">
|
||||
<el-select v-model="queryParams.majorName" placeholder="请选择专业" clearable style="width: 100%" @change="handleMajorChange">
|
||||
<el-option v-for="major in majorOptions" :key="major.name" :label="major.name" :value="major.name" />
|
||||
<el-option v-for="major in majorOptions" :key="major.value" :label="major.label" :value="major.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-select v-model="queryParams.className" placeholder="请选择班级" clearable style="width: 100%">
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.name" :label="classItem.name" :value="classItem.name" />
|
||||
<el-option v-for="classItem in classOptions" :key="classItem.value" :label="classItem.label" :value="classItem.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -106,10 +106,28 @@
|
||||
<el-tag v-else-if="scope.row.status === 1" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 3" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 4" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 5" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知状态</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否需要转介" align="center" prop="needReferral" width="120">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.status === 1">
|
||||
<el-tag v-if="scope.row.needReferral === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="scope.row.needReferral === 1" type="success">否</el-tag>
|
||||
<span v-else>-</span>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本学期到访次数" align="center" prop="semesterVisitCount" width="120">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.semesterVisitCount > 0" type="info">{{ scope.row.semesterVisitCount }}次</el-tag>
|
||||
<span v-else>0次</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="150">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
@@ -129,6 +147,31 @@
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待审核状态可审核通过或拒绝 -->
|
||||
<el-tooltip content="审核通过" placement="top" v-if="scope.row.status === 4">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['xljk:consultationappointment:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="审核拒绝" placement="top" v-if="scope.row.status === 4">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleReject(scope.row)"
|
||||
v-hasPermi="['xljk:consultationappointment:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 待咨询状态可记录咨询结果 -->
|
||||
<el-tooltip content="记录结果" placement="top" v-if="scope.row.status === 0">
|
||||
<el-button
|
||||
@@ -161,29 +204,45 @@
|
||||
<el-descriptions-item label="咨询师">{{ form.counselorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约日期">{{ parseTime(form.appointmentDate, '{y}-{m}-{d}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="预约时段">{{ form.appointmentDetailTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询原因" :span="2">{{ form.reason }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询主题">{{ form.consultTheme || '未选择' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询原因">{{ form.reason }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="form.status === 0" type="primary">待咨询</el-tag>
|
||||
<el-tag v-else-if="form.status === 1" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="form.status === 2" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="form.status === 3" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="form.status === 4" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="form.status === 5" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知状态({{ form.status }})</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(form.createTime) }}</el-descriptions-item>
|
||||
|
||||
<!-- 只有在已到访状态时才显示咨询结果相关字段 -->
|
||||
<template v-if="form.status == 1">
|
||||
<el-descriptions-item label="实际到访时间">
|
||||
{{ form.actualVisitTime ? parseTime(form.actualVisitTime, '{y}-{m}-{d} {h}:{i}:{s}') : '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="沟通时长">
|
||||
{{ form.communicationDuration ? form.communicationDuration + ' 分钟' : '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询内容" :span="2">
|
||||
<el-descriptions-item label="心理病史">
|
||||
<el-tag v-if="form.psychologicalHistory === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="form.psychologicalHistory === 1" type="success">否</el-tag>
|
||||
<span v-else>未记录</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="心理病史详情" :span="2" v-if="form.psychologicalHistory === 2">
|
||||
{{ form.psychologicalHistoryDetail || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="基本情况" :span="2">
|
||||
{{ form.basicSituation || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="重大生活事件" :span="2">
|
||||
{{ form.majorLifeEvents || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="本次咨询过程" :span="2">
|
||||
{{ form.consultationContent || '未记录' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="咨询师建议" :span="2">
|
||||
{{ form.counselorSuggestion || '未记录' }}
|
||||
<el-descriptions-item label="是否需要转介">
|
||||
<el-tag v-if="form.needReferral === 2" type="warning">是</el-tag>
|
||||
<el-tag v-else-if="form.needReferral === 1" type="success">否</el-tag>
|
||||
<span v-else>未记录</span>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
@@ -196,8 +255,8 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 记录咨询结果对话框 -->
|
||||
<el-dialog title="记录咨询结果" v-model="recordDialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="100px">
|
||||
<el-dialog title="记录咨询结果" v-model="recordDialogVisible" width="800px" append-to-body>
|
||||
<el-form ref="recordFormRef" :model="recordForm" :rules="recordRules" label-width="120px">
|
||||
<el-form-item label="学生姓名">
|
||||
<el-input v-model="recordForm.studentName" disabled />
|
||||
</el-form-item>
|
||||
@@ -215,27 +274,35 @@
|
||||
</el-form-item>
|
||||
<!-- 只有在已到访状态时才显示以下字段 -->
|
||||
<template v-if="recordForm.status === 1">
|
||||
<el-form-item label="实际到访时间" prop="actualVisitTime" class="no-wrap-label">
|
||||
<el-date-picker
|
||||
v-model="recordForm.actualVisitTime"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="沟通时长" prop="communicationDuration">
|
||||
<div class="flex-row align-center">
|
||||
<el-input-number v-model="recordForm.communicationDuration" :min="1" :max="180" />
|
||||
<span class="ml-2">分钟</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询内容" prop="consultationContent">
|
||||
<el-input v-model="recordForm.consultationContent" type="textarea" placeholder="请输入咨询内容" :rows="4" />
|
||||
<el-form-item label="心理病史" prop="psychologicalHistory">
|
||||
<el-radio-group v-model="recordForm.psychologicalHistory" @change="handlePsychologicalHistoryChange">
|
||||
<el-radio :value="2">是</el-radio>
|
||||
<el-radio :value="1">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询师建议" prop="counselorSuggestion">
|
||||
<el-input v-model="recordForm.counselorSuggestion" type="textarea" placeholder="请输入咨询师建议" :rows="4" />
|
||||
<el-form-item label="心理病史详情" prop="psychologicalHistoryDetail" v-if="recordForm.psychologicalHistory === 2">
|
||||
<el-input v-model="recordForm.psychologicalHistoryDetail" type="textarea" placeholder="请详细描述心理病史" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="基本情况" prop="basicSituation">
|
||||
<el-input v-model="recordForm.basicSituation" type="textarea" placeholder="请输入基本情况" :rows="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="重大生活事件" prop="majorLifeEvents">
|
||||
<el-input v-model="recordForm.majorLifeEvents" type="textarea" placeholder="请输入重大生活事件(可选)" :rows="3" />
|
||||
</el-form-item>
|
||||
<el-form-item label="本次咨询过程" prop="consultationContent">
|
||||
<el-input v-model="recordForm.consultationContent" type="textarea" placeholder="请输入本次咨询过程" :rows="4" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要转介" prop="needReferral">
|
||||
<el-radio-group v-model="recordForm.needReferral" @change="handleReferralChange">
|
||||
<el-radio :value="2">是</el-radio>
|
||||
<el-radio :value="1">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
@@ -268,7 +335,8 @@ import {
|
||||
CounselorOption,
|
||||
CollegeOption,
|
||||
MajorOption,
|
||||
ClassOption
|
||||
ClassOption,
|
||||
XljkConsultationAppointmentQuery
|
||||
} from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
@@ -281,7 +349,9 @@ const appointment_status = ref([
|
||||
{ label: '待咨询', value: 0 },
|
||||
{ label: '已到访', value: 1 },
|
||||
{ label: '已取消', value: 2 },
|
||||
{ label: '未到访', value: 3 }
|
||||
{ label: '未到访', value: 3 },
|
||||
{ label: '待审核', value: 4 },
|
||||
{ label: '已驳回', value: 5 }
|
||||
]);
|
||||
|
||||
// 遮罩层
|
||||
@@ -304,19 +374,17 @@ const open = ref(false);
|
||||
const dateRange = ref<string[]>([]);
|
||||
// 咨询师选项
|
||||
const counselorOptions = ref<CounselorOption[]>([]);
|
||||
// 院系选项
|
||||
const collegeOptions = ref<CollegeOption[]>([]);
|
||||
// 专业选项
|
||||
const majorOptions = ref<MajorOption[]>([]);
|
||||
// 班级选项
|
||||
const classOptions = ref<ClassOption[]>([]);
|
||||
// 修复院系、专业、班级选项的类型定义
|
||||
const collegeOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const majorOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
const classOptions = ref<Array<{ value: string; label: string }>>([]);
|
||||
// 是否显示记录对话框
|
||||
const recordDialogVisible = ref(false);
|
||||
// 提交按钮加载状态
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
// 查询参数的类型定义和字段修复
|
||||
const queryParams = reactive<XljkConsultationAppointmentQuery>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
@@ -339,20 +407,28 @@ const recordForm = reactive<XljkConsultationAppointmentForm>({
|
||||
scheduleId: undefined,
|
||||
appointmentDate: '',
|
||||
appointmentTime: '',
|
||||
appointmentDetailTime: '',
|
||||
reason: '',
|
||||
status: 1, // 默认已到访,使用数值类型
|
||||
actualVisitTime: undefined,
|
||||
communicationDuration: 30, // 默认30分钟
|
||||
consultationContent: '',
|
||||
counselorSuggestion: ''
|
||||
counselorSuggestion: '',
|
||||
studentName: '',
|
||||
psychologicalHistory: 1, // 默认否
|
||||
psychologicalHistoryDetail: '',
|
||||
basicSituation: '',
|
||||
majorLifeEvents: '',
|
||||
needReferral: 1 // 默认不需要转介
|
||||
});
|
||||
|
||||
// 记录表单校验
|
||||
const recordRules = ref({
|
||||
// 修复表单验证规则的类型定义
|
||||
const recordRules = ref<any>({
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
psychologicalHistory: [{ required: true, message: '请选择心理病史', trigger: 'change' }],
|
||||
basicSituation: [{ required: true, message: '请输入基本情况', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入本次咨询过程', trigger: 'blur' }],
|
||||
needReferral: [{ required: true, message: '请选择是否需要转介', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听状态变化,动态调整表单验证规则
|
||||
@@ -368,9 +444,11 @@ watch(
|
||||
// 已到访
|
||||
recordRules.value = {
|
||||
status: [{ required: true, message: '请选择咨询结果', trigger: 'change' }],
|
||||
actualVisitTime: [{ required: true, message: '请选择实际到访时间', trigger: 'change' }],
|
||||
communicationDuration: [{ required: true, message: '请输入沟通时长', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入咨询内容', trigger: 'blur' }]
|
||||
psychologicalHistory: [{ required: true, message: '请选择心理病史', trigger: 'change' }],
|
||||
basicSituation: [{ required: true, message: '请输入基本情况', trigger: 'blur' }],
|
||||
consultationContent: [{ required: true, message: '请输入本次咨询过程', trigger: 'blur' }],
|
||||
needReferral: [{ required: true, message: '请选择是否需要转介', trigger: 'change' }]
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -384,7 +462,9 @@ const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 处理日期范围
|
||||
queryParams.params = {};
|
||||
if (!queryParams.params) {
|
||||
queryParams.params = {};
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
queryParams.params.beginAppointmentDate = dateRange.value[0];
|
||||
queryParams.params.endAppointmentDate = dateRange.value[1];
|
||||
@@ -503,24 +583,13 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
console.log('转换后的status:', form.status);
|
||||
}
|
||||
|
||||
// 检查实际到访时间
|
||||
console.log('实际到访时间:', form.actualVisitTime);
|
||||
if (form.status === 1 && !form.actualVisitTime) {
|
||||
console.warn('已到访状态但实际到访时间为空,尝试使用行数据');
|
||||
if (row.actualVisitTime) {
|
||||
form.actualVisitTime = row.actualVisitTime;
|
||||
console.log('使用行数据的实际到访时间:', form.actualVisitTime);
|
||||
} else {
|
||||
console.warn('行数据也没有实际到访时间');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('复制后的表单数据:', form);
|
||||
|
||||
// 如果是已到访状态,确保咨询结果相关字段有值
|
||||
if (form.status === 1) {
|
||||
console.log('已到访状态,咨询结果相关字段:', {
|
||||
actualVisitTime: form.actualVisitTime,
|
||||
communicationDuration: form.communicationDuration,
|
||||
consultationContent: form.consultationContent,
|
||||
counselorSuggestion: form.counselorSuggestion
|
||||
@@ -558,6 +627,58 @@ const handleView = async (row: XljkConsultationAppointmentVO) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 审核通过操作 */
|
||||
const handleApprove = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认审核通过该预约申请?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
// 构建更新数据
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
status: "0" // 审核通过,状态改为待咨询
|
||||
};
|
||||
|
||||
await updateXljkAppointment(updateData);
|
||||
ElMessage.success('审核通过成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审核通过失败', error);
|
||||
ElMessage.error('审核通过失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 审核拒绝操作 */
|
||||
const handleReject = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认拒绝该预约申请?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
// 构建更新数据
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
status: "5" // 拒绝,状态改为已驳回
|
||||
};
|
||||
|
||||
await updateXljkAppointment(updateData);
|
||||
ElMessage.success('审核拒绝成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审核拒绝失败', error);
|
||||
ElMessage.error('审核拒绝失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** 记录咨询结果操作 */
|
||||
const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
try {
|
||||
@@ -575,10 +696,15 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
appointmentDetailTime: '',
|
||||
reason: '',
|
||||
status: 1, // 默认已完成
|
||||
actualVisitTime: new Date(),
|
||||
communicationDuration: 30,
|
||||
consultationContent: '',
|
||||
counselorSuggestion: ''
|
||||
counselorSuggestion: '',
|
||||
studentName: '',
|
||||
psychologicalHistory: 1, // 默认否(1=否,2=是)
|
||||
psychologicalHistoryDetail: '',
|
||||
basicSituation: '',
|
||||
majorLifeEvents: '',
|
||||
needReferral: 1 // 默认不需要转介(1=不需要,2=需要)
|
||||
};
|
||||
|
||||
Object.assign(recordForm, defaultValues);
|
||||
@@ -594,6 +720,16 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
recordForm.appointmentDetailTime = res.data.appointmentDetailTime;
|
||||
recordForm.reason = res.data.reason;
|
||||
recordForm.studentName = res.data.studentName;
|
||||
|
||||
// 如果API返回了已有的咨询记录数据,保持原有值
|
||||
if (res.data.needReferral !== undefined && res.data.needReferral !== null) {
|
||||
recordForm.needReferral = res.data.needReferral;
|
||||
console.log('从API获取的needReferral值:', res.data.needReferral, res.data.needReferral === 2 ? '是(需要转介)' : res.data.needReferral === 1 ? '否(不需要转介)' : '未知状态');
|
||||
}
|
||||
if (res.data.psychologicalHistory !== undefined && res.data.psychologicalHistory !== null) {
|
||||
recordForm.psychologicalHistory = res.data.psychologicalHistory;
|
||||
console.log('从API获取的psychologicalHistory值:', res.data.psychologicalHistory, res.data.psychologicalHistory === 2 ? '是(有病史)' : res.data.psychologicalHistory === 1 ? '否(无病史)' : '未知状态');
|
||||
}
|
||||
} else {
|
||||
console.warn('API响应格式不正确或无数据:', res);
|
||||
// 使用行数据填充
|
||||
@@ -608,6 +744,7 @@ const handleRecord = async (row: XljkConsultationAppointmentVO) => {
|
||||
}
|
||||
|
||||
console.log('填充后的记录表单:', recordForm);
|
||||
console.log('最终needReferral值:', recordForm.needReferral, recordForm.needReferral === 2 ? '是(需要转介)' : recordForm.needReferral === 1 ? '否(不需要转介)' : '未知状态');
|
||||
recordDialogVisible.value = true;
|
||||
} catch (error) {
|
||||
console.error('获取预约详情失败', error);
|
||||
@@ -625,6 +762,8 @@ const submitRecordForm = async () => {
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
|
||||
console.log('提交前的recordForm数据:', recordForm);
|
||||
|
||||
// 构建提交数据
|
||||
const submitData: any = {
|
||||
id: recordForm.id,
|
||||
@@ -633,39 +772,59 @@ const submitRecordForm = async () => {
|
||||
|
||||
// 如果是已到访状态,添加其他字段
|
||||
if (recordForm.status === 1) {
|
||||
// 确保日期格式正确
|
||||
if (recordForm.actualVisitTime) {
|
||||
// 如果是Date对象,转换为ISO字符串格式
|
||||
if (recordForm.actualVisitTime instanceof Date) {
|
||||
const year = recordForm.actualVisitTime.getFullYear();
|
||||
const month = String(recordForm.actualVisitTime.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(recordForm.actualVisitTime.getDate()).padStart(2, '0');
|
||||
const hours = String(recordForm.actualVisitTime.getHours()).padStart(2, '0');
|
||||
const minutes = String(recordForm.actualVisitTime.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(recordForm.actualVisitTime.getSeconds()).padStart(2, '0');
|
||||
|
||||
submitData.actualVisitTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
} else {
|
||||
submitData.actualVisitTime = recordForm.actualVisitTime;
|
||||
}
|
||||
console.log('提交的实际到访时间:', submitData.actualVisitTime);
|
||||
}
|
||||
|
||||
submitData.communicationDuration = recordForm.communicationDuration;
|
||||
submitData.consultationContent = recordForm.consultationContent;
|
||||
submitData.counselorSuggestion = recordForm.counselorSuggestion;
|
||||
submitData.counselorSuggestion = recordForm.counselorSuggestion || '';
|
||||
submitData.psychologicalHistory = recordForm.psychologicalHistory;
|
||||
submitData.psychologicalHistoryDetail = recordForm.psychologicalHistoryDetail || '';
|
||||
submitData.basicSituation = recordForm.basicSituation;
|
||||
submitData.majorLifeEvents = recordForm.majorLifeEvents || '';
|
||||
submitData.needReferral = recordForm.needReferral;
|
||||
|
||||
console.log('已到访状态,提交的数据字段:');
|
||||
console.log('- psychologicalHistoryDetail:', submitData.psychologicalHistoryDetail, '类型:', typeof submitData.psychologicalHistoryDetail, '长度:', submitData.psychologicalHistoryDetail?.length);
|
||||
console.log('- basicSituation:', submitData.basicSituation, '类型:', typeof submitData.basicSituation, '长度:', submitData.basicSituation?.length);
|
||||
console.log('- majorLifeEvents:', submitData.majorLifeEvents, '类型:', typeof submitData.majorLifeEvents, '长度:', submitData.majorLifeEvents?.length);
|
||||
console.log('- needReferral:', submitData.needReferral, '类型:', typeof submitData.needReferral);
|
||||
console.log('- psychologicalHistory:', submitData.psychologicalHistory, '类型:', typeof submitData.psychologicalHistory);
|
||||
} else {
|
||||
// 未到访状态,设置默认值
|
||||
submitData.actualVisitTime = null;
|
||||
submitData.communicationDuration = 0;
|
||||
submitData.consultationContent = '';
|
||||
submitData.counselorSuggestion = '';
|
||||
submitData.psychologicalHistory = null;
|
||||
submitData.psychologicalHistoryDetail = '';
|
||||
submitData.basicSituation = '';
|
||||
submitData.majorLifeEvents = '';
|
||||
submitData.needReferral = null;
|
||||
}
|
||||
|
||||
await recordXljkConsultation(recordForm.id as number, submitData);
|
||||
console.log('最终提交的数据:', submitData);
|
||||
|
||||
const response = await recordXljkConsultation(recordForm.id as number, submitData);
|
||||
console.log('API响应:', response);
|
||||
|
||||
ElMessage.success('记录成功');
|
||||
recordDialogVisible.value = false;
|
||||
getList();
|
||||
|
||||
// 重新查询列表,并验证数据是否保存成功
|
||||
await getList();
|
||||
|
||||
// 验证数据保存情况
|
||||
try {
|
||||
const verifyRes = await getXljkAppointment(recordForm.id as number);
|
||||
console.log('验证保存结果,获取的数据:', verifyRes.data);
|
||||
if (verifyRes.code === 200 && verifyRes.data) {
|
||||
console.log('验证字段保存情况:');
|
||||
console.log('- psychologicalHistoryDetail 保存值:', verifyRes.data.psychologicalHistoryDetail);
|
||||
console.log('- basicSituation 保存值:', verifyRes.data.basicSituation);
|
||||
console.log('- majorLifeEvents 保存值:', verifyRes.data.majorLifeEvents);
|
||||
console.log('- needReferral 保存值:', verifyRes.data.needReferral);
|
||||
console.log('- psychologicalHistory 保存值:', verifyRes.data.psychologicalHistory);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('验证保存结果失败:', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交记录失败', error);
|
||||
ElMessage.error('提交记录失败');
|
||||
@@ -710,8 +869,8 @@ const handleDelete = async (row?: XljkConsultationAppointmentVO) => {
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = async () => {
|
||||
// 构建查询参数,处理日期范围和params对象
|
||||
const exportParams = { ...queryParams };
|
||||
// 构建查询参数,处理日期范围
|
||||
const exportParams: any = { ...queryParams };
|
||||
delete exportParams.params; // 移除params对象
|
||||
|
||||
// 将日期范围添加到参数中
|
||||
@@ -771,7 +930,7 @@ const handleExport = async () => {
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
document.body.removeChild(a);
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error('导出失败:', error);
|
||||
ElMessage.error(`导出失败: ${error.message}`);
|
||||
}
|
||||
@@ -788,7 +947,7 @@ const handleCollegeChange = async (collegeName: string) => {
|
||||
if (collegeName) {
|
||||
// 获取该院系下的专业
|
||||
try {
|
||||
const res = await getMajorOptions(collegeName);
|
||||
const res = await getXljkConsultationMajorOptions(collegeName);
|
||||
majorOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取专业选项失败', error);
|
||||
@@ -808,7 +967,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
if (majorName && queryParams.collegeName) {
|
||||
// 获取专业下的班级
|
||||
try {
|
||||
const res = await getClassOptions(queryParams.collegeName, majorName);
|
||||
const res = await getXljkConsultationClassOptions(queryParams.collegeName, majorName);
|
||||
classOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取班级选项失败', error);
|
||||
@@ -816,7 +975,7 @@ const handleMajorChange = async (majorName: string) => {
|
||||
} else if (queryParams.collegeName) {
|
||||
// 如果清空专业但有院系,则获取院系下所有班级
|
||||
try {
|
||||
const res = await getClassOptions(queryParams.collegeName);
|
||||
const res = await getXljkConsultationClassOptions(queryParams.collegeName);
|
||||
classOptions.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('获取班级选项失败', error);
|
||||
@@ -827,6 +986,18 @@ const handleMajorChange = async (majorName: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 转介选择变化时的调试日志 */
|
||||
const handleReferralChange = (value: number) => {
|
||||
console.log('转介字段值变化:', value, value === 2 ? '是(需要转介)' : value === 1 ? '否(不需要转介)' : '未知状态');
|
||||
console.log('当前recordForm.needReferral:', recordForm.needReferral);
|
||||
};
|
||||
|
||||
/** 心理病史选择变化时的调试日志 */
|
||||
const handlePsychologicalHistoryChange = (value: number) => {
|
||||
console.log('心理病史字段值变化:', value, value === 2 ? '是(有心理病史)' : value === 1 ? '否(无心理病史)' : '未知状态');
|
||||
console.log('当前recordForm.psychologicalHistory:', recordForm.psychologicalHistory);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
console.log('初始化appointment_status字典数据:', appointment_status.value);
|
||||
|
||||
@@ -33,56 +33,75 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格内容:显示全天和时间段 -->
|
||||
<!-- 表格内容:显示时间段 -->
|
||||
<div class="week-body">
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>全天</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<!-- 这里可以放全天事件,目前为空 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间段行 -->
|
||||
<div class="time-slots-container">
|
||||
<!-- 上午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>上午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="time-slot-card morning"
|
||||
:class="{
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
{{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
<!-- 动态显示所有排班时段 -->
|
||||
<template v-for="timeSlotType in getUniqueTimeSlots()" :key="timeSlotType">
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>{{ getTimeSlotLabel(timeSlotType) }}</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-for="schedule in getSchedulesForDateAndTimeSlot(day.date, timeSlotType)"
|
||||
:key="`${schedule.scheduleId}-${schedule.startTime}`"
|
||||
class="time-slot-card"
|
||||
:class="{
|
||||
'morning': schedule.timeSlot === '0',
|
||||
'noon': schedule.timeSlot === '1',
|
||||
'afternoon': schedule.timeSlot === '2',
|
||||
'fully-booked': schedule.isFullyBooked || schedule.isExpired
|
||||
}"
|
||||
@click="handleScheduleClickEvent(schedule)"
|
||||
>
|
||||
{{ schedule.startTime }}-{{ schedule.endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>下午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="time-slot-card afternoon"
|
||||
:class="{
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
{{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
<!-- 兼容性:如果没有新排班数据,继续显示原有的中午/下午时段 -->
|
||||
<template v-if="getUniqueTimeSlots().length === 0">
|
||||
<!-- 中午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>中午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="time-slot-card morning"
|
||||
:class="{
|
||||
'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
{{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div class="time-row">
|
||||
<div class="time-label-cell">
|
||||
<span>下午</span>
|
||||
</div>
|
||||
<div v-for="day in weekDays" :key="day.date" class="day-cell">
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="time-slot-card afternoon"
|
||||
:class="{
|
||||
'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired
|
||||
}"
|
||||
@click="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
{{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,25 +130,44 @@
|
||||
>
|
||||
<div class="month-day-number">{{ day.dayNumber }}</div>
|
||||
|
||||
<!-- 上午时段 -->
|
||||
<!-- 显示该日期的所有排班 -->
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="month-slot morning"
|
||||
:class="{ 'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getMorningSlot(day.date))"
|
||||
v-for="schedule in getSchedulesForDate(day.date)"
|
||||
:key="`${schedule.scheduleId}-${schedule.startTime}`"
|
||||
class="month-slot"
|
||||
:class="{
|
||||
'morning': schedule.timeSlot === '0',
|
||||
'noon': schedule.timeSlot === '1',
|
||||
'afternoon': schedule.timeSlot === '2',
|
||||
'fully-booked': schedule.isFullyBooked || schedule.isExpired
|
||||
}"
|
||||
@click.stop="handleScheduleClickEvent(schedule)"
|
||||
>
|
||||
上午 {{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
{{ schedule.timeDisplay }}
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="month-slot afternoon"
|
||||
:class="{ 'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
下午 {{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
<!-- 兼容性:如果没有新排班数据,继续显示原有的中午/下午时段 -->
|
||||
<template v-if="getSchedulesForDate(day.date).length === 0">
|
||||
<!-- 中午时段 -->
|
||||
<div
|
||||
v-if="getMorningSlot(day.date)"
|
||||
class="month-slot morning"
|
||||
:class="{ 'fully-booked': getMorningSlot(day.date).allBooked || getMorningSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getMorningSlot(day.date))"
|
||||
>
|
||||
中午 {{ getMorningSlot(day.date).startTime }}-{{ getMorningSlot(day.date).endTime }}
|
||||
</div>
|
||||
|
||||
<!-- 下午时段 -->
|
||||
<div
|
||||
v-if="getAfternoonSlot(day.date)"
|
||||
class="month-slot afternoon"
|
||||
:class="{ 'fully-booked': getAfternoonSlot(day.date).allBooked || getAfternoonSlot(day.date).isExpired }"
|
||||
@click.stop="handleSlotClick(getAfternoonSlot(day.date))"
|
||||
>
|
||||
下午 {{ getAfternoonSlot(day.date).startTime }}-{{ getAfternoonSlot(day.date).endTime }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +180,7 @@ import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue';
|
||||
import { AvailableTimeSlot } from '@/api/xljk/consultation/types';
|
||||
import { AvailableTimeSlot, ScheduleWithStatus } from '@/api/xljk/consultation/types';
|
||||
|
||||
// 定义组件的Props
|
||||
const props = defineProps({
|
||||
@@ -150,6 +188,10 @@ const props = defineProps({
|
||||
type: Array as () => AvailableTimeSlot[],
|
||||
default: () => []
|
||||
},
|
||||
calendarSchedules: {
|
||||
type: Array as () => ScheduleWithStatus[],
|
||||
default: () => []
|
||||
},
|
||||
defaultView: {
|
||||
type: String,
|
||||
default: 'week',
|
||||
@@ -158,7 +200,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
// 定义组件的事件
|
||||
const emit = defineEmits(['dateRangeChange', 'slotClick', 'viewChange']);
|
||||
const emit = defineEmits(['dateRangeChange', 'slotClick', 'scheduleClick', 'viewChange']);
|
||||
|
||||
// 当前日期和周期的开始日期
|
||||
const today = ref(new Date());
|
||||
@@ -382,7 +424,7 @@ const monthDays = computed(() => {
|
||||
// 如果是最后一行,添加额外日志
|
||||
if (week === 5) {
|
||||
console.log(`日期 ${formattedDate} (${date.toLocaleDateString()}) 数据状态:`, {
|
||||
上午时段: morningSlot ? '有数据' : '无数据',
|
||||
中午时段: morningSlot ? '有数据' : '无数据',
|
||||
下午时段: afternoonSlot ? '有数据' : '无数据',
|
||||
isOtherMonth
|
||||
});
|
||||
@@ -412,7 +454,7 @@ function formatDate(date: Date) {
|
||||
return parseTime(date, '{y}-{m}-{d}');
|
||||
}
|
||||
|
||||
// 获取指定日期的上午时段
|
||||
// 获取指定日期的中午时段
|
||||
function getMorningSlot(dateStr: string) {
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '0');
|
||||
}
|
||||
@@ -422,6 +464,76 @@ function getAfternoonSlot(dateStr: string) {
|
||||
return props.availableSlots.find((slot) => slot.date === dateStr && slot.timeSlot === '1');
|
||||
}
|
||||
|
||||
// 获取指定日期的所有排班
|
||||
function getSchedulesForDate(dateStr: string) {
|
||||
return props.calendarSchedules.filter(schedule => schedule.date === dateStr)
|
||||
.sort((a, b) => {
|
||||
// 先按时段类型排序
|
||||
const timeSlotCompare = a.timeSlot.localeCompare(b.timeSlot);
|
||||
if (timeSlotCompare !== 0) return timeSlotCompare;
|
||||
// 再按开始时间排序
|
||||
return a.startTime.localeCompare(b.startTime);
|
||||
});
|
||||
}
|
||||
|
||||
// 处理排班点击
|
||||
function handleScheduleClickEvent(schedule: ScheduleWithStatus) {
|
||||
if (!schedule) return;
|
||||
|
||||
// 如果该时段已过期,则不允许预约
|
||||
if (schedule.isExpired) {
|
||||
ElMessage.warning('该时段已过期,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该时段全部约满,则不允许预约
|
||||
if (schedule.isFullyBooked) {
|
||||
ElMessage.warning('该时段已全部约满,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
emit('scheduleClick', schedule);
|
||||
}
|
||||
|
||||
// 获取所有唯一的时段类型
|
||||
function getUniqueTimeSlots() {
|
||||
const timeSlots = new Set<string>();
|
||||
props.calendarSchedules.forEach(schedule => {
|
||||
timeSlots.add(schedule.timeSlot);
|
||||
});
|
||||
return Array.from(timeSlots).sort();
|
||||
}
|
||||
|
||||
// 获取时段类型标签
|
||||
function getTimeSlotLabel(timeSlot: string) {
|
||||
switch (timeSlot) {
|
||||
case '0': return '中午';
|
||||
case '1': return '下午';
|
||||
case '2': return '下午';
|
||||
default: return '时段' + timeSlot;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定日期和时段类型的排班
|
||||
function getSchedulesForDateAndTimeSlot(dateStr: string, timeSlot: string) {
|
||||
const result = props.calendarSchedules
|
||||
.filter(schedule => schedule.date === dateStr && schedule.timeSlot === timeSlot)
|
||||
.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
||||
|
||||
// 特别调试9月18日time_slot=0的情况
|
||||
if (dateStr === '2025-09-18' && timeSlot === '0') {
|
||||
console.log('🔍 WeekView调试 - getSchedulesForDateAndTimeSlot:', {
|
||||
dateStr,
|
||||
timeSlot,
|
||||
totalSchedules: props.calendarSchedules.length,
|
||||
filteredResult: result,
|
||||
resultCount: result.length
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 监听视图变化
|
||||
watch(
|
||||
() => props.defaultView,
|
||||
@@ -551,12 +663,18 @@ watch(
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.time-slot-card.noon {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.time-slot-card.fully-booked {
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 月视图样式 */
|
||||
.month-grid {
|
||||
flex: 1;
|
||||
@@ -645,9 +763,15 @@ watch(
|
||||
background-color: #67c23a;
|
||||
}
|
||||
|
||||
.month-slot.noon {
|
||||
background-color: #e6a23c;
|
||||
}
|
||||
|
||||
.month-slot.fully-booked {
|
||||
background-color: #c0c0c0;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -19,9 +19,11 @@
|
||||
<!-- 使用WeekView组件,支持月视图和周视图切换 -->
|
||||
<WeekView
|
||||
:available-slots="availableSlots"
|
||||
:calendar-schedules="calendarSchedules"
|
||||
:default-view="calendarView"
|
||||
@date-range-change="handleDateRangeChange"
|
||||
@slot-click="handleSlotClick"
|
||||
@schedule-click="handleScheduleClick"
|
||||
@view-change="handleViewChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -35,27 +37,31 @@
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-input v-model="counselorDisplayName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="selectedOffice" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约日期" prop="appointmentDate">
|
||||
<el-input v-model="formattedDate" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="studentName">
|
||||
<el-input v-model="studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="预约时段" prop="appointmentDetailTime">
|
||||
<el-select v-model="appointmentForm.appointmentDetailTime" placeholder="请选择具体预约时段" style="width: 100%">
|
||||
<el-input v-model="appointmentForm.appointmentDetailTime" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询主题" prop="consultTheme">
|
||||
<el-select v-model="appointmentForm.consultTheme" placeholder="请选择咨询主题*" style="width: 100%">
|
||||
<el-option
|
||||
v-for="slot in availableDetailSlots"
|
||||
:key="slot.timeSlot"
|
||||
:label="slot.timeSlot"
|
||||
:value="slot.timeSlot"
|
||||
:disabled="slot.isBooked || slot.isExpired"
|
||||
:style="getTimeSlotStyle(slot)"
|
||||
v-for="theme in xljk_consult_theme"
|
||||
:key="theme.value"
|
||||
:label="theme.label"
|
||||
:value="theme.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="学号" prop="studentNo" v-if="!hasStudentInfo">
|
||||
<el-input v-model="studentNo" placeholder="请输入学号" @blur="handleStudentNoBlur" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="studentName">
|
||||
<el-input v-model="studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="咨询原因" prop="reason">
|
||||
<el-input v-model="appointmentForm.reason" type="textarea" placeholder="请简要描述咨询原因" :rows="4" />
|
||||
</el-form-item>
|
||||
@@ -69,13 +75,14 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 我的预约列表弹窗 -->
|
||||
<el-dialog v-model="myAppointmentsVisible" title="我的预约记录" width="800px" append-to-body destroy-on-close>
|
||||
<el-table :data="myAppointmentsList" border style="width: 100%">
|
||||
<el-table-column label="预约日期" prop="appointmentDate" width="120" />
|
||||
<el-table-column label="预约时段" prop="appointmentDetailTime" width="120" />
|
||||
<el-table-column label="咨询师" prop="counselorName" width="100" />
|
||||
<el-table-column label="咨询原因" prop="reason" show-overflow-tooltip />
|
||||
<el-table-column label="状态" prop="status" width="100">
|
||||
<el-dialog v-model="myAppointmentsVisible" title="我的预约记录" width="1200px" append-to-body destroy-on-close>
|
||||
<el-table :data="myAppointmentsList" border style="width: 100%" header-align="center">
|
||||
<el-table-column label="预约日期" prop="appointmentDate" width="120" align="center" />
|
||||
<el-table-column label="预约时段" prop="appointmentDetailTime" width="140" align="center" />
|
||||
<el-table-column label="咨询师" prop="counselorName" width="100" align="center" />
|
||||
<el-table-column label="办公室" prop="office" width="200" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="咨询原因" prop="reason" min-width="200" show-overflow-tooltip header-align="center" />
|
||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<!-- 添加调试信息 -->
|
||||
<div style="display: none">状态值: {{ scope.row.status }} 类型: {{ typeof scope.row.status }} 字典数据: {{ appointment_status }}</div>
|
||||
@@ -85,6 +92,8 @@
|
||||
<el-tag v-else-if="scope.row.status === 1 || scope.row.status === '1'" type="success">已到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2 || scope.row.status === '2'" type="danger">已取消</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 3 || scope.row.status === '3'" type="warning">未到访</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 4 || scope.row.status === '4'" type="info">待审核</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 5 || scope.row.status === '5'" type="danger">已驳回</el-tag>
|
||||
<el-tag v-else type="info">未知({{ scope.row.status }})</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -96,7 +105,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 使用更直接的方式显示取消按钮 -->
|
||||
<template v-if="scope.row.status === 0 || scope.row.status === '0'">
|
||||
<template v-if="scope.row.status === 0 || scope.row.status === '0' || scope.row.status === 4 || scope.row.status === '4'">
|
||||
<el-button link type="danger" size="small" @click="cancelAppointment(scope.row)"> 取消预约 </el-button>
|
||||
</template>
|
||||
<span v-else>--</span>
|
||||
@@ -117,6 +126,7 @@
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import {
|
||||
getXljkAvailableTimeSlots,
|
||||
getXljkCalendarSchedules,
|
||||
submitXljkAppointment,
|
||||
getXljkMyAppointments,
|
||||
updateXljkAppointment,
|
||||
@@ -124,7 +134,7 @@ import {
|
||||
getXljkCounselorOptions,
|
||||
getXljkAvailableDetailSlots
|
||||
} from '@/api/xljk/consultation';
|
||||
import { AvailableTimeSlot, XljkConsultationAppointmentForm, CounselorOption, TimeSlotDetail } from '@/api/xljk/consultation/types';
|
||||
import { AvailableTimeSlot, XljkConsultationAppointmentForm, CounselorOption, TimeSlotDetail, ScheduleWithStatus } from '@/api/xljk/consultation/types';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
@@ -134,12 +144,17 @@ const { proxy } = getCurrentInstance() as any;
|
||||
// 不再使用后端字典数据
|
||||
// const { appointment_status } = toRefs(proxy?.useDict('appointment_status'));
|
||||
|
||||
// 获取咨询主题字典数据
|
||||
const { xljk_consult_theme } = toRefs<any>(proxy?.useDict('xljk_consult_theme'));
|
||||
|
||||
// 本地定义状态字典(与预约记录管理页面保持一致)
|
||||
const appointment_status = ref([
|
||||
{ label: '待咨询', value: 0, listClass: 'primary' },
|
||||
{ label: '已到访', value: 1, listClass: 'success' },
|
||||
{ label: '已取消', value: 2, listClass: 'danger' },
|
||||
{ label: '未到访', value: 3, listClass: 'warning' }
|
||||
{ label: '未到访', value: 3, listClass: 'warning' },
|
||||
{ label: '待审核', value: 4, listClass: 'info' },
|
||||
{ label: '已驳回', value: 5, listClass: 'danger' }
|
||||
]);
|
||||
|
||||
// 用户信息
|
||||
@@ -150,6 +165,7 @@ const hasStudentInfo = computed(() => Boolean(studentId.value));
|
||||
|
||||
// 日历相关
|
||||
const availableSlots = ref<AvailableTimeSlot[]>([]);
|
||||
const calendarSchedules = ref<ScheduleWithStatus[]>([]);
|
||||
const calendarView = ref('month'); // 默认使用月视图
|
||||
|
||||
// 预约表单相关
|
||||
@@ -161,6 +177,7 @@ const studentNo = ref('');
|
||||
const studentName = ref('');
|
||||
const studentId = ref<string | number | null>(null);
|
||||
const availableDetailSlots = ref<TimeSlotDetail[]>([]);
|
||||
const selectedOffice = ref(''); // 添加办公室字段
|
||||
|
||||
const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
counselorId: undefined,
|
||||
@@ -168,6 +185,7 @@ const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
appointmentDate: '',
|
||||
appointmentTime: '',
|
||||
appointmentDetailTime: '',
|
||||
consultTheme: '', // 咨询主题
|
||||
reason: '',
|
||||
status: 0 // 默认待咨询状态,使用数值类型
|
||||
});
|
||||
@@ -175,7 +193,7 @@ const appointmentForm = reactive<XljkConsultationAppointmentForm>({
|
||||
const appointmentRules = {
|
||||
reason: [{ required: true, message: '请输入咨询原因', trigger: 'blur' }],
|
||||
studentNo: [{ required: true, message: '请输入学号', trigger: 'blur' }],
|
||||
appointmentDetailTime: [{ required: true, message: '请选择具体预约时段', trigger: 'change' }]
|
||||
consultTheme: [{ required: true, message: '请选择咨询主题', trigger: 'change' }]
|
||||
};
|
||||
|
||||
// 我的预约相关
|
||||
@@ -196,7 +214,13 @@ const formattedTimeSlot = computed(() => {
|
||||
|
||||
// 计算属性:咨询师名称
|
||||
const counselorDisplayName = computed(() => {
|
||||
return selectedSlot.value?.counselorName || '正在加载...';
|
||||
if (selectedSlot.value?.counselorName) {
|
||||
return selectedSlot.value.counselorName;
|
||||
}
|
||||
if (selectedSlot.value?.counselorId) {
|
||||
return '正在加载...';
|
||||
}
|
||||
return '请选择咨询师';
|
||||
});
|
||||
|
||||
// 处理日期范围变化
|
||||
@@ -230,27 +254,52 @@ function handleViewChange(view: string) {
|
||||
// 加载可用预约时段
|
||||
async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
try {
|
||||
// 加载新的排班数据
|
||||
const scheduleRes = await getXljkCalendarSchedules(startDate, endDate);
|
||||
|
||||
if (scheduleRes.code === 200) {
|
||||
calendarSchedules.value = scheduleRes.data || [];
|
||||
}
|
||||
|
||||
// 为了兼容现有逻辑,同时保留原有的时段数据加载
|
||||
const res = await getXljkAvailableTimeSlots(startDate, endDate);
|
||||
if (res.code === 200) {
|
||||
availableSlots.value = res.data || [];
|
||||
|
||||
// 如果有可用时段但没有咨询师姓名,尝试加载咨询师信息
|
||||
if (availableSlots.value.length > 0 && !availableSlots.value[0].counselorName) {
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
const counselors = counselorRes.data;
|
||||
// 为每个时段添加咨询师姓名
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
// 如果有可用时段,检查并补充咨询师姓名
|
||||
if (availableSlots.value.length > 0) {
|
||||
// 检查是否有时段缺少咨询师姓名或办公室信息
|
||||
const hasMissingCounselorInfo = availableSlots.value.some(slot =>
|
||||
(!slot.counselorName || !slot.office) && slot.counselorId
|
||||
);
|
||||
|
||||
if (hasMissingCounselorInfo) {
|
||||
try {
|
||||
console.log('检测到缺少咨询师信息,开始获取咨询师详细信息...');
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
const counselors = counselorRes.data;
|
||||
console.log('获取到咨询师列表:', counselors);
|
||||
// 为每个时段添加咨询师姓名和办公室信息
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
if ((!slot.counselorName || !slot.office) && slot.counselorId) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
const updatedSlot = {
|
||||
...slot,
|
||||
counselorName: slot.counselorName || counselor.name,
|
||||
office: slot.office || counselor.office
|
||||
};
|
||||
console.log('更新时段信息:', updatedSlot);
|
||||
return updatedSlot;
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,19 +309,103 @@ async function loadAvailableSlots(startDate: string, endDate: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新的排班点击
|
||||
async function handleScheduleClick(schedule: ScheduleWithStatus) {
|
||||
console.log('点击排班:', schedule);
|
||||
|
||||
// 如果该时段已过期,则不允许预约
|
||||
if (schedule.isExpired) {
|
||||
ElMessage.warning('该时段已过期,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果该时段全部约满,则不允许预约
|
||||
if (schedule.isFullyBooked) {
|
||||
ElMessage.warning('该时段已全部约满,请选择其他时段');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取咨询师名称和办公室信息
|
||||
let counselorName = schedule.counselorName || '';
|
||||
let office = schedule.office || '';
|
||||
|
||||
// 如果缺少咨询师姓名或办公室信息,则去获取完整的咨询师信息
|
||||
if ((!counselorName || !office) && schedule.counselorId) {
|
||||
try {
|
||||
console.log('获取咨询师详细信息,counselorId:', schedule.counselorId);
|
||||
const res = await getXljkCounselorOptions();
|
||||
if (res.code === 200 && res.data) {
|
||||
const counselor = res.data.find((item: CounselorOption) => item.id === schedule.counselorId);
|
||||
if (counselor) {
|
||||
counselorName = counselorName || counselor.name;
|
||||
office = office || counselor.office || '';
|
||||
console.log('获取到咨询师信息:', { name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', schedule.counselorId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('最终的办公室信息:', office);
|
||||
|
||||
// 填充预约表单 - 直接使用排班数据,不需要再选择详细时段
|
||||
appointmentForm.counselorId = schedule.counselorId;
|
||||
appointmentForm.scheduleId = schedule.scheduleId;
|
||||
appointmentForm.appointmentDate = schedule.date;
|
||||
appointmentForm.appointmentTime = `${schedule.startTime}-${schedule.endTime}`;
|
||||
appointmentForm.appointmentDetailTime = `${schedule.startTime}-${schedule.endTime}`; // 直接使用完整时段
|
||||
appointmentForm.consultTheme = ''; // 清空咨询主题,等待用户选择
|
||||
appointmentForm.reason = ''; // 清空咨询原因,避免带出上次的内容
|
||||
|
||||
// 设置选中的时段信息以便显示
|
||||
selectedSlot.value = {
|
||||
scheduleId: schedule.scheduleId,
|
||||
counselorId: schedule.counselorId,
|
||||
counselorName: counselorName,
|
||||
office: office,
|
||||
date: schedule.date,
|
||||
timeSlot: schedule.timeSlot,
|
||||
startTime: schedule.startTime,
|
||||
endTime: schedule.endTime,
|
||||
allBooked: schedule.isFullyBooked,
|
||||
isExpired: schedule.isExpired
|
||||
} as AvailableTimeSlot;
|
||||
|
||||
// 设置办公室信息
|
||||
selectedOffice.value = office;
|
||||
|
||||
// 如果已登录且有用户ID,初始化学生信息
|
||||
if (userId.value) {
|
||||
// 初始设置为当前用户ID
|
||||
studentId.value = userId.value;
|
||||
studentName.value = nickname.value || '';
|
||||
studentNo.value = nickname.value || '';
|
||||
}
|
||||
|
||||
appointmentFormVisible.value = true;
|
||||
}
|
||||
|
||||
// 处理时段点击
|
||||
async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
selectedSlot.value = slot;
|
||||
|
||||
// 如果没有咨询师姓名,尝试获取咨询师信息
|
||||
if (!slot.counselorName && slot.counselorId) {
|
||||
// 如果没有咨询师姓名或办公室信息,尝试获取咨询师信息
|
||||
if ((!slot.counselorName || !slot.office) && slot.counselorId) {
|
||||
try {
|
||||
console.log('获取咨询师详细信息,counselorId:', slot.counselorId);
|
||||
const res = await getXljkCounselorOptions();
|
||||
if (res.code === 200 && res.data) {
|
||||
const counselor = res.data.find((item: CounselorOption) => item.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
slot.counselorName = counselor.name;
|
||||
slot.counselorName = slot.counselorName || counselor.name;
|
||||
slot.office = slot.office || counselor.office || '';
|
||||
selectedSlot.value = { ...slot };
|
||||
console.log('获取到咨询师信息:', { name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', slot.counselorId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -280,12 +413,16 @@ async function handleSlotClick(slot: AvailableTimeSlot) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置办公室信息
|
||||
selectedOffice.value = slot.office || '';
|
||||
|
||||
// 填充预约表单
|
||||
appointmentForm.counselorId = slot.counselorId;
|
||||
appointmentForm.scheduleId = slot.scheduleId;
|
||||
appointmentForm.appointmentDate = slot.date;
|
||||
appointmentForm.appointmentTime = `${slot.startTime}-${slot.endTime}`;
|
||||
appointmentForm.appointmentDetailTime = ''; // 清空详细时段,等待用户选择
|
||||
appointmentForm.consultTheme = ''; // 清空咨询主题,等待用户选择
|
||||
appointmentForm.reason = ''; // 清空咨询原因,避免带出上次的内容
|
||||
|
||||
// 加载详细时段
|
||||
@@ -387,7 +524,12 @@ function submitAppointmentForm() {
|
||||
}
|
||||
|
||||
if (!appointmentForm.appointmentDetailTime) {
|
||||
ElMessage.warning('请选择具体预约时段');
|
||||
ElMessage.warning('预约时段信息缺失,请重新点击时段');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!appointmentForm.consultTheme) {
|
||||
ElMessage.warning('请选择咨询主题');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -452,12 +594,36 @@ async function viewMyAppointments() {
|
||||
let res = await getXljkMyAppointments(id, true); // 强制刷新数据
|
||||
console.log('查询结果:', res);
|
||||
|
||||
// 获取咨询师信息用于补充办公室字段
|
||||
let counselors: CounselorOption[] = [];
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data;
|
||||
console.log('获取到咨询师列表:', counselors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = res.data.map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
@@ -473,7 +639,7 @@ async function viewMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -491,11 +657,23 @@ async function viewMyAppointments() {
|
||||
console.log('数字类型查询结果:', res);
|
||||
|
||||
if (res.code === 200) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = (res.data || []).map((item: any) => {
|
||||
console.log('原始记录项:', item);
|
||||
|
||||
// 注意:移除了本地状态优先的逻辑,始终使用后端最新状态
|
||||
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
@@ -511,7 +689,7 @@ async function viewMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '类型:', typeof item.status);
|
||||
console.log('处理后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office, '类型:', typeof item.status);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -594,7 +772,7 @@ async function initCounselorInfo() {
|
||||
availableSlots.value = availableSlots.value.map((slot) => {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === slot.counselorId);
|
||||
if (counselor) {
|
||||
return { ...slot, counselorName: counselor.name };
|
||||
return { ...slot, counselorName: counselor.name, office: counselor.office };
|
||||
}
|
||||
return slot;
|
||||
});
|
||||
@@ -616,9 +794,34 @@ async function refreshMyAppointments() {
|
||||
try {
|
||||
// 强制刷新数据
|
||||
const res = await getXljkMyAppointments(studentId.value, true);
|
||||
|
||||
// 获取咨询师信息用于补充办公室字段
|
||||
let counselors: CounselorOption[] = [];
|
||||
try {
|
||||
const counselorRes = await getXljkCounselorOptions();
|
||||
if (counselorRes.code === 200 && counselorRes.data) {
|
||||
counselors = counselorRes.data;
|
||||
console.log('刷新时获取到咨询师列表:', counselors);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取咨询师信息失败', error);
|
||||
}
|
||||
|
||||
if (res.code === 200) {
|
||||
// 处理数据,确保状态字段是数值类型
|
||||
// 处理数据,确保状态字段是数值类型,并补充办公室信息
|
||||
myAppointmentsList.value = (res.data || []).map((item: any) => {
|
||||
// 补充办公室信息
|
||||
if (item.counselorId && counselors.length > 0) {
|
||||
const counselor = counselors.find((c: CounselorOption) => c.id === item.counselorId);
|
||||
if (counselor) {
|
||||
item.office = counselor.office || '';
|
||||
item.counselorName = item.counselorName || counselor.name || '';
|
||||
console.log('刷新时补充咨询师信息:', { counselorId: item.counselorId, name: counselor.name, office: counselor.office });
|
||||
} else {
|
||||
console.log('刷新时未找到对应的咨询师信息, counselorId:', item.counselorId);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果status是undefined或null,设置默认值为0
|
||||
if (item.status === undefined || item.status === null) {
|
||||
item.status = 0; // 默认为待咨询
|
||||
@@ -631,6 +834,7 @@ async function refreshMyAppointments() {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('刷新后的预约记录项:', item.id, '状态:', item.status, '办公室:', item.office);
|
||||
return item;
|
||||
});
|
||||
ElMessage.success('刷新成功');
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="queryParams.office" placeholder="请输入办公室" clearable style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="排班日期" prop="dateRange">
|
||||
<el-date-picker
|
||||
@@ -28,7 +33,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="时段类型" prop="timeSlot">
|
||||
<el-select v-model="queryParams.timeSlot" placeholder="请选择时段类型" clearable style="width: 100%">
|
||||
<el-option label="上午" value="0" />
|
||||
<el-option label="中午" value="0" />
|
||||
<el-option label="下午" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -75,8 +80,9 @@
|
||||
<el-table v-loading="loading" border :data="scheduleList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="咨询师" align="center" prop="counselorName" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column label="办公室" align="center" prop="office" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="日期范围" align="center" prop="dateRange" min-width="180" />
|
||||
<el-table-column label="上午时段" align="center" min-width="120">
|
||||
<el-table-column label="中午时段" align="center" min-width="120">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.morningEnabled">{{ scope.row.morningTime }}</span>
|
||||
<span v-else>-</span>
|
||||
@@ -129,13 +135,16 @@
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改预约时段对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="scheduleFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="咨询师" prop="counselorId">
|
||||
<el-select v-model="form.counselorId" placeholder="请选择咨询师" style="width: 100%">
|
||||
<el-select v-model="form.counselorId" placeholder="请选择咨询师" style="width: 100%" @change="handleCounselorChange">
|
||||
<el-option v-for="item in counselorOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="办公室" prop="office">
|
||||
<el-input v-model="form.office" placeholder="请输入办公室" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排班日期" prop="dateRange" v-if="!form.id">
|
||||
<el-date-picker
|
||||
v-model="form.dateRange"
|
||||
@@ -151,60 +160,92 @@
|
||||
<el-date-picker v-model="form.scheduleDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 100%" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 上午时段设置 -->
|
||||
<el-divider content-position="left">上午时段</el-divider>
|
||||
<el-form-item label="启用上午" prop="morningEnabled">
|
||||
<el-switch v-model="form.morningEnabled" />
|
||||
</el-form-item>
|
||||
<template v-if="form.morningEnabled">
|
||||
<el-form-item label="开始时间" prop="morningStartTime">
|
||||
<el-time-picker
|
||||
v-model="morningStartTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="morningTimePickerOptions"
|
||||
@change="handleMorningStartTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="morningEndTime">
|
||||
<el-time-picker
|
||||
v-model="morningEndTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="morningTimePickerOptions"
|
||||
@change="handleMorningEndTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 中午时段设置 -->
|
||||
<el-divider content-position="left">中午时段</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用中午" prop="noonEnabled">
|
||||
<el-switch v-model="form.noonEnabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<el-button v-if="form.noonEnabled" type="primary" link icon="Plus" class="add-time-slot-btn" @click="addNoonTimeSlot">增加时段</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template v-if="form.noonEnabled">
|
||||
<div v-for="(slot, index) in form.noonTimeSlots" :key="`noon-${index}`" class="time-slot-row">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '开始时间' : ''" :prop="`noonTimeSlots.${index}.startTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.startTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleNoonStartTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '结束时间' : ''" :prop="`noonTimeSlots.${index}.endTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.endTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleNoonEndTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" class="time-slot-actions">
|
||||
<el-button v-if="index > 0" type="danger" link icon="Delete" class="delete-time-slot-btn" @click="removeNoonTimeSlot(index)"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 下午时段设置 -->
|
||||
<el-divider content-position="left">下午时段</el-divider>
|
||||
<el-form-item label="启用下午" prop="afternoonEnabled">
|
||||
<el-switch v-model="form.afternoonEnabled" />
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用下午" prop="afternoonEnabled">
|
||||
<el-switch v-model="form.afternoonEnabled" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" class="text-right">
|
||||
<el-button v-if="form.afternoonEnabled" type="primary" link icon="Plus" class="add-time-slot-btn" @click="addAfternoonTimeSlot">增加时段</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template v-if="form.afternoonEnabled">
|
||||
<el-form-item label="开始时间" prop="afternoonStartTime">
|
||||
<el-time-picker
|
||||
v-model="afternoonStartTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="afternoonTimePickerOptions"
|
||||
@change="handleAfternoonStartTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="结束时间" prop="afternoonEndTime">
|
||||
<el-time-picker
|
||||
v-model="afternoonEndTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
:picker-options="afternoonTimePickerOptions"
|
||||
@change="handleAfternoonEndTimeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div v-for="(slot, index) in form.afternoonTimeSlots" :key="`afternoon-${index}`" class="time-slot-row">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '开始时间' : ''" :prop="`afternoonTimeSlots.${index}.startTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.startTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleAfternoonStartTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-form-item :label="index === 0 ? '结束时间' : ''" :prop="`afternoonTimeSlots.${index}.endTime`">
|
||||
<el-time-picker
|
||||
v-model="slot.endTimeTemp"
|
||||
format="HH:mm"
|
||||
placeholder="选择时间"
|
||||
style="width: 100%"
|
||||
@change="(time) => handleAfternoonEndTimeChange(time, index)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" class="time-slot-actions">
|
||||
<el-button v-if="index > 0" type="danger" link icon="Delete" class="delete-time-slot-btn" @click="removeAfternoonTimeSlot(index)"></el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form-item label="状态" prop="status">
|
||||
@@ -268,34 +309,13 @@ const open = ref(false);
|
||||
const dateRange = ref<string[]>([]);
|
||||
// 咨询师选项
|
||||
const counselorOptions = ref<CounselorOption[]>([]);
|
||||
// 时间选择器临时变量
|
||||
const startTimeTemp = ref<Date | null>(null);
|
||||
const endTimeTemp = ref<Date | null>(null);
|
||||
const morningStartTimeTemp = ref<Date | null>(null);
|
||||
const morningEndTimeTemp = ref<Date | null>(null);
|
||||
const afternoonStartTimeTemp = ref<Date | null>(null);
|
||||
const afternoonEndTimeTemp = ref<Date | null>(null);
|
||||
|
||||
// 时间选择器配置
|
||||
const morningTimePickerOptions = {
|
||||
start: '08:00',
|
||||
step: '00:30',
|
||||
end: '12:00',
|
||||
format: 'HH:mm'
|
||||
};
|
||||
|
||||
const afternoonTimePickerOptions = {
|
||||
start: '14:00',
|
||||
step: '00:30',
|
||||
end: '17:00',
|
||||
format: 'HH:mm'
|
||||
};
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
counselorId: undefined,
|
||||
office: undefined,
|
||||
timeSlot: undefined,
|
||||
status: undefined,
|
||||
params: {} as any
|
||||
@@ -305,23 +325,50 @@ const queryParams = reactive({
|
||||
const form = reactive<any>({
|
||||
id: undefined,
|
||||
counselorId: undefined,
|
||||
office: '',
|
||||
scheduleDate: '',
|
||||
dateRange: [],
|
||||
morningEnabled: true,
|
||||
morningStartTime: '08:30',
|
||||
morningEndTime: '12:00',
|
||||
noonEnabled: true,
|
||||
noonTimeSlots: [
|
||||
{
|
||||
startTime: '12:00',
|
||||
endTime: '12:40',
|
||||
startTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(12, 0, 0, 0);
|
||||
return time;
|
||||
})(),
|
||||
endTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(12, 40, 0, 0);
|
||||
return time;
|
||||
})()
|
||||
}
|
||||
],
|
||||
afternoonEnabled: true,
|
||||
afternoonStartTime: '14:30',
|
||||
afternoonEndTime: '17:00',
|
||||
timeSlot: '0',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
afternoonTimeSlots: [
|
||||
{
|
||||
startTime: '17:00',
|
||||
endTime: '17:40',
|
||||
startTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(17, 0, 0, 0);
|
||||
return time;
|
||||
})(),
|
||||
endTimeTemp: (() => {
|
||||
const time = new Date();
|
||||
time.setHours(17, 40, 0, 0);
|
||||
return time;
|
||||
})()
|
||||
}
|
||||
],
|
||||
status: '0'
|
||||
});
|
||||
|
||||
// 表单校验
|
||||
const rules = {
|
||||
counselorId: [{ required: true, message: '咨询师不能为空', trigger: 'change' }],
|
||||
office: [{ required: true, message: '办公室不能为空', trigger: 'blur' }],
|
||||
scheduleDate: [{ required: false, message: '排班日期不能为空', trigger: 'change' }],
|
||||
dateRange: [{ required: true, message: '排班日期范围不能为空', trigger: 'change' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||
@@ -377,38 +424,42 @@ const cancel = () => {
|
||||
const reset = () => {
|
||||
form.id = undefined;
|
||||
form.counselorId = undefined;
|
||||
form.office = '';
|
||||
form.scheduleDate = '';
|
||||
form.dateRange = [];
|
||||
form.morningEnabled = true;
|
||||
form.noonEnabled = true;
|
||||
form.afternoonEnabled = true;
|
||||
form.morningStartTime = '08:30';
|
||||
form.morningEndTime = '12:00';
|
||||
form.afternoonStartTime = '14:30';
|
||||
form.afternoonEndTime = '17:00';
|
||||
form.timeSlot = '0';
|
||||
form.startTime = '';
|
||||
form.endTime = '';
|
||||
form.status = '0';
|
||||
|
||||
startTimeTemp.value = null;
|
||||
endTimeTemp.value = null;
|
||||
// 设置中午时段默认值
|
||||
const noonStartTime = new Date();
|
||||
noonStartTime.setHours(12, 0, 0, 0);
|
||||
const noonEndTime = new Date();
|
||||
noonEndTime.setHours(12, 40, 0, 0);
|
||||
|
||||
// 设置默认时间
|
||||
const morningStart = new Date();
|
||||
morningStart.setHours(8, 30, 0, 0);
|
||||
morningStartTimeTemp.value = morningStart;
|
||||
form.noonTimeSlots = [
|
||||
{
|
||||
startTime: '12:00',
|
||||
endTime: '12:40',
|
||||
startTimeTemp: noonStartTime,
|
||||
endTimeTemp: noonEndTime
|
||||
}
|
||||
];
|
||||
|
||||
const morningEnd = new Date();
|
||||
morningEnd.setHours(12, 0, 0, 0);
|
||||
morningEndTimeTemp.value = morningEnd;
|
||||
// 设置下午时段默认值
|
||||
const afternoonStartTime = new Date();
|
||||
afternoonStartTime.setHours(17, 0, 0, 0);
|
||||
const afternoonEndTime = new Date();
|
||||
afternoonEndTime.setHours(17, 40, 0, 0);
|
||||
|
||||
const afternoonStart = new Date();
|
||||
afternoonStart.setHours(14, 30, 0, 0);
|
||||
afternoonStartTimeTemp.value = afternoonStart;
|
||||
|
||||
const afternoonEnd = new Date();
|
||||
afternoonEnd.setHours(17, 0, 0, 0);
|
||||
afternoonEndTimeTemp.value = afternoonEnd;
|
||||
form.afternoonTimeSlots = [
|
||||
{
|
||||
startTime: '17:00',
|
||||
endTime: '17:40',
|
||||
startTimeTemp: afternoonStartTime,
|
||||
endTimeTemp: afternoonEndTime
|
||||
}
|
||||
];
|
||||
|
||||
scheduleFormRef.value?.resetFields();
|
||||
};
|
||||
@@ -441,7 +492,7 @@ const handleAdd = () => {
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: XljkConsultationScheduleVO) => {
|
||||
const handleView = async (row: XljkConsultationScheduleSummaryVO) => {
|
||||
try {
|
||||
const res = await getXljkSchedule(row.id);
|
||||
if (res.code === 200 && res.data) {
|
||||
@@ -455,7 +506,7 @@ const handleView = async (row: XljkConsultationScheduleVO) => {
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
if (row && row.id) {
|
||||
try {
|
||||
// 修复:将字符串状态正确转换为数字进行比较
|
||||
@@ -491,6 +542,7 @@ const submitForm = async () => {
|
||||
await updateXljkSchedule({
|
||||
id: form.id,
|
||||
counselorId: form.counselorId,
|
||||
office: form.office,
|
||||
scheduleDate: form.scheduleDate,
|
||||
timeSlot: form.timeSlot,
|
||||
startTime: form.startTime,
|
||||
@@ -514,26 +566,67 @@ const submitForm = async () => {
|
||||
}
|
||||
|
||||
// 检查是否至少启用了一个时段
|
||||
if (!form.morningEnabled && !form.afternoonEnabled) {
|
||||
ElMessage.warning('请至少启用一个时段(上午或下午)');
|
||||
if (!form.noonEnabled && !form.afternoonEnabled) {
|
||||
ElMessage.warning('请至少启用一个时段(中午或下午)');
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保时间数据同步 - 从时间选择器同步到字符串字段
|
||||
if (form.noonEnabled) {
|
||||
form.noonTimeSlots.forEach((slot, index) => {
|
||||
if (slot.startTimeTemp && !slot.startTime) {
|
||||
const hours = slot.startTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.startTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.startTime = `${hours}:${minutes}`;
|
||||
}
|
||||
if (slot.endTimeTemp && !slot.endTime) {
|
||||
const hours = slot.endTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.endTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.endTime = `${hours}:${minutes}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (form.afternoonEnabled) {
|
||||
form.afternoonTimeSlots.forEach((slot, index) => {
|
||||
if (slot.startTimeTemp && !slot.startTime) {
|
||||
const hours = slot.startTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.startTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.startTime = `${hours}:${minutes}`;
|
||||
}
|
||||
if (slot.endTimeTemp && !slot.endTime) {
|
||||
const hours = slot.endTimeTemp.getHours().toString().padStart(2, '0');
|
||||
const minutes = slot.endTimeTemp.getMinutes().toString().padStart(2, '0');
|
||||
slot.endTime = `${hours}:${minutes}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 批量创建预约时段的请求数据
|
||||
const batchCreateData = {
|
||||
counselorId: form.counselorId,
|
||||
office: form.office,
|
||||
startDate: form.dateRange[0],
|
||||
endDate: form.dateRange[1],
|
||||
morningEnabled: form.morningEnabled,
|
||||
morningStartTime: form.morningStartTime,
|
||||
morningEndTime: form.morningEndTime,
|
||||
noonEnabled: form.noonEnabled,
|
||||
noonTimeSlots: form.noonTimeSlots.map(slot => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime
|
||||
})),
|
||||
afternoonEnabled: form.afternoonEnabled,
|
||||
afternoonStartTime: form.afternoonStartTime,
|
||||
afternoonEndTime: form.afternoonEndTime,
|
||||
afternoonTimeSlots: form.afternoonTimeSlots.map(slot => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime
|
||||
})),
|
||||
status: form.status,
|
||||
skipWeekends: true // 自动跳过周末
|
||||
};
|
||||
|
||||
// 调试日志 - 确认时间数据
|
||||
console.log('提交的批量创建数据:', batchCreateData);
|
||||
console.log('中午时段数据:', form.noonTimeSlots);
|
||||
console.log('下午时段数据:', form.afternoonTimeSlots);
|
||||
|
||||
// 调用批量创建API
|
||||
const res = await addXljkSchedule(batchCreateData);
|
||||
if (res.code === 200) {
|
||||
@@ -551,7 +644,7 @@ const submitForm = async () => {
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: XljkConsultationScheduleVO) => {
|
||||
const handleDelete = async (row?: XljkConsultationScheduleSummaryVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
const confirmMessage = row
|
||||
? `是否确认删除该预约时段?${row.scheduleCount > 1 ? `(包含${row.scheduleCount}条记录)` : ''}`
|
||||
@@ -594,47 +687,147 @@ const handleEndTimeChange = (time: Date) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理上午开始时间变化 */
|
||||
const handleMorningStartTimeChange = (time: Date) => {
|
||||
/** 处理中午开始时间变化 */
|
||||
const handleNoonStartTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.morningStartTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.noonTimeSlots[index].startTime = timeString;
|
||||
form.noonTimeSlots[index].startTimeTemp = time;
|
||||
} else {
|
||||
form.morningStartTime = '';
|
||||
form.noonTimeSlots[index].startTime = '';
|
||||
form.noonTimeSlots[index].startTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理上午结束时间变化 */
|
||||
const handleMorningEndTimeChange = (time: Date) => {
|
||||
/** 处理中午结束时间变化 */
|
||||
const handleNoonEndTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.morningEndTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.noonTimeSlots[index].endTime = timeString;
|
||||
form.noonTimeSlots[index].endTimeTemp = time;
|
||||
} else {
|
||||
form.morningEndTime = '';
|
||||
form.noonTimeSlots[index].endTime = '';
|
||||
form.noonTimeSlots[index].endTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增中午时段 */
|
||||
const addNoonTimeSlot = () => {
|
||||
const lastSlot = form.noonTimeSlots[form.noonTimeSlots.length - 1];
|
||||
let startTime = '12:00';
|
||||
let endTime = '12:40';
|
||||
|
||||
if (lastSlot && lastSlot.endTime) {
|
||||
startTime = lastSlot.endTime;
|
||||
// 计算结束时间(开始时间+40分钟)
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
const startDate = new Date();
|
||||
startDate.setHours(hours, minutes, 0, 0);
|
||||
const endDate = new Date(startDate.getTime() + 40 * 60 * 1000); // 加40分钟
|
||||
endTime = `${endDate.getHours().toString().padStart(2, '0')}:${endDate.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 创建对应的Date对象用于时间选择器
|
||||
const startTimeDate = new Date();
|
||||
const [startHours, startMinutes] = startTime.split(':').map(Number);
|
||||
startTimeDate.setHours(startHours, startMinutes, 0, 0);
|
||||
|
||||
const endTimeDate = new Date();
|
||||
const [endHours, endMinutes] = endTime.split(':').map(Number);
|
||||
endTimeDate.setHours(endHours, endMinutes, 0, 0);
|
||||
|
||||
form.noonTimeSlots.push({
|
||||
startTime,
|
||||
endTime,
|
||||
startTimeTemp: startTimeDate,
|
||||
endTimeTemp: endTimeDate
|
||||
});
|
||||
};
|
||||
|
||||
/** 移除中午时段 */
|
||||
const removeNoonTimeSlot = (index: number) => {
|
||||
form.noonTimeSlots.splice(index, 1);
|
||||
};
|
||||
|
||||
/** 处理下午开始时间变化 */
|
||||
const handleAfternoonStartTimeChange = (time: Date) => {
|
||||
const handleAfternoonStartTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.afternoonStartTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.afternoonTimeSlots[index].startTime = timeString;
|
||||
form.afternoonTimeSlots[index].startTimeTemp = time;
|
||||
} else {
|
||||
form.afternoonStartTime = '';
|
||||
form.afternoonTimeSlots[index].startTime = '';
|
||||
form.afternoonTimeSlots[index].startTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 处理下午结束时间变化 */
|
||||
const handleAfternoonEndTimeChange = (time: Date) => {
|
||||
const handleAfternoonEndTimeChange = (time: Date, index: number) => {
|
||||
if (time) {
|
||||
const hours = time.getHours().toString().padStart(2, '0');
|
||||
const minutes = time.getMinutes().toString().padStart(2, '0');
|
||||
form.afternoonEndTime = `${hours}:${minutes}`;
|
||||
const timeString = `${hours}:${minutes}`;
|
||||
form.afternoonTimeSlots[index].endTime = timeString;
|
||||
form.afternoonTimeSlots[index].endTimeTemp = time;
|
||||
} else {
|
||||
form.afternoonEndTime = '';
|
||||
form.afternoonTimeSlots[index].endTime = '';
|
||||
form.afternoonTimeSlots[index].endTimeTemp = null;
|
||||
}
|
||||
};
|
||||
|
||||
/** 新增下午时段 */
|
||||
const addAfternoonTimeSlot = () => {
|
||||
const lastSlot = form.afternoonTimeSlots[form.afternoonTimeSlots.length - 1];
|
||||
let startTime = '17:00';
|
||||
let endTime = '17:40';
|
||||
|
||||
if (lastSlot && lastSlot.endTime) {
|
||||
startTime = lastSlot.endTime;
|
||||
// 计算结束时间(开始时间+40分钟)
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
const startDate = new Date();
|
||||
startDate.setHours(hours, minutes, 0, 0);
|
||||
const endDate = new Date(startDate.getTime() + 40 * 60 * 1000); // 加40分钟
|
||||
endTime = `${endDate.getHours().toString().padStart(2, '0')}:${endDate.getMinutes().toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 创建对应的Date对象用于时间选择器
|
||||
const startTimeDate = new Date();
|
||||
const [startHours, startMinutes] = startTime.split(':').map(Number);
|
||||
startTimeDate.setHours(startHours, startMinutes, 0, 0);
|
||||
|
||||
const endTimeDate = new Date();
|
||||
const [endHours, endMinutes] = endTime.split(':').map(Number);
|
||||
endTimeDate.setHours(endHours, endMinutes, 0, 0);
|
||||
|
||||
form.afternoonTimeSlots.push({
|
||||
startTime,
|
||||
endTime,
|
||||
startTimeTemp: startTimeDate,
|
||||
endTimeTemp: endTimeDate
|
||||
});
|
||||
};
|
||||
|
||||
/** 移除下午时段 */
|
||||
const removeAfternoonTimeSlot = (index: number) => {
|
||||
form.afternoonTimeSlots.splice(index, 1);
|
||||
};
|
||||
|
||||
/** 处理咨询师变化 */
|
||||
const handleCounselorChange = (counselorId: number) => {
|
||||
if (counselorId) {
|
||||
const selectedCounselor = counselorOptions.value.find(counselor => counselor.id === counselorId);
|
||||
if (selectedCounselor && selectedCounselor.office) {
|
||||
form.office = selectedCounselor.office;
|
||||
}
|
||||
} else {
|
||||
form.office = '';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -684,4 +877,140 @@ onMounted(() => {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 时间槽行样式 */
|
||||
.time-slot-row {
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 时间槽操作按钮样式 */
|
||||
.time-slot-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
/* 右对齐样式 */
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 新增时段按钮样式 */
|
||||
.add-time-slot-btn {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #409eff !important;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:hover {
|
||||
color: #66b1ff !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.add-time-slot-btn:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 更强的选择器覆盖 Element Plus 默认样式 */
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #409eff !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:hover {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #66b1ff !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.el-button.add-time-slot-btn.el-button--primary.is-link:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 删除时段按钮样式 */
|
||||
.delete-time-slot-btn {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f56c6c !important;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:hover {
|
||||
color: #f78989 !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.delete-time-slot-btn:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 更强的选择器覆盖 Element Plus 默认样式 */
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f56c6c !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:hover {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #f78989 !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:focus {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.el-button.delete-time-slot-btn.el-button--danger.is-link:active {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user