“出国申请审批权限修改”
This commit is contained in:
commit
f907b9cd40
97 files changed
+16889
-1631
No files matched your search
@@ -0,0 +1,48 @@
|
||||
server:
|
||||
port: 9090
|
||||
spring:
|
||||
application:
|
||||
name: ruoyi-monitor-admin
|
||||
profiles:
|
||||
active: @profiles.active@
|
||||
|
||||
logging:
|
||||
config: classpath:logback-plus.xml
|
||||
|
||||
--- # 监控中心服务端配置
|
||||
spring:
|
||||
security:
|
||||
user:
|
||||
name: @monitor.username@
|
||||
password: @monitor.password@
|
||||
boot:
|
||||
admin:
|
||||
ui:
|
||||
title: RuoYi-Vue-Plus服务监控中心
|
||||
context-path: /admin
|
||||
|
||||
--- # Actuator 监控端点的配置项
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*'
|
||||
endpoint:
|
||||
health:
|
||||
show-details: ALWAYS
|
||||
logfile:
|
||||
external-file: ./logs/ruoyi-monitor-admin.log
|
||||
|
||||
--- # 监控配置
|
||||
spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: true
|
||||
# 设置 Spring Boot Admin Server 地址
|
||||
url: http://172.16.0.67:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
username: ${spring.boot.admin.client.username}
|
||||
userpassword: ${spring.boot.admin.client.password}
|
||||
username: @monitor.username@
|
||||
password: @monitor.password@
|
||||
@@ -38,7 +38,7 @@ spring.boot.admin.client:
|
||||
# 增加客户端开关
|
||||
enabled: true
|
||||
# 设置 Spring Boot Admin Server 地址
|
||||
url: http://172.16.0.67:9090/admin
|
||||
url: http://127.0.0.1:9090/admin
|
||||
instance:
|
||||
service-host-type: IP
|
||||
metadata:
|
||||
|
||||
+5
-2
@@ -1,6 +1,5 @@
|
||||
package org.dromara.workflow.domain.bo;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
@@ -12,7 +11,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* 驳回参数请求
|
||||
*
|
||||
@@ -50,6 +48,11 @@ public class BackProcessBo implements Serializable {
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 手写签名(Base64编码的图片数据)
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* 通知
|
||||
*/
|
||||
|
||||
+5
@@ -48,6 +48,11 @@ public class CompleteTaskBo implements Serializable {
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 手写签名(Base64编码的图片数据)
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* 消息通知
|
||||
*/
|
||||
|
||||
+5
@@ -151,6 +151,11 @@ public class FlowHisTaskVo implements Serializable {
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 手写签名(Base64编码的图片数据)
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
/**
|
||||
* 业务详情 存业务类的json
|
||||
*/
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package org.dromara.workflow.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import org.dromara.workflow.domain.vo.FlowHisTaskVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 历史任务扩展Mapper
|
||||
* 用于处理Warm-Flow框架不支持的自定义字段
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Mapper
|
||||
public interface FlwHisTaskExtMapper {
|
||||
|
||||
/**
|
||||
* 更新历史任务的签名字段
|
||||
*
|
||||
* @param taskId 任务ID
|
||||
* @param signature 签名数据(Base64)
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Update("UPDATE flow_his_task SET signature = #{signature} WHERE task_id = #{taskId}")
|
||||
int updateSignatureByTaskId(@Param("taskId") Long taskId, @Param("signature") String signature);
|
||||
|
||||
/**
|
||||
* 根据实例ID更新签名字段(批量更新场景)
|
||||
*
|
||||
* @param instanceId 流程实例ID
|
||||
* @param signature 签名数据(Base64)
|
||||
* @return 影响行数
|
||||
*/
|
||||
@Update("UPDATE flow_his_task SET signature = #{signature} WHERE instance_id = #{instanceId}")
|
||||
int updateSignatureByInstanceId(@Param("instanceId") Long instanceId, @Param("signature") String signature);
|
||||
|
||||
/**
|
||||
* 根据实例ID查询历史任务列表(包含签名字段)
|
||||
*
|
||||
* @param instanceId 流程实例ID
|
||||
* @param nodeType 节点类型
|
||||
* @return 历史任务列表
|
||||
*/
|
||||
List<FlowHisTaskVo> selectHisTaskListWithSignature(@Param("instanceId") Long instanceId,
|
||||
@Param("nodeType") Integer nodeType);
|
||||
}
|
||||
+27
-19
@@ -42,6 +42,7 @@ import org.dromara.workflow.domain.vo.FlowHisTaskVo;
|
||||
import org.dromara.workflow.domain.vo.FlowInstanceVo;
|
||||
import org.dromara.workflow.handler.FlowProcessEventHandler;
|
||||
import org.dromara.workflow.mapper.FlwCategoryMapper;
|
||||
import org.dromara.workflow.mapper.FlwHisTaskExtMapper;
|
||||
import org.dromara.workflow.mapper.FlwInstanceMapper;
|
||||
import org.dromara.workflow.service.IFlwInstanceService;
|
||||
import org.dromara.workflow.service.IFlwTaskService;
|
||||
@@ -71,6 +72,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
private final IFlwTaskService flwTaskService;
|
||||
private final FlwInstanceMapper flwInstanceMapper;
|
||||
private final FlwCategoryMapper flwCategoryMapper;
|
||||
private final FlwHisTaskExtMapper flwHisTaskExtMapper;
|
||||
|
||||
/**
|
||||
* 分页查询正在运行的流程实例
|
||||
@@ -128,15 +130,21 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
*/
|
||||
private QueryWrapper<FlowInstanceBo> buildQueryWrapper(FlowInstanceBo flowInstanceBo) {
|
||||
QueryWrapper<FlowInstanceBo> queryWrapper = Wrappers.query();
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name", flowInstanceBo.getNodeName());
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name", flowInstanceBo.getFlowName());
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code", flowInstanceBo.getFlowCode());
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getNodeName()), "fi.node_name",
|
||||
flowInstanceBo.getNodeName());
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowName()), "fd.flow_name",
|
||||
flowInstanceBo.getFlowName());
|
||||
queryWrapper.like(StringUtils.isNotBlank(flowInstanceBo.getFlowCode()), "fd.flow_code",
|
||||
flowInstanceBo.getFlowCode());
|
||||
if (StringUtils.isNotBlank(flowInstanceBo.getCategory())) {
|
||||
List<Long> categoryIds = flwCategoryMapper.selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
|
||||
List<Long> categoryIds = flwCategoryMapper
|
||||
.selectCategoryIdsByParentId(Convert.toLong(flowInstanceBo.getCategory()));
|
||||
queryWrapper.in("fd.category", StreamUtils.toList(categoryIds, Convert::toStr));
|
||||
}
|
||||
queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id", flowInstanceBo.getBusinessId());
|
||||
queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by", flowInstanceBo.getCreateByIds());
|
||||
queryWrapper.eq(StringUtils.isNotBlank(flowInstanceBo.getBusinessId()), "fi.business_id",
|
||||
flowInstanceBo.getBusinessId());
|
||||
queryWrapper.in(CollUtil.isNotEmpty(flowInstanceBo.getCreateByIds()), "fi.create_by",
|
||||
flowInstanceBo.getCreateByIds());
|
||||
queryWrapper.eq("fi.del_flag", "0");
|
||||
queryWrapper.orderByDesc("fi.create_time");
|
||||
return queryWrapper;
|
||||
@@ -149,7 +157,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
*/
|
||||
@Override
|
||||
public FlowInstance selectInstByBusinessId(String businessId) {
|
||||
return flowInstanceMapper.selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
|
||||
return flowInstanceMapper
|
||||
.selectOne(new LambdaQueryWrapper<FlowInstance>().eq(FlowInstance::getBusinessId, businessId));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +189,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteByBusinessIds(List<Long> businessIds) {
|
||||
List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>().in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr)));
|
||||
List<FlowInstance> flowInstances = flowInstanceMapper.selectList(new LambdaQueryWrapper<FlowInstance>()
|
||||
.in(FlowInstance::getBusinessId, StreamUtils.toList(businessIds, Convert::toStr)));
|
||||
if (CollUtil.isEmpty(flowInstances)) {
|
||||
log.warn("未找到对应的流程实例信息,无法执行删除操作。");
|
||||
return false;
|
||||
@@ -204,8 +214,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
}
|
||||
// 获取定义信息
|
||||
Map<Long, Definition> definitionMap = defService.getByIds(
|
||||
StreamUtils.toList(instances, Instance::getDefinitionId)
|
||||
).stream().collect(Collectors.toMap(Definition::getId, definition -> definition));
|
||||
StreamUtils.toList(instances, Instance::getDefinitionId)).stream()
|
||||
.collect(Collectors.toMap(Definition::getId, definition -> definition));
|
||||
|
||||
// 逐一触发删除事件
|
||||
instances.forEach(instance -> {
|
||||
@@ -302,14 +312,11 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
}
|
||||
list.addAll(flowHisTaskVos);
|
||||
}
|
||||
//历史任务
|
||||
LambdaQueryWrapper<FlowHisTask> wrapper = Wrappers.lambdaQuery();
|
||||
wrapper.eq(FlowHisTask::getInstanceId, instanceId);
|
||||
wrapper.eq(FlowHisTask::getNodeType, NodeType.BETWEEN.getKey());
|
||||
wrapper.orderByDesc(FlowHisTask::getCreateTime).orderByDesc(FlowHisTask::getUpdateTime);
|
||||
List<FlowHisTask> flowHisTasks = flowHisTaskMapper.selectList(wrapper);
|
||||
if (CollUtil.isNotEmpty(flowHisTasks)) {
|
||||
list.addAll(BeanUtil.copyToList(flowHisTasks, FlowHisTaskVo.class));
|
||||
// 历史任务(使用自定义Mapper查询,包含签名字段)
|
||||
List<FlowHisTaskVo> flowHisTaskVos = flwHisTaskExtMapper.selectHisTaskListWithSignature(instanceId,
|
||||
NodeType.BETWEEN.getKey());
|
||||
if (CollUtil.isNotEmpty(flowHisTaskVos)) {
|
||||
list.addAll(flowHisTaskVos);
|
||||
}
|
||||
return Map.of("list", list, "instanceId", instanceId);
|
||||
}
|
||||
@@ -336,7 +343,8 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
|
||||
@Override
|
||||
public Map<String, Object> instanceVariable(Long instanceId) {
|
||||
FlowInstance flowInstance = flowInstanceMapper.selectById(instanceId);
|
||||
Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap()).orElse(Collections.emptyMap());
|
||||
Map<String, Object> variableMap = Optional.ofNullable(flowInstance.getVariableMap())
|
||||
.orElse(Collections.emptyMap());
|
||||
List<Map<String, Object>> variableList = variableMap.entrySet().stream()
|
||||
.map(entry -> Map.of("key", entry.getKey(), "value", entry.getValue()))
|
||||
.toList();
|
||||
|
||||
+14
@@ -38,6 +38,7 @@ import org.dromara.warm.flow.orm.mapper.FlowInstanceMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowNodeMapper;
|
||||
import org.dromara.warm.flow.orm.mapper.FlowTaskMapper;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.mapper.FlwHisTaskExtMapper;
|
||||
import org.dromara.workflow.common.constant.FlowConstant;
|
||||
import org.dromara.workflow.common.enums.TaskAssigneeType;
|
||||
import org.dromara.workflow.common.enums.TaskStatusEnum;
|
||||
@@ -86,6 +87,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
private final IFlwTaskAssigneeService flwTaskAssigneeService;
|
||||
private final IFlwCommonService flwCommonService;
|
||||
private final IFlwNodeExtService flwNodeExtService;
|
||||
private final FlwHisTaskExtMapper flwHisTaskExtMapper;
|
||||
|
||||
/**
|
||||
* 启动任务
|
||||
@@ -185,6 +187,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
.hisTaskExt(completeTaskBo.getFileId());
|
||||
// 执行任务跳转,并根据返回的处理人设置下一步处理人
|
||||
taskService.skip(taskId, flowParams);
|
||||
|
||||
// 保存手写签名到历史任务表
|
||||
if (StringUtils.isNotBlank(completeTaskBo.getSignature())) {
|
||||
flwHisTaskExtMapper.updateSignatureByTaskId(taskId, completeTaskBo.getSignature());
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
@@ -409,6 +417,12 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
|
||||
.hisStatus(TaskStatusEnum.BACK.getStatus())
|
||||
.hisTaskExt(bo.getFileId());
|
||||
taskService.skip(task.getId(), flowParams);
|
||||
|
||||
// 保存手写签名到历史任务表(驳回场景)
|
||||
if (StringUtils.isNotBlank(bo.getSignature())) {
|
||||
flwHisTaskExtMapper.updateSignatureByTaskId(taskId, bo.getSignature());
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
<?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.workflow.mapper.FlwHisTaskExtMapper">
|
||||
|
||||
<!-- FlowHisTaskVo结果映射 -->
|
||||
<resultMap id="FlowHisTaskVoResult" type="org.dromara.workflow.domain.vo.FlowHisTaskVo">
|
||||
<id property="id" column="id"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="definitionId" column="definition_id"/>
|
||||
<result property="instanceId" column="instance_id"/>
|
||||
<result property="taskId" column="task_id"/>
|
||||
<result property="cooperateType" column="cooperate_type"/>
|
||||
<result property="nodeCode" column="node_code"/>
|
||||
<result property="nodeName" column="node_name"/>
|
||||
<result property="nodeType" column="node_type"/>
|
||||
<result property="targetNodeCode" column="target_node_code"/>
|
||||
<result property="targetNodeName" column="target_node_name"/>
|
||||
<result property="approver" column="approver"/>
|
||||
<result property="collaborator" column="collaborator"/>
|
||||
<result property="skipType" column="skip_type"/>
|
||||
<result property="flowStatus" column="flow_status"/>
|
||||
<result property="message" column="message"/>
|
||||
<result property="signature" column="signature"/>
|
||||
<result property="ext" column="ext"/>
|
||||
<result property="formCustom" column="form_custom"/>
|
||||
<result property="formPath" column="form_path"/>
|
||||
</resultMap>
|
||||
|
||||
<!-- 根据实例ID查询历史任务列表(包含签名字段) -->
|
||||
<select id="selectHisTaskListWithSignature" resultMap="FlowHisTaskVoResult">
|
||||
SELECT
|
||||
id,
|
||||
create_time,
|
||||
update_time,
|
||||
tenant_id,
|
||||
del_flag,
|
||||
definition_id,
|
||||
instance_id,
|
||||
task_id,
|
||||
cooperate_type,
|
||||
node_code,
|
||||
node_name,
|
||||
node_type,
|
||||
target_node_code,
|
||||
target_node_name,
|
||||
approver,
|
||||
collaborator,
|
||||
skip_type,
|
||||
flow_status,
|
||||
message,
|
||||
signature,
|
||||
ext,
|
||||
form_custom,
|
||||
form_path
|
||||
FROM flow_his_task
|
||||
WHERE instance_id = #{instanceId}
|
||||
AND node_type = #{nodeType}
|
||||
AND del_flag = '0'
|
||||
ORDER BY create_time DESC, update_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
+1
@@ -59,6 +59,7 @@
|
||||
a.instance_id,
|
||||
a.flow_status flow_task_status,
|
||||
a.message,
|
||||
a.signature,
|
||||
a.ext,
|
||||
a.create_time,
|
||||
a.update_time,
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package org.dromara.xg.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.xg.domain.vo.XgCollegeLeaderVo;
|
||||
import org.dromara.xg.domain.bo.XgCollegeLeaderBo;
|
||||
import org.dromara.xg.service.IXgCollegeLeaderService;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
@Tag(name = "二级学院分管领导配置")
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/xg/ejxyfgld")
|
||||
public class XgCollegeLeaderController extends BaseController {
|
||||
|
||||
private final IXgCollegeLeaderService xgCollegeLeaderService;
|
||||
|
||||
/**
|
||||
* 查询二级学院分管领导配置列表
|
||||
*/
|
||||
@Operation(summary = "查询二级学院分管领导配置列表")
|
||||
@SaCheckPermission("xg:ejxyfgld:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<XgCollegeLeaderVo> list(XgCollegeLeaderBo bo, PageQuery pageQuery) {
|
||||
return xgCollegeLeaderService.queryPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID查询分管领导列表
|
||||
*/
|
||||
@Operation(summary = "根据学院ID查询分管领导列表")
|
||||
@SaCheckPermission("xg:ejxyfgld:list")
|
||||
@GetMapping("/listByCollege/{collegeId}")
|
||||
public R<List<XgCollegeLeaderVo>> listByCollege(@PathVariable Long collegeId) {
|
||||
List<XgCollegeLeaderVo> list = xgCollegeLeaderService.queryByCollegeId(collegeId);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出二级学院分管领导配置列表
|
||||
*/
|
||||
@Operation(summary = "导出二级学院分管领导配置列表")
|
||||
@SaCheckPermission("xg:ejxyfgld:export")
|
||||
@Log(title = "二级学院分管领导配置", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(XgCollegeLeaderBo bo, HttpServletResponse response) {
|
||||
List<XgCollegeLeaderVo> list = xgCollegeLeaderService.queryList(bo);
|
||||
ExcelUtil.exportExcel(list, "二级学院分管领导配置", XgCollegeLeaderVo.class, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级学院分管领导配置详细信息
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@Operation(summary = "获取二级学院分管领导配置详细信息")
|
||||
@SaCheckPermission("xg:ejxyfgld:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<XgCollegeLeaderVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
|
||||
return R.ok(xgCollegeLeaderService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增二级学院分管领导配置
|
||||
*/
|
||||
@Operation(summary = "新增二级学院分管领导配置")
|
||||
@SaCheckPermission("xg:ejxyfgld:add")
|
||||
@Log(title = "二级学院分管领导配置", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody XgCollegeLeaderBo bo) {
|
||||
return toAjax(xgCollegeLeaderService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改二级学院分管领导配置
|
||||
*/
|
||||
@Operation(summary = "修改二级学院分管领导配置")
|
||||
@SaCheckPermission("xg:ejxyfgld:edit")
|
||||
@Log(title = "二级学院分管领导配置", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody XgCollegeLeaderBo bo) {
|
||||
return toAjax(xgCollegeLeaderService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除二级学院分管领导配置
|
||||
*
|
||||
* @param ids 主键串
|
||||
*/
|
||||
@Operation(summary = "删除二级学院分管领导配置")
|
||||
@SaCheckPermission("xg:ejxyfgld:remove")
|
||||
@Log(title = "二级学院分管领导配置", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] ids) {
|
||||
return toAjax(xgCollegeLeaderService.deleteWithValidByIds(List.of(ids), true));
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package org.dromara.xg.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
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.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.xg.domain.vo.XgDormApplicationVo;
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.vo.XgStudentLeaveVo;
|
||||
import org.dromara.xg.domain.bo.XgDormApplicationBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgStudentLeaveBo;
|
||||
import org.dromara.xg.service.IXgStudentDynamicsService;
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/studentDynamics")
|
||||
public class XgStudentDynamicsController extends BaseController {
|
||||
|
||||
private final IXgStudentDynamicsService xgStudentDynamicsService;
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生调宿退宿申请列表
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:dormApplication:list")
|
||||
@GetMapping("/dormApplicationList")
|
||||
public TableDataInfo<XgDormApplicationVo> getDormApplicationList(XgDormApplicationBo bo, PageQuery pageQuery) {
|
||||
return xgStudentDynamicsService.queryDormApplicationPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生假期留宿信息列表
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:holidayStay:list")
|
||||
@GetMapping("/holidayStayList")
|
||||
public TableDataInfo<XgHolidayStayVo> getHolidayStayList(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
return xgStudentDynamicsService.queryHolidayStayPageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生请假信息列表
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:studentLeave:list")
|
||||
@GetMapping("/studentLeaveList")
|
||||
public TableDataInfo<XgStudentLeaveVo> getStudentLeaveList(XgStudentLeaveBo bo, PageQuery pageQuery) {
|
||||
return xgStudentDynamicsService.queryStudentLeavePageList(bo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调宿退宿申请详细信息
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:dormApplication:query")
|
||||
@GetMapping("/dormApplication/{id}")
|
||||
public R<XgDormApplicationVo> getDormApplicationInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
|
||||
return R.ok(xgStudentDynamicsService.queryDormApplicationById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿详细信息
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:holidayStay:query")
|
||||
@GetMapping("/holidayStay/{id}")
|
||||
public R<XgHolidayStayVo> getHolidayStayInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
|
||||
return R.ok(xgStudentDynamicsService.queryHolidayStayById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生请假详细信息
|
||||
*/
|
||||
@SaCheckPermission("studentDynamics:studentLeave:query")
|
||||
@GetMapping("/studentLeave/{id}")
|
||||
public R<XgStudentLeaveVo> getStudentLeaveInfo(@NotNull(message = "主键不能为空") @PathVariable Long id) {
|
||||
return R.ok(xgStudentDynamicsService.queryStudentLeaveById(id));
|
||||
}
|
||||
}
|
||||
+17
@@ -823,6 +823,23 @@ public class XgStudentMsgController extends BaseController {
|
||||
return R.ok(approverUserIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID获取二级学院分管领导ID列表
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @return 分管领导ID列表
|
||||
*/
|
||||
@SaCheckPermission("studentMsg:studentMsg:query")
|
||||
@GetMapping("/getCollegeLeaders/{studentId}")
|
||||
public R<List<Long>> getCollegeLeaders(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
|
||||
log.info("根据学生ID获取二级学院分管领导ID列表 - 学生ID: {}", studentId);
|
||||
List<Long> leaderIds = xgStudentMsgService.getCollegeLeadersByStudentId(studentId);
|
||||
if (leaderIds.isEmpty()) {
|
||||
return R.fail("未找到该学生所在学院的分管领导");
|
||||
}
|
||||
return R.ok(leaderIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入数据
|
||||
*
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public class XgSysUserController extends BaseController {
|
||||
private final IXgSysUserService xgSysUserService;
|
||||
|
||||
/**
|
||||
* 查询有效用户列表
|
||||
* 查询有效用户列表(教职工account_type=1)
|
||||
*/
|
||||
@SaCheckPermission("xg:sysUser:list")
|
||||
@GetMapping("/activeList")
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.dromara.xg.domain;
|
||||
|
||||
import org.dromara.common.tenant.core.TenantEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置对象 xg_college_leader
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("xg_college_leader")
|
||||
public class XgCollegeLeader extends TenantEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
private Long deleted;
|
||||
|
||||
/**
|
||||
* 二级学院ID,关联sys_dept.dept_id
|
||||
*/
|
||||
private Long collegeId;
|
||||
|
||||
/**
|
||||
* 二级学院名称
|
||||
*/
|
||||
private String collegeName;
|
||||
|
||||
/**
|
||||
* 分管领导ID,关联sys_user.user_id
|
||||
*/
|
||||
private Long leaderId;
|
||||
|
||||
/**
|
||||
* 分管领导姓名
|
||||
*/
|
||||
private String leaderName;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -27,6 +27,11 @@ public class XgSysUser extends TenantEntity {
|
||||
@TableId(value = "user_id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.dromara.xg.domain.bo;
|
||||
|
||||
import org.dromara.xg.domain.XgCollegeLeader;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import jakarta.validation.constraints.*;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置业务对象 xg_college_leader
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@AutoMapper(target = XgCollegeLeader.class, reverseConvertGenerate = false)
|
||||
public class XgCollegeLeaderBo extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 删除标志(0代表存在 1代表删除)
|
||||
*/
|
||||
private Long deleted;
|
||||
|
||||
/**
|
||||
* 二级学院ID,关联sys_dept.dept_id
|
||||
*/
|
||||
@NotNull(message = "二级学院不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long collegeId;
|
||||
|
||||
/**
|
||||
* 二级学院名称
|
||||
*/
|
||||
private String collegeName;
|
||||
|
||||
/**
|
||||
* 分管领导ID,关联sys_user.user_id
|
||||
*/
|
||||
@NotNull(message = "分管领导不能为空", groups = { AddGroup.class, EditGroup.class })
|
||||
private Long leaderId;
|
||||
|
||||
/**
|
||||
* 分管领导姓名
|
||||
*/
|
||||
private String leaderName;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package org.dromara.xg.domain.vo;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import org.dromara.xg.domain.XgCollegeLeader;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置视图对象 xg_college_leader
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = XgCollegeLeader.class)
|
||||
public class XgCollegeLeaderVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID(雪花ID)
|
||||
*/
|
||||
@ExcelProperty(value = "主键ID")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 二级学院ID,关联sys_dept.dept_id
|
||||
*/
|
||||
@ExcelProperty(value = "二级学院ID")
|
||||
private Long collegeId;
|
||||
|
||||
/**
|
||||
* 二级学院名称
|
||||
*/
|
||||
@ExcelProperty(value = "二级学院名称")
|
||||
private String collegeName;
|
||||
|
||||
/**
|
||||
* 分管领导ID,关联sys_user.user_id
|
||||
*/
|
||||
@ExcelProperty(value = "分管领导ID")
|
||||
private Long leaderId;
|
||||
|
||||
/**
|
||||
* 分管领导姓名
|
||||
*/
|
||||
@ExcelProperty(value = "分管领导姓名")
|
||||
private String leaderName;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
@ExcelProperty(value = "备注")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
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;
|
||||
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.dromara.xg.mapper;
|
||||
|
||||
import org.dromara.xg.domain.XgCollegeLeader;
|
||||
import org.dromara.xg.domain.vo.XgCollegeLeaderVo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置Mapper接口
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
public interface XgCollegeLeaderMapper extends BaseMapperPlus<XgCollegeLeader, XgCollegeLeaderVo> {
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package org.dromara.xg.mapper;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)Mapper接口
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
public interface XgStudentDynamicsMapper {
|
||||
|
||||
/**
|
||||
* 检查用户是否有宿舍管理权限
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 管理员类型列表 (1全校管理员 2校区管理员 3宿舍区管理员 4宿舍楼管理员)
|
||||
*/
|
||||
List<String> checkUserDormManagerType(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取全校管理员权限的所有楼栋ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 楼栋ID列表
|
||||
*/
|
||||
List<Long> getBuildingIdsBySchoolAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取校区管理员权限的楼栋ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 楼栋ID列表
|
||||
*/
|
||||
List<Long> getBuildingIdsByCampusAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取宿舍区管理员权限的楼栋ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 楼栋ID列表
|
||||
*/
|
||||
List<Long> getBuildingIdsByAreaAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取宿舍楼管理员权限的楼栋ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 楼栋ID列表
|
||||
*/
|
||||
List<Long> getBuildingIdsByBuildingAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取全校管理员权限的所有学生ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 学生ID列表
|
||||
*/
|
||||
List<Long> getStudentIdsBySchoolAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取校区管理员权限的学生ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 学生ID列表
|
||||
*/
|
||||
List<Long> getStudentIdsByCampusAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取宿舍区管理员权限的学生ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 学生ID列表
|
||||
*/
|
||||
List<Long> getStudentIdsByAreaAdmin(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 获取宿舍楼管理员权限的学生ID列表
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 学生ID列表
|
||||
*/
|
||||
List<Long> getStudentIdsByBuildingAdmin(@Param("userId") Long userId);
|
||||
}
|
||||
+8
@@ -78,6 +78,14 @@ public interface XgStudentMsgMapper extends BaseMapperPlus<XgStudentMsg, XgStude
|
||||
List<Long> selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId,
|
||||
@Param("roleKey") String roleKey);
|
||||
|
||||
/**
|
||||
* 根据学院ID查询二级学院分管领导ID列表
|
||||
*
|
||||
* @param collegeId 学院ID
|
||||
* @return 分管领导ID列表
|
||||
*/
|
||||
List<Long> selectCollegeLeaderIdsByCollegeId(@Param("collegeId") Long collegeId);
|
||||
|
||||
/**
|
||||
* 查询二级学院下的学院、专业、班级学生统计信息
|
||||
*
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package org.dromara.xg.service;
|
||||
|
||||
import org.dromara.xg.domain.vo.XgCollegeLeaderVo;
|
||||
import org.dromara.xg.domain.bo.XgCollegeLeaderBo;
|
||||
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 Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
public interface IXgCollegeLeaderService {
|
||||
|
||||
/**
|
||||
* 查询二级学院分管领导配置
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 二级学院分管领导配置
|
||||
*/
|
||||
XgCollegeLeaderVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询二级学院分管领导配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 二级学院分管领导配置分页列表
|
||||
*/
|
||||
TableDataInfo<XgCollegeLeaderVo> queryPageList(XgCollegeLeaderBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的二级学院分管领导配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 二级学院分管领导配置列表
|
||||
*/
|
||||
List<XgCollegeLeaderVo> queryList(XgCollegeLeaderBo bo);
|
||||
|
||||
/**
|
||||
* 新增二级学院分管领导配置
|
||||
*
|
||||
* @param bo 二级学院分管领导配置
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(XgCollegeLeaderBo bo);
|
||||
|
||||
/**
|
||||
* 修改二级学院分管领导配置
|
||||
*
|
||||
* @param bo 二级学院分管领导配置
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(XgCollegeLeaderBo bo);
|
||||
|
||||
/**
|
||||
* 校验并批量删除二级学院分管领导配置信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 根据学院ID查询分管领导列表
|
||||
*
|
||||
* @param collegeId 学院ID
|
||||
* @return 分管领导列表
|
||||
*/
|
||||
List<XgCollegeLeaderVo> queryByCollegeId(Long collegeId);
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package org.dromara.xg.service;
|
||||
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.xg.domain.vo.XgDormApplicationVo;
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.vo.XgStudentLeaveVo;
|
||||
import org.dromara.xg.domain.bo.XgDormApplicationBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgStudentLeaveBo;
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)Service接口
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
public interface IXgStudentDynamicsService {
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生调宿退宿申请列表
|
||||
*/
|
||||
TableDataInfo<XgDormApplicationVo> queryDormApplicationPageList(XgDormApplicationBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生假期留宿信息列表
|
||||
*/
|
||||
TableDataInfo<XgHolidayStayVo> queryHolidayStayPageList(XgHolidayStayBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生请假信息列表
|
||||
*/
|
||||
TableDataInfo<XgStudentLeaveVo> queryStudentLeavePageList(XgStudentLeaveBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 根据主键查询调宿退宿申请详情
|
||||
*/
|
||||
XgDormApplicationVo queryDormApplicationById(Long id);
|
||||
|
||||
/**
|
||||
* 根据主键查询假期留宿详情
|
||||
*/
|
||||
XgHolidayStayVo queryHolidayStayById(Long id);
|
||||
|
||||
/**
|
||||
* 根据主键查询学生请假详情
|
||||
*/
|
||||
XgStudentLeaveVo queryStudentLeaveById(Long id);
|
||||
}
|
||||
+8
@@ -140,6 +140,14 @@ public interface IXgStudentMsgService {
|
||||
*/
|
||||
List<Long> getCollegeLeaveApproversByStudentId(Long studentId);
|
||||
|
||||
/**
|
||||
* 根据学生ID获取二级学院分管领导ID列表
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @return 分管领导ID列表
|
||||
*/
|
||||
List<Long> getCollegeLeadersByStudentId(Long studentId);
|
||||
|
||||
/**
|
||||
* 批量申请床位
|
||||
*
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package org.dromara.xg.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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.dromara.xg.domain.bo.XgCollegeLeaderBo;
|
||||
import org.dromara.xg.domain.vo.XgCollegeLeaderVo;
|
||||
import org.dromara.xg.domain.XgCollegeLeader;
|
||||
import org.dromara.xg.mapper.XgCollegeLeaderMapper;
|
||||
import org.dromara.xg.service.IXgCollegeLeaderService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置Service业务层处理
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-24
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class XgCollegeLeaderServiceImpl implements IXgCollegeLeaderService {
|
||||
|
||||
private final XgCollegeLeaderMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询二级学院分管领导配置
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 二级学院分管领导配置
|
||||
*/
|
||||
@Override
|
||||
public XgCollegeLeaderVo queryById(Long id) {
|
||||
return baseMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询二级学院分管领导配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 二级学院分管领导配置分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgCollegeLeaderVo> queryPageList(XgCollegeLeaderBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<XgCollegeLeader> lqw = buildQueryWrapper(bo);
|
||||
Page<XgCollegeLeaderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的二级学院分管领导配置列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 二级学院分管领导配置列表
|
||||
*/
|
||||
@Override
|
||||
public List<XgCollegeLeaderVo> queryList(XgCollegeLeaderBo bo) {
|
||||
LambdaQueryWrapper<XgCollegeLeader> lqw = buildQueryWrapper(bo);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID查询分管领导列表
|
||||
*
|
||||
* @param collegeId 学院ID
|
||||
* @return 分管领导列表
|
||||
*/
|
||||
@Override
|
||||
public List<XgCollegeLeaderVo> queryByCollegeId(Long collegeId) {
|
||||
LambdaQueryWrapper<XgCollegeLeader> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(XgCollegeLeader::getCollegeId, collegeId);
|
||||
lqw.orderByDesc(XgCollegeLeader::getCreateTime);
|
||||
return baseMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<XgCollegeLeader> buildQueryWrapper(XgCollegeLeaderBo bo) {
|
||||
Map<String, Object> params = bo.getParams();
|
||||
LambdaQueryWrapper<XgCollegeLeader> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getCollegeId() != null, XgCollegeLeader::getCollegeId, bo.getCollegeId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getCollegeName()), XgCollegeLeader::getCollegeName, bo.getCollegeName());
|
||||
lqw.eq(bo.getLeaderId() != null, XgCollegeLeader::getLeaderId, bo.getLeaderId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getLeaderName()), XgCollegeLeader::getLeaderName, bo.getLeaderName());
|
||||
lqw.orderByDesc(XgCollegeLeader::getCreateTime);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增二级学院分管领导配置
|
||||
*
|
||||
* @param bo 二级学院分管领导配置
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertByBo(XgCollegeLeaderBo bo) {
|
||||
XgCollegeLeader add = MapstructUtils.convert(bo, XgCollegeLeader.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = baseMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改二级学院分管领导配置
|
||||
*
|
||||
* @param bo 二级学院分管领导配置
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean updateByBo(XgCollegeLeaderBo bo) {
|
||||
XgCollegeLeader update = MapstructUtils.convert(bo, XgCollegeLeader.class);
|
||||
validEntityBeforeSave(update);
|
||||
return baseMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(XgCollegeLeader entity) {
|
||||
// 校验是否已存在相同的配置
|
||||
LambdaQueryWrapper<XgCollegeLeader> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(XgCollegeLeader::getCollegeId, entity.getCollegeId());
|
||||
lqw.eq(XgCollegeLeader::getLeaderId, entity.getLeaderId());
|
||||
if (entity.getId() != null) {
|
||||
lqw.ne(XgCollegeLeader::getId, entity.getId());
|
||||
}
|
||||
long count = baseMapper.selectCount(lqw);
|
||||
if (count > 0) {
|
||||
throw new RuntimeException("该学院已配置此分管领导,不能重复配置!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并批量删除二级学院分管领导配置信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// 可以添加业务校验逻辑
|
||||
}
|
||||
return baseMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
+353
@@ -0,0 +1,353 @@
|
||||
package org.dromara.xg.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.xg.domain.vo.XgDormApplicationVo;
|
||||
import org.dromara.xg.domain.vo.XgHolidayStayVo;
|
||||
import org.dromara.xg.domain.vo.XgStudentLeaveVo;
|
||||
import org.dromara.xg.domain.bo.XgDormApplicationBo;
|
||||
import org.dromara.xg.domain.bo.XgHolidayStayBo;
|
||||
import org.dromara.xg.domain.bo.XgStudentLeaveBo;
|
||||
import org.dromara.xg.domain.XgDormApplication;
|
||||
import org.dromara.xg.domain.XgHolidayStay;
|
||||
import org.dromara.xg.domain.XgStudentLeave;
|
||||
import org.dromara.xg.mapper.XgDormApplicationMapper;
|
||||
import org.dromara.xg.mapper.XgHolidayStayMapper;
|
||||
import org.dromara.xg.mapper.XgStudentLeaveMapper;
|
||||
import org.dromara.xg.mapper.XgStudentDynamicsMapper;
|
||||
import org.dromara.xg.service.IXgStudentDynamicsService;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)Service实现
|
||||
*
|
||||
* @author zlwu
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class XgStudentDynamicsServiceImpl implements IXgStudentDynamicsService {
|
||||
|
||||
private final XgDormApplicationMapper dormApplicationMapper;
|
||||
private final XgHolidayStayMapper holidayStayMapper;
|
||||
private final XgStudentLeaveMapper studentLeaveMapper;
|
||||
private final XgStudentDynamicsMapper studentDynamicsMapper;
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生调宿退宿申请列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgDormApplicationVo> queryDormApplicationPageList(XgDormApplicationBo bo,
|
||||
PageQuery pageQuery) {
|
||||
// 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<XgDormApplication> lqw = buildDormApplicationQueryWrapper(bo, currentUserId);
|
||||
|
||||
// 分页查询
|
||||
Page<XgDormApplicationVo> result = dormApplicationMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生假期留宿信息列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgHolidayStayVo> queryHolidayStayPageList(XgHolidayStayBo bo, PageQuery pageQuery) {
|
||||
// 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<XgHolidayStay> lqw = buildHolidayStayQueryWrapper(bo, currentUserId);
|
||||
|
||||
// 分页查询
|
||||
Page<XgHolidayStayVo> result = holidayStayMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生请假信息列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XgStudentLeaveVo> queryStudentLeavePageList(XgStudentLeaveBo bo, PageQuery pageQuery) {
|
||||
// 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<XgStudentLeave> lqw = buildStudentLeaveQueryWrapper(bo, currentUserId);
|
||||
|
||||
// 分页查询
|
||||
Page<XgStudentLeaveVo> result = studentLeaveMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键查询调宿退宿申请详情
|
||||
*/
|
||||
@Override
|
||||
public XgDormApplicationVo queryDormApplicationById(Long id) {
|
||||
return dormApplicationMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键查询假期留宿详情
|
||||
*/
|
||||
@Override
|
||||
public XgHolidayStayVo queryHolidayStayById(Long id) {
|
||||
return holidayStayMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键查询学生请假详情
|
||||
*/
|
||||
@Override
|
||||
public XgStudentLeaveVo queryStudentLeaveById(Long id) {
|
||||
return studentLeaveMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建调宿退宿申请查询条件(带宿管员权限)
|
||||
*/
|
||||
private LambdaQueryWrapper<XgDormApplication> buildDormApplicationQueryWrapper(XgDormApplicationBo bo,
|
||||
Long currentUserId) {
|
||||
LambdaQueryWrapper<XgDormApplication> lqw = new LambdaQueryWrapper<>();
|
||||
|
||||
// 获取宿管员管理的楼栋ID列表
|
||||
List<Long> managedBuildingIds = getManagedBuildingIds(currentUserId);
|
||||
|
||||
if (managedBuildingIds.isEmpty()) {
|
||||
// 如果没有管理权限,返回空结果
|
||||
lqw.eq(XgDormApplication::getId, -1);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
// 只查询宿管员权限内楼栋的学生申请
|
||||
lqw.in(XgDormApplication::getCurrentBuildingId, managedBuildingIds);
|
||||
|
||||
// 添加其他查询条件
|
||||
// 姓名或学号检索(OR关系)
|
||||
if (bo.getStudentName() != null && bo.getStudentNo() != null) {
|
||||
lqw.and(wrapper -> wrapper
|
||||
.like(XgDormApplication::getStudentName, bo.getStudentName())
|
||||
.or()
|
||||
.like(XgDormApplication::getStudentNo, bo.getStudentNo()));
|
||||
} else {
|
||||
lqw.like(bo.getStudentName() != null, XgDormApplication::getStudentName, bo.getStudentName());
|
||||
lqw.like(bo.getStudentNo() != null, XgDormApplication::getStudentNo, bo.getStudentNo());
|
||||
}
|
||||
lqw.like(bo.getCollegeName() != null, XgDormApplication::getCollegeName, bo.getCollegeName());
|
||||
lqw.like(bo.getMajorName() != null, XgDormApplication::getMajorName, bo.getMajorName());
|
||||
lqw.like(bo.getClassName() != null, XgDormApplication::getClassName, bo.getClassName());
|
||||
lqw.eq(bo.getApplicationType() != null, XgDormApplication::getApplicationType, bo.getApplicationType());
|
||||
lqw.eq(bo.getApplicationStatus() != null, XgDormApplication::getApplicationStatus, bo.getApplicationStatus());
|
||||
lqw.ge(bo.getApplyTime() != null, XgDormApplication::getApplyTime, bo.getApplyTime());
|
||||
|
||||
// 默认按创建时间倒序
|
||||
lqw.orderByDesc(XgDormApplication::getCreateTime);
|
||||
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建假期留宿查询条件(带宿管员权限)
|
||||
*/
|
||||
private LambdaQueryWrapper<XgHolidayStay> buildHolidayStayQueryWrapper(XgHolidayStayBo bo, Long currentUserId) {
|
||||
LambdaQueryWrapper<XgHolidayStay> lqw = new LambdaQueryWrapper<>();
|
||||
|
||||
// 获取宿管员管理的楼栋ID列表
|
||||
List<Long> managedBuildingIds = getManagedBuildingIds(currentUserId);
|
||||
|
||||
if (managedBuildingIds.isEmpty()) {
|
||||
// 如果没有管理权限,返回空结果
|
||||
lqw.eq(XgHolidayStay::getId, -1);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
// 只查询宿管员权限内楼栋的学生留宿
|
||||
lqw.in(XgHolidayStay::getBuildingId, managedBuildingIds);
|
||||
|
||||
// 添加其他查询条件
|
||||
// 姓名或学号检索(OR关系)
|
||||
if (bo.getStudentName() != null && bo.getStudentNo() != null) {
|
||||
lqw.and(wrapper -> wrapper
|
||||
.like(XgHolidayStay::getStudentName, bo.getStudentName())
|
||||
.or()
|
||||
.like(XgHolidayStay::getStudentNo, bo.getStudentNo()));
|
||||
} else {
|
||||
lqw.like(bo.getStudentName() != null, XgHolidayStay::getStudentName, bo.getStudentName());
|
||||
lqw.like(bo.getStudentNo() != null, XgHolidayStay::getStudentNo, bo.getStudentNo());
|
||||
}
|
||||
lqw.like(bo.getCollegeName() != null, XgHolidayStay::getCollegeName, bo.getCollegeName());
|
||||
lqw.like(bo.getMajorName() != null, XgHolidayStay::getMajorName, bo.getMajorName());
|
||||
lqw.like(bo.getClassName() != null, XgHolidayStay::getClassName, bo.getClassName());
|
||||
lqw.eq(bo.getHolidayType() != null, XgHolidayStay::getHolidayType, bo.getHolidayType());
|
||||
lqw.ge(bo.getApplyStartDate() != null, XgHolidayStay::getApplyStartDate, bo.getApplyStartDate());
|
||||
lqw.le(bo.getApplyEndDate() != null, XgHolidayStay::getApplyEndDate, bo.getApplyEndDate());
|
||||
|
||||
// 默认按创建时间倒序
|
||||
lqw.orderByDesc(XgHolidayStay::getCreateTime);
|
||||
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建学生请假查询条件(带宿管员权限)
|
||||
*/
|
||||
private LambdaQueryWrapper<XgStudentLeave> buildStudentLeaveQueryWrapper(XgStudentLeaveBo bo, Long currentUserId) {
|
||||
LambdaQueryWrapper<XgStudentLeave> lqw = new LambdaQueryWrapper<>();
|
||||
|
||||
// 获取宿管员权限内的学生ID列表
|
||||
List<Long> managedStudentIds = getManagedStudentIds(currentUserId);
|
||||
|
||||
if (managedStudentIds.isEmpty()) {
|
||||
// 如果没有管理权限,返回空结果
|
||||
lqw.eq(XgStudentLeave::getId, -1);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
// 只查询宿管员权限内的学生请假
|
||||
lqw.in(XgStudentLeave::getStudentId, managedStudentIds);
|
||||
|
||||
// 添加其他查询条件
|
||||
// 姓名或学号检索(OR关系)
|
||||
if (bo.getStudentName() != null && bo.getStudentNo() != null) {
|
||||
lqw.and(wrapper -> wrapper
|
||||
.like(XgStudentLeave::getStudentName, bo.getStudentName())
|
||||
.or()
|
||||
.like(XgStudentLeave::getStudentNo, bo.getStudentNo()));
|
||||
} else {
|
||||
lqw.like(bo.getStudentName() != null, XgStudentLeave::getStudentName, bo.getStudentName());
|
||||
lqw.like(bo.getStudentNo() != null, XgStudentLeave::getStudentNo, bo.getStudentNo());
|
||||
}
|
||||
lqw.like(bo.getCollegeName() != null, XgStudentLeave::getCollegeName, bo.getCollegeName());
|
||||
lqw.like(bo.getMajorName() != null, XgStudentLeave::getMajorName, bo.getMajorName());
|
||||
lqw.like(bo.getClassName() != null, XgStudentLeave::getClassName, bo.getClassName());
|
||||
lqw.like(bo.getLeaveTypeName() != null, XgStudentLeave::getLeaveTypeName, bo.getLeaveTypeName());
|
||||
lqw.eq(bo.getApprovalStatus() != null, XgStudentLeave::getApprovalStatus, bo.getApprovalStatus());
|
||||
lqw.ge(bo.getStartTime() != null, XgStudentLeave::getStartTime, bo.getStartTime());
|
||||
lqw.le(bo.getEndTime() != null, XgStudentLeave::getEndTime, bo.getEndTime());
|
||||
|
||||
// 默认按创建时间倒序
|
||||
lqw.orderByDesc(XgStudentLeave::getCreateTime);
|
||||
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宿管员管理的楼栋ID列表(支持多级权限合并)
|
||||
*/
|
||||
private List<Long> getManagedBuildingIds(Long userId) {
|
||||
// 查询用户管理权限类型
|
||||
List<String> managerTypes = studentDynamicsMapper.checkUserDormManagerType(userId);
|
||||
|
||||
if (managerTypes == null || managerTypes.isEmpty()) {
|
||||
// 没有宿舍管理权限,返回空列表
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 存储所有权限的楼栋ID,使用Set去重
|
||||
Set<Long> buildingIdSet = new HashSet<>();
|
||||
|
||||
// 按照权限分别查询并合并结果(支持用户拥有多种角色的情况)
|
||||
if (managerTypes.contains("1")) {
|
||||
// 全校管理员,获取所有楼栋
|
||||
List<Long> allBuildings = studentDynamicsMapper.getBuildingIdsBySchoolAdmin(userId);
|
||||
if (allBuildings != null) {
|
||||
buildingIdSet.addAll(allBuildings);
|
||||
}
|
||||
// 全校管理员可以查看所有楼栋,直接返回
|
||||
return new ArrayList<>(buildingIdSet);
|
||||
}
|
||||
|
||||
// 对于非全校管理员,合并多种权限的结果
|
||||
if (managerTypes.contains("2")) {
|
||||
// 校区管理员,获取管理的校区下的楼栋
|
||||
List<Long> campusBuildings = studentDynamicsMapper.getBuildingIdsByCampusAdmin(userId);
|
||||
if (campusBuildings != null) {
|
||||
buildingIdSet.addAll(campusBuildings);
|
||||
}
|
||||
}
|
||||
|
||||
if (managerTypes.contains("3")) {
|
||||
// 宿舍区管理员,获取管理的宿舍区下的楼栋
|
||||
List<Long> areaBuildings = studentDynamicsMapper.getBuildingIdsByAreaAdmin(userId);
|
||||
if (areaBuildings != null) {
|
||||
buildingIdSet.addAll(areaBuildings);
|
||||
}
|
||||
}
|
||||
|
||||
if (managerTypes.contains("4")) {
|
||||
// 宿舍楼管理员,获取管理的楼栋
|
||||
List<Long> directBuildings = studentDynamicsMapper.getBuildingIdsByBuildingAdmin(userId);
|
||||
if (directBuildings != null) {
|
||||
buildingIdSet.addAll(directBuildings);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(buildingIdSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取宿管员权限内的学生ID列表(支持多级权限合并)
|
||||
*/
|
||||
private List<Long> getManagedStudentIds(Long userId) {
|
||||
// 查询用户管理权限类型
|
||||
List<String> managerTypes = studentDynamicsMapper.checkUserDormManagerType(userId);
|
||||
|
||||
if (managerTypes == null || managerTypes.isEmpty()) {
|
||||
// 没有宿舍管理权限,返回空列表
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 存储所有权限的学生ID,使用Set去重
|
||||
Set<Long> studentIdSet = new HashSet<>();
|
||||
|
||||
// 按照权限分别查询并合并结果(支持用户拥有多种角色的情况)
|
||||
if (managerTypes.contains("1")) {
|
||||
// 全校管理员,获取所有学生
|
||||
List<Long> allStudents = studentDynamicsMapper.getStudentIdsBySchoolAdmin(userId);
|
||||
if (allStudents != null) {
|
||||
studentIdSet.addAll(allStudents);
|
||||
}
|
||||
// 全校管理员可以查看所有学生,直接返回
|
||||
return new ArrayList<>(studentIdSet);
|
||||
}
|
||||
|
||||
// 对于非全校管理员,合并多种权限的结果
|
||||
if (managerTypes.contains("2")) {
|
||||
// 校区管理员,获取管理的校区下的学生
|
||||
List<Long> campusStudents = studentDynamicsMapper.getStudentIdsByCampusAdmin(userId);
|
||||
if (campusStudents != null) {
|
||||
studentIdSet.addAll(campusStudents);
|
||||
}
|
||||
}
|
||||
|
||||
if (managerTypes.contains("3")) {
|
||||
// 宿舍区管理员,获取管理的宿舍区下的学生
|
||||
List<Long> areaStudents = studentDynamicsMapper.getStudentIdsByAreaAdmin(userId);
|
||||
if (areaStudents != null) {
|
||||
studentIdSet.addAll(areaStudents);
|
||||
}
|
||||
}
|
||||
|
||||
if (managerTypes.contains("4")) {
|
||||
// 宿舍楼管理员,获取管理的楼栋下的学生
|
||||
List<Long> buildingStudents = studentDynamicsMapper.getStudentIdsByBuildingAdmin(userId);
|
||||
if (buildingStudents != null) {
|
||||
studentIdSet.addAll(buildingStudents);
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>(studentIdSet);
|
||||
}
|
||||
}
|
||||
+26
@@ -1095,6 +1095,32 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService {
|
||||
return approverUserIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID获取二级学院分管领导ID列表
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @return 分管领导ID列表
|
||||
*/
|
||||
@Override
|
||||
public List<Long> getCollegeLeadersByStudentId(Long studentId) {
|
||||
log.info("根据学生ID获取二级学院分管领导ID列表 - 学生ID: {}", studentId);
|
||||
|
||||
// 1. 根据学生ID查询学院ID
|
||||
Long collegeId = baseMapper.selectCollegeIdByStudentId(studentId);
|
||||
if (collegeId == null) {
|
||||
log.warn("未找到学生对应的学院信息 - 学生ID: {}", studentId);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
log.info("学生所在学院ID: {}", collegeId);
|
||||
|
||||
// 2. 根据学院ID查询二级学院分管领导ID列表
|
||||
List<Long> leaderIds = baseMapper.selectCollegeLeaderIdsByCollegeId(collegeId);
|
||||
|
||||
log.info("找到的二级学院分管领导数量: {}", leaderIds.size());
|
||||
return leaderIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量申请床位
|
||||
*
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?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.xg.mapper.XgCollegeLeaderMapper">
|
||||
|
||||
</mapper>
|
||||
|
||||
+8
@@ -171,6 +171,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
AND (d.dept_id = #{collegeId} OR FIND_IN_SET(#{collegeId}, d.ancestors))
|
||||
</select>
|
||||
|
||||
<!-- 根据学院ID查询二级学院分管领导ID列表 -->
|
||||
<select id="selectCollegeLeaderIdsByCollegeId" resultType="java.lang.Long">
|
||||
SELECT leader_id
|
||||
FROM xg_college_leader
|
||||
WHERE college_id = #{collegeId}
|
||||
AND deleted = 0
|
||||
</select>
|
||||
|
||||
<!-- 查询二级学院下的学院、专业、班级学生统计信息 -->
|
||||
<select id="getCollegeStudentStats" resultType="java.util.Map">
|
||||
SELECT
|
||||
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
<?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.xg.mapper.XgStudentDynamicsMapper">
|
||||
|
||||
<!-- 检查用户是否有宿舍管理权限 -->
|
||||
<select id="checkUserDormManagerType" resultType="String">
|
||||
SELECT
|
||||
DISTINCT manager_type
|
||||
FROM
|
||||
xg_dorm_manager
|
||||
WHERE
|
||||
user_id = #{userId}
|
||||
AND deleted = '0'
|
||||
AND status = '0'
|
||||
</select>
|
||||
|
||||
<!-- 获取全校管理员权限的所有楼栋ID列表 -->
|
||||
<select id="getBuildingIdsBySchoolAdmin" resultType="Long">
|
||||
SELECT
|
||||
id
|
||||
FROM
|
||||
xg_dorm_building
|
||||
WHERE
|
||||
deleted = '0'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM xg_dorm_manager m
|
||||
WHERE m.user_id = #{userId}
|
||||
AND m.manager_type = '1'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取校区管理员权限的楼栋ID列表 -->
|
||||
<select id="getBuildingIdsByCampusAdmin" resultType="Long">
|
||||
SELECT
|
||||
b.id
|
||||
FROM
|
||||
xg_dorm_building b
|
||||
WHERE
|
||||
b.deleted = '0'
|
||||
AND b.campus_id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '2'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取宿舍区管理员权限的楼栋ID列表 -->
|
||||
<select id="getBuildingIdsByAreaAdmin" resultType="Long">
|
||||
SELECT
|
||||
b.id
|
||||
FROM
|
||||
xg_dorm_building b
|
||||
WHERE
|
||||
b.deleted = '0'
|
||||
AND b.area_id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '3'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取宿舍楼管理员权限的楼栋ID列表 -->
|
||||
<select id="getBuildingIdsByBuildingAdmin" resultType="Long">
|
||||
SELECT
|
||||
b.id
|
||||
FROM
|
||||
xg_dorm_building b
|
||||
WHERE
|
||||
b.deleted = '0'
|
||||
AND b.id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '4'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取全校管理员权限的所有学生ID列表 -->
|
||||
<select id="getStudentIdsBySchoolAdmin" resultType="Long">
|
||||
SELECT
|
||||
sm.id
|
||||
FROM
|
||||
xg_student_msg sm
|
||||
WHERE
|
||||
sm.deleted = 0
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM xg_dorm_manager m
|
||||
WHERE m.user_id = #{userId}
|
||||
AND m.manager_type = '1'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取校区管理员权限的学生ID列表 -->
|
||||
<select id="getStudentIdsByCampusAdmin" resultType="Long">
|
||||
SELECT
|
||||
sm.id
|
||||
FROM
|
||||
xg_student_msg sm
|
||||
INNER JOIN
|
||||
xg_dorm_building db ON sm.dormitory_building = db.id
|
||||
WHERE
|
||||
sm.deleted = 0
|
||||
AND db.deleted = '0'
|
||||
AND db.campus_id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '2'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取宿舍区管理员权限的学生ID列表 -->
|
||||
<select id="getStudentIdsByAreaAdmin" resultType="Long">
|
||||
SELECT
|
||||
sm.id
|
||||
FROM
|
||||
xg_student_msg sm
|
||||
INNER JOIN
|
||||
xg_dorm_building db ON sm.dormitory_building = db.id
|
||||
WHERE
|
||||
sm.deleted = 0
|
||||
AND db.deleted = '0'
|
||||
AND db.area_id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '3'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
<!-- 获取宿舍楼管理员权限的学生ID列表 -->
|
||||
<select id="getStudentIdsByBuildingAdmin" resultType="Long">
|
||||
SELECT
|
||||
sm.id
|
||||
FROM
|
||||
xg_student_msg sm
|
||||
INNER JOIN
|
||||
xg_dorm_building db ON sm.dormitory_building = db.id
|
||||
WHERE
|
||||
sm.deleted = 0
|
||||
AND db.deleted = '0'
|
||||
AND db.id IN (
|
||||
SELECT
|
||||
s.auth_source
|
||||
FROM
|
||||
xg_dorm_manager m
|
||||
JOIN
|
||||
xg_dorm_manager_source s ON m.id = s.manager_id
|
||||
WHERE
|
||||
m.user_id = #{userId}
|
||||
AND m.manager_type = '4'
|
||||
AND m.deleted = '0'
|
||||
AND m.status = '0'
|
||||
AND s.deleted = '0'
|
||||
AND s.status = '0'
|
||||
)
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -6,10 +6,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<resultMap type="org.dromara.xg.domain.XgSysUser" id="XgSysUserResult">
|
||||
<id property="userId" column="user_id"/>
|
||||
<result property="userName" column="user_name"/>
|
||||
<result property="nickName" column="nick_name"/>
|
||||
<result property="phonenumber" column="phonenumber"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="accountType" column="account_type"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
@@ -18,7 +20,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<!-- 查询有效用户列表 -->
|
||||
<select id="selectActiveUserList" resultMap="XgSysUserResult">
|
||||
select user_id, nick_name, phonenumber
|
||||
select user_id, user_name, nick_name, phonenumber, account_type
|
||||
from sys_user
|
||||
where status = '0' and del_flag = '0'
|
||||
order by create_time desc
|
||||
@@ -26,7 +28,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
|
||||
<!-- 根据账户类型查询有效用户列表 -->
|
||||
<select id="selectActiveUserListByAccountType" parameterType="String" resultMap="XgSysUserResult">
|
||||
select user_id, nick_name, phonenumber
|
||||
select user_id, user_name, nick_name, phonenumber, account_type
|
||||
from sys_user
|
||||
where status = '0' and del_flag = '0' and account_type = #{accountType}
|
||||
order by create_time desc
|
||||
|
||||
+22
@@ -112,6 +112,18 @@ public class XskhDisciplineRecordController extends BaseController {
|
||||
return R.ok(xskhDisciplineRecordService.getStudentOptions(keyword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询学生选项列表(用于移动端)
|
||||
*/
|
||||
@SaCheckPermission("disciplineRecord:disciplineRecord:query")
|
||||
@GetMapping("/studentOptionsPage")
|
||||
public R<List<XskhStudentOptionVo>> getStudentOptionsPage(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
|
||||
@RequestParam(required = false, defaultValue = "20") Integer pageSize) {
|
||||
return R.ok(xskhDisciplineRecordService.getStudentOptionsPage(keyword, pageNum, pageSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前登录学生的违纪处分记录列表
|
||||
*/
|
||||
@@ -166,4 +178,14 @@ public class XskhDisciplineRecordController extends BaseController {
|
||||
@RequestParam String endTime) {
|
||||
return R.ok(xskhDisciplineRecordService.getMaxScoreByTypeAndPeriod(typeId, startTime, endTime));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
|
||||
*/
|
||||
@SaCheckPermission("disciplineRecord:disciplineRecord:query")
|
||||
@GetMapping("/myCreated")
|
||||
public TableDataInfo<XskhDisciplineRecordVo> getMyCreatedDisciplineRecords(XskhDisciplineRecordBo bo,
|
||||
PageQuery pageQuery) {
|
||||
return xskhDisciplineRecordService.queryMyCreatedPageList(bo, pageQuery);
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -23,9 +23,13 @@ public interface XskhDisciplineRecordMapper extends BaseMapperPlus<XskhDisciplin
|
||||
* 查询学生选项列表(用于下拉框)
|
||||
*
|
||||
* @param keyword 搜索关键词(学号或姓名)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 学生选项列表
|
||||
*/
|
||||
List<XskhStudentOptionVo> selectStudentOptions(String keyword);
|
||||
List<XskhStudentOptionVo> selectStudentOptions(@Param("keyword") String keyword,
|
||||
@Param("pageNum") Integer pageNum,
|
||||
@Param("pageSize") Integer pageSize);
|
||||
|
||||
/**
|
||||
* 查询班主任班级的违纪处分记录列表(班主任专用)
|
||||
|
||||
+22
@@ -50,9 +50,22 @@ public interface IXskhDisciplineRecordService {
|
||||
|
||||
/**
|
||||
* 查询学生选项列表(用于下拉框)
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @return 学生选项列表
|
||||
*/
|
||||
List<XskhStudentOptionVo> getStudentOptions(String keyword);
|
||||
|
||||
/**
|
||||
* 分页查询学生选项列表(用于移动端)
|
||||
*
|
||||
* @param keyword 搜索关键词
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页数量
|
||||
* @return 学生选项列表
|
||||
*/
|
||||
List<XskhStudentOptionVo> getStudentOptionsPage(String keyword, Integer pageNum, Integer pageSize);
|
||||
|
||||
/**
|
||||
* 查询当前登录学生的违纪处分记录列表
|
||||
*/
|
||||
@@ -101,4 +114,13 @@ public interface IXskhDisciplineRecordService {
|
||||
* @return 最高总扣分
|
||||
*/
|
||||
java.math.BigDecimal getMaxScoreByTypeAndPeriod(Long typeId, String startTime, String endTime);
|
||||
|
||||
/**
|
||||
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 违纪记录分页列表
|
||||
*/
|
||||
TableDataInfo<XskhDisciplineRecordVo> queryMyCreatedPageList(XskhDisciplineRecordBo bo, PageQuery pageQuery);
|
||||
}
|
||||
+51
-1
@@ -165,7 +165,15 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer
|
||||
*/
|
||||
@Override
|
||||
public List<XskhStudentOptionVo> getStudentOptions(String keyword) {
|
||||
return baseMapper.selectStudentOptions(keyword);
|
||||
return baseMapper.selectStudentOptions(keyword, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询学生选项列表(用于移动端)
|
||||
*/
|
||||
@Override
|
||||
public List<XskhStudentOptionVo> getStudentOptionsPage(String keyword, Integer pageNum, Integer pageSize) {
|
||||
return baseMapper.selectStudentOptions(keyword, pageNum, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,4 +294,46 @@ public class XskhDisciplineRecordServiceImpl implements IXskhDisciplineRecordSer
|
||||
log.info("违纪类型 {} 的最高总扣分为: {}", typeId, maxScore);
|
||||
return maxScore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户创建的违纪记录列表(支持按学生姓名或学号搜索)
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<XskhDisciplineRecordVo> queryMyCreatedPageList(XskhDisciplineRecordBo bo,
|
||||
PageQuery pageQuery) {
|
||||
// 获取当前登录用户ID
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
log.info("查询用户 {} 创建的违纪记录列表", currentUserId);
|
||||
|
||||
// 构建查询条件,强制只查询当前用户创建的记录
|
||||
LambdaQueryWrapper<XskhDisciplineRecord> lqw = Wrappers.lambdaQuery();
|
||||
|
||||
// 核心条件:只查询当前用户创建的记录
|
||||
lqw.eq(XskhDisciplineRecord::getCreateBy, currentUserId);
|
||||
|
||||
// 支持按学生姓名或学号模糊搜索(使用OR连接)
|
||||
if (StringUtils.isNotBlank(bo.getStudentName()) || StringUtils.isNotBlank(bo.getStudentNo())) {
|
||||
String keyword = StringUtils.isNotBlank(bo.getStudentName()) ? bo.getStudentName() : bo.getStudentNo();
|
||||
lqw.and(wrapper -> wrapper
|
||||
.like(XskhDisciplineRecord::getStudentName, keyword)
|
||||
.or()
|
||||
.like(XskhDisciplineRecord::getStudentNo, keyword));
|
||||
}
|
||||
|
||||
// 其他可选查询条件
|
||||
lqw.eq(bo.getStudentId() != null, XskhDisciplineRecord::getStudentId, bo.getStudentId());
|
||||
lqw.eq(bo.getCollegeId() != null, XskhDisciplineRecord::getCollegeId, bo.getCollegeId());
|
||||
lqw.eq(bo.getMajorId() != null, XskhDisciplineRecord::getMajorId, bo.getMajorId());
|
||||
lqw.eq(bo.getClassId() != null, XskhDisciplineRecord::getClassId, bo.getClassId());
|
||||
lqw.eq(bo.getDisciplineTypeId() != null, XskhDisciplineRecord::getDisciplineTypeId, bo.getDisciplineTypeId());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), XskhDisciplineRecord::getStatus, bo.getStatus());
|
||||
|
||||
// 按创建时间倒序排列
|
||||
lqw.orderByDesc(XskhDisciplineRecord::getCreateTime);
|
||||
|
||||
// 执行分页查询
|
||||
Page<XskhDisciplineRecordVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
log.info("查询到 {} 条记录", result.getTotal());
|
||||
return TableDataInfo.build(result);
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -49,7 +49,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
select id, student_id, student_no, student_name, college_id, college_name, major_id, major_name, class_id, class_name, discipline_type_id, discipline_type_name, discipline_date, discipline_reason, discipline_content, score_deduction, status, violation_source, attachment_oss_ids, create_by, create_time, create_dept, update_by, update_time, deleted, tenant_id from xskh_discipline_record
|
||||
</sql>
|
||||
|
||||
<select id="selectStudentOptions" parameterType="String" resultMap="StudentOptionResult">
|
||||
<select id="selectStudentOptions" resultMap="StudentOptionResult">
|
||||
select
|
||||
id, student_no, name, college_id, college_name,
|
||||
major_id, major_name, class_id, class_name
|
||||
@@ -61,7 +61,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
or name like concat('%', #{keyword}, '%'))
|
||||
</if>
|
||||
order by student_no
|
||||
<choose>
|
||||
<when test="pageNum != null and pageSize != null">
|
||||
limit ${(pageNum - 1) * pageSize}, #{pageSize, jdbcType=INTEGER}
|
||||
</when>
|
||||
<otherwise>
|
||||
limit 50
|
||||
</otherwise>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
<select id="selectClassTeacherVoPage" resultType="XskhDisciplineRecordVo">
|
||||
|
||||
@@ -1,4 +1,104 @@
|
||||
-- ----------------------------
|
||||
-- 0、warm-flow-all.sql,地址:https://gitee.com/dromara/warm-flow/blob/master/sql/mysql/warm-flow-all.sql
|
||||
-- ----------------------------
|
||||
CREATE TABLE `flow_definition`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT '主键id',
|
||||
`flow_code` varchar(40) NOT NULL COMMENT '流程编码',
|
||||
`flow_name` varchar(100) NOT NULL COMMENT '流程名称',
|
||||
`category` varchar(100) DEFAULT NULL COMMENT '流程类别',
|
||||
`version` varchar(20) NOT NULL COMMENT '流程版本',
|
||||
`is_publish` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否发布(0未发布 1已发布 9失效)',
|
||||
`form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
|
||||
`form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径',
|
||||
`activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
|
||||
`listener_type` varchar(100) DEFAULT NULL COMMENT '监听器类型',
|
||||
`listener_path` varchar(400) DEFAULT NULL COMMENT '监听器路径',
|
||||
`ext` varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||
`tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB COMMENT ='流程定义表';
|
||||
|
||||
CREATE TABLE `flow_node`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT '主键id',
|
||||
`node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
|
||||
`definition_id` bigint NOT NULL COMMENT '流程定义id',
|
||||
`node_code` varchar(100) NOT NULL COMMENT '流程节点编码',
|
||||
`node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称',
|
||||
`permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)',
|
||||
`node_ratio` decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值',
|
||||
`coordinate` varchar(100) DEFAULT NULL COMMENT '坐标',
|
||||
`any_node_skip` varchar(100) DEFAULT NULL COMMENT '任意结点跳转',
|
||||
`listener_type` varchar(100) DEFAULT NULL COMMENT '监听器类型',
|
||||
`listener_path` varchar(400) DEFAULT NULL COMMENT '监听器路径',
|
||||
`handler_type` varchar(100) DEFAULT NULL COMMENT '处理器类型',
|
||||
`handler_path` varchar(400) DEFAULT NULL COMMENT '处理器路径',
|
||||
`form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
|
||||
`form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径',
|
||||
`version` varchar(20) NOT NULL COMMENT '版本',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`ext` text COMMENT '扩展属性',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||
`tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB COMMENT ='流程节点表';
|
||||
|
||||
CREATE TABLE `flow_skip`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT '主键id',
|
||||
`definition_id` bigint NOT NULL COMMENT '流程定义id',
|
||||
`now_node_code` varchar(100) NOT NULL COMMENT '当前流程节点的编码',
|
||||
`now_node_type` tinyint(1) DEFAULT NULL COMMENT '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
|
||||
`next_node_code` varchar(100) NOT NULL COMMENT '下一个流程节点的编码',
|
||||
`next_node_type` tinyint(1) DEFAULT NULL COMMENT '下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
|
||||
`skip_name` varchar(100) DEFAULT NULL COMMENT '跳转名称',
|
||||
`skip_type` varchar(40) DEFAULT NULL COMMENT '跳转类型(PASS审批通过 REJECT退回)',
|
||||
`skip_condition` varchar(200) DEFAULT NULL COMMENT '跳转条件',
|
||||
`coordinate` varchar(100) DEFAULT NULL COMMENT '坐标',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||
`tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB COMMENT ='节点跳转关联表';
|
||||
|
||||
CREATE TABLE `flow_instance`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT '主键id',
|
||||
`definition_id` bigint NOT NULL COMMENT '对应flow_definition表的id',
|
||||
`business_id` varchar(40) NOT NULL COMMENT '业务id',
|
||||
`node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
|
||||
`node_code` varchar(40) NOT NULL COMMENT '流程节点编码',
|
||||
`node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称',
|
||||
`variable` text COMMENT '任务变量',
|
||||
`flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)',
|
||||
`activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)',
|
||||
`def_json` text COMMENT '流程定义json',
|
||||
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`ext` varchar(500) DEFAULT NULL COMMENT '扩展字段,预留给业务系统使用',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||
`tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
) ENGINE = InnoDB COMMENT ='流程实例表';
|
||||
|
||||
CREATE TABLE `flow_task`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT '主键id',
|
||||
`definition_id` bigint NOT NULL COMMENT '对应flow_definition表的id',
|
||||
`instance_id` bigint NOT NULL COMMENT '对应flow_instance表的id',
|
||||
`node_code` varchar(100) NOT NULL COMMENT '节点编码',
|
||||
`node_name` varchar(100) DEFAULT NULL COMMENT '节点名称',
|
||||
`node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)',
|
||||
`flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)',
|
||||
`form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)',
|
||||
`form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# 代码调整记录 - 2025-01-08 - Cursor AI
|
||||
|
||||
## 📝 变更概述
|
||||
**变更时间**: 2025-01-08 15:30:00
|
||||
**变更人员**: Cursor AI
|
||||
**变更类型**: 数据库结构修复
|
||||
**影响模块**: 社团管理 - 活动报名功能
|
||||
|
||||
## 🔴 **问题描述**
|
||||
**问题现象**: 活动报名接口数据插入失败,报错缺少字段
|
||||
**根本原因**: `stgl_activity_registration` 数据库表结构与实体类不匹配,缺失多个字段
|
||||
|
||||
**缺失字段清单**:
|
||||
- `remark` - 申请理由(非必填)
|
||||
- `gender` - 性别
|
||||
- `phone` - 联系电话
|
||||
- `email` - 邮箱
|
||||
- `department` - 院系
|
||||
- `major` - 专业
|
||||
- `class_name` - 班级
|
||||
|
||||
## ✅ **解决方案**
|
||||
|
||||
### 1. 创建修复SQL脚本
|
||||
**文件**: `docs/设计文档/database/社团管理/修复活动报名表字段.sql`
|
||||
- 添加7个缺失字段到 `stgl_activity_registration` 表
|
||||
- 新增性能优化索引 (`idx_gender`, `idx_phone`, `idx_department`)
|
||||
- 包含验证和检查语句
|
||||
|
||||
### 2. 更新建表脚本
|
||||
**文件**: `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
|
||||
- 在 `stgl_activity_registration` 表中补充完整字段定义
|
||||
- 调整字段顺序,符合逻辑分组(基本信息、审核信息、成绩信息、学生个人信息)
|
||||
- 新增对应索引
|
||||
|
||||
## 📊 **影响范围**
|
||||
- ✅ **后端**: StglActivityRegistration 实体类字段完整匹配
|
||||
- ✅ **数据库**: 表结构字段补全,支持完整业务数据
|
||||
- ✅ **API**: 活动报名相关接口正常工作
|
||||
- ✅ **前端**: 报名表单数据可正常提交和保存
|
||||
|
||||
## 🧪 **验证方法**
|
||||
```sql
|
||||
-- 1. 执行修复脚本
|
||||
source docs/设计文档/database/社团管理/修复活动报名表字段.sql
|
||||
|
||||
-- 2. 验证表结构
|
||||
DESCRIBE stgl_activity_registration;
|
||||
|
||||
-- 3. 测试插入数据
|
||||
-- 使用活动报名API接口测试完整数据提交
|
||||
```
|
||||
|
||||
## 📋 **待执行任务**
|
||||
- [x] 分析问题原因
|
||||
- [x] 创建修复SQL脚本
|
||||
- [x] 更新建表脚本
|
||||
- [x] 记录变更日志
|
||||
- [ ] **需要执行**: 在数据库中运行修复脚本
|
||||
- [ ] **需要测试**: 验证活动报名功能正常
|
||||
|
||||
## 🔗 **相关文件**
|
||||
- `backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivityRegistration.java`
|
||||
- `backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityRegistrationMapper.xml`
|
||||
- `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
|
||||
- `docs/设计文档/database/社团管理/修复活动报名表字段.sql` (新增)
|
||||
|
||||
## 💡 **经验总结**
|
||||
1. **数据库与实体类一致性**: 开发时必须确保数据库表结构与实体类字段完全匹配
|
||||
2. **渐进式开发问题**: 实体类后续添加字段时,必须同步更新数据库表结构
|
||||
3. **文档同步重要性**: 建表脚本必须与实际代码保持同步,避免部署时出现问题
|
||||
4. **字段分组设计**: 表字段应该按业务逻辑分组,提高可读性和维护性
|
||||
|
||||
---
|
||||
*本记录严格按照项目规范要求,代码变更与文档同步更新*
|
||||
@@ -1,241 +0,0 @@
|
||||
# 代码调整记录 - 2025-01-14 - cursor
|
||||
|
||||
## 📝 变更概述
|
||||
**变更日期**: 2025-01-14 15:30:00
|
||||
**处理人员**: cursor
|
||||
**变更类型**: 前端功能修复 + UI优化
|
||||
|
||||
## 🎯 变更内容
|
||||
|
||||
### 1. 修复预约时段状态切换功能 (XljkConsultationSchedule)
|
||||
|
||||
**问题描述**:
|
||||
- 心理健康咨询预约时段管理页面,点击编辑按钮后状态切换异常
|
||||
- 无论当前状态如何,都会变成停用状态
|
||||
- 显示双重状态标签,界面异常
|
||||
|
||||
**根本原因分析**:
|
||||
1. **状态值类型不一致**: 前端接收的`status`是字符串类型(`"0"`, `"1"`),但切换逻辑使用数字比较(`=== 1`)
|
||||
2. **字典数据缺失**: `schedule_status`字典未正确配置,导致显示异常
|
||||
3. **状态更新接口数据类型错误**: 发送数字状态但接收端可能期望字符串
|
||||
|
||||
**修复方案**:
|
||||
|
||||
#### 1.1 修复状态切换逻辑
|
||||
**文件**: `web/src/views/xljk/consultation/schedule/index.vue`
|
||||
|
||||
**修改前**:
|
||||
```typescript
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
|
||||
if (row && row.id) {
|
||||
try {
|
||||
await updateXljkSchedule({
|
||||
id: row.id,
|
||||
status: row.status === 1 ? 0 : 1 // 错误:字符串与数字比较
|
||||
} as XljkConsultationScheduleForm);
|
||||
ElMessage.success('状态更新成功');
|
||||
getList();
|
||||
} catch (error) {
|
||||
console.error('更新时段状态失败', error);
|
||||
ElMessage.error('状态更新失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```typescript
|
||||
const handleUpdate = async (row?: XljkConsultationScheduleVO) => {
|
||||
if (row && row.id) {
|
||||
try {
|
||||
// 修复:将字符串状态正确转换为数字进行比较
|
||||
const currentStatus = parseInt(row.status as string);
|
||||
const newStatus = currentStatus === 1 ? 0 : 1;
|
||||
|
||||
console.log(`状态切换调试 - 当前状态: ${row.status} (${typeof row.status}), 转换后: ${currentStatus}, 新状态: ${newStatus}`);
|
||||
|
||||
await updateXljkSchedule({
|
||||
id: row.id,
|
||||
status: newStatus.toString()
|
||||
} as XljkConsultationScheduleForm);
|
||||
|
||||
ElMessage.success(`状态已${newStatus === 0 ? '启用' : '停用'}`);
|
||||
getList();
|
||||
} catch (error) {
|
||||
console.error('更新时段状态失败', error);
|
||||
ElMessage.error('状态更新失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**修复要点**:
|
||||
- ✅ 使用 `parseInt()` 将字符串状态转换为数字进行比较
|
||||
- ✅ 添加调试日志输出,便于问题排查
|
||||
- ✅ 改进用户提示消息,明确显示状态变更结果
|
||||
- ✅ 确保发送的状态值类型一致性
|
||||
|
||||
#### 1.2 创建字典数据配置
|
||||
**文件**: `backend/script/sql/xljk-dict-data.sql`
|
||||
|
||||
**新增内容**:
|
||||
```sql
|
||||
-- 预约时段状态字典
|
||||
INSERT INTO sys_dict_type VALUES (102, '000000', '预约时段状态', 'schedule_status', 103, 1, sysdate(), NULL, NULL, '预约时段状态列表');
|
||||
|
||||
-- 预约时段状态字典数据
|
||||
INSERT INTO sys_dict_data VALUES (300, '000000', 1, '正常', '0', 'schedule_status', '', 'success', 'Y', 103, 1, sysdate(), NULL, NULL, '正常状态');
|
||||
INSERT INTO sys_dict_data VALUES (301, '000000', 2, '停用', '1', 'schedule_status', '', 'danger', 'N', 103, 1, sysdate(), NULL, NULL, '停用状态');
|
||||
```
|
||||
|
||||
**配置要点**:
|
||||
- ✅ 创建 `schedule_status` 字典类型
|
||||
- ✅ 配置状态值:`0-正常`(success样式)、`1-停用`(danger样式)
|
||||
- ✅ 同时创建相关的预约状态和时段类型字典
|
||||
|
||||
### 2. 优化预约时段管理列表列宽 (2025-01-14 15:45:00)
|
||||
|
||||
**调整目标**: 缩小上午时段、下午时段、创建时间3个列的列宽,优化页面布局
|
||||
|
||||
**列宽调整对比**:
|
||||
|
||||
| 字段名称 | 调整前 | 调整后 | 变化 |
|
||||
|----------|--------|--------|------|
|
||||
| **上午时段** | `min-width="150"` | `min-width="120"` | -30px |
|
||||
| **下午时段** | `min-width="150"` | `min-width="120"` | -30px |
|
||||
| **创建时间** | `width="150"` | `width="120"` | -30px |
|
||||
|
||||
**修改位置**: `web/src/views/xljk/consultation/schedule/index.vue` (第84-102行)
|
||||
|
||||
**修改内容**:
|
||||
```vue
|
||||
<!-- 上午时段列 -->
|
||||
<el-table-column label="上午时段" align="center" min-width="120">
|
||||
|
||||
<!-- 下午时段列 -->
|
||||
<el-table-column label="下午时段" align="center" min-width="120">
|
||||
|
||||
<!-- 创建时间列 -->
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="120">
|
||||
```
|
||||
|
||||
**优化效果**:
|
||||
- ✅ 总体节省空间:90px (30px × 3列)
|
||||
- ✅ 上午/下午时段列保持响应式 (`min-width`)
|
||||
- ✅ 创建时间列固定宽度更紧凑
|
||||
- ✅ 为其他列提供更多显示空间
|
||||
- ✅ 提升页面整体布局效率
|
||||
|
||||
**表格布局更新**:
|
||||
- **原总最小宽度**: 1115px
|
||||
- **新总最小宽度**: 1025px
|
||||
- **空间优化**: 节省90px宽度
|
||||
|
||||
### 3. 移除预约限制管理页面创建时间字段 (2025-01-14 16:00:00)
|
||||
|
||||
**调整目标**: 移除预约限制管理(未到访记录)列表界面的创建时间字段显示
|
||||
|
||||
**页面位置**: `web/src/views/xljk/consultation/noshow/index.vue`
|
||||
|
||||
**移除字段**:
|
||||
```vue
|
||||
<!-- 已移除的创建时间列 -->
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="150">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**修改位置**: 第92-96行
|
||||
|
||||
**优化效果**:
|
||||
- ✅ **简化界面**: 移除不必要的时间信息显示
|
||||
- ✅ **节省空间**: 释放150px列宽空间
|
||||
- ✅ **聚焦核心**: 突出学生信息、限制状态等关键信息
|
||||
- ✅ **提升体验**: 减少信息冗余,提高页面简洁度
|
||||
|
||||
**当前保留字段**:
|
||||
- 学生姓名、学号、院系、专业、班级
|
||||
- 未到访日期、限制到期日、状态
|
||||
- 操作(查看、解除限制)
|
||||
|
||||
**表格布局优化**:
|
||||
- **移除列宽**: 150px
|
||||
- **界面更简洁**: 专注于业务核心信息
|
||||
- **操作便捷**: 保留查看详情和解除限制功能
|
||||
|
||||
## 🔧 技术细节
|
||||
|
||||
### 问题原因深度分析
|
||||
|
||||
1. **JavaScript类型转换问题**:
|
||||
```javascript
|
||||
// 问题代码
|
||||
"1" === 1 // false (字符串与数字比较)
|
||||
|
||||
// 修复代码
|
||||
parseInt("1") === 1 // true (转换后比较)
|
||||
```
|
||||
|
||||
2. **数据流类型不一致**:
|
||||
```
|
||||
后端返回 → status: "0" (字符串)
|
||||
前端比较 → status === 1 (数字)
|
||||
结果 → 永远返回false,总是执行 : 1 分支
|
||||
```
|
||||
|
||||
3. **字典配置缺失**:
|
||||
```vue
|
||||
<dict-tag :options="schedule_status" :value="scope.row.status" />
|
||||
```
|
||||
当 `schedule_status` 字典为空时,`dict-tag` 组件无法正确渲染,可能显示原始值或双重标签。
|
||||
|
||||
### 测试验证
|
||||
|
||||
**验证步骤**:
|
||||
1. ✅ 启动前端开发服务器
|
||||
2. ✅ 导航至心理健康 → 咨询管理 → 预约时段
|
||||
3. ✅ 检查状态列显示是否正常(应显示"正常"或"停用")
|
||||
4. ✅ 点击编辑按钮测试状态切换
|
||||
5. ✅ 观察控制台调试日志
|
||||
6. ✅ 验证状态在正常/停用之间正确切换
|
||||
|
||||
**预期结果**:
|
||||
- 状态列正确显示"正常"/"停用"标签
|
||||
- 点击编辑按钮后状态正确切换
|
||||
- 用户提示消息明确显示变更结果
|
||||
- 控制台输出调试信息供问题排查
|
||||
|
||||
## 📊 影响范围
|
||||
|
||||
**直接影响**:
|
||||
- ✅ 心理健康咨询预约时段管理功能恢复正常
|
||||
- ✅ 状态切换功能可正确启用/停用时段
|
||||
|
||||
**间接影响**:
|
||||
- ✅ 改善用户体验,操作反馈更清晰
|
||||
- ✅ 为类似的状态切换功能提供标准修复模式
|
||||
- ✅ 完善了心理健康模块的字典数据配置
|
||||
|
||||
## 🚀 后续优化建议
|
||||
|
||||
1. **统一类型处理**: 考虑在前端建立统一的状态值处理工具函数
|
||||
2. **完善错误处理**: 添加更详细的错误处理和用户提示
|
||||
3. **字典数据管理**: 建立字典数据的统一管理和维护机制
|
||||
4. **测试用例**: 为状态切换功能添加单元测试和集成测试
|
||||
|
||||
## 📋 变更文件清单
|
||||
|
||||
| 文件路径 | 变更类型 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `web/src/views/xljk/consultation/schedule/index.vue` | 修改 | 修复状态切换逻辑 + 优化列宽布局 |
|
||||
| `web/src/views/xljk/consultation/noshow/index.vue` | 修改 | 移除创建时间字段显示 |
|
||||
| `backend/script/sql/xljk-dict-data.sql` | 新增 | 创建心理健康模块字典数据配置 |
|
||||
| `docs/变更管理/code-adjust-record-2025-01-14-cursor.md` | 新增 | 本变更记录文档 |
|
||||
|
||||
---
|
||||
|
||||
**验证完成**: ✅ 功能测试通过
|
||||
**文档更新**: ✅ 已同步更新
|
||||
**代码审查**: ✅ 自检完成
|
||||
@@ -1,66 +0,0 @@
|
||||
# 代码调整记录 - 2025-01-15 - cursor
|
||||
|
||||
## 🔧 数据库字段修复
|
||||
|
||||
### 📅 变更时间
|
||||
2025-01-15 14:30:00
|
||||
|
||||
### 🎯 变更目标
|
||||
修复 `stgl_activity` 表缺失的 `remark` 字段
|
||||
|
||||
### ⚠️ 问题描述
|
||||
在社团活动管理功能中出现以下错误:
|
||||
```
|
||||
Unknown column 'sa.remark' in 'field list'
|
||||
```
|
||||
|
||||
**错误原因分析:**
|
||||
1. 实体类 `StglActivity.java` 中定义了 `remark` 字段
|
||||
2. MyBatis映射文件 `StglActivityMapper.xml` 中的SQL查询包含了 `sa.remark` 字段
|
||||
3. 但数据库表 `stgl_activity` 中实际缺少 `remark` 字段
|
||||
|
||||
### 🔍 修复过程
|
||||
|
||||
#### 1. 问题定位
|
||||
- 检查实体类:`StglActivity.java` 第216-217行定义了remark字段
|
||||
- 检查映射文件:`StglActivityMapper.xml` 第59行和第40行引用了remark字段
|
||||
- 检查数据库表:`stgl_activity` 表确实缺少remark字段
|
||||
|
||||
#### 2. 修复方案
|
||||
为 `stgl_activity` 表添加缺失的 `remark` 字段
|
||||
|
||||
#### 3. 执行的SQL语句
|
||||
```sql
|
||||
ALTER TABLE `stgl_activity`
|
||||
ADD COLUMN `remark` varchar(500) DEFAULT NULL COMMENT '备注' AFTER `import_source`;
|
||||
```
|
||||
|
||||
#### 4. 修复结果验证
|
||||
- ✅ remark字段成功添加到stgl_activity表
|
||||
- ✅ 字段类型:varchar(500)
|
||||
- ✅ 字段注释:备注
|
||||
- ✅ 字段位置:import_source字段之后
|
||||
|
||||
### 📁 相关文件
|
||||
- **实体类**: `backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/StglActivity.java`
|
||||
- **映射文件**: `backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml`
|
||||
- **修复脚本**: `backend/script/sql/fix-stgl-activity-remark-field.sql`
|
||||
- **建表脚本**: `docs/设计文档/database/社团管理/社团管理建表脚本.sql`
|
||||
|
||||
### 🎯 影响范围
|
||||
- **功能模块**: 社团活动管理
|
||||
- **影响接口**: 所有涉及stgl_activity表查询的接口
|
||||
- **数据完整性**: 修复后代码与数据库结构保持一致
|
||||
|
||||
### ✅ 验证结果
|
||||
修复完成后,社团活动管理相关功能应该能正常运行,不再出现字段缺失错误。
|
||||
|
||||
### 📝 经验教训
|
||||
1. **数据库同步**: 确保实体类、映射文件与数据库表结构完全一致
|
||||
2. **建表脚本**: 需要检查建表脚本是否包含所有必要字段
|
||||
3. **字段映射**: MyBatis映射文件中的字段必须在数据库表中实际存在
|
||||
|
||||
---
|
||||
**修复人员**: Cursor AI
|
||||
**修复时间**: 2025-01-15 14:30:00
|
||||
**状态**: ✅ 已完成
|
||||
@@ -1,137 +0,0 @@
|
||||
# 代码调整记录 - 2025-01-21 - Cursor AI
|
||||
|
||||
## 📋 变更概述
|
||||
|
||||
**时间**: 2025-01-21 10:30:00
|
||||
**操作人**: Cursor AI
|
||||
**变更类型**: 🐛 Bug修复
|
||||
**影响范围**: 志愿者成员管理模块
|
||||
|
||||
## 🔍 问题描述
|
||||
|
||||
**问题现象**: 当前登录用户`2025LY23117`是志愿者马拉松工作组的队长,该队伍总共有3个人,但在志愿者成员管理页面只能看到一个成员。
|
||||
|
||||
**根本原因**: 志愿者角色类型(roleType)在前后端定义不一致,导致权限过滤逻辑失效。
|
||||
|
||||
### 问题详情
|
||||
|
||||
| 组件 | 队长值 | 队员值 | 状态 |
|
||||
|------|--------|--------|------|
|
||||
| 前端字典(修复前) | 1 | 2 | ❌ 错误 |
|
||||
| 后端权限过滤 | 0 | 1 | ✅ 正确 |
|
||||
| 数据库SQL映射 | 0 | 1 | ✅ 正确 |
|
||||
| 字典表(修复前) | 1 | 2 | ❌ 错误 |
|
||||
|
||||
**影响**:
|
||||
- 权限过滤方法`getCaptainTeamIds`查找`roleType=0`时找不到队长记录
|
||||
- 查询条件被设置为`relationId=-1L`,导致返回空结果
|
||||
- 用户只能看到第一个队伍的数据
|
||||
|
||||
## 🛠️ 解决方案
|
||||
|
||||
**采用方案A**: 统一使用数据库SQL的定义(队长=0,队员=1)
|
||||
|
||||
### 修复内容
|
||||
|
||||
#### 1. 修改字典表数据
|
||||
**文件**: `backend/script/sql/stgl_volunteer_role_dict.sql`
|
||||
- 队长的`dict_value`从'1'改为'0'
|
||||
- 队员的`dict_value`从'2'改为'1'
|
||||
|
||||
#### 2. 修改前端字典定义
|
||||
**文件**: `web/src/views/stgl/volunteer/member/index.vue`
|
||||
- 第358-361行:更新手动定义的字典
|
||||
- 第432行:修改默认角色值为'1'(队员)
|
||||
- 第242行:移除parseInt转换,保持字符串类型
|
||||
- 第632行:修改类型转换为String()
|
||||
|
||||
#### 3. 创建数据库更新脚本
|
||||
**文件**: `script/update-volunteer-role-dict.sql`
|
||||
- 创建线上数据库更新脚本
|
||||
- 包含验证查询和完成提示
|
||||
|
||||
## 📝 修改细节
|
||||
|
||||
### 代码变更
|
||||
|
||||
```javascript
|
||||
// 修复前
|
||||
const stgl_volunteer_role = ref([
|
||||
{ value: '1', label: '队长' },
|
||||
{ value: '2', label: '队员' }
|
||||
]);
|
||||
|
||||
// 修复后
|
||||
const stgl_volunteer_role = ref([
|
||||
{ value: '0', label: '队长' },
|
||||
{ value: '1', label: '队员' }
|
||||
]);
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 修复前字典数据
|
||||
VALUES (..., '队长', '1', ...)
|
||||
VALUES (..., '队员', '2', ...)
|
||||
|
||||
-- 修复后字典数据
|
||||
VALUES (..., '队长', '0', ...)
|
||||
VALUES (..., '队员', '1', ...)
|
||||
```
|
||||
|
||||
### 权限过滤逻辑验证
|
||||
|
||||
后端权限过滤逻辑保持不变(已正确):
|
||||
```java
|
||||
captainBo.setRoleType(0); // 队长角色 = 0
|
||||
```
|
||||
|
||||
数据库SQL映射保持不变(已正确):
|
||||
```xml
|
||||
WHEN 0 THEN '队长' <!-- 队长 = 0 -->
|
||||
WHEN 1 THEN '队员' <!-- 队员 = 1 -->
|
||||
```
|
||||
|
||||
## 🎯 预期效果
|
||||
|
||||
修复完成后:
|
||||
1. ✅ 志愿者队长能够正确查看所属队伍的所有成员
|
||||
2. ✅ 权限过滤逻辑正常工作
|
||||
3. ✅ 前后端角色类型定义统一
|
||||
4. ✅ 字典数据显示正确
|
||||
|
||||
## 📋 部署步骤
|
||||
|
||||
1. **执行数据库更新脚本**:
|
||||
```bash
|
||||
mysql -u username -p database < script/update-volunteer-role-dict.sql
|
||||
```
|
||||
|
||||
2. **重新构建前端**:
|
||||
```bash
|
||||
cd web
|
||||
npm run build
|
||||
```
|
||||
|
||||
3. **重启后端服务**(如有缓存)
|
||||
|
||||
4. **验证功能**:
|
||||
- 使用队长账号登录
|
||||
- 访问志愿者成员管理页面
|
||||
- 确认能看到所有队伍成员
|
||||
|
||||
## 🔍 验证要点
|
||||
|
||||
- [ ] 队长能查看所属队伍的所有成员
|
||||
- [ ] 角色显示正确(队长/队员)
|
||||
- [ ] 新增成员时角色选择正常
|
||||
- [ ] 权限过滤按预期工作
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- **问题分析**: 本文档"问题描述"部分
|
||||
- **技术规范**: `.cursor/cursorrules-backend.md` - 权限管理规范
|
||||
- **API文档**: `docs/开发文档/API/志愿者管理API.md`
|
||||
|
||||
## 🏷️ 标签
|
||||
|
||||
`#志愿者管理` `#权限过滤` `#角色类型` `#前后端统一` `#字典数据` `#Bug修复`
|
||||
@@ -0,0 +1,300 @@
|
||||
# 后端文件上传数据丢失问题
|
||||
|
||||
**发现日期**: 2025-01-24
|
||||
**问题模块**: 文件上传/OSS服务
|
||||
**影响范围**: 所有使用附件上传的业务模块
|
||||
**严重程度**: ⚠️ 中等(影响用户体验,但有前端兜底)
|
||||
|
||||
---
|
||||
|
||||
## 问题描述
|
||||
|
||||
后端在保存文件时丢失了真实的文件名和扩展名信息,导致前端回显时无法正确识别文件类型。
|
||||
|
||||
### 问题表现
|
||||
|
||||
从 `/resource/oss/listByIds/{ossIds}` API 返回的数据中:
|
||||
|
||||
```javascript
|
||||
{
|
||||
ossId: "1981738218223079425",
|
||||
originalName: "12038a7c-48bd-4978-bcbb-1119a201a6e9", // ❌ UUID,不是用户上传的真实文件名
|
||||
fileSuffix: "9", // ❌ 数字字符,不是真实扩展名(应该是 jpg/png 等)
|
||||
fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139", // ❌ 没有扩展名
|
||||
url: "/resource/oss/download/1981738218223079425"
|
||||
}
|
||||
```
|
||||
|
||||
### 期望的数据格式
|
||||
|
||||
```javascript
|
||||
{
|
||||
ossId: "1981738218223079425",
|
||||
originalName: "违纪现场照片.jpg", // ✅ 用户上传时的真实文件名
|
||||
fileSuffix: "jpg", // ✅ 真实的文件扩展名
|
||||
fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139.jpg", // ✅ 包含扩展名
|
||||
url: "/resource/oss/download/1981738218223079425"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 可能的原因
|
||||
|
||||
1. **文件上传时未保存原始文件名**
|
||||
- 上传接口 `/resource/oss/upload` 可能未正确提取文件名
|
||||
- `originalName` 字段被UUID覆盖
|
||||
|
||||
2. **扩展名提取错误**
|
||||
- `fileSuffix` 字段提取逻辑有误
|
||||
- 从文件路径末尾提取了一个字符(如 `9`、`e`、`d`)而不是真实扩展名
|
||||
|
||||
3. **数据库保存逻辑问题**
|
||||
- `sys_oss` 表中的字段未正确保存
|
||||
- 可能是某个字段映射错误
|
||||
|
||||
### 影响的业务场景
|
||||
|
||||
- ✅ **新增上传**:正常工作(文件可以上传)
|
||||
- ❌ **回显展示**:文件类型识别错误,图片无法正常显示
|
||||
- ❌ **下载功能**:下载的文件名显示为UUID,用户体验差
|
||||
- ❌ **文件管理**:管理员无法识别文件内容
|
||||
|
||||
---
|
||||
|
||||
## 复现步骤
|
||||
|
||||
1. 在移动端进入"宿舍卫生检查"模块
|
||||
2. 点击"录入违纪",上传一张图片(如 `photo.jpg`)
|
||||
3. 保存违纪记录
|
||||
4. 刷新页面或重新进入
|
||||
5. 点击"编辑"查看违纪记录
|
||||
6. **问题**:附件无法显示为图片,文件名显示为UUID
|
||||
|
||||
---
|
||||
|
||||
## 调试信息
|
||||
|
||||
### 前端上传请求
|
||||
|
||||
```javascript
|
||||
// 调用上传接口
|
||||
POST /resource/oss/upload
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
// FormData:
|
||||
file: [Blob] photo.jpg
|
||||
bizType: "dorm_violation_attachment"
|
||||
```
|
||||
|
||||
### 后端返回(上传成功)
|
||||
|
||||
```javascript
|
||||
{
|
||||
code: 200,
|
||||
data: {
|
||||
ossId: "1981738218223079425",
|
||||
url: "/resource/oss/download/1981738218223079425",
|
||||
fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139" // ⚠️ 缺少扩展名
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 后端返回(回显查询)
|
||||
|
||||
```javascript
|
||||
// GET /resource/oss/listByIds/1981738218223079425
|
||||
{
|
||||
code: 200,
|
||||
data: [{
|
||||
ossId: "1981738218223079425",
|
||||
originalName: "12038a7c-48bd-4978-bcbb-1119a201a6e9", // ❌ UUID
|
||||
fileSuffix: "9", // ❌ 错误
|
||||
fileName: "2025/10/24/aa991fe4d6114995a0562d783cea56139",
|
||||
url: "/resource/oss/download/1981738218223079425"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 建议的修复方案
|
||||
|
||||
### 方案1:修复上传接口(推荐)⭐
|
||||
|
||||
**修改文件**:`backend/src/main/java/com/ruoyi/resource/controller/SysOssController.java`
|
||||
|
||||
**修复点**:
|
||||
1. 上传时正确提取并保存 `originalName`(用户上传的文件名)
|
||||
2. 正确提取文件扩展名保存到 `fileSuffix`
|
||||
3. 保存到OSS的文件名应包含扩展名
|
||||
|
||||
```java
|
||||
@PostMapping("/upload")
|
||||
public R<SysOss> upload(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(required = false) String bizType) {
|
||||
// 获取原始文件名
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
|
||||
// 提取真实扩展名
|
||||
String extension = "";
|
||||
if (originalFilename != null && originalFilename.contains(".")) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
// 生成OSS文件名(包含扩展名)
|
||||
String ossFileName = generateOssFileName() + "." + extension;
|
||||
|
||||
// 保存到数据库
|
||||
SysOss oss = new SysOss();
|
||||
oss.setOriginalName(originalFilename); // ✅ 保存真实文件名
|
||||
oss.setFileSuffix(extension); // ✅ 保存真实扩展名
|
||||
oss.setFileName(ossFileName); // ✅ 包含扩展名的OSS文件名
|
||||
|
||||
// ... 其他逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 方案2:修复查询接口
|
||||
|
||||
**修改文件**:`backend/src/main/java/com/ruoyi/resource/service/impl/SysOssServiceImpl.java`
|
||||
|
||||
**修复点**:
|
||||
1. 从数据库正确读取 `originalName` 和 `fileSuffix`
|
||||
2. 确保返回的VO类包含正确的字段值
|
||||
|
||||
```java
|
||||
public List<SysOssVo> listByIds(String ossIds) {
|
||||
// ... 查询逻辑 ...
|
||||
|
||||
List<SysOssVo> voList = new ArrayList<>();
|
||||
for (SysOss oss : ossList) {
|
||||
SysOssVo vo = new SysOssVo();
|
||||
vo.setOssId(oss.getOssId());
|
||||
vo.setOriginalName(oss.getOriginalName()); // ✅ 确保读取正确
|
||||
vo.setFileSuffix(oss.getFileSuffix()); // ✅ 确保读取正确
|
||||
vo.setFileName(oss.getFileName());
|
||||
vo.setUrl(oss.getUrl());
|
||||
voList.add(vo);
|
||||
}
|
||||
|
||||
return voList;
|
||||
}
|
||||
```
|
||||
|
||||
### 方案3:数据库表结构检查
|
||||
|
||||
**检查表**:`sys_oss`
|
||||
|
||||
**需要确认的字段**:
|
||||
```sql
|
||||
-- 检查字段定义
|
||||
SHOW COLUMNS FROM sys_oss LIKE '%name%';
|
||||
SHOW COLUMNS FROM sys_oss LIKE '%suffix%';
|
||||
|
||||
-- 检查数据示例
|
||||
SELECT oss_id, original_name, file_suffix, file_name
|
||||
FROM sys_oss
|
||||
WHERE oss_id = '1981738218223079425';
|
||||
```
|
||||
|
||||
**期望的字段定义**:
|
||||
- `original_name` VARCHAR(255) - 用户上传的原始文件名
|
||||
- `file_suffix` VARCHAR(10) - 文件扩展名(jpg/png/pdf等)
|
||||
- `file_name` VARCHAR(500) - OSS中的文件路径(应包含扩展名)
|
||||
|
||||
---
|
||||
|
||||
## 临时解决方案(前端兜底)
|
||||
|
||||
**已实施**:在 `ViolationRecordDialog.vue` 中添加了兜底逻辑
|
||||
|
||||
```javascript
|
||||
// 检测 fileSuffix 是否为有效扩展名
|
||||
if (item.fileSuffix && item.fileSuffix.length <= 5 && /^[a-zA-Z]+$/.test(item.fileSuffix)) {
|
||||
// 使用 fileSuffix
|
||||
realExtension = item.fileSuffix.toLowerCase();
|
||||
fileType = this.getFileTypeBySuffix(realExtension);
|
||||
} else {
|
||||
// 默认按图片处理(违纪证明材料大概率是图片)
|
||||
realExtension = 'jpg';
|
||||
fullFileName = `违纪证明_${timestamp + index}.jpg`;
|
||||
fileType = 'image/jpeg';
|
||||
}
|
||||
```
|
||||
|
||||
**局限性**:
|
||||
- ✅ 可以临时解决图片回显问题
|
||||
- ❌ 无法恢复真实的文件名
|
||||
- ❌ 非图片文件可能被错误识别
|
||||
- ❌ 用户体验不佳
|
||||
|
||||
---
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 后端修复后的验证
|
||||
|
||||
**步骤**:
|
||||
1. 上传一张图片 `test.jpg`
|
||||
2. 调用 `/resource/oss/listByIds/{ossId}` 查询
|
||||
3. 检查返回数据:
|
||||
- `originalName` 应该是 `test.jpg`
|
||||
- `fileSuffix` 应该是 `jpg`
|
||||
- `fileName` 应该包含 `.jpg` 扩展名
|
||||
|
||||
**期望日志**:
|
||||
```
|
||||
违纪记录:原始数据 - originalName=test.jpg, fileSuffix=jpg, fileName=2025/10/24/xxx.jpg
|
||||
违纪记录:最终文件名=test.jpg, 扩展名=jpg, 文件类型=image/jpeg
|
||||
```
|
||||
|
||||
### 2. 前端验证
|
||||
|
||||
**步骤**:
|
||||
1. 上传图片后编辑违纪记录
|
||||
2. 检查附件回显是否正常
|
||||
3. 检查文件名是否正确显示
|
||||
|
||||
**期望结果**:
|
||||
- ✅ 图片正常显示(不是灰白背景)
|
||||
- ✅ 文件名显示为真实文件名(不是UUID)
|
||||
- ✅ 下载文件名正确
|
||||
|
||||
---
|
||||
|
||||
## 相关文件
|
||||
|
||||
**后端**:
|
||||
- `backend/src/main/java/com/ruoyi/resource/controller/SysOssController.java`
|
||||
- `backend/src/main/java/com/ruoyi/resource/service/impl/SysOssServiceImpl.java`
|
||||
- `backend/src/main/java/com/ruoyi/resource/domain/vo/SysOssVo.java`
|
||||
- `backend/src/main/java/com/ruoyi/resource/domain/SysOss.java`
|
||||
|
||||
**前端**:
|
||||
- `mobile/src/pages/xgss/wsjc/components/ViolationRecordDialog.vue`(已添加兜底逻辑)
|
||||
|
||||
**数据库**:
|
||||
- `sys_oss` 表
|
||||
|
||||
---
|
||||
|
||||
## 优先级和影响
|
||||
|
||||
**优先级**: P2(中等)
|
||||
|
||||
**影响评估**:
|
||||
- **用户体验**: ⬇️ 下降(文件名显示为UUID,无法识别内容)
|
||||
- **功能可用性**: ✅ 正常(有前端兜底,基本功能可用)
|
||||
- **数据完整性**: ⚠️ 受影响(历史数据无法恢复真实文件名)
|
||||
|
||||
**建议处理时间**: 2周内修复
|
||||
|
||||
---
|
||||
|
||||
**创建时间**: 2025-01-24
|
||||
**创建人**: Cursor AI
|
||||
**状态**: 🔴 待修复
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"@vue/shared": "^3.0.0",
|
||||
"core-js": "^3.8.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"flyio": "^0.6.2",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"uview-ui": "2.0.38",
|
||||
|
||||
@@ -150,6 +150,41 @@ page {
|
||||
border-color: #007dfb !important;
|
||||
}
|
||||
|
||||
/* Tabs组件主题色覆盖 - 全局统一底部方块颜色 */
|
||||
.u-tabs__wrapper__nav__line {
|
||||
background-color: #0051d9 !important;
|
||||
}
|
||||
|
||||
.u-tabs .u-tabs__wrapper__nav__line {
|
||||
background: #0051d9 !important;
|
||||
}
|
||||
|
||||
/* Navbar导航栏主题色覆盖 - 勤工俭学模块统一背景色 */
|
||||
.u-navbar {
|
||||
background-color: #0051d9 !important;
|
||||
}
|
||||
|
||||
.u-navbar__content {
|
||||
background-color: #0051d9 !important;
|
||||
}
|
||||
|
||||
.u-navbar__content__title {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* uni-app 原生导航栏样式覆盖 */
|
||||
uni-page-head {
|
||||
background-color: #0051d9 !important;
|
||||
}
|
||||
|
||||
.uni-page-head {
|
||||
background-color: #0051d9 !important;
|
||||
}
|
||||
|
||||
.uni-page-head__title {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ========== 全局温暖商务风格样式 ========== */
|
||||
|
||||
/* 全局页面背景 */
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 字典数据API - 重新导出common/dict.js中的方法以保持兼容性
|
||||
import {
|
||||
getDicts,
|
||||
getDict,
|
||||
useDict,
|
||||
getDictLabel,
|
||||
getDictValue,
|
||||
hasDictValue,
|
||||
refreshDict,
|
||||
getDictTagType,
|
||||
} from '@/common/dict.js';
|
||||
|
||||
// 重新导出所有字典相关方法
|
||||
export {
|
||||
getDicts,
|
||||
getDict,
|
||||
useDict,
|
||||
getDictLabel,
|
||||
getDictValue,
|
||||
hasDictValue,
|
||||
refreshDict,
|
||||
getDictTagType,
|
||||
};
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
getDicts,
|
||||
getDict,
|
||||
useDict,
|
||||
getDictLabel,
|
||||
getDictValue,
|
||||
hasDictValue,
|
||||
refreshDict,
|
||||
getDictTagType,
|
||||
};
|
||||
@@ -1,200 +0,0 @@
|
||||
# 移动端字典工具使用指南
|
||||
|
||||
## 📋 简介
|
||||
|
||||
为移动端(Vue 2.6.14 + uni-app 2.x)提供完整的字典数据获取和管理功能,与web端保持一致的API设计。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 基础导入
|
||||
```javascript
|
||||
import { useDict, getDict, getDictLabel } from '@/common/dict.js'
|
||||
```
|
||||
|
||||
### 2. 在页面中使用
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dictionaries: {},
|
||||
userInfo: {
|
||||
gender: '0',
|
||||
status: '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadDictionaries()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadDictionaries() {
|
||||
// 批量加载字典
|
||||
const dictManager = useDict('sys_user_sex', 'sys_normal_disable')
|
||||
|
||||
// Vue 2响应式更新(必须设置)
|
||||
dictManager.setUpdateCallback((dictType, data) => {
|
||||
this.$set(this.dictionaries, dictType, data)
|
||||
})
|
||||
},
|
||||
|
||||
// 获取字典标签显示
|
||||
getDictText(dictType, value) {
|
||||
const dictData = this.dictionaries[dictType] || []
|
||||
return getDictLabel(dictData, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 在模板中显示
|
||||
```vue
|
||||
<template>
|
||||
<view>
|
||||
<!-- 显示用户性别 -->
|
||||
<text>性别: {{ getDictText('sys_user_sex', userInfo.gender) }}</text>
|
||||
|
||||
<!-- 显示用户状态 -->
|
||||
<text>状态: {{ getDictText('sys_normal_disable', userInfo.status) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 📚 主要功能
|
||||
|
||||
| 功能 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| 批量字典管理 | `useDict(...types)` | 推荐使用,支持多字典并发加载 |
|
||||
| 单个字典获取 | `getDict(type, cache?)` | 获取单个字典数据 |
|
||||
| 字典标签获取 | `getDictLabel(data, value)` | 根据值获取标签 |
|
||||
| 字典值获取 | `getDictValue(data, label)` | 根据标签获取值 |
|
||||
| 值存在检查 | `hasDictValue(data, value)` | 检查值是否存在 |
|
||||
| 缓存刷新 | `refreshDict(types)` | 刷新指定字典缓存 |
|
||||
|
||||
## 🎯 使用场景
|
||||
|
||||
### 场景1: uview组件集成
|
||||
```javascript
|
||||
// radio-group
|
||||
<u-radio-group v-model="form.gender">
|
||||
<u-radio
|
||||
v-for="item in dictionaries.sys_user_sex"
|
||||
:key="item.value"
|
||||
:name="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</u-radio>
|
||||
</u-radio-group>
|
||||
|
||||
// select组件
|
||||
computed: {
|
||||
selectOptions() {
|
||||
return (this.dictionaries.sys_user_sex || []).map(item => ({
|
||||
value: item.value,
|
||||
label: item.label
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 场景2: 表格数据转换
|
||||
```javascript
|
||||
computed: {
|
||||
processedData() {
|
||||
return this.tableData.map(row => ({
|
||||
...row,
|
||||
genderText: getDictLabel(this.dictionaries.sys_user_sex || [], row.gender),
|
||||
statusText: getDictLabel(this.dictionaries.sys_normal_disable || [], row.status)
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 场景3: 单独获取字典
|
||||
```javascript
|
||||
async methods: {
|
||||
async loadSingleDict() {
|
||||
try {
|
||||
const genderDict = await getDict('sys_user_sex')
|
||||
console.log('性别字典:', genderDict)
|
||||
} catch (error) {
|
||||
console.error('加载失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ⚡ 性能特性
|
||||
|
||||
- **智能缓存**: 24小时有效期,自动管理
|
||||
- **并发加载**: 多字典同时请求,提升加载速度
|
||||
- **响应式更新**: 完美适配Vue 2响应式系统
|
||||
- **错误处理**: 完善的错误处理和兜底机制
|
||||
|
||||
## 🔧 高级用法
|
||||
|
||||
### 缓存管理
|
||||
```javascript
|
||||
import { dictCache, refreshDict } from '@/common/dict.js'
|
||||
|
||||
// 清除指定缓存
|
||||
dictCache.remove('sys_user_sex')
|
||||
|
||||
// 清除所有缓存
|
||||
dictCache.clear()
|
||||
|
||||
// 刷新缓存
|
||||
await refreshDict(['sys_user_sex', 'sys_normal_disable'])
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
```javascript
|
||||
// 安全的字典获取(带默认值)
|
||||
safeDictLabel(dictType, value, defaultLabel = '-') {
|
||||
try {
|
||||
const dictData = this.dictionaries[dictType] || []
|
||||
const label = getDictLabel(dictData, value)
|
||||
return label || defaultLabel
|
||||
} catch (error) {
|
||||
console.error(`获取字典失败 ${dictType}:${value}`, error)
|
||||
return defaultLabel
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📖 相关文件
|
||||
|
||||
- **核心实现**: `/mobile/src/common/dict.js`
|
||||
- **使用示例**: `/mobile/src/common/dict-usage-example.js`
|
||||
- **变更记录**: `/docs/变更管理/code-adjust-record-2025-01-27-移动端字典工具实现.md`
|
||||
|
||||
## ⚠️ 重要提醒
|
||||
|
||||
### Vue 2必须使用回调机制
|
||||
```javascript ❌
|
||||
// 错误:直接赋值不会触发响应式更新
|
||||
this.dictionaries = dictManager.data
|
||||
|
||||
// 正确:使用回调确保响应式更新 ✅
|
||||
dictManager.setUpdateCallback((dictType, data) => {
|
||||
this.$set(this.dictionaries, dictType, data)
|
||||
})
|
||||
```
|
||||
|
||||
### 兼容性
|
||||
- ✅ Vue 2.6.14+
|
||||
- ✅ uni-app 2.x
|
||||
- ✅ H5 / 小程序 / App
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
1. **优先使用useDict**: 支持并发加载,性能更好
|
||||
2. **合理使用缓存**: 频繁变动的字典设置`useCache=false`
|
||||
3. **添加错误处理**: 关键业务场景要有异常处理
|
||||
4. **使用callback机制**: Vue 2中必须使用setUpdateCallback
|
||||
|
||||
---
|
||||
✨ **完成时间**: 2025-01-27 20:15:00
|
||||
👨💻 **开发者**: wuzilong
|
||||
📱 **适用版本**: Vue 2.6.14 + uni-app 2.x
|
||||
@@ -12,6 +12,7 @@ export const ENCRYPT_CONFIG = {
|
||||
TENANT_ID: '000000',
|
||||
// 加密请求头
|
||||
ENCRYPT_HEADER: 'encrypt-key',
|
||||
//TODO 开发环境需要修改为false
|
||||
// 是否启用加密
|
||||
ENCRYPT_ENABLED: true,
|
||||
};
|
||||
@@ -36,8 +37,9 @@ export const REQUEST_CONFIG = {
|
||||
? 'http://localhost/login?redirect=/index'
|
||||
: 'https://hzdyjs.camasort.com/login?redirect=/index', // 生产环境前端域名
|
||||
|
||||
//TODO 生产环境需要修改为true
|
||||
// 是否启用加密
|
||||
encryptEnabled: true,
|
||||
encryptEnabled: false,
|
||||
// 防重复提交间隔时间(ms)
|
||||
repeatSubmitInterval: 500,
|
||||
};
|
||||
|
||||
@@ -73,18 +73,36 @@
|
||||
<!-- 办理人信息 -->
|
||||
<view class="approver-section">
|
||||
<text class="section-label">办理人:</text>
|
||||
<view class="approver-tags">
|
||||
<template v-if="record.approveName">
|
||||
<view class="approver-info-list">
|
||||
<template
|
||||
v-if="record.approverList && record.approverList.length > 0"
|
||||
>
|
||||
<view
|
||||
v-for="(approver, approverIndex) in record.approverList"
|
||||
:key="approverIndex"
|
||||
class="approver-info-item"
|
||||
>
|
||||
<u-tag
|
||||
v-for="(name, nameIndex) in record.approveName.split(
|
||||
',',
|
||||
)"
|
||||
:key="nameIndex"
|
||||
:text="name.trim()"
|
||||
:text="approver.name"
|
||||
:type="getApproverType()"
|
||||
size="mini"
|
||||
class="approver-tag"
|
||||
/>
|
||||
<view
|
||||
v-if="approver.phone"
|
||||
class="approver-phone-wrapper"
|
||||
@click="handleCallPhone(approver.phone)"
|
||||
>
|
||||
<u-icon
|
||||
name="phone-fill"
|
||||
size="28"
|
||||
color="#1989fa"
|
||||
/>
|
||||
<text class="approver-phone">
|
||||
{{ approver.phone }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<u-tag
|
||||
v-else
|
||||
@@ -253,10 +271,43 @@ export default {
|
||||
console.log('实例ID:', this.insId);
|
||||
console.log('记录数量:', this.historyList.length);
|
||||
|
||||
// 处理附件信息(与web端保持一致)
|
||||
// 处理附件信息和审批人信息(与web端保持一致)
|
||||
if (this.historyList.length > 0) {
|
||||
console.log('开始处理附件信息...');
|
||||
console.log('开始处理附件信息和审批人信息...');
|
||||
|
||||
// 收集所有审批人ID
|
||||
const allApproverIds = new Set();
|
||||
for (const item of this.historyList) {
|
||||
if (item.approver) {
|
||||
// approver字段可能包含多个ID,用逗号分隔
|
||||
const ids = item.approver
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
ids.forEach((id) => allApproverIds.add(id));
|
||||
}
|
||||
}
|
||||
|
||||
// 批量查询审批人信息(包含手机号码)
|
||||
let userInfoMap = {};
|
||||
if (allApproverIds.size > 0) {
|
||||
console.log(
|
||||
'开始批量查询审批人信息,ID列表:',
|
||||
Array.from(allApproverIds),
|
||||
);
|
||||
try {
|
||||
userInfoMap = await this.getUserInfoByIds(
|
||||
Array.from(allApproverIds),
|
||||
);
|
||||
console.log('审批人信息查询完成:', userInfoMap);
|
||||
} catch (error) {
|
||||
console.error('批量查询审批人信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理每条审批记录
|
||||
for (const item of this.historyList) {
|
||||
// 处理附件信息
|
||||
if (item.ext) {
|
||||
try {
|
||||
const attachmentRes = await this.getAttachmentsByIds(item.ext);
|
||||
@@ -268,8 +319,31 @@ export default {
|
||||
} else {
|
||||
item.attachmentList = [];
|
||||
}
|
||||
|
||||
// 处理审批人信息(包含姓名和手机号)
|
||||
if (item.approver && item.approveName) {
|
||||
const approverIds = item.approver
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
const approverNames = item.approveName
|
||||
.split(',')
|
||||
.map((name) => name.trim())
|
||||
.filter((name) => name);
|
||||
|
||||
item.approverList = approverIds.map((id, index) => {
|
||||
const userInfo = userInfoMap[id];
|
||||
return {
|
||||
id: id,
|
||||
name: approverNames[index] || '未知',
|
||||
phone: userInfo ? userInfo.phonenumber : null,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
item.approverList = [];
|
||||
}
|
||||
console.log('附件信息处理完成');
|
||||
}
|
||||
console.log('附件信息和审批人信息处理完成');
|
||||
}
|
||||
} else {
|
||||
console.error('API响应异常:', response);
|
||||
@@ -300,6 +374,67 @@ export default {
|
||||
return response;
|
||||
},
|
||||
|
||||
// 批量查询用户信息(包含手机号码)
|
||||
async getUserInfoByIds(userIds) {
|
||||
try {
|
||||
const response = await request({
|
||||
url: '/system/user/optionselect',
|
||||
method: 'GET',
|
||||
params: {
|
||||
userIds: userIds.join(','),
|
||||
},
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
// 将数组转换为以userId为key的Map,方便后续查找
|
||||
const userMap = {};
|
||||
response.data.forEach((user) => {
|
||||
userMap[user.userId] = user;
|
||||
});
|
||||
return userMap;
|
||||
} else {
|
||||
console.error('查询用户信息失败:', response.msg);
|
||||
return {};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('批量查询用户信息异常:', error);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
// 拨打电话
|
||||
handleCallPhone(phoneNumber) {
|
||||
if (!phoneNumber) {
|
||||
uni.showToast({
|
||||
title: '手机号码为空',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '拨打电话',
|
||||
content: `确定要拨打 ${phoneNumber} 吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phoneNumber,
|
||||
success: () => {
|
||||
console.log('拨打电话成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('拨打电话失败:', err);
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 获取状态样式类(使用字典方式)
|
||||
getStatusClass(flowStatus) {
|
||||
console.log('flowStatus:', flowStatus);
|
||||
@@ -670,16 +805,44 @@ export default {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.approver-tags {
|
||||
.approver-info-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.approver-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.approver-tag {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.approver-phone-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 8rpx;
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.approver-phone-wrapper:active {
|
||||
background-color: #e6f7ff;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.approver-phone {
|
||||
font-size: 26rpx;
|
||||
color: #1989fa;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
|
||||
+35
-4
@@ -200,6 +200,13 @@
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/wsjc/wsjcMainAdd",
|
||||
"style": {
|
||||
"navigationBarTitleText": "宿舍卫生检查",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xgss/xssswsjc/xssswsjc",
|
||||
"style": {
|
||||
@@ -239,6 +246,22 @@
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xskh/wjjlEntry/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "违纪记录录入",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xskh/wjjlEntry/disciplineRecordForm",
|
||||
"style": {
|
||||
"navigationBarTitleText": "违纪记录",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xskh/khsq/khsq",
|
||||
"style": {
|
||||
@@ -267,7 +290,7 @@
|
||||
"path": "pages/workStudy/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "勤工俭学",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
@@ -275,7 +298,7 @@
|
||||
"path": "pages/workStudy/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "岗位详情",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
@@ -283,7 +306,7 @@
|
||||
"path": "pages/workStudy/apply",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请岗位",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
@@ -291,7 +314,7 @@
|
||||
"path": "pages/workStudy/applicationDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "申请详情",
|
||||
"navigationBarBackgroundColor": "#1890ff",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
@@ -326,6 +349,14 @@
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/xg/studentDynamics/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "学生动向信息",
|
||||
"navigationBarBackgroundColor": "#0051d9",
|
||||
"navigationBarTextStyle": "white"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
<ServiceItem
|
||||
<!-- <ServiceItem
|
||||
v-if="hasPermi(['student:dormviolation:listapp'])"
|
||||
title="宿舍违纪"
|
||||
@click="gotoDormViolation"
|
||||
@@ -193,7 +193,7 @@
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
</ServiceItem> -->
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:dormhygiene:listapp'])"
|
||||
title="宿舍卫生检查"
|
||||
@@ -221,6 +221,20 @@
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:dynamics:list'])"
|
||||
title="学生动向信息(宿管)"
|
||||
@click="gotoStudentDynamics"
|
||||
>
|
||||
<template #icon>
|
||||
<image
|
||||
src="/static/0011.png"
|
||||
mode="aspectFit"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:workstudy:list'])"
|
||||
title="勤工俭学"
|
||||
@@ -249,6 +263,20 @@
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:violation:entry'])"
|
||||
title="违纪记录录入"
|
||||
@click="gotoViolationEntry"
|
||||
>
|
||||
<template #icon>
|
||||
<image
|
||||
src="/static/00125.png"
|
||||
mode="aspectFit"
|
||||
style="width: 24px; height: 24px"
|
||||
/>
|
||||
</template>
|
||||
</ServiceItem>
|
||||
|
||||
<ServiceItem
|
||||
v-if="hasPermi(['student:assessment:applyapp'])"
|
||||
title="考核申请"
|
||||
@@ -719,11 +747,13 @@ export default {
|
||||
this.hasPermi(['student:counsel:list']) ||
|
||||
this.hasPermi(['student:info:editapp']) ||
|
||||
this.hasPermi(['student:internship:list']) ||
|
||||
this.hasPermi(['student:dormviolation:listapp']) ||
|
||||
// this.hasPermi(['student:dormviolation:listapp']) ||
|
||||
this.hasPermi(['student:visit:list']) ||
|
||||
this.hasPermi(['student:dormhygiene:listapp']) ||
|
||||
this.hasPermi(['student:dynamics:list']) ||
|
||||
this.hasPermi(['student:workstudy:list']) ||
|
||||
this.hasPermi(['student:violation:listapp']) ||
|
||||
this.hasPermi(['student:violation:entry']) ||
|
||||
this.hasPermi(['student:assessment:applyapp']) ||
|
||||
this.hasPermi(['student:counsel:reservation'])
|
||||
);
|
||||
@@ -974,6 +1004,13 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到违纪记录录入页面
|
||||
gotoViolationEntry() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xskh/wjjlEntry/index',
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到考核申请页面
|
||||
gotoAssessmentApply() {
|
||||
uni.navigateTo({
|
||||
@@ -988,6 +1025,13 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到学生动向信息(宿管)页面
|
||||
gotoStudentDynamics() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xg/studentDynamics/index',
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到修改密码页面
|
||||
gotoResetPassword() {
|
||||
uni.navigateTo({
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="internship-page">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="实习打卡" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 顶部用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="user-info">
|
||||
@@ -23,9 +30,11 @@
|
||||
<!-- 实习信息卡片 -->
|
||||
<view class="internship-card" v-if="internshipInfo">
|
||||
<view class="card-header">
|
||||
<u-icon name="bookmark" size="18" color="#409eff"></u-icon>
|
||||
<view class="header-left">
|
||||
<u-icon name="file-text" size="18" color="#409eff"></u-icon>
|
||||
<text class="card-title">实习信息</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="internship-info">
|
||||
<view class="info-row">
|
||||
<text class="label">实习单位:</text>
|
||||
@@ -59,9 +68,11 @@
|
||||
|
||||
<!-- 今日签到卡片 -->
|
||||
<view class="checkin-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="clock" size="18" color="#67c23a"></u-icon>
|
||||
<view class="card-header card-header-between">
|
||||
<view class="header-left">
|
||||
<u-icon name="checkmark-circle" size="18" color="#67c23a"></u-icon>
|
||||
<text class="card-title">今日签到</text>
|
||||
</view>
|
||||
<text class="date-text">{{ currentDate }}</text>
|
||||
</view>
|
||||
|
||||
@@ -85,14 +96,20 @@
|
||||
<view class="checkin-actions" v-else>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="large"
|
||||
shape="round"
|
||||
:loading="checkinLoading"
|
||||
:disabled="checkinLoading || !canCheckin"
|
||||
@click="handleCheckin"
|
||||
:custom-style="checkinButtonStyle"
|
||||
class="checkin-btn"
|
||||
:custom-style="{ height: '88rpx' }"
|
||||
>
|
||||
<u-icon name="map" size="20" color="#fff" style="margin-right: 8px"></u-icon>
|
||||
{{ checkinLoading ? '签到中...' : '立即签到' }}
|
||||
<u-icon
|
||||
name="checkmark-circle"
|
||||
size="20"
|
||||
color="#fff"
|
||||
style="margin-right: 8rpx"
|
||||
></u-icon>
|
||||
<text style="color: #fff">{{ checkinLoading ? '签到中...' : '立即签到' }}</text>
|
||||
</u-button>
|
||||
|
||||
<view class="checkin-tips" v-if="!canCheckin">
|
||||
@@ -106,6 +123,7 @@
|
||||
<view class="location-card">
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<u-icon name="map" size="18" color="#f56c6c"></u-icon>
|
||||
<text class="card-title">签到位置</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -114,14 +132,14 @@
|
||||
<view class="location-row">
|
||||
<u-button
|
||||
type="primary"
|
||||
size="default"
|
||||
plain
|
||||
shape="round"
|
||||
@click="handleGetLocation"
|
||||
:loading="locationLoading"
|
||||
class="location-btn"
|
||||
:custom-style="{ height: '88rpx' }"
|
||||
>
|
||||
<u-icon name="map-fill" size="20" color="#2979ff" />
|
||||
<text style="margin-left: 8rpx">
|
||||
<u-icon name="map-fill" size="20" color="#fff" />
|
||||
<text style="margin-left: 8rpx; color: #fff">
|
||||
{{ checkIsWechatBrowser() ? '微信定位' : '定位' }}
|
||||
</text>
|
||||
</u-button>
|
||||
@@ -129,13 +147,23 @@
|
||||
|
||||
<!-- 位置信息显示 -->
|
||||
<view v-if="currentLocation" class="location-info-section">
|
||||
<!-- 坐标信息 -->
|
||||
<view class="location-coords-container">
|
||||
<!-- 坐标信息和查看位置按钮 -->
|
||||
<view class="location-coords-row">
|
||||
<text class="location-coords">
|
||||
经纬度:{{ currentLocation.latitude.toFixed(6) }},{{
|
||||
currentLocation.longitude.toFixed(6)
|
||||
}}
|
||||
</text>
|
||||
<u-button
|
||||
size="small"
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="openLocationInMap"
|
||||
class="view-map-btn"
|
||||
>
|
||||
<u-icon name="eye" size="14" color="#fff" />
|
||||
<text style="margin-left: 4rpx; color: #fff">查看位置</text>
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 精度信息 -->
|
||||
@@ -158,20 +186,6 @@
|
||||
></u-tag>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 查看位置按钮 -->
|
||||
<view class="location-action-container">
|
||||
<u-button
|
||||
size="default"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openLocationInMap"
|
||||
class="view-map-btn"
|
||||
>
|
||||
<u-icon name="eye" size="16" color="#2979ff" />
|
||||
<text>查看位置</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -179,7 +193,7 @@
|
||||
<view class="location-error-card" v-if="locationError && !currentLocation">
|
||||
<view class="card-header">
|
||||
<view class="header-left">
|
||||
<u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
|
||||
<u-icon name="map" size="18" color="#f56c6c"></u-icon>
|
||||
<text class="card-title">位置信息</text>
|
||||
</view>
|
||||
<u-button
|
||||
@@ -243,9 +257,11 @@
|
||||
<!-- 考勤统计 -->
|
||||
<view class="statistics-card">
|
||||
<view class="card-header">
|
||||
<u-icon size="18" color="#e6a23c"></u-icon>
|
||||
<view class="header-left">
|
||||
<u-icon name="calendar" size="18" color="#e6a23c"></u-icon>
|
||||
<text class="card-title">本月考勤</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stats-grid">
|
||||
<view class="stat-item">
|
||||
<text class="stat-number">{{ monthStats.totalDays || 0 }}</text>
|
||||
@@ -354,21 +370,15 @@ export default {
|
||||
|
||||
// 当前日期
|
||||
currentDate: '',
|
||||
|
||||
// 样式
|
||||
checkinButtonStyle: {
|
||||
width: '100%',
|
||||
height: '50px',
|
||||
borderRadius: '25px',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
border: 'none',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// === 微信SDK相关方法 ===
|
||||
|
||||
// 检查微信环境并自动初始化SDK
|
||||
@@ -1435,18 +1445,17 @@ export default {
|
||||
<style scoped>
|
||||
.internship-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fa;
|
||||
padding: 16px;
|
||||
background-color: #f5f6fa;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
margin-bottom: 16px;
|
||||
color: white;
|
||||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@@ -1455,7 +1464,7 @@ export default {
|
||||
}
|
||||
|
||||
.avatar-section {
|
||||
margin-right: 16px;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.info-section {
|
||||
@@ -1463,15 +1472,16 @@ export default {
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 18px;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 2px;
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
/* 通用卡片样式 */
|
||||
@@ -1480,216 +1490,222 @@ export default {
|
||||
.location-card,
|
||||
.statistics-card,
|
||||
.records-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin: 0 20rpx 20rpx 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
min-height: 32px; /* 确保最小高度 */
|
||||
gap: 8px; /* 确保元素之间有间距 */
|
||||
min-height: 48rpx;
|
||||
}
|
||||
|
||||
.card-header-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0; /* 允许flex子项收缩 */
|
||||
margin-right: 12px; /* 与右侧按钮保持距离 */
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
margin-left: 0;
|
||||
color: #303133;
|
||||
white-space: nowrap; /* 防止文字换行 */
|
||||
overflow: hidden; /* 隐藏溢出文字 */
|
||||
text-overflow: ellipsis; /* 显示省略号 */
|
||||
flex-shrink: 1; /* 允许收缩 */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
flex-shrink: 0;
|
||||
min-width: 70px; /* 确保按钮最小宽度 */
|
||||
min-width: 100rpx;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 实习信息 */
|
||||
.internship-info {
|
||||
padding-top: 8px;
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
width: 80px;
|
||||
min-width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 签到状态 */
|
||||
.checkin-status {
|
||||
padding: 16px;
|
||||
padding: 24rpx;
|
||||
background: #f0f9ff;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #67c23a;
|
||||
border-radius: 12rpx;
|
||||
border-left: 6rpx solid #67c23a;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 16px;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #67c23a;
|
||||
margin-left: 8px;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.time-info,
|
||||
.location-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.time-label,
|
||||
.location-label {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
margin-right: 8px;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.time-value,
|
||||
.location-value {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 签到操作 */
|
||||
.checkin-actions {
|
||||
padding: 16px 0;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.checkin-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkin-tips {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.tips-text {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
margin-left: 4px;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 位置信息(参考请假页面样式) */
|
||||
/* 位置信息 */
|
||||
.location-row {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.location-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #2979ff;
|
||||
background: #fff;
|
||||
color: #2979ff;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.location-info-section {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
margin-top: 24rpx;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-radius: 12rpx;
|
||||
border: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.location-coords-container {
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.location-coords-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.location-coords {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
font-family: monospace;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.location-action-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.view-map-btn {
|
||||
min-width: 120px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 56rpx !important;
|
||||
min-width: auto !important;
|
||||
width: auto !important;
|
||||
flex-shrink: 0;
|
||||
padding: 0 20rpx !important;
|
||||
}
|
||||
|
||||
/* 精度信息 */
|
||||
.accuracy-section {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.accuracy-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
padding: 16rpx 20rpx;
|
||||
background: #f0f9ff;
|
||||
border-radius: 8px;
|
||||
border-radius: 12rpx;
|
||||
border: 1px solid #b3e5fc;
|
||||
}
|
||||
|
||||
.accuracy-icon {
|
||||
margin-right: 8px;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.accuracy-text {
|
||||
font-size: 13px;
|
||||
font-size: 26rpx;
|
||||
color: #67c23a;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.accuracy-tag {
|
||||
margin-left: 8px;
|
||||
margin-left: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -1697,41 +1713,37 @@ export default {
|
||||
.location-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 4px; /* 与上方内容保持间距 */
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.map-btn {
|
||||
min-width: 140px;
|
||||
width: 100%; /* 在小屏幕上占满宽度 */
|
||||
max-width: 200px; /* 最大宽度限制 */
|
||||
min-width: 200rpx;
|
||||
width: 100%;
|
||||
max-width: 300rpx;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
margin-left: 6px;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* 小屏幕适配 */
|
||||
@media screen and (max-width: 375px) {
|
||||
.card-header {
|
||||
flex-wrap: nowrap; /* 保持单行,避免换行 */
|
||||
gap: 4px;
|
||||
min-height: 36px; /* 稍微增加高度 */
|
||||
flex-wrap: nowrap;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
min-width: 0; /* 允许完全收缩 */
|
||||
flex: 1;
|
||||
margin-right: 6px;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 15px; /* 稍微减小字体 */
|
||||
max-width: 100px; /* 限制最大宽度 */
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
min-width: 58px; /* 小屏幕下减小按钮宽度 */
|
||||
font-size: 12px; /* 减小按钮字体 */
|
||||
min-width: 58px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.map-btn {
|
||||
@@ -1742,17 +1754,12 @@ export default {
|
||||
|
||||
/* 极小屏幕适配(iPhone SE等) */
|
||||
@media screen and (max-width: 320px) {
|
||||
.card-header {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
margin-right: 4px;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
max-width: 80px;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
@@ -1783,55 +1790,55 @@ export default {
|
||||
.location-error-card {
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin: 0 20rpx 20rpx 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.error-content {
|
||||
padding-top: 8px;
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.error-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
margin-right: 8px;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 16px;
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #faad14;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 30rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.error-tips-section {
|
||||
background: #fff7e6;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
border: 1px solid #ffd591;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
color: #d48806;
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tips-list {
|
||||
@@ -1841,7 +1848,7 @@ export default {
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tip-item:last-child {
|
||||
@@ -1849,12 +1856,12 @@ export default {
|
||||
}
|
||||
|
||||
.tip-icon {
|
||||
margin-right: 8px;
|
||||
margin-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 13px;
|
||||
font-size: 24rpx;
|
||||
color: #d48806;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@@ -1863,40 +1870,40 @@ export default {
|
||||
.stats-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 8px;
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
padding: 20rpx 12rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin: 0 4px;
|
||||
border-radius: 12rpx;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
display: block;
|
||||
font-size: 20px;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
margin-bottom: 4px;
|
||||
color: #2979ff;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
font-size: 24rpx;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 签到记录 */
|
||||
.records-list {
|
||||
padding-top: 8px;
|
||||
padding-top: 12rpx;
|
||||
}
|
||||
|
||||
.record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
@@ -1905,17 +1912,17 @@ export default {
|
||||
}
|
||||
|
||||
.record-date {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #303133;
|
||||
width: 100px;
|
||||
width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #606266;
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
@@ -1927,72 +1934,21 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px 0;
|
||||
padding: 60rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media screen and (max-width: 375px) {
|
||||
.coord-row {
|
||||
padding: 10px;
|
||||
/* uview组件样式调整 */
|
||||
::v-deep .u-tag {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.coord-divider {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.coord-value {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.map-btn {
|
||||
min-width: 120px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-tips-section {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 414px) {
|
||||
.location-address {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.coord-row {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.accuracy-info {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.coord-value {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色模式适配 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.location-address,
|
||||
.coord-row {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.accuracy-info {
|
||||
background: rgba(103, 194, 58, 0.1);
|
||||
border-color: rgba(103, 194, 58, 0.3);
|
||||
}
|
||||
|
||||
.error-tips-section {
|
||||
background: rgba(255, 247, 230, 0.1);
|
||||
border-color: rgba(255, 213, 145, 0.3);
|
||||
}
|
||||
::v-deep .u-button {
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,14 @@
|
||||
<template>
|
||||
<view class="visit-add-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="新增走访" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="content-wrapper">
|
||||
<u-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@@ -204,15 +212,18 @@
|
||||
</u-form-item>
|
||||
</view>
|
||||
</u-form>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="submitForm"
|
||||
:loading="submitLoading"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
>
|
||||
<u-icon name="checkmark-circle" size="16" style="margin-right: 4rpx"></u-icon>
|
||||
提交走访记录
|
||||
</u-button>
|
||||
</view>
|
||||
@@ -229,11 +240,7 @@
|
||||
<view class="student-selector">
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择学生</text>
|
||||
<u-icon
|
||||
name="close"
|
||||
size="20"
|
||||
@click="showStudentSelector = false"
|
||||
/>
|
||||
<u-icon name="close" size="20" @click="showStudentSelector = false" />
|
||||
</view>
|
||||
|
||||
<view class="search-section">
|
||||
@@ -260,7 +267,9 @@
|
||||
</view>
|
||||
<view class="student-detail">
|
||||
<text class="detail-text">{{ student.className || '-' }}</text>
|
||||
<text class="detail-text">{{ student.companyName || '暂无实习信息' }}</text>
|
||||
<text class="detail-text">
|
||||
{{ student.companyName || '暂无实习信息' }}
|
||||
</text>
|
||||
</view>
|
||||
<u-icon
|
||||
name="checkmark"
|
||||
@@ -281,7 +290,7 @@
|
||||
|
||||
<view class="student-loading" v-if="studentLoading">
|
||||
<u-loading-icon mode="circle" />
|
||||
<text style="margin-left: 10rpx; color: #909399;">加载中...</text>
|
||||
<text style="margin-left: 10rpx; color: #909399">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -325,23 +334,21 @@ export default {
|
||||
visitSummary: '',
|
||||
visitImages: '',
|
||||
remark: '',
|
||||
status: 'planned' // 默认状态为计划中
|
||||
status: 'planned', // 默认状态为计划中
|
||||
},
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
studentId: [
|
||||
{ required: true, message: '请选择学生', trigger: ['blur', 'change'] }
|
||||
],
|
||||
studentId: [{ required: true, message: '请选择学生', trigger: ['blur', 'change'] }],
|
||||
visitDate: [
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] }
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] },
|
||||
],
|
||||
visitorName: [
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] }
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] },
|
||||
],
|
||||
visitPurpose: [
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] }
|
||||
]
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] },
|
||||
],
|
||||
},
|
||||
|
||||
// 学生选择
|
||||
@@ -363,7 +370,7 @@ export default {
|
||||
maxImageCount: 10,
|
||||
|
||||
// 加载状态
|
||||
submitLoading: false
|
||||
submitLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -372,6 +379,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 初始化表单
|
||||
async initForm() {
|
||||
// 获取当前登录用户信息,自动填入走访人
|
||||
@@ -412,7 +424,7 @@ export default {
|
||||
const params = {
|
||||
classTeacherId,
|
||||
pageNum: 1,
|
||||
pageSize: 100 // 获取较多数据供选择
|
||||
pageSize: 100, // 获取较多数据供选择
|
||||
};
|
||||
|
||||
const response = await getStudentInternshipList(params);
|
||||
@@ -553,7 +565,6 @@ export default {
|
||||
console.log('最终form.visitDate:', this.form.visitDate);
|
||||
console.log('最终visitDateValue:', this.visitDateValue);
|
||||
console.log('日期设置成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('日期处理异常:', error.message);
|
||||
console.error('异常详情 - value:', value, 'type:', typeof value);
|
||||
@@ -602,7 +613,7 @@ export default {
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.handleImageUpload(res.tempFilePaths);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -616,7 +627,7 @@ export default {
|
||||
// 暂时使用本地路径作为示例
|
||||
this.imageList.push({
|
||||
url: filePath,
|
||||
path: filePath
|
||||
path: filePath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -632,10 +643,10 @@ export default {
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
const urls = this.imageList.map((item) => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
current: index,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -645,8 +656,6 @@ export default {
|
||||
this.form.visitImages = JSON.stringify(this.imageList);
|
||||
},
|
||||
|
||||
|
||||
|
||||
// 提交表单
|
||||
async submitForm() {
|
||||
// 表单验证
|
||||
@@ -661,7 +670,7 @@ export default {
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: '1' // 已提交状态 (0-草稿 1-已提交 2-已审核)
|
||||
status: '1', // 已提交状态 (0-草稿 1-已提交 2-已审核)
|
||||
};
|
||||
|
||||
const response = await addVisit(formData);
|
||||
@@ -680,8 +689,8 @@ export default {
|
||||
} finally {
|
||||
this.submitLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -689,6 +698,9 @@ export default {
|
||||
.visit-add-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding-bottom: 120rpx; // 为底部按钮留出空间
|
||||
}
|
||||
|
||||
@@ -696,6 +708,12 @@ export default {
|
||||
background: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 0 16rpx;
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
<template>
|
||||
<view class="visit-detail-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="走访详情" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page :loading="pageLoading" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<view v-if="!pageLoading" class="content-wrapper">
|
||||
<!-- 学生信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">学生信息</view>
|
||||
@@ -68,7 +80,9 @@
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.visitPurpose">
|
||||
<text class="info-label">走访目的:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.visitPurpose }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.visitPurpose }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -79,23 +93,33 @@
|
||||
<view class="section-content">
|
||||
<view class="info-item info-item-full" v-if="visitInfo.companySituation">
|
||||
<text class="info-label">企业情况:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.companySituation }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.companySituation }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.studentPerformance">
|
||||
<text class="info-label">学生表现:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.studentPerformance }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.studentPerformance }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.problemsFound">
|
||||
<text class="info-label">发现问题:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.problemsFound }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.problemsFound }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.suggestions">
|
||||
<text class="info-label">改进建议:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.suggestions }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.suggestions }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item info-item-full" v-if="visitInfo.visitSummary">
|
||||
<text class="info-label">走访总结:</text>
|
||||
<text class="info-value info-value-multiline">{{ visitInfo.visitSummary }}</text>
|
||||
<text class="info-value info-value-multiline">
|
||||
{{ visitInfo.visitSummary }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -146,28 +170,27 @@
|
||||
<view class="action-section" v-if="canEdit">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="handleEdit"
|
||||
:loading="loading"
|
||||
style="margin-right: 20rpx;"
|
||||
style="flex: 1; margin-right: 20rpx"
|
||||
>
|
||||
<u-icon name="edit-pen" size="16" style="margin-right: 4rpx"></u-icon>
|
||||
编辑走访记录
|
||||
</u-button>
|
||||
<u-button
|
||||
type="error"
|
||||
shape="round"
|
||||
@click="handleDelete"
|
||||
:loading="loading"
|
||||
plain
|
||||
style="flex: 1"
|
||||
>
|
||||
<u-icon name="trash" size="16" style="margin-right: 4rpx"></u-icon>
|
||||
删除记录
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 加载遮罩 -->
|
||||
<u-loading-page
|
||||
:loading="pageLoading"
|
||||
loading-text="加载中..."
|
||||
loading-mode="circle"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -189,7 +212,7 @@ export default {
|
||||
visitInfo: {},
|
||||
|
||||
// 图片数据
|
||||
imageList: []
|
||||
imageList: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -197,10 +220,12 @@ export default {
|
||||
// 是否可以编辑
|
||||
canEdit() {
|
||||
// 只有计划中或草稿状态的记录可以编辑
|
||||
return this.visitInfo.status === 'planned' ||
|
||||
return (
|
||||
this.visitInfo.status === 'planned' ||
|
||||
this.visitInfo.status === '0' ||
|
||||
this.visitInfo.status === 'draft';
|
||||
}
|
||||
this.visitInfo.status === 'draft'
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -214,6 +239,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 加载走访记录详情
|
||||
async loadVisitDetail() {
|
||||
this.pageLoading = true;
|
||||
@@ -232,7 +262,6 @@ export default {
|
||||
this.imageList = [];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取数据失败', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
@@ -282,17 +311,17 @@ export default {
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
const urls = this.imageList.map((item) => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
current: index,
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑走访记录
|
||||
handleEdit() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/visit/edit?id=${this.visitId}`
|
||||
url: `/pages/visit/edit?id=${this.visitId}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -305,7 +334,7 @@ export default {
|
||||
if (res.confirm) {
|
||||
await this.deleteVisit();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -329,8 +358,8 @@ export default {
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -338,6 +367,9 @@ export default {
|
||||
.visit-detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
@@ -347,6 +379,7 @@ export default {
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 20rpx 16rpx;
|
||||
@@ -354,7 +387,7 @@ export default {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
background: #f8f9fa;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.section-content {
|
||||
@@ -390,8 +423,9 @@ export default {
|
||||
flex: 1;
|
||||
|
||||
&.info-value-multiline {
|
||||
line-height: 1.6;
|
||||
line-height: 1.8;
|
||||
word-break: break-all;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,6 +440,12 @@ export default {
|
||||
height: 200rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
@@ -416,9 +456,11 @@ export default {
|
||||
|
||||
.action-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<view class="visit-edit-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="编辑走访" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page :loading="pageLoading" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view v-if="!pageLoading" class="content-wrapper">
|
||||
<u-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
@@ -194,23 +205,28 @@
|
||||
</u-form-item>
|
||||
</view>
|
||||
</u-form>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<view v-if="!pageLoading" class="bottom-actions">
|
||||
<u-button
|
||||
type="default"
|
||||
type="info"
|
||||
shape="round"
|
||||
@click="saveDraft"
|
||||
:loading="draftLoading"
|
||||
style="margin-right: 20rpx;"
|
||||
style="margin-right: 20rpx"
|
||||
>
|
||||
<u-icon name="folder" size="16" style="margin-right: 4rpx"></u-icon>
|
||||
保存草稿
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="submitForm"
|
||||
:loading="submitLoading"
|
||||
style="flex: 1;"
|
||||
style="flex: 1"
|
||||
>
|
||||
<u-icon name="checkmark-circle" size="16" style="margin-right: 4rpx"></u-icon>
|
||||
更新走访记录
|
||||
</u-button>
|
||||
</view>
|
||||
@@ -226,11 +242,7 @@
|
||||
/>
|
||||
|
||||
<!-- 加载遮罩 -->
|
||||
<u-loading-page
|
||||
:loading="pageLoading"
|
||||
loading-text="加载中..."
|
||||
loading-mode="circle"
|
||||
/>
|
||||
<u-loading-page :loading="pageLoading" loading-text="加载中..." loading-mode="circle" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -268,20 +280,20 @@ export default {
|
||||
visitSummary: '',
|
||||
visitImages: '',
|
||||
remark: '',
|
||||
status: ''
|
||||
status: '',
|
||||
},
|
||||
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
visitDate: [
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] }
|
||||
{ required: true, message: '请选择走访日期', trigger: ['blur', 'change'] },
|
||||
],
|
||||
visitorName: [
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] }
|
||||
{ required: true, message: '请输入走访人员姓名', trigger: ['blur', 'change'] },
|
||||
],
|
||||
visitPurpose: [
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] }
|
||||
]
|
||||
{ required: true, message: '请输入走访目的', trigger: ['blur', 'change'] },
|
||||
],
|
||||
},
|
||||
|
||||
// 日期选择
|
||||
@@ -295,7 +307,7 @@ export default {
|
||||
|
||||
// 加载状态
|
||||
submitLoading: false,
|
||||
draftLoading: false
|
||||
draftLoading: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -310,6 +322,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 加载走访记录详情
|
||||
async loadVisitDetail() {
|
||||
this.pageLoading = true;
|
||||
@@ -337,7 +354,7 @@ export default {
|
||||
visitSummary: response.data.visitSummary,
|
||||
visitImages: response.data.visitImages,
|
||||
remark: response.data.remark,
|
||||
status: response.data.status
|
||||
status: response.data.status,
|
||||
};
|
||||
|
||||
// 处理图片数据
|
||||
@@ -354,7 +371,6 @@ export default {
|
||||
if (response.data.visitDate) {
|
||||
this.visitDateValue = new Date(response.data.visitDate).getTime();
|
||||
}
|
||||
|
||||
} else {
|
||||
uni.showToast({ title: response.msg || '获取数据失败', icon: 'none' });
|
||||
uni.navigateBack();
|
||||
@@ -392,7 +408,7 @@ export default {
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.handleImageUpload(res.tempFilePaths);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -406,7 +422,7 @@ export default {
|
||||
// 暂时使用本地路径作为示例
|
||||
this.imageList.push({
|
||||
url: filePath,
|
||||
path: filePath
|
||||
path: filePath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -422,10 +438,10 @@ export default {
|
||||
|
||||
// 预览图片
|
||||
previewImage(index) {
|
||||
const urls = this.imageList.map(item => item.url);
|
||||
const urls = this.imageList.map((item) => item.url);
|
||||
uni.previewImage({
|
||||
urls,
|
||||
current: index
|
||||
current: index,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -441,7 +457,7 @@ export default {
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: 'planned' // 草稿状态
|
||||
status: 'planned', // 草稿状态
|
||||
};
|
||||
|
||||
const response = await updateVisit(formData);
|
||||
@@ -476,7 +492,7 @@ export default {
|
||||
try {
|
||||
const formData = {
|
||||
...this.form,
|
||||
status: 'completed' // 已完成状态
|
||||
status: 'completed', // 已完成状态
|
||||
};
|
||||
|
||||
const response = await updateVisit(formData);
|
||||
@@ -495,8 +511,8 @@ export default {
|
||||
} finally {
|
||||
this.submitLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -504,6 +520,9 @@ export default {
|
||||
.visit-edit-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding-bottom: 120rpx; // 为底部按钮留出空间
|
||||
}
|
||||
|
||||
@@ -511,6 +530,12 @@ export default {
|
||||
background: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:first-child {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 24rpx 0 16rpx;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="visit-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="班主任走访" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<u-search
|
||||
@@ -9,7 +16,6 @@
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
height="72"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
@@ -49,6 +55,7 @@
|
||||
<view class="filter-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="handleFilter"
|
||||
:loading="loading"
|
||||
@@ -56,10 +63,11 @@
|
||||
筛选
|
||||
</u-button>
|
||||
<u-button
|
||||
type="default"
|
||||
type="info"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="resetFilter"
|
||||
style="margin-left: 10rpx;"
|
||||
style="margin-left: 10rpx"
|
||||
>
|
||||
重置
|
||||
</u-button>
|
||||
@@ -69,26 +77,32 @@
|
||||
<!-- 筛选按钮 -->
|
||||
<view class="action-bar">
|
||||
<u-button
|
||||
type="default"
|
||||
type="info"
|
||||
shape="round"
|
||||
size="small"
|
||||
:icon="showFilter ? 'arrow-up' : 'arrow-down'"
|
||||
@click="showFilter = !showFilter"
|
||||
>
|
||||
{{ showFilter ? '收起筛选' : '展开筛选' }}
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="plus"
|
||||
@click="handleAdd"
|
||||
>
|
||||
<u-button type="primary" shape="round" size="small" icon="plus" @click="handleAdd">
|
||||
新增走访
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page
|
||||
:loading="loading && visitList.length === 0"
|
||||
loading-text="加载中..."
|
||||
></u-loading-page>
|
||||
|
||||
<!-- 走访记录列表 -->
|
||||
<view class="list-container">
|
||||
<view class="list-content" v-if="visitList.length > 0">
|
||||
<view v-if="!loading || visitList.length > 0" class="content-wrapper">
|
||||
<view v-if="visitList.length === 0" class="empty-container">
|
||||
<u-empty text="暂无走访记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<view class="list-content" v-else>
|
||||
<view
|
||||
class="visit-card"
|
||||
v-for="item in visitList"
|
||||
@@ -103,7 +117,7 @@
|
||||
</view>
|
||||
<view class="status-badge">
|
||||
<u-tag
|
||||
:text="item.statusName || item.status"
|
||||
:text="getStatusLabel(item.status)"
|
||||
:type="getStatusType(item.status)"
|
||||
size="mini"
|
||||
/>
|
||||
@@ -120,58 +134,73 @@
|
||||
<view class="card-row">
|
||||
<u-icon name="calendar" size="16" color="#909399" />
|
||||
<text class="info-text">{{ item.visitDate || '-' }}</text>
|
||||
<u-icon name="account" size="16" color="#909399" style="margin-left: 20rpx;" />
|
||||
<u-icon
|
||||
name="account"
|
||||
size="16"
|
||||
color="#909399"
|
||||
style="margin-left: 20rpx"
|
||||
/>
|
||||
<text class="info-text">{{ item.visitorName || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 班级专业信息 -->
|
||||
<view class="card-row">
|
||||
<u-icon name="bookmark" size="16" color="#909399" />
|
||||
<text class="info-text">{{ item.className || '-' }} / {{ item.majorName || '-' }}</text>
|
||||
<text class="info-text">
|
||||
{{ item.className || '-' }} / {{ item.majorName || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="card-actions">
|
||||
<u-button
|
||||
type="default"
|
||||
size="mini"
|
||||
type="primary"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click.stop="handleEdit(item)"
|
||||
v-if="item.status === 'planned' || item.status === '0'"
|
||||
>
|
||||
<u-icon
|
||||
name="edit-pen"
|
||||
size="14"
|
||||
color="#fff"
|
||||
style="margin-right: 4rpx"
|
||||
></u-icon>
|
||||
编辑
|
||||
</u-button>
|
||||
<u-button
|
||||
type="error"
|
||||
size="mini"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click.stop="handleDelete(item)"
|
||||
style="margin-left: 10rpx;"
|
||||
style="margin-left: 10rpx"
|
||||
v-if="item.status === 'planned' || item.status === '0'"
|
||||
>
|
||||
<u-icon
|
||||
name="trash"
|
||||
size="14"
|
||||
color="#fff"
|
||||
style="margin-right: 4rpx"
|
||||
></u-icon>
|
||||
删除
|
||||
</u-button>
|
||||
<u-button
|
||||
type="info"
|
||||
shape="round"
|
||||
size="small"
|
||||
plain
|
||||
@click.stop="handleView(item)"
|
||||
v-else
|
||||
>
|
||||
<u-icon name="eye" size="14" style="margin-right: 4rpx"></u-icon>
|
||||
查看
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-else-if="!loading">
|
||||
<u-empty
|
||||
mode="data"
|
||||
text="暂无走访记录"
|
||||
textColor="#c8c9cc"
|
||||
iconColor="#c8c9cc"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view class="loading-state" v-if="loading">
|
||||
<u-loading-icon mode="circle" />
|
||||
<text style="margin-left: 10rpx; color: #909399;">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 上拉加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && visitList.length > 0">
|
||||
<view class="load-more" v-if="hasMore">
|
||||
<u-loadmore
|
||||
:status="loadMoreStatus"
|
||||
@loadmore="loadMore"
|
||||
@@ -180,6 +209,7 @@
|
||||
no-more-text="没有更多了"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 状态选择器 -->
|
||||
<u-picker
|
||||
@@ -218,7 +248,7 @@ export default {
|
||||
visitorName: '',
|
||||
status: '',
|
||||
beginVisitDate: '',
|
||||
endVisitDate: ''
|
||||
endVisitDate: '',
|
||||
},
|
||||
|
||||
// 列表数据
|
||||
@@ -238,7 +268,7 @@ export default {
|
||||
// 日期选择
|
||||
showDatePicker: false,
|
||||
dateRangeText: '',
|
||||
selectedDateRange: []
|
||||
selectedDateRange: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -257,6 +287,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 初始化页面
|
||||
async initPage() {
|
||||
await this.loadStatusDict();
|
||||
@@ -272,22 +307,24 @@ export default {
|
||||
this.statusOptions = [
|
||||
[
|
||||
{ text: '全部状态', value: '' },
|
||||
...response.data.map(item => ({
|
||||
...response.data.map((item) => ({
|
||||
text: item.dictLabel,
|
||||
value: item.dictValue
|
||||
}))
|
||||
]
|
||||
value: item.dictValue,
|
||||
})),
|
||||
],
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载状态字典失败:', error);
|
||||
// 设置默认状态选项
|
||||
this.statusOptions = [[
|
||||
this.statusOptions = [
|
||||
[
|
||||
{ text: '全部状态', value: '' },
|
||||
{ text: '计划中', value: 'planned' },
|
||||
{ text: '已完成', value: 'completed' },
|
||||
{ text: '已取消', value: 'cancelled' }
|
||||
]];
|
||||
{ text: '已取消', value: 'cancelled' },
|
||||
],
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -310,7 +347,7 @@ export default {
|
||||
const { rows, total } = response;
|
||||
|
||||
if (isLoadMore) {
|
||||
this.visitList = [...this.visitList, ...rows || []];
|
||||
this.visitList = [...this.visitList, ...(rows || [])];
|
||||
} else {
|
||||
this.visitList = rows || [];
|
||||
}
|
||||
@@ -372,7 +409,7 @@ export default {
|
||||
visitorName: '',
|
||||
status: '',
|
||||
beginVisitDate: '',
|
||||
endVisitDate: ''
|
||||
endVisitDate: '',
|
||||
};
|
||||
this.statusText = '';
|
||||
this.dateRangeText = '';
|
||||
@@ -405,6 +442,32 @@ export default {
|
||||
this.showDatePicker = false;
|
||||
},
|
||||
|
||||
// 获取状态标签文本
|
||||
getStatusLabel(status) {
|
||||
// 如果statusDict字典已加载,从字典中查找
|
||||
if (this.statusDict && this.statusDict.length > 0) {
|
||||
const dictItem = this.statusDict.find((item) => item.dictValue === String(status));
|
||||
if (dictItem) {
|
||||
return dictItem.dictLabel;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果字典未加载或找不到,使用默认映射
|
||||
switch (String(status)) {
|
||||
case 'planned':
|
||||
case '0':
|
||||
return '计划中';
|
||||
case 'completed':
|
||||
case '1':
|
||||
return '已完成';
|
||||
case 'cancelled':
|
||||
case '2':
|
||||
return '已取消';
|
||||
default:
|
||||
return status || '未知';
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态类型
|
||||
getStatusType(status) {
|
||||
switch (status) {
|
||||
@@ -425,21 +488,21 @@ export default {
|
||||
// 查看详情
|
||||
handleView(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/visit/detail?id=${item.visitId}`
|
||||
url: `/pages/visit/detail?id=${item.visitId}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 新增走访
|
||||
handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/visit/add'
|
||||
url: '/pages/visit/add',
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑走访
|
||||
handleEdit(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/visit/edit?id=${item.visitId}`
|
||||
url: `/pages/visit/edit?id=${item.visitId}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -452,7 +515,7 @@ export default {
|
||||
if (res.confirm) {
|
||||
await this.deleteVisit(item.visitId);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -474,8 +537,8 @@ export default {
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -515,24 +578,38 @@ export default {
|
||||
border-bottom: 1rpx solid #ebeef5;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 80rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
.visit-card {
|
||||
background: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.99);
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
@@ -545,6 +622,10 @@ export default {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-row {
|
||||
@@ -571,20 +652,9 @@ export default {
|
||||
border-top: 1rpx solid #ebeef5;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 80rpx 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="detail-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="申请详情" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 申请状态卡片 -->
|
||||
<view class="status-card" v-if="applicationData">
|
||||
<view class="card-title">申请状态</view>
|
||||
@@ -17,7 +24,10 @@
|
||||
<text class="status-label">申请时间:</text>
|
||||
<text class="status-value">{{ applicationData.createTime }}</text>
|
||||
</view>
|
||||
<view class="status-item" v-if="applicationData.updateTime !== applicationData.createTime">
|
||||
<view
|
||||
class="status-item"
|
||||
v-if="applicationData.updateTime !== applicationData.createTime"
|
||||
>
|
||||
<text class="status-label">更新时间:</text>
|
||||
<text class="status-value">{{ applicationData.updateTime }}</text>
|
||||
</view>
|
||||
@@ -32,11 +42,11 @@
|
||||
<view class="position-name">{{ applicationData.positionName }}</view>
|
||||
<view class="position-detail">
|
||||
<view class="detail-item">
|
||||
<u-icon name="list" size="28" color="#666" />
|
||||
<u-icon name="list" size="18" color="#666" />
|
||||
<text>岗位类型:{{ applicationData.positionType }}</text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<u-icon name="map" size="28" color="#666" />
|
||||
<u-icon name="map" size="18" color="#666" />
|
||||
<text>工作地点:{{ applicationData.workLocation }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -61,7 +71,9 @@
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">联系电话:</text>
|
||||
<text class="info-value contact-phone" @click="callPhone">{{ applicationData.contactPhone }}</text>
|
||||
<text class="info-value contact-phone" @click="callPhone">
|
||||
{{ applicationData.contactPhone }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="info-label">学院:</text>
|
||||
@@ -187,7 +199,7 @@
|
||||
size="large"
|
||||
@click="editApplication"
|
||||
v-if="canEdit"
|
||||
style="margin-right: 20rpx;"
|
||||
style="margin-right: 20rpx"
|
||||
>
|
||||
编辑申请
|
||||
</u-button>
|
||||
@@ -208,10 +220,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getApplicationDetail,
|
||||
cancelApplication
|
||||
} from '@/api/workStudy.js';
|
||||
import { getApplicationDetail, cancelApplication } from '@/api/workStudy.js';
|
||||
|
||||
export default {
|
||||
name: 'ApplicationDetail',
|
||||
@@ -220,15 +229,17 @@ export default {
|
||||
applicationId: '',
|
||||
applicationData: null,
|
||||
loading: false,
|
||||
canceling: false
|
||||
canceling: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
// 是否有审核记录
|
||||
hasReviewRecord() {
|
||||
return this.applicationData &&
|
||||
(this.applicationData.collegeReviewStatus || this.applicationData.finalReviewStatus);
|
||||
return (
|
||||
this.applicationData &&
|
||||
(this.applicationData.collegeReviewStatus || this.applicationData.finalReviewStatus)
|
||||
);
|
||||
},
|
||||
|
||||
// 是否显示操作按钮
|
||||
@@ -238,15 +249,19 @@ export default {
|
||||
|
||||
// 是否可以编辑
|
||||
canEdit() {
|
||||
return this.applicationData &&
|
||||
['DRAFT', 'COLLEGE_REJECTED'].includes(this.applicationData.status);
|
||||
return (
|
||||
this.applicationData &&
|
||||
['DRAFT', 'COLLEGE_REJECTED'].includes(this.applicationData.status)
|
||||
);
|
||||
},
|
||||
|
||||
// 是否可以撤销
|
||||
canCancel() {
|
||||
return this.applicationData &&
|
||||
['DRAFT', 'SUBMITTED', 'COLLEGE_REVIEWING'].includes(this.applicationData.status);
|
||||
}
|
||||
return (
|
||||
this.applicationData &&
|
||||
['DRAFT', 'SUBMITTED', 'COLLEGE_REVIEWING'].includes(this.applicationData.status)
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -256,7 +271,7 @@ export default {
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '申请ID不能为空',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
@@ -265,6 +280,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 加载申请详情
|
||||
async loadApplicationDetail() {
|
||||
this.loading = true;
|
||||
@@ -281,7 +301,7 @@ export default {
|
||||
console.error('加载申请详情失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -297,7 +317,7 @@ export default {
|
||||
if (!this.applicationData) return;
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/apply?positionId=${this.applicationData.positionId}&applicationId=${this.applicationData.id}&mode=edit`
|
||||
url: `/pages/workStudy/apply?positionId=${this.applicationData.positionId}&applicationId=${this.applicationData.id}&mode=edit`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -307,7 +327,7 @@ export default {
|
||||
const { confirm } = await uni.showModal({
|
||||
title: '确认撤销',
|
||||
content: '确定要撤销此申请吗?',
|
||||
showCancel: true
|
||||
showCancel: true,
|
||||
});
|
||||
|
||||
if (!confirm) return; // 用户取消
|
||||
@@ -318,7 +338,7 @@ export default {
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '撤销成功',
|
||||
icon: 'success'
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
// 重新加载数据
|
||||
@@ -332,7 +352,7 @@ export default {
|
||||
console.error('撤销申请失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '撤销失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.canceling = false;
|
||||
@@ -348,9 +368,9 @@ export default {
|
||||
console.error('拨打电话失败:', error);
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -364,15 +384,15 @@ export default {
|
||||
// 获取申请状态文字
|
||||
getApplicationStatusText(status) {
|
||||
const statusMap = {
|
||||
'DRAFT': '草稿',
|
||||
'SUBMITTED': '已提交',
|
||||
'COLLEGE_REVIEWING': '学院审核中',
|
||||
'COLLEGE_APPROVED': '学院审核通过',
|
||||
'COLLEGE_REJECTED': '学院审核未通过',
|
||||
'FINAL_REVIEWING': '学生处审核中',
|
||||
'FINAL_APPROVED': '审核通过',
|
||||
'FINAL_REJECTED': '审核未通过',
|
||||
'CANCELLED': '已撤销'
|
||||
DRAFT: '草稿',
|
||||
SUBMITTED: '已提交',
|
||||
COLLEGE_REVIEWING: '学院审核中',
|
||||
COLLEGE_APPROVED: '学院审核通过',
|
||||
COLLEGE_REJECTED: '学院审核未通过',
|
||||
FINAL_REVIEWING: '学生处审核中',
|
||||
FINAL_APPROVED: '审核通过',
|
||||
FINAL_REJECTED: '审核未通过',
|
||||
CANCELLED: '已撤销',
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
},
|
||||
@@ -380,15 +400,15 @@ export default {
|
||||
// 获取申请状态类型
|
||||
getApplicationStatusType(status) {
|
||||
const typeMap = {
|
||||
'DRAFT': 'info',
|
||||
'SUBMITTED': 'warning',
|
||||
'COLLEGE_REVIEWING': 'warning',
|
||||
'COLLEGE_APPROVED': 'success',
|
||||
'COLLEGE_REJECTED': 'error',
|
||||
'FINAL_REVIEWING': 'warning',
|
||||
'FINAL_APPROVED': 'success',
|
||||
'FINAL_REJECTED': 'error',
|
||||
'CANCELLED': 'info'
|
||||
DRAFT: 'info',
|
||||
SUBMITTED: 'warning',
|
||||
COLLEGE_REVIEWING: 'warning',
|
||||
COLLEGE_APPROVED: 'success',
|
||||
COLLEGE_REJECTED: 'error',
|
||||
FINAL_REVIEWING: 'warning',
|
||||
FINAL_APPROVED: 'success',
|
||||
FINAL_REJECTED: 'error',
|
||||
CANCELLED: 'info',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
},
|
||||
@@ -396,9 +416,9 @@ export default {
|
||||
// 获取审核状态文字
|
||||
getReviewStatusText(status) {
|
||||
const statusMap = {
|
||||
'PENDING': '待审核',
|
||||
'APPROVED': '通过',
|
||||
'REJECTED': '未通过'
|
||||
PENDING: '待审核',
|
||||
APPROVED: '通过',
|
||||
REJECTED: '未通过',
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
@@ -406,38 +426,41 @@ export default {
|
||||
// 获取审核状态类型
|
||||
getReviewStatusType(status) {
|
||||
const typeMap = {
|
||||
'PENDING': 'warning',
|
||||
'APPROVED': 'success',
|
||||
'REJECTED': 'error'
|
||||
PENDING: 'warning',
|
||||
APPROVED: 'success',
|
||||
REJECTED: 'error',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
padding-bottom: 120rpx; /* 为底部按钮留空间 */
|
||||
}
|
||||
|
||||
.status-card, .info-card, .content-card, .review-card {
|
||||
.status-card,
|
||||
.info-card,
|
||||
.content-card,
|
||||
.review-card {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 状态卡片 */
|
||||
@@ -463,12 +486,12 @@ export default {
|
||||
|
||||
.status-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 岗位信息 */
|
||||
@@ -516,14 +539,14 @@ export default {
|
||||
|
||||
.info-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
min-width: 160rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -536,7 +559,7 @@ export default {
|
||||
/* 内容卡片 */
|
||||
.content-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
@@ -563,7 +586,7 @@ export default {
|
||||
.review-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.review-info {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="apply-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="申请岗位" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 岗位信息卡片 -->
|
||||
<view class="position-card" v-if="positionData">
|
||||
<view class="card-title">申请岗位</view>
|
||||
@@ -14,21 +21,12 @@
|
||||
|
||||
<!-- 申请表单 -->
|
||||
<view class="form-container">
|
||||
<u-form
|
||||
:model="formData"
|
||||
ref="form"
|
||||
label-width="140"
|
||||
label-position="left"
|
||||
>
|
||||
<u-form :model="formData" ref="form" label-width="140" label-position="left">
|
||||
<!-- 基本信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">基本信息</view>
|
||||
|
||||
<u-form-item
|
||||
label="学生姓名"
|
||||
prop="studentName"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="学生姓名" prop="studentName" :required="true">
|
||||
<u-input
|
||||
v-model="formData.studentName"
|
||||
placeholder="请输入学生姓名"
|
||||
@@ -36,11 +34,7 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="学号"
|
||||
prop="studentNumber"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="学号" prop="studentNumber" :required="true">
|
||||
<u-input
|
||||
v-model="formData.studentNumber"
|
||||
placeholder="请输入学号"
|
||||
@@ -48,11 +42,7 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="身份证号"
|
||||
prop="idCard"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="身份证号" prop="idCard" :required="true">
|
||||
<u-input
|
||||
v-model="formData.idCard"
|
||||
placeholder="请输入身份证号"
|
||||
@@ -60,11 +50,7 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="联系电话"
|
||||
prop="contactPhone"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="联系电话" prop="contactPhone" :required="true">
|
||||
<u-input
|
||||
v-model="formData.contactPhone"
|
||||
placeholder="请输入联系电话"
|
||||
@@ -77,11 +63,7 @@
|
||||
<view class="form-section">
|
||||
<view class="section-title">学籍信息</view>
|
||||
|
||||
<u-form-item
|
||||
label="学院名称"
|
||||
prop="collegeName"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="学院名称" prop="collegeName" :required="true">
|
||||
<u-input
|
||||
v-model="formData.collegeName"
|
||||
placeholder="学院名称"
|
||||
@@ -89,11 +71,7 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="专业名称"
|
||||
prop="majorName"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="专业名称" prop="majorName" :required="true">
|
||||
<u-input
|
||||
v-model="formData.majorName"
|
||||
placeholder="专业名称"
|
||||
@@ -101,57 +79,48 @@
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="班级名称"
|
||||
prop="className"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="班级名称" prop="className" :required="true">
|
||||
<u-input
|
||||
v-model="formData.className"
|
||||
placeholder="班级名称"
|
||||
:disabled="true"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 申请信息 -->
|
||||
<view class="form-section">
|
||||
<view class="section-title">申请信息</view>
|
||||
|
||||
<u-form-item
|
||||
label="家庭经济情况"
|
||||
prop="familyFinancialStatus"
|
||||
:required="true"
|
||||
<u-form-item label="家庭经济情况" prop="familyFinancialStatus" :required="true">
|
||||
<u-picker
|
||||
:columns="familyStatusColumns"
|
||||
v-model="familyStatusIndex"
|
||||
:show="showFamilyStatusPicker"
|
||||
@confirm="onFamilyStatusConfirm"
|
||||
@close="showFamilyStatusPicker = false"
|
||||
></u-picker>
|
||||
<view class="picker-input" @click="showFamilyStatusPicker = true">
|
||||
<text
|
||||
class="picker-text"
|
||||
:class="{ placeholder: !formData.familyFinancialStatus }"
|
||||
>
|
||||
<u-radio-group v-model="formData.familyFinancialStatus">
|
||||
<u-radio
|
||||
v-for="option in familyStatusOptions"
|
||||
:key="option.value"
|
||||
:name="option.value"
|
||||
:label="option.label"
|
||||
style="margin-right: 20rpx; margin-bottom: 15rpx; font-size: 28rpx;"
|
||||
/>
|
||||
</u-radio-group>
|
||||
{{ formData.familyFinancialStatus || '请选择家庭经济情况' }}
|
||||
</text>
|
||||
<u-icon name="arrow-right" size="14" color="#c8c9cc"></u-icon>
|
||||
</view>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="计划工作时长"
|
||||
prop="plannedWorkHours"
|
||||
>
|
||||
<u-form-item label="计划工作时长" prop="plannedWorkHours">
|
||||
<u-input
|
||||
v-model="formData.plannedWorkHours"
|
||||
placeholder="请输入计划工作时长(小时/周)"
|
||||
type="number"
|
||||
placeholder-style="font-size: 24rpx"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="申请理由"
|
||||
prop="applicationReason"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="申请理由" prop="applicationReason" :required="true">
|
||||
<u-textarea
|
||||
v-model="formData.applicationReason"
|
||||
placeholder="请详细说明申请此岗位的理由"
|
||||
@@ -159,14 +128,11 @@
|
||||
:show-word-limit="true"
|
||||
height="200"
|
||||
count
|
||||
placeholder-style="font-size: 24rpx"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="个人能力及经验"
|
||||
prop="personalAbility"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="个人能力及经验" prop="personalAbility" :required="true">
|
||||
<u-textarea
|
||||
v-model="formData.personalAbility"
|
||||
placeholder="请描述与岗位相关的个人能力和经验"
|
||||
@@ -174,14 +140,11 @@
|
||||
:show-word-limit="true"
|
||||
height="200"
|
||||
count
|
||||
placeholder-style="font-size: 24rpx"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="时间安排"
|
||||
prop="timeArrangement"
|
||||
:required="true"
|
||||
>
|
||||
<u-form-item label="时间安排" prop="timeArrangement" :required="true">
|
||||
<u-textarea
|
||||
v-model="formData.timeArrangement"
|
||||
placeholder="请说明您的时间安排,确保能够胜任此岗位工作"
|
||||
@@ -189,13 +152,11 @@
|
||||
:show-word-limit="true"
|
||||
height="150"
|
||||
count
|
||||
placeholder-style="font-size: 24rpx"
|
||||
/>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item
|
||||
label="备注"
|
||||
prop="remark"
|
||||
>
|
||||
<u-form-item label="备注" prop="remark">
|
||||
<u-textarea
|
||||
v-model="formData.remark"
|
||||
placeholder="其他需要说明的情况(选填)"
|
||||
@@ -203,6 +164,7 @@
|
||||
:show-word-limit="true"
|
||||
height="120"
|
||||
count
|
||||
placeholder-style="font-size: 24rpx"
|
||||
/>
|
||||
</u-form-item>
|
||||
</view>
|
||||
@@ -212,28 +174,20 @@
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<u-button
|
||||
type="default"
|
||||
plain
|
||||
type="info"
|
||||
size="large"
|
||||
@click="saveDraft"
|
||||
:loading="saving"
|
||||
style="margin-right: 20rpx;"
|
||||
:custom-style="draftButtonStyle"
|
||||
>
|
||||
保存草稿
|
||||
</u-button>
|
||||
<u-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="submitApplication"
|
||||
:loading="submitting"
|
||||
>
|
||||
<u-button type="primary" size="large" @click="submitApplication" :loading="submitting">
|
||||
提交申请
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page :loading="loading" loading-text="加载中..." />
|
||||
</view>
|
||||
@@ -246,7 +200,7 @@ import {
|
||||
getApplicationOptions,
|
||||
addApplication,
|
||||
updateApplication,
|
||||
getApplicationDetail
|
||||
getApplicationDetail,
|
||||
} from '@/api/workStudy.js';
|
||||
|
||||
export default {
|
||||
@@ -274,7 +228,7 @@ export default {
|
||||
applicationReason: '',
|
||||
personalAbility: '',
|
||||
timeArrangement: '',
|
||||
remark: ''
|
||||
remark: '',
|
||||
},
|
||||
|
||||
// 家庭经济情况选项(与web端保持一致)
|
||||
@@ -282,13 +236,25 @@ export default {
|
||||
{ value: '特困', label: '特困' },
|
||||
{ value: '贫困', label: '贫困' },
|
||||
{ value: '一般', label: '一般' },
|
||||
{ value: '良好', label: '良好' }
|
||||
{ value: '良好', label: '良好' },
|
||||
],
|
||||
|
||||
// 家庭经济情况选择器相关
|
||||
showFamilyStatusPicker: false,
|
||||
familyStatusIndex: [0],
|
||||
// uview-ui 2.x 单列选择器:使用纯字符串数组(二维)最稳定
|
||||
// 格式:[[字符串1, 字符串2, ...]] - 外层数组表示列,内层数组表示选项
|
||||
familyStatusColumns: [['特困', '贫困', '一般', '良好']],
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
saving: false,
|
||||
submitting: false
|
||||
submitting: false,
|
||||
|
||||
// 按钮样式
|
||||
draftButtonStyle: {
|
||||
marginRight: '20rpx',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -302,7 +268,7 @@ export default {
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '岗位ID不能为空',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
@@ -311,16 +277,54 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 家庭经济情况选择器确认
|
||||
// uview-ui 2.x Picker 组件 - 纯字符串数组模式
|
||||
// confirm 事件返回:{ value: [选中值1, 选中值2, ...], indexs: [索引1, 索引2, ...] }
|
||||
// 单列选择器:value 是 ['贫困'],indexs 是 [1]
|
||||
onFamilyStatusConfirm(e) {
|
||||
console.log('=== 家庭经济情况选择器确认 ===');
|
||||
console.log('完整事件参数:', e);
|
||||
console.log('e.value:', e.value); // 例如:['贫困']
|
||||
console.log('e.indexs:', e.indexs); // 例如:[1]
|
||||
|
||||
// 单列选择器,取第一列的值
|
||||
if (e && e.value && Array.isArray(e.value) && e.value.length > 0) {
|
||||
const selectedValue = e.value[0]; // 直接获取字符串,如 "贫困"
|
||||
console.log('选中的值:', selectedValue);
|
||||
|
||||
// 直接赋值
|
||||
this.formData.familyFinancialStatus = selectedValue;
|
||||
|
||||
// 更新索引用于回显
|
||||
if (e.indexs && Array.isArray(e.indexs)) {
|
||||
this.familyStatusIndex = e.indexs;
|
||||
console.log('更新索引为:', this.familyStatusIndex);
|
||||
}
|
||||
|
||||
console.log(
|
||||
'✅ 更新成功,familyFinancialStatus =',
|
||||
this.formData.familyFinancialStatus,
|
||||
);
|
||||
} else {
|
||||
console.error('❌ 无效的选择器返回值:', e);
|
||||
}
|
||||
|
||||
this.showFamilyStatusPicker = false;
|
||||
console.log('=== 选择器关闭 ===\n');
|
||||
},
|
||||
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// 并行加载数据
|
||||
const promises = [
|
||||
this.loadPositionData(),
|
||||
this.loadStudentInfo()
|
||||
];
|
||||
const promises = [this.loadPositionData(), this.loadStudentInfo()];
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
@@ -331,12 +335,11 @@ export default {
|
||||
|
||||
// 填充学生基本信息
|
||||
this.fillStudentInfo();
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载数据失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
@@ -373,13 +376,11 @@ export default {
|
||||
const data = response.data;
|
||||
|
||||
// 填充表单数据
|
||||
Object.keys(this.formData).forEach(key => {
|
||||
Object.keys(this.formData).forEach((key) => {
|
||||
if (data[key] !== undefined) {
|
||||
this.formData[key] = data[key];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
@@ -397,7 +398,6 @@ export default {
|
||||
if (this.mode === 'create') {
|
||||
this.formData.idCard = this.studentInfo.idCard || '';
|
||||
this.formData.contactPhone = this.studentInfo.phone || ''; // 修正:phone
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -415,24 +415,29 @@ export default {
|
||||
{ field: 'familyFinancialStatus', name: '家庭经济情况' },
|
||||
{ field: 'applicationReason', name: '申请理由' },
|
||||
{ field: 'personalAbility', name: '个人能力及经验' },
|
||||
{ field: 'timeArrangement', name: '时间安排' }
|
||||
{ field: 'timeArrangement', name: '时间安排' },
|
||||
];
|
||||
|
||||
for (const item of requiredFields) {
|
||||
if (!this.formData[item.field] || this.formData[item.field].trim() === '') {
|
||||
uni.showToast({
|
||||
title: `请填写${item.name}`,
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证身份证号格式
|
||||
if (this.formData.idCard && !/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(this.formData.idCard)) {
|
||||
if (
|
||||
this.formData.idCard &&
|
||||
!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(
|
||||
this.formData.idCard,
|
||||
)
|
||||
) {
|
||||
uni.showToast({
|
||||
title: '身份证号格式不正确',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -441,7 +446,7 @@ export default {
|
||||
if (this.formData.contactPhone && !/^1[3-9]\d{9}$/.test(this.formData.contactPhone)) {
|
||||
uni.showToast({
|
||||
title: '手机号格式不正确',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -460,7 +465,7 @@ export default {
|
||||
...this.formData,
|
||||
positionId: this.positionId,
|
||||
positionName: this.positionData.positionName,
|
||||
positionType: this.positionData.positionType || ''
|
||||
positionType: this.positionData.positionType || '',
|
||||
};
|
||||
|
||||
let response;
|
||||
@@ -474,7 +479,7 @@ export default {
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
if (autoNavigate) {
|
||||
@@ -490,7 +495,7 @@ export default {
|
||||
console.error('保存草稿失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '保存失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return false;
|
||||
} finally {
|
||||
@@ -506,7 +511,7 @@ export default {
|
||||
const { confirm } = await uni.showModal({
|
||||
title: '确认提交',
|
||||
content: '确定要提交申请吗?提交后将无法修改。',
|
||||
showCancel: true
|
||||
showCancel: true,
|
||||
});
|
||||
|
||||
if (!confirm) return; // 用户取消
|
||||
@@ -522,50 +527,49 @@ export default {
|
||||
|
||||
uni.showToast({
|
||||
title: '提交成功',
|
||||
icon: 'success'
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('提交申请失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '提交失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.apply-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
padding-bottom: 120rpx; /* 为底部按钮留空间 */
|
||||
}
|
||||
|
||||
.position-card {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.position-info {
|
||||
@@ -593,9 +597,9 @@ export default {
|
||||
.form-container {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
@@ -609,10 +613,10 @@ export default {
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 12rpx;
|
||||
border-bottom: 2rpx solid #1890ff;
|
||||
border-bottom: 2rpx solid #3c9cff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -623,7 +627,7 @@ export default {
|
||||
left: 0;
|
||||
width: 60rpx;
|
||||
height: 2rpx;
|
||||
background-color: #1890ff;
|
||||
background-color: #3c9cff;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
@@ -661,4 +665,27 @@ export default {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* picker输入框样式 */
|
||||
.picker-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 16rpx 24rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.picker-text.placeholder {
|
||||
font-size: 24rpx; /* placeholder 字体更小 */
|
||||
color: #c8c9cc;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<view class="detail-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="岗位详情" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 岗位基本信息 -->
|
||||
<view class="info-card" v-if="positionData">
|
||||
<view class="card-header">
|
||||
@@ -24,13 +31,17 @@
|
||||
<view class="core-info">
|
||||
<view class="info-item">
|
||||
<view class="info-label">
|
||||
<u-icon name="account" size="32" color="#1890ff" />
|
||||
<u-icon name="account" size="18" color="#303133" />
|
||||
<text>招录情况</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<view class="recruitment-info">
|
||||
<text class="recruitment-text">需求 {{ positionData.requiredCount }} 人</text>
|
||||
<text class="recruitment-text current">已招录 {{ positionData.currentCount || 0 }} 人</text>
|
||||
<text class="recruitment-text">
|
||||
需求 {{ positionData.requiredCount }} 人
|
||||
</text>
|
||||
<text class="recruitment-text current">
|
||||
已招录 {{ positionData.currentCount || 0 }} 人
|
||||
</text>
|
||||
</view>
|
||||
<u-line-progress
|
||||
:percent="getRecruitmentProgress()"
|
||||
@@ -43,7 +54,7 @@
|
||||
|
||||
<view class="info-item">
|
||||
<view class="info-label">
|
||||
<u-icon name="clock" size="32" color="#1890ff" />
|
||||
<u-icon name="clock" size="18" color="#303133" />
|
||||
<text>工作时间</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
@@ -53,17 +64,19 @@
|
||||
|
||||
<view class="info-item">
|
||||
<view class="info-label">
|
||||
<u-icon name="calendar" size="32" color="#1890ff" />
|
||||
<u-icon name="calendar" size="18" color="#303133" />
|
||||
<text>有效期限</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value">{{ positionData.startDate }} 至 {{ positionData.endDate }}</text>
|
||||
<text class="info-value">
|
||||
{{ positionData.startDate }} 至 {{ positionData.endDate }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<view class="info-label">
|
||||
<u-icon name="account" size="32" color="#1890ff" />
|
||||
<u-icon name="account" size="18" color="#303133" />
|
||||
<text>联系老师</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
@@ -73,17 +86,19 @@
|
||||
|
||||
<view class="info-item">
|
||||
<view class="info-label">
|
||||
<u-icon name="phone" size="32" color="#1890ff" />
|
||||
<u-icon name="phone" size="18" color="#303133" />
|
||||
<text>联系电话</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
<text class="info-value contact-phone" @click="callPhone">{{ positionData.contactPhone }}</text>
|
||||
<text class="info-value contact-phone" @click="callPhone">
|
||||
{{ positionData.contactPhone }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="positionData.salaryInfo">
|
||||
<view class="info-label">
|
||||
<u-icon name="rmb-circle" size="32" color="#1890ff" />
|
||||
<u-icon name="rmb-circle" size="18" color="#303133" />
|
||||
<text>薪资情况</text>
|
||||
</view>
|
||||
<view class="info-content">
|
||||
@@ -96,7 +111,7 @@
|
||||
<!-- 岗位要求 -->
|
||||
<view class="content-card" v-if="positionData && positionData.positionRequirements">
|
||||
<view class="card-title">
|
||||
<u-icon name="file-text" size="32" color="#1890ff" />
|
||||
<u-icon name="file-text" size="18" color="#303133" />
|
||||
<text>岗位要求</text>
|
||||
</view>
|
||||
<view class="content-text">{{ positionData.positionRequirements }}</view>
|
||||
@@ -105,7 +120,7 @@
|
||||
<!-- 工作内容 -->
|
||||
<view class="content-card" v-if="positionData && positionData.workContent">
|
||||
<view class="card-title">
|
||||
<u-icon name="list" size="32" color="#1890ff" />
|
||||
<u-icon name="list" size="18" color="#303133" />
|
||||
<text>工作内容</text>
|
||||
</view>
|
||||
<view class="content-text">{{ positionData.workContent }}</view>
|
||||
@@ -114,7 +129,7 @@
|
||||
<!-- 申请状态 -->
|
||||
<view class="status-card" v-if="applicationStatus">
|
||||
<view class="card-title">
|
||||
<u-icon name="info-circle" size="32" color="#1890ff" />
|
||||
<u-icon name="info-circle" size="18" color="#303133" />
|
||||
<text>我的申请状态</text>
|
||||
</view>
|
||||
<view class="status-content">
|
||||
@@ -131,7 +146,10 @@
|
||||
<text class="status-label">申请时间:</text>
|
||||
<text class="status-value">{{ applicationStatus.lastApplicationTime }}</text>
|
||||
</view>
|
||||
<view class="status-actions" v-if="applicationStatus.canEdit || applicationStatus.canCancel">
|
||||
<view
|
||||
class="status-actions"
|
||||
v-if="applicationStatus.canEdit || applicationStatus.canCancel"
|
||||
>
|
||||
<u-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@@ -145,7 +163,7 @@
|
||||
size="small"
|
||||
@click="cancelApplication"
|
||||
v-if="applicationStatus.canCancel"
|
||||
style="margin-left: 20rpx;"
|
||||
style="margin-left: 20rpx"
|
||||
>
|
||||
撤销申请
|
||||
</u-button>
|
||||
@@ -156,7 +174,7 @@
|
||||
<!-- 备注信息 -->
|
||||
<view class="content-card" v-if="positionData && positionData.remark">
|
||||
<view class="card-title">
|
||||
<u-icon name="chat" size="32" color="#1890ff" />
|
||||
<u-icon name="chat" size="18" color="#303133" />
|
||||
<text>备注信息</text>
|
||||
</view>
|
||||
<view class="content-text">{{ positionData.remark }}</view>
|
||||
@@ -181,10 +199,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getPositionWithApplicationStatus,
|
||||
cancelApplication
|
||||
} from '@/api/workStudy.js';
|
||||
import { getPositionWithApplicationStatus, cancelApplication } from '@/api/workStudy.js';
|
||||
|
||||
export default {
|
||||
name: 'WorkStudyDetail',
|
||||
@@ -194,7 +209,7 @@ export default {
|
||||
positionData: null,
|
||||
applicationStatus: null,
|
||||
loading: false,
|
||||
submitting: false
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -205,7 +220,7 @@ export default {
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '岗位ID不能为空',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
@@ -214,6 +229,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 加载岗位详情
|
||||
async loadPositionDetail() {
|
||||
this.loading = true;
|
||||
@@ -231,7 +251,7 @@ export default {
|
||||
console.error('加载岗位详情失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -247,20 +267,24 @@ export default {
|
||||
if (!this.canApply()) {
|
||||
uni.showToast({
|
||||
title: this.getApplyButtonText(),
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已有申请且可以编辑,则编辑申请
|
||||
if (this.applicationStatus && this.applicationStatus.hasApplication && this.applicationStatus.canEdit) {
|
||||
if (
|
||||
this.applicationStatus &&
|
||||
this.applicationStatus.hasApplication &&
|
||||
this.applicationStatus.canEdit
|
||||
) {
|
||||
this.editApplication();
|
||||
return;
|
||||
}
|
||||
|
||||
// 否则新建申请
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/apply?positionId=${this.positionId}`
|
||||
url: `/pages/workStudy/apply?positionId=${this.positionId}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -268,7 +292,7 @@ export default {
|
||||
editApplication() {
|
||||
if (this.applicationStatus && this.applicationStatus.applicationId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/apply?positionId=${this.positionId}&applicationId=${this.applicationStatus.applicationId}&mode=edit`
|
||||
url: `/pages/workStudy/apply?positionId=${this.positionId}&applicationId=${this.applicationStatus.applicationId}&mode=edit`,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -283,7 +307,7 @@ export default {
|
||||
const { confirm } = await uni.showModal({
|
||||
title: '确认撤销',
|
||||
content: '确定要撤销此申请吗?',
|
||||
showCancel: true
|
||||
showCancel: true,
|
||||
});
|
||||
|
||||
if (!confirm) return; // 用户取消
|
||||
@@ -294,7 +318,7 @@ export default {
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '撤销成功',
|
||||
icon: 'success'
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
// 重新加载数据
|
||||
@@ -308,7 +332,7 @@ export default {
|
||||
console.error('撤销申请失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '撤销失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.submitting = false;
|
||||
@@ -324,9 +348,9 @@ export default {
|
||||
console.error('拨打电话失败:', error);
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -334,13 +358,13 @@ export default {
|
||||
// 获取状态文字
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'DRAFT': '草稿',
|
||||
'PUBLISHED': '已发布',
|
||||
'RECRUITING': '招录中',
|
||||
'FULL': '已满员',
|
||||
'EXPIRED': '已过期',
|
||||
'SUSPENDED': '已暂停',
|
||||
'CANCELLED': '已取消'
|
||||
DRAFT: '草稿',
|
||||
PUBLISHED: '已发布',
|
||||
RECRUITING: '招录中',
|
||||
FULL: '已满员',
|
||||
EXPIRED: '已过期',
|
||||
SUSPENDED: '已暂停',
|
||||
CANCELLED: '已取消',
|
||||
};
|
||||
return statusMap[status] || '未知状态';
|
||||
},
|
||||
@@ -348,13 +372,13 @@ export default {
|
||||
// 获取状态类型
|
||||
getStatusType(status) {
|
||||
const typeMap = {
|
||||
'DRAFT': 'info',
|
||||
'PUBLISHED': 'warning',
|
||||
'RECRUITING': 'success',
|
||||
'FULL': 'primary',
|
||||
'EXPIRED': 'error',
|
||||
'SUSPENDED': 'warning',
|
||||
'CANCELLED': 'error'
|
||||
DRAFT: 'info',
|
||||
PUBLISHED: 'warning',
|
||||
RECRUITING: 'success',
|
||||
FULL: 'primary',
|
||||
EXPIRED: 'error',
|
||||
SUSPENDED: 'warning',
|
||||
CANCELLED: 'error',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
},
|
||||
@@ -386,8 +410,10 @@ export default {
|
||||
// 如果已有申请,判断是否可以重新申请
|
||||
if (this.applicationStatus && this.applicationStatus.hasApplication) {
|
||||
// 如果申请被拒绝,可以重新申请
|
||||
return this.applicationStatus.applicationStatus === 'FINAL_REJECTED' ||
|
||||
this.applicationStatus.applicationStatus === 'COLLEGE_REJECTED';
|
||||
return (
|
||||
this.applicationStatus.applicationStatus === 'FINAL_REJECTED' ||
|
||||
this.applicationStatus.applicationStatus === 'COLLEGE_REJECTED'
|
||||
);
|
||||
}
|
||||
|
||||
// 检查是否已满员
|
||||
@@ -417,7 +443,11 @@ export default {
|
||||
|
||||
if (status === 'DRAFT') {
|
||||
return '完善申请信息';
|
||||
} else if (status === 'SUBMITTED' || status === 'COLLEGE_REVIEWING' || status === 'FINAL_REVIEWING') {
|
||||
} else if (
|
||||
status === 'SUBMITTED' ||
|
||||
status === 'COLLEGE_REVIEWING' ||
|
||||
status === 'FINAL_REVIEWING'
|
||||
) {
|
||||
return '申请审核中';
|
||||
} else if (status === 'FINAL_APPROVED') {
|
||||
return '申请已通过';
|
||||
@@ -436,35 +466,37 @@ export default {
|
||||
// 获取申请状态类型
|
||||
getApplicationStatusType(status) {
|
||||
const typeMap = {
|
||||
'DRAFT': 'info',
|
||||
'SUBMITTED': 'warning',
|
||||
'COLLEGE_REVIEWING': 'warning',
|
||||
'COLLEGE_APPROVED': 'success',
|
||||
'COLLEGE_REJECTED': 'error',
|
||||
'FINAL_REVIEWING': 'warning',
|
||||
'FINAL_APPROVED': 'success',
|
||||
'FINAL_REJECTED': 'error',
|
||||
'CANCELLED': 'info'
|
||||
DRAFT: 'info',
|
||||
SUBMITTED: 'warning',
|
||||
COLLEGE_REVIEWING: 'warning',
|
||||
COLLEGE_APPROVED: 'success',
|
||||
COLLEGE_REJECTED: 'error',
|
||||
FINAL_REVIEWING: 'warning',
|
||||
FINAL_APPROVED: 'success',
|
||||
FINAL_REJECTED: 'error',
|
||||
CANCELLED: 'info',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
padding-bottom: 120rpx; /* 为底部按钮留空间 */
|
||||
}
|
||||
|
||||
.info-card, .content-card, .status-card {
|
||||
.info-card,
|
||||
.content-card,
|
||||
.status-card {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -481,7 +513,7 @@ export default {
|
||||
.position-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@@ -530,7 +562,7 @@ export default {
|
||||
.info-label text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
@@ -540,7 +572,7 @@ export default {
|
||||
|
||||
.info-value {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@@ -566,7 +598,7 @@ export default {
|
||||
}
|
||||
|
||||
.recruitment-text.current {
|
||||
color: #1890ff;
|
||||
color: #0051d9;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -579,15 +611,15 @@ export default {
|
||||
}
|
||||
|
||||
.card-title text {
|
||||
font-size: 32rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<template>
|
||||
<view class="work-study-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="勤工俭学" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-container">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索岗位名称或联系老师"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
height="72"
|
||||
bg-color="#f5f6fa"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
@@ -55,19 +61,14 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleFilter"
|
||||
:loading="loading"
|
||||
>
|
||||
<u-button type="primary" size="small" @click="handleFilter" :loading="loading">
|
||||
筛选
|
||||
</u-button>
|
||||
<u-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click="resetFilter"
|
||||
style="margin-left: 10rpx;"
|
||||
style="margin-left: 10rpx"
|
||||
>
|
||||
重置
|
||||
</u-button>
|
||||
@@ -107,7 +108,9 @@
|
||||
<view class="card-header">
|
||||
<view class="position-info">
|
||||
<view class="position-name">{{ item.positionName }}</view>
|
||||
<view class="position-type">{{ item.positionScopeName || '未设置' }}</view>
|
||||
<view class="position-type">
|
||||
{{ item.positionScopeName || '未设置' }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-badge">
|
||||
<u-tag
|
||||
@@ -122,30 +125,36 @@
|
||||
<!-- 岗位信息 -->
|
||||
<view class="card-content">
|
||||
<view class="info-row">
|
||||
<u-icon name="account" size="28" color="#666" />
|
||||
<u-icon name="account" size="18" color="#666" />
|
||||
<text class="info-text">需求人数:{{ item.requiredCount }}人</text>
|
||||
<text class="info-text current-count">已招录:{{ item.currentCount || 0 }}人</text>
|
||||
<text class="info-text current-count">
|
||||
已招录:{{ item.currentCount || 0 }}人
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="clock" size="28" color="#666" />
|
||||
<u-icon name="clock" size="18" color="#666" />
|
||||
<text class="info-text">工作时间:{{ item.workTime }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="map" size="28" color="#666" />
|
||||
<u-icon name="map" size="18" color="#666" />
|
||||
<text class="info-text">联系老师:{{ item.contactTeacher }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="phone" size="28" color="#666" />
|
||||
<u-icon name="phone" size="18" color="#666" />
|
||||
<text class="info-text">联系电话:{{ item.contactPhone }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<u-icon name="calendar" size="28" color="#666" />
|
||||
<text class="info-text">有效期:{{ item.startDate }} 至 {{ item.endDate }}</text>
|
||||
<u-icon name="calendar" size="18" color="#666" />
|
||||
<text class="info-text">
|
||||
有效期:{{ item.startDate }} 至 {{ item.endDate }}
|
||||
</text>
|
||||
</view>
|
||||
<!-- 申请状态信息 -->
|
||||
<view class="info-row" v-if="getPositionApplicationStatus(item)">
|
||||
<u-icon name="checkmark-circle" size="28" color="#1890ff" />
|
||||
<text class="info-text" style="color: #1890ff;">我的申请:{{ getPositionApplicationStatus(item) }}</text>
|
||||
<u-icon name="checkmark-circle" size="18" color="#1890ff" />
|
||||
<text class="info-text" style="color: #1890ff">
|
||||
我的申请:{{ getPositionApplicationStatus(item) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -163,7 +172,7 @@
|
||||
size="small"
|
||||
@click.stop="handleApply($event, item)"
|
||||
:disabled="!canApply(item)"
|
||||
style="margin-left: 20rpx;"
|
||||
style="margin-left: 20rpx"
|
||||
>
|
||||
{{ getApplyButtonText(item) }}
|
||||
</u-button>
|
||||
@@ -173,20 +182,12 @@
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-else-if="!loading">
|
||||
<u-empty
|
||||
text="暂无岗位信息"
|
||||
icon="search"
|
||||
:show="true"
|
||||
/>
|
||||
<u-empty text="暂无岗位信息" icon="search" :show="true" />
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="hasMore && positionList.length > 0">
|
||||
<u-loadmore
|
||||
:status="loadMoreStatus"
|
||||
:load-text="loadText"
|
||||
@loadmore="loadMore"
|
||||
/>
|
||||
<u-loadmore :status="loadMoreStatus" :load-text="loadText" @loadmore="loadMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -218,16 +219,22 @@
|
||||
<!-- 申请状态 -->
|
||||
<view class="card-content">
|
||||
<view class="info-row">
|
||||
<u-icon name="file-text" size="28" color="#666" />
|
||||
<text class="info-text">申请状态:{{ getApplicationStatusText(item) }}</text>
|
||||
<u-icon name="file-text" size="18" color="#666" />
|
||||
<text class="info-text">
|
||||
申请状态:{{ getApplicationStatusText(item) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="item.collegeReviewStatus">
|
||||
<u-icon name="checkmark-circle" size="28" color="#666" />
|
||||
<text class="info-text">学院审核:{{ getReviewStatusText(item.collegeReviewStatus) }}</text>
|
||||
<u-icon name="checkmark-circle" size="18" color="#666" />
|
||||
<text class="info-text">
|
||||
学院审核:{{ getReviewStatusText(item.collegeReviewStatus) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row" v-if="item.finalReviewStatus">
|
||||
<u-icon name="checkmark-circle" size="28" color="#666" />
|
||||
<text class="info-text">学生处审核:{{ getReviewStatusText(item.finalReviewStatus) }}</text>
|
||||
<u-icon name="checkmark-circle" size="18" color="#666" />
|
||||
<text class="info-text">
|
||||
学生处审核:{{ getReviewStatusText(item.finalReviewStatus) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -245,7 +252,7 @@
|
||||
size="small"
|
||||
@click.stop="handleCancelApplication($event, item)"
|
||||
v-if="canCancelApplication(item)"
|
||||
style="margin-left: 20rpx;"
|
||||
style="margin-left: 20rpx"
|
||||
>
|
||||
撤销申请
|
||||
</u-button>
|
||||
@@ -255,11 +262,7 @@
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-else-if="!loading">
|
||||
<u-empty
|
||||
text="暂无申请记录"
|
||||
icon="file-text"
|
||||
:show="true"
|
||||
/>
|
||||
<u-empty text="暂无申请记录" icon="file-text" :show="true" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -292,7 +295,7 @@ import {
|
||||
getMyApplications,
|
||||
cancelApplication,
|
||||
getCurrentStudentInfo,
|
||||
getStudentApplicationStatusList
|
||||
getStudentApplicationStatusList,
|
||||
} from '@/api/workStudy.js';
|
||||
|
||||
export default {
|
||||
@@ -304,10 +307,7 @@ export default {
|
||||
|
||||
// 标签页
|
||||
currentTab: 0, // 确保是数字类型
|
||||
tabsList: [
|
||||
{ name: '可申请岗位' },
|
||||
{ name: '我的申请' }
|
||||
],
|
||||
tabsList: [{ name: '可申请岗位' }, { name: '我的申请' }],
|
||||
|
||||
// 筛选条件
|
||||
showFilter: false,
|
||||
@@ -321,16 +321,16 @@ export default {
|
||||
[
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '本学院', value: 'COLLEGE' },
|
||||
{ label: '全校', value: 'UNIVERSITY' }
|
||||
]
|
||||
{ label: '全校', value: 'UNIVERSITY' },
|
||||
],
|
||||
],
|
||||
statusOptions: [
|
||||
[
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '已发布', value: 'PUBLISHED' },
|
||||
{ label: '招录中', value: 'RECRUITING' },
|
||||
{ label: '已暂停', value: 'SUSPENDED' }
|
||||
]
|
||||
{ label: '已暂停', value: 'SUSPENDED' },
|
||||
],
|
||||
],
|
||||
|
||||
// 查询参数
|
||||
@@ -339,7 +339,7 @@ export default {
|
||||
pageSize: 10,
|
||||
positionName: '',
|
||||
positionScope: '',
|
||||
status: ''
|
||||
status: '',
|
||||
},
|
||||
|
||||
// 列表数据
|
||||
@@ -352,7 +352,7 @@ export default {
|
||||
loadText: {
|
||||
loadmore: '点击或上拉加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了'
|
||||
nomore: '没有更多了',
|
||||
},
|
||||
|
||||
// 加载状态
|
||||
@@ -363,7 +363,7 @@ export default {
|
||||
studentInfo: null,
|
||||
|
||||
// 学生申请状态列表(用于判断申请状态)
|
||||
studentApplications: []
|
||||
studentApplications: [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -400,6 +400,11 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
try {
|
||||
@@ -413,7 +418,7 @@ export default {
|
||||
console.error('初始化数据失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载数据失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -481,7 +486,7 @@ export default {
|
||||
console.error('加载岗位列表失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
this.loadMoreStatus = 'loadmore';
|
||||
} finally {
|
||||
@@ -508,7 +513,7 @@ export default {
|
||||
console.error('加载申请列表失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '加载失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -588,7 +593,7 @@ export default {
|
||||
item = event;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/detail?id=${item.id}`
|
||||
url: `/pages/workStudy/detail?id=${item.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -599,12 +604,17 @@ export default {
|
||||
position = event;
|
||||
}
|
||||
|
||||
const application = this.studentApplications.find(app => app.positionId === position.id);
|
||||
const application = this.studentApplications.find(
|
||||
(app) => app.positionId === position.id,
|
||||
);
|
||||
|
||||
// 如果已有申请且状态为已提交或审核中,跳转到申请管理页面
|
||||
if (application && ['submitted', 'college_reviewing', 'final_reviewing'].includes(application.status)) {
|
||||
if (
|
||||
application &&
|
||||
['submitted', 'college_reviewing', 'final_reviewing'].includes(application.status)
|
||||
) {
|
||||
uni.switchTab({
|
||||
url: '/pages/workStudy/index'
|
||||
url: '/pages/workStudy/index',
|
||||
});
|
||||
// 切换到"我的申请"标签页
|
||||
this.currentTab = 1;
|
||||
@@ -616,7 +626,7 @@ export default {
|
||||
if (application && application.status === 'approved') {
|
||||
uni.showToast({
|
||||
title: '您已被该岗位录用',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -624,13 +634,13 @@ export default {
|
||||
if (!this.canApply(position)) {
|
||||
uni.showToast({
|
||||
title: '该岗位当前不可申请',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/apply?positionId=${position.id}`
|
||||
url: `/pages/workStudy/apply?positionId=${position.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -641,7 +651,7 @@ export default {
|
||||
item = event;
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/workStudy/applicationDetail?id=${item.id}`
|
||||
url: `/pages/workStudy/applicationDetail?id=${item.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -655,7 +665,7 @@ export default {
|
||||
const { confirm } = await uni.showModal({
|
||||
title: '确认撤销',
|
||||
content: '确定要撤销此申请吗?',
|
||||
showCancel: true
|
||||
showCancel: true,
|
||||
});
|
||||
|
||||
if (!confirm) return; // 用户取消
|
||||
@@ -664,7 +674,7 @@ export default {
|
||||
if (response.code === 200) {
|
||||
uni.showToast({
|
||||
title: '撤销成功',
|
||||
icon: 'success'
|
||||
icon: 'success',
|
||||
});
|
||||
this.loadMyApplications(true);
|
||||
} else {
|
||||
@@ -674,7 +684,7 @@ export default {
|
||||
console.error('撤销申请失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '撤销失败',
|
||||
icon: 'none'
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -700,20 +710,22 @@ export default {
|
||||
// 获取状态类型
|
||||
getStatusType(status) {
|
||||
const typeMap = {
|
||||
'DRAFT': 'info',
|
||||
'PUBLISHED': 'warning',
|
||||
'RECRUITING': 'success',
|
||||
'FULL': 'primary',
|
||||
'EXPIRED': 'error',
|
||||
'SUSPENDED': 'warning',
|
||||
'CANCELLED': 'error'
|
||||
DRAFT: 'info',
|
||||
PUBLISHED: 'warning',
|
||||
RECRUITING: 'success',
|
||||
FULL: 'primary',
|
||||
EXPIRED: 'error',
|
||||
SUSPENDED: 'warning',
|
||||
CANCELLED: 'error',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
},
|
||||
|
||||
// 判断是否可以申请(完全参考web端实现)
|
||||
canApply(position) {
|
||||
const application = this.studentApplications.find(app => app.positionId === position.id);
|
||||
const application = this.studentApplications.find(
|
||||
(app) => app.positionId === position.id,
|
||||
);
|
||||
|
||||
// 如果已录用,禁用按钮
|
||||
if (application && application.status === 'approved') {
|
||||
@@ -745,7 +757,9 @@ export default {
|
||||
|
||||
// 获取申请按钮文字(参考web端实现8种状态)
|
||||
getApplyButtonText(position) {
|
||||
const application = this.studentApplications.find(app => app.positionId === position.id);
|
||||
const application = this.studentApplications.find(
|
||||
(app) => app.positionId === position.id,
|
||||
);
|
||||
|
||||
// 如果已录用
|
||||
if (application && application.status === 'approved') {
|
||||
@@ -753,7 +767,10 @@ export default {
|
||||
}
|
||||
|
||||
// 如果有申请且状态为审核中
|
||||
if (application && ['submitted', 'college_reviewing', 'final_reviewing'].includes(application.status)) {
|
||||
if (
|
||||
application &&
|
||||
['submitted', 'college_reviewing', 'final_reviewing'].includes(application.status)
|
||||
) {
|
||||
return '申请审核中';
|
||||
}
|
||||
|
||||
@@ -836,13 +853,13 @@ export default {
|
||||
const status = item.status ? item.status.toLowerCase() : '';
|
||||
|
||||
const typeMap = {
|
||||
'draft': 'info',
|
||||
'submitted': 'warning',
|
||||
'college_reviewing': 'warning',
|
||||
'final_reviewing': 'warning',
|
||||
'approved': 'success',
|
||||
'rejected': 'error',
|
||||
'cancelled': 'info'
|
||||
draft: 'info',
|
||||
submitted: 'warning',
|
||||
college_reviewing: 'warning',
|
||||
final_reviewing: 'warning',
|
||||
approved: 'success',
|
||||
rejected: 'error',
|
||||
cancelled: 'info',
|
||||
};
|
||||
return typeMap[status] || 'default';
|
||||
},
|
||||
@@ -850,9 +867,9 @@ export default {
|
||||
// 获取审核状态文字
|
||||
getReviewStatusText(status) {
|
||||
const statusMap = {
|
||||
'PENDING': '待审核',
|
||||
'APPROVED': '通过',
|
||||
'REJECTED': '未通过'
|
||||
PENDING: '待审核',
|
||||
APPROVED: '通过',
|
||||
REJECTED: '未通过',
|
||||
};
|
||||
return statusMap[status] || '未知';
|
||||
},
|
||||
@@ -863,31 +880,35 @@ export default {
|
||||
|
||||
// 支持两种格式:web端的小写格式和移动端的大写格式
|
||||
const status = item.status ? item.status.toLowerCase() : '';
|
||||
return ['draft', 'submitted', 'college_reviewing'].includes(status) ||
|
||||
['DRAFT', 'SUBMITTED', 'COLLEGE_REVIEWING'].includes(item.status);
|
||||
return (
|
||||
['draft', 'submitted', 'college_reviewing'].includes(status) ||
|
||||
['DRAFT', 'SUBMITTED', 'COLLEGE_REVIEWING'].includes(item.status)
|
||||
);
|
||||
},
|
||||
|
||||
// 获取岗位的申请状态(用于在岗位卡片中显示)
|
||||
getPositionApplicationStatus(position) {
|
||||
const application = this.studentApplications.find(app => app.positionId === position.id);
|
||||
const application = this.studentApplications.find(
|
||||
(app) => app.positionId === position.id,
|
||||
);
|
||||
if (!application) return null;
|
||||
|
||||
return this.getApplicationStatusText(application);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.work-study-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
.search-container {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
@@ -927,12 +948,13 @@ export default {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.position-card, .application-card {
|
||||
.position-card,
|
||||
.application-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
@@ -942,18 +964,20 @@ export default {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.position-info, .application-info {
|
||||
.position-info,
|
||||
.application-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.position-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
color: #303133;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.position-type, .apply-time {
|
||||
.position-type,
|
||||
.apply-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
@@ -974,14 +998,14 @@ export default {
|
||||
|
||||
.info-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
color: #606266;
|
||||
margin-left: 12rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.current-count {
|
||||
margin-left: 20rpx;
|
||||
color: #1890ff;
|
||||
color: #0051d9;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
<template>
|
||||
<view class="dorm-application-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-container">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入姓名或学号"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
:animation="true"
|
||||
@search="handleQuery"
|
||||
@custom="handleQuery"
|
||||
@clear="handleClear"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page
|
||||
:loading="loading && dataList.length === 0"
|
||||
loading-text="加载中..."
|
||||
></u-loading-page>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
|
||||
<!-- 空状态 -->
|
||||
<view v-if="dataList.length === 0" class="empty-container">
|
||||
<u-empty text="暂无调宿/退宿申请记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
:lower-threshold="50"
|
||||
@scrolltolower="handleLoadMore"
|
||||
>
|
||||
<view class="list-container">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="item.id || index"
|
||||
class="list-card"
|
||||
>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="card-header">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName || '-' }}</text>
|
||||
<text class="student-no">{{ item.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<u-tag
|
||||
:text="getApplicationTypeText(item.applicationType)"
|
||||
:type="getApplicationTypeTagType(item.applicationType)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="info-row">
|
||||
<text class="label">学院:</text>
|
||||
<text class="value">{{ item.collegeName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">专业:</text>
|
||||
<text class="value">{{ item.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">班级:</text>
|
||||
<text class="value">{{ item.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">当前宿舍:</text>
|
||||
<text class="value">
|
||||
{{ item.currentBuildingName }}{{ item.currentRoomNo
|
||||
}}{{ item.currentBedNo }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="item.applicationType === '1'" class="info-row">
|
||||
<text class="label">目标宿舍:</text>
|
||||
<text class="value">
|
||||
{{ item.targetBuildingName }}{{ item.targetRoomNo
|
||||
}}{{ item.targetBedNo }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">审批状态:</text>
|
||||
<u-tag
|
||||
:text="getApprovalStatusText(item.applicationStatus)"
|
||||
:type="getApprovalStatusTagType(item.applicationStatus)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">申请时间:</text>
|
||||
<text class="value">{{ formatTime(item.applyTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="card-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleViewApproval(item)"
|
||||
>
|
||||
<u-icon
|
||||
name="list"
|
||||
size="16"
|
||||
color="#fff"
|
||||
style="margin-right: 4px"
|
||||
></u-icon>
|
||||
审批记录
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="load-more-wrapper">
|
||||
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 审批记录组件 -->
|
||||
<approval-record ref="approvalRecordRef"></approval-record>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||||
|
||||
export default {
|
||||
name: 'DormApplicationTab',
|
||||
components: {
|
||||
ApprovalRecord,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dataList: [],
|
||||
total: 0,
|
||||
searchKeyword: '', // 搜索关键字(姓名或学号)
|
||||
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
},
|
||||
|
||||
// 加载更多状态
|
||||
loadMoreStatus: 'loadmore',
|
||||
loadMoreText: {
|
||||
loadmore: '点击加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了',
|
||||
},
|
||||
|
||||
// 字典数据
|
||||
dictApplicationType: [],
|
||||
dictApprovalStatus: [],
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
await this.loadDictData();
|
||||
await this.getList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载字典数据
|
||||
async loadDictData() {
|
||||
try {
|
||||
this.dictApplicationType = await getDict('dorm_application_type');
|
||||
this.dictApprovalStatus = await getDict('student_leave_approval_status');
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
async getList() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 根据搜索关键字设置查询参数
|
||||
const params = {
|
||||
...this.queryParams,
|
||||
studentName: this.searchKeyword,
|
||||
studentNo: this.searchKeyword,
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: '/studentDynamics/dormApplicationList',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
if (this.queryParams.pageNum === 1) {
|
||||
this.dataList = res.rows || [];
|
||||
} else {
|
||||
this.dataList = [...this.dataList, ...(res.rows || [])];
|
||||
}
|
||||
this.total = res.total || 0;
|
||||
|
||||
// 更新加载更多状态
|
||||
if (this.dataList.length >= this.total) {
|
||||
this.loadMoreStatus = 'nomore';
|
||||
} else {
|
||||
this.loadMoreStatus = 'loadmore';
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取调宿退宿申请列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
handleLoadMore() {
|
||||
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadMoreStatus = 'loading';
|
||||
this.queryParams.pageNum++;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 搜索操作
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.dataList = [];
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 清空搜索
|
||||
handleClear() {
|
||||
this.searchKeyword = '';
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(timeStr) {
|
||||
if (!timeStr) return '-';
|
||||
try {
|
||||
const date = new Date(timeStr);
|
||||
if (isNaN(date.getTime())) return timeStr;
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
} catch (error) {
|
||||
console.error('时间格式化失败:', error);
|
||||
return timeStr;
|
||||
}
|
||||
},
|
||||
|
||||
// 获取申请类型文本
|
||||
getApplicationTypeText(value) {
|
||||
return getDictLabel(this.dictApplicationType, value) || '-';
|
||||
},
|
||||
|
||||
// 获取申请类型标签类型
|
||||
getApplicationTypeTagType(value) {
|
||||
const fallbackMap = {
|
||||
1: 'primary', // 调宿
|
||||
2: 'warning', // 退宿
|
||||
};
|
||||
return getDictTagType(this.dictApplicationType, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 获取审批状态文本
|
||||
getApprovalStatusText(value) {
|
||||
return getDictLabel(this.dictApprovalStatus, value) || '-';
|
||||
},
|
||||
|
||||
// 获取审批状态标签类型
|
||||
getApprovalStatusTagType(value) {
|
||||
const fallbackMap = {
|
||||
0: 'warning', // 待审批
|
||||
1: 'success', // 已通过
|
||||
2: 'error', // 已驳回
|
||||
};
|
||||
return getDictTagType(this.dictApprovalStatus, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 查看审批记录
|
||||
handleViewApproval(item) {
|
||||
console.log('查看审批记录:', item);
|
||||
if (!item.id) {
|
||||
uni.showToast({
|
||||
title: '数据异常,无法查看审批记录',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 调用审批记录组件,传入业务ID
|
||||
this.$refs.approvalRecordRef.init(item.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dorm-application-tab {
|
||||
background-color: #f5f6fa;
|
||||
min-height: calc(100vh - 176rpx);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 100rpx 20rpx;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
|
||||
}
|
||||
|
||||
.list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
min-width: 140rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.load-more-wrapper {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
// uview组件样式调整
|
||||
::v-deep .u-search {
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,392 @@
|
||||
<template>
|
||||
<view class="holiday-stay-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-container">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入姓名或学号"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
:animation="true"
|
||||
@search="handleQuery"
|
||||
@custom="handleQuery"
|
||||
@clear="handleClear"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page
|
||||
:loading="loading && dataList.length === 0"
|
||||
loading-text="加载中..."
|
||||
></u-loading-page>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
|
||||
<!-- 空状态 -->
|
||||
<view v-if="dataList.length === 0" class="empty-container">
|
||||
<u-empty text="暂无假期留宿记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
:lower-threshold="50"
|
||||
@scrolltolower="handleLoadMore"
|
||||
>
|
||||
<view class="list-container">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="item.id || index"
|
||||
class="list-card"
|
||||
>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="card-header">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName || '-' }}</text>
|
||||
<text class="student-no">{{ item.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<u-tag
|
||||
:text="getHolidayTypeText(item.holidayType)"
|
||||
:type="getHolidayTypeTagType(item.holidayType)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="info-row">
|
||||
<text class="label">学院:</text>
|
||||
<text class="value">{{ item.collegeName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">专业:</text>
|
||||
<text class="value">{{ item.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">班级:</text>
|
||||
<text class="value">{{ item.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">宿舍信息:</text>
|
||||
<text class="value">
|
||||
{{ item.buildingName }}{{ item.roomName }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">留宿时间:</text>
|
||||
<text class="value">
|
||||
{{ item.applyStartDate }} 至 {{ item.applyEndDate }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">留宿原因:</text>
|
||||
<u-tag
|
||||
:text="getStayReasonTypeText(item.stayReasonType)"
|
||||
:type="getStayReasonTypeTagType(item.stayReasonType)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">联系电话:</text>
|
||||
<text class="value">{{ item.studentPhone || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">紧急联系人:</text>
|
||||
<text class="value">{{ item.emergencyContact || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 - 暂不开发
|
||||
<view class="card-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleView(item)"
|
||||
>查看</u-button>
|
||||
</view>
|
||||
-->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="load-more-wrapper">
|
||||
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
|
||||
export default {
|
||||
name: 'HolidayStayTab',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dataList: [],
|
||||
total: 0,
|
||||
searchKeyword: '', // 搜索关键字(姓名或学号)
|
||||
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
},
|
||||
|
||||
// 加载更多状态
|
||||
loadMoreStatus: 'loadmore',
|
||||
loadMoreText: {
|
||||
loadmore: '点击加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了',
|
||||
},
|
||||
|
||||
// 字典数据
|
||||
dictHolidayType: [],
|
||||
dictStayReasonType: [],
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
await this.loadDictData();
|
||||
await this.getList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载字典数据
|
||||
async loadDictData() {
|
||||
try {
|
||||
this.dictHolidayType = await getDict('xg_holiday_type');
|
||||
this.dictStayReasonType = await getDict('xg_stay_reason_type');
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
async getList() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 根据搜索关键字设置查询参数
|
||||
const params = {
|
||||
...this.queryParams,
|
||||
studentName: this.searchKeyword,
|
||||
studentNo: this.searchKeyword,
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: '/studentDynamics/holidayStayList',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
if (this.queryParams.pageNum === 1) {
|
||||
this.dataList = res.rows || [];
|
||||
} else {
|
||||
this.dataList = [...this.dataList, ...(res.rows || [])];
|
||||
}
|
||||
this.total = res.total || 0;
|
||||
|
||||
// 更新加载更多状态
|
||||
if (this.dataList.length >= this.total) {
|
||||
this.loadMoreStatus = 'nomore';
|
||||
} else {
|
||||
this.loadMoreStatus = 'loadmore';
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取假期留宿信息列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
handleLoadMore() {
|
||||
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadMoreStatus = 'loading';
|
||||
this.queryParams.pageNum++;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 搜索操作
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.dataList = [];
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 清空搜索
|
||||
handleClear() {
|
||||
this.searchKeyword = '';
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
// 获取假期类型文本
|
||||
getHolidayTypeText(value) {
|
||||
return getDictLabel(this.dictHolidayType, value) || '-';
|
||||
},
|
||||
|
||||
// 获取假期类型标签类型
|
||||
getHolidayTypeTagType(value) {
|
||||
const fallbackMap = {
|
||||
1: 'primary', // 寒假
|
||||
2: 'warning', // 暑假
|
||||
3: 'success', // 国庆
|
||||
4: 'info', // 其他
|
||||
};
|
||||
return getDictTagType(this.dictHolidayType, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 获取留宿原因类型文本
|
||||
getStayReasonTypeText(value) {
|
||||
return getDictLabel(this.dictStayReasonType, value) || '-';
|
||||
},
|
||||
|
||||
// 获取留宿原因类型标签类型
|
||||
getStayReasonTypeTagType(value) {
|
||||
const fallbackMap = {
|
||||
1: 'primary', // 学习
|
||||
2: 'warning', // 实习
|
||||
3: 'success', // 工作
|
||||
4: 'info', // 其他
|
||||
};
|
||||
return getDictTagType(this.dictStayReasonType, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 查看详情 - 暂不开发
|
||||
// handleView(row) {
|
||||
// console.log('查看详情:', row);
|
||||
// },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.holiday-stay-tab {
|
||||
background-color: #f5f6fa;
|
||||
min-height: calc(100vh - 176rpx);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 100rpx 20rpx;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
|
||||
}
|
||||
|
||||
.list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
min-width: 140rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.load-more-wrapper {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
// uview组件样式调整
|
||||
::v-deep .u-search {
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,435 @@
|
||||
<template>
|
||||
<view class="student-leave-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<view class="search-container">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="请输入姓名或学号"
|
||||
:show-action="true"
|
||||
action-text="搜索"
|
||||
:animation="true"
|
||||
@search="handleQuery"
|
||||
@custom="handleQuery"
|
||||
@clear="handleClear"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page
|
||||
:loading="loading && dataList.length === 0"
|
||||
loading-text="加载中..."
|
||||
></u-loading-page>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
|
||||
<!-- 空状态 -->
|
||||
<view v-if="dataList.length === 0" class="empty-container">
|
||||
<u-empty text="暂无学生请假记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view
|
||||
v-else
|
||||
scroll-y
|
||||
class="scroll-view"
|
||||
:lower-threshold="50"
|
||||
@scrolltolower="handleLoadMore"
|
||||
>
|
||||
<view class="list-container">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="item.id || index"
|
||||
class="list-card"
|
||||
>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="card-header">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName || '-' }}</text>
|
||||
<text class="student-no">{{ item.studentNo || '-' }}</text>
|
||||
</view>
|
||||
<u-tag
|
||||
:text="item.leaveTypeName || '-'"
|
||||
type="primary"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="info-row">
|
||||
<text class="label">学院:</text>
|
||||
<text class="value">{{ item.collegeName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">专业:</text>
|
||||
<text class="value">{{ item.majorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">班级:</text>
|
||||
<text class="value">{{ item.className || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">请假时间:</text>
|
||||
<text class="value">
|
||||
{{ item.startTime }} 至 {{ item.endTime }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">请假时长:</text>
|
||||
<text class="value">{{ item.leaveDuration || 0 }}天</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">是否离校:</text>
|
||||
<u-tag
|
||||
:text="getIsLeaveSchoolText(item.isLeaveSchool)"
|
||||
:type="getIsLeaveSchoolTagType(item.isLeaveSchool)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">审批状态:</text>
|
||||
<u-tag
|
||||
:text="getApprovalStatusText(item.approvalStatus)"
|
||||
:type="getApprovalStatusTagType(item.approvalStatus)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="label">销假状态:</text>
|
||||
<u-tag
|
||||
:text="getCancelStatusText(item.cancelStatus)"
|
||||
:type="getCancelStatusTagType(item.cancelStatus)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="card-actions">
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
plain
|
||||
@click="handleViewApproval(item)"
|
||||
>
|
||||
<u-icon
|
||||
name="list"
|
||||
size="16"
|
||||
color="#fff"
|
||||
style="margin-right: 4px"
|
||||
></u-icon>
|
||||
审批记录
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="load-more-wrapper">
|
||||
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 审批记录组件 -->
|
||||
<approval-record ref="approvalRecordRef"></approval-record>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
||||
|
||||
export default {
|
||||
name: 'StudentLeaveTab',
|
||||
components: {
|
||||
ApprovalRecord,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dataList: [],
|
||||
total: 0,
|
||||
searchKeyword: '', // 搜索关键字(姓名或学号)
|
||||
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
},
|
||||
|
||||
// 加载更多状态
|
||||
loadMoreStatus: 'loadmore',
|
||||
loadMoreText: {
|
||||
loadmore: '点击加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了',
|
||||
},
|
||||
|
||||
// 字典数据
|
||||
dictYesNo: [],
|
||||
dictApprovalStatus: [],
|
||||
dictCancelStatus: [],
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
await this.loadDictData();
|
||||
await this.getList();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载字典数据
|
||||
async loadDictData() {
|
||||
try {
|
||||
this.dictYesNo = await getDict('sys_yesno');
|
||||
this.dictApprovalStatus = await getDict('student_leave_approval_status');
|
||||
this.dictCancelStatus = await getDict('xg_xsqj_cancel_status');
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取列表数据
|
||||
async getList() {
|
||||
this.loading = true;
|
||||
try {
|
||||
// 根据搜索关键字设置查询参数
|
||||
const params = {
|
||||
...this.queryParams,
|
||||
studentName: this.searchKeyword,
|
||||
studentNo: this.searchKeyword,
|
||||
};
|
||||
|
||||
const res = await request({
|
||||
url: '/studentDynamics/studentLeaveList',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
if (this.queryParams.pageNum === 1) {
|
||||
this.dataList = res.rows || [];
|
||||
} else {
|
||||
this.dataList = [...this.dataList, ...(res.rows || [])];
|
||||
}
|
||||
this.total = res.total || 0;
|
||||
|
||||
// 更新加载更多状态
|
||||
if (this.dataList.length >= this.total) {
|
||||
this.loadMoreStatus = 'nomore';
|
||||
} else {
|
||||
this.loadMoreStatus = 'loadmore';
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取学生请假信息列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
handleLoadMore() {
|
||||
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadMoreStatus = 'loading';
|
||||
this.queryParams.pageNum++;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 搜索操作
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.dataList = [];
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 清空搜索
|
||||
handleClear() {
|
||||
this.searchKeyword = '';
|
||||
this.handleQuery();
|
||||
},
|
||||
|
||||
// 获取是否离校文本
|
||||
getIsLeaveSchoolText(value) {
|
||||
return getDictLabel(this.dictYesNo, value) || '-';
|
||||
},
|
||||
|
||||
// 获取是否离校标签类型
|
||||
getIsLeaveSchoolTagType(value) {
|
||||
const fallbackMap = {
|
||||
Y: 'warning', // 是
|
||||
N: 'info', // 否
|
||||
};
|
||||
return getDictTagType(this.dictYesNo, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 获取审批状态文本
|
||||
getApprovalStatusText(value) {
|
||||
return getDictLabel(this.dictApprovalStatus, value) || '-';
|
||||
},
|
||||
|
||||
// 获取审批状态标签类型
|
||||
getApprovalStatusTagType(value) {
|
||||
const fallbackMap = {
|
||||
0: 'warning', // 待审批
|
||||
1: 'success', // 已通过
|
||||
2: 'error', // 已驳回
|
||||
};
|
||||
return getDictTagType(this.dictApprovalStatus, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 获取销假状态文本
|
||||
getCancelStatusText(value) {
|
||||
return getDictLabel(this.dictCancelStatus, value) || '-';
|
||||
},
|
||||
|
||||
// 获取销假状态标签类型
|
||||
getCancelStatusTagType(value) {
|
||||
const fallbackMap = {
|
||||
0: 'warning', // 未销假
|
||||
1: 'success', // 已销假
|
||||
};
|
||||
return getDictTagType(this.dictCancelStatus, value, fallbackMap);
|
||||
},
|
||||
|
||||
// 查看审批记录
|
||||
handleViewApproval(item) {
|
||||
console.log('查看审批记录:', item);
|
||||
if (!item.id) {
|
||||
uni.showToast({
|
||||
title: '数据异常,无法查看审批记录',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 调用审批记录组件,传入业务ID
|
||||
this.$refs.approvalRecordRef.init(item.id);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.student-leave-tab {
|
||||
background-color: #f5f6fa;
|
||||
min-height: calc(100vh - 176rpx);
|
||||
}
|
||||
|
||||
.search-container {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 100rpx 20rpx;
|
||||
}
|
||||
|
||||
.scroll-view {
|
||||
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
|
||||
}
|
||||
|
||||
.list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.student-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
min-width: 140rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.load-more-wrapper {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
// uview组件样式调整
|
||||
::v-deep .u-search {
|
||||
background-color: #f5f6fa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<view class="student-dynamics-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="学生动向信息" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- Tab切换 -->
|
||||
<u-tabs
|
||||
:list="tabList"
|
||||
:current="currentTab"
|
||||
@change="handleTabChange"
|
||||
active-color="#0051d9"
|
||||
inactive-color="#606266"
|
||||
line-color="#0051d9"
|
||||
:line-width="60"
|
||||
:line-height="4"
|
||||
:scrollable="false"
|
||||
></u-tabs>
|
||||
|
||||
<!-- Tab内容区域 -->
|
||||
<view class="tab-content">
|
||||
<!-- 调宿/退宿信息 -->
|
||||
<view v-show="currentTab === 0" class="tab-pane">
|
||||
<dorm-application-tab ref="dormApplicationTabRef"></dorm-application-tab>
|
||||
</view>
|
||||
|
||||
<!-- 假期留宿信息 -->
|
||||
<view v-show="currentTab === 1" class="tab-pane">
|
||||
<holiday-stay-tab ref="holidayStayTabRef"></holiday-stay-tab>
|
||||
</view>
|
||||
|
||||
<!-- 学生请假信息 -->
|
||||
<view v-show="currentTab === 2" class="tab-pane">
|
||||
<student-leave-tab ref="studentLeaveTabRef"></student-leave-tab>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DormApplicationTab from './components/DormApplicationTab.vue';
|
||||
import HolidayStayTab from './components/HolidayStayTab.vue';
|
||||
import StudentLeaveTab from './components/StudentLeaveTab.vue';
|
||||
|
||||
export default {
|
||||
name: 'StudentDynamics',
|
||||
components: {
|
||||
DormApplicationTab,
|
||||
HolidayStayTab,
|
||||
StudentLeaveTab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Tab列表
|
||||
tabList: [
|
||||
{ name: '调宿/退宿信息' },
|
||||
{ name: '假期留宿信息' },
|
||||
{ name: '学生请假信息' },
|
||||
],
|
||||
// 当前激活的Tab
|
||||
currentTab: 0,
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
console.log('学生动向信息管理页面已加载');
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// Tab切换处理
|
||||
handleTabChange(item) {
|
||||
console.log('切换到Tab页:', item);
|
||||
// uview的@change事件返回的是对象,需要提取index属性
|
||||
this.currentTab = item.index;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.student-dynamics-container {
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
min-height: calc(100vh - 88rpx - 88rpx); // 减去navbar和tabs的高度
|
||||
}
|
||||
</style>
|
||||
@@ -17,7 +17,7 @@
|
||||
:list="tabList"
|
||||
:current="safeCurrentTab"
|
||||
@click="handleTabClick"
|
||||
active-color="#3c9cff"
|
||||
active-color="#0051d9"
|
||||
inactive-color="#606266"
|
||||
:font-size="32"
|
||||
:bar-width="60"
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# 宿舍卫生检查组件说明
|
||||
|
||||
## 组件列表
|
||||
|
||||
### 1. SingleRoomCheck.vue - 单宿舍检查组件
|
||||
|
||||
**功能说明:**
|
||||
- 移动端单个宿舍的卫生检查表单组件
|
||||
- 与web端 `DormHygieneCheckSingleDialog.vue` 功能完全一致
|
||||
- 用于嵌入在弹窗中进行单个宿舍的详细检查
|
||||
|
||||
**主要功能模块:**
|
||||
|
||||
#### 1.1 宿舍整体情况
|
||||
- 违纪类型选择(从API加载)
|
||||
- 宿舍扣分输入(0-100分)
|
||||
- 卫生等级选择(字典:`xg_dorm_hygiene_grade`)
|
||||
- 检查附件上传(支持图片,最多15个)
|
||||
|
||||
#### 1.2 宿舍学生情况
|
||||
- 学生基本信息展示(姓名、学号、性别、床位、班级、专业)
|
||||
- 学生评分项:
|
||||
- 个人扣分(0-100分)
|
||||
- 出勤状态(字典:`xg_student_attendance_status`)
|
||||
- 学生评级(字典:`xg_dorm_hygiene_grade`)
|
||||
- 请假信息展示(如果有请假记录)
|
||||
- 违纪记录录入与管理
|
||||
|
||||
**Props:**
|
||||
```javascript
|
||||
{
|
||||
roomInfo: Object, // 房间信息(必填)
|
||||
checkInfo: Object, // 检查基础信息(必填)
|
||||
existingData: Object // 编辑时的现有数据(可选)
|
||||
}
|
||||
```
|
||||
|
||||
**Events:**
|
||||
```javascript
|
||||
{
|
||||
submit: (data) => {}, // 保存检查结果
|
||||
cancel: () => {} // 取消操作
|
||||
}
|
||||
```
|
||||
|
||||
**API接口:**
|
||||
- `GET /disciplineType/disciplineType/listByAreaType` - 加载违纪类型
|
||||
- `GET /dormBedStudent/dormBedStudent/getRoomStudents` - 加载房间学生信息
|
||||
- `GET /studentLeave/studentLeave/queryByCheckDate` - 查询学生请假信息
|
||||
- `POST /dormHygieneCheck/dormHygieneCheck/saveCompleteData` - 保存完整检查数据
|
||||
- `POST /resource/oss/upload` - 上传附件
|
||||
|
||||
**字典类型:**
|
||||
- `xg_dorm_hygiene_grade` - 卫生等级
|
||||
- `xg_student_attendance_status` - 出勤状态
|
||||
|
||||
---
|
||||
|
||||
### 2. ViolationRecordDialog.vue - 违纪记录弹窗
|
||||
|
||||
**功能说明:**
|
||||
- 学生违纪记录的录入和编辑弹窗
|
||||
- 支持新增和编辑两种模式
|
||||
|
||||
**主要字段:**
|
||||
- 违纪类型(从API加载)
|
||||
- 扣分分数(自动填入最低扣分)
|
||||
- 违纪日期
|
||||
- 违纪地点
|
||||
- 违纪描述
|
||||
- 处理意见
|
||||
|
||||
**Props:**
|
||||
```javascript
|
||||
{
|
||||
visible: Boolean, // 弹窗显示状态
|
||||
studentInfo: Object, // 学生信息
|
||||
violationData: Object // 违纪数据(编辑模式)
|
||||
}
|
||||
```
|
||||
|
||||
**Events:**
|
||||
```javascript
|
||||
{
|
||||
confirm: (data) => {}, // 确认保存
|
||||
close: () => {} // 关闭弹窗
|
||||
}
|
||||
```
|
||||
|
||||
**API接口:**
|
||||
- `GET /disciplineType/disciplineType/listByAreaType` - 加载违纪类型
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 在 wsjcMainAdd.vue 中使用 SingleRoomCheck
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<u-popup
|
||||
:show="showRoomCheckPopup"
|
||||
mode="bottom"
|
||||
:round="20"
|
||||
closeable
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handlePopupClose"
|
||||
>
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">
|
||||
{{ currentRoom ? currentRoom.roomName : '' }} - 卫生检查
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-scroll" :style="{ height: popupHeight + 'px' }">
|
||||
<SingleRoomCheck
|
||||
v-if="showRoomCheckPopup && currentRoom"
|
||||
:room-info="currentRoom"
|
||||
:check-info="formData"
|
||||
:existing-data="currentEditData"
|
||||
@submit="handleSingleCheckSubmit"
|
||||
@cancel="handlePopupClose"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SingleRoomCheck from './components/SingleRoomCheck.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SingleRoomCheck,
|
||||
},
|
||||
// ...
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 1. API调用
|
||||
- 统一使用 `mobile/src/common/request.js` 中的 `request` 方法
|
||||
- 附件上传使用 `smartUploadFile` 方法(自动适配微信环境)
|
||||
|
||||
### 2. 字典数据
|
||||
- 使用 `mobile/src/common/dict.js` 中的方法
|
||||
- `getDict(dictType)` - 获取字典数据
|
||||
- `getDictLabel(dictData, value)` - 获取字典标签
|
||||
- `getDictTagType(dictData, value)` - 获取u-tag类型
|
||||
|
||||
### 3. 样式规范
|
||||
- 使用uview-ui 2.x组件
|
||||
- 字典数据用 `<u-tag>` 展示
|
||||
- 遵循移动端UI设计规范
|
||||
|
||||
### 4. 数据提交
|
||||
- 与web端API接口完全一致
|
||||
- 参数名和数据结构保持一致
|
||||
- 返回数据格式相同
|
||||
|
||||
---
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Vue 2.6.14
|
||||
- uni-app 2.x
|
||||
- uview-ui 2.0.38
|
||||
- dayjs(日期处理)
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **附件上传兼容性**
|
||||
- 移动端附件功能兼容微信小程序
|
||||
- 参考 `wsjcAdd.vue` 的附件实现
|
||||
- 使用 `smartUploadFile` 方法自动适配环境
|
||||
|
||||
2. **字典数据加载**
|
||||
- 组件初始化时自动加载字典数据
|
||||
- 使用缓存机制,避免重复请求
|
||||
|
||||
3. **请假信息自动填充**
|
||||
- 加载学生信息时自动查询请假记录
|
||||
- 有请假记录的学生自动设置出勤状态为"请假"
|
||||
|
||||
4. **违纪类型自动扣分**
|
||||
- 选择违纪类型后自动填入最低扣分
|
||||
- 用户可手动修改
|
||||
|
||||
5. **数据验证**
|
||||
- 扣分范围:0-100分
|
||||
- 必填字段验证
|
||||
- 表单提交前统一验证
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
**2025-01-24**
|
||||
- 创建 SingleRoomCheck.vue 组件
|
||||
- 创建 ViolationRecordDialog.vue 组件
|
||||
- 实现与web端完全一致的功能
|
||||
- 适配移动端UI和交互方式
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -205,15 +205,77 @@
|
||||
<view class="fab-button" @click="handleAdd">
|
||||
<u-icon name="plus" size="24" color="#ffffff" />
|
||||
</view>
|
||||
|
||||
<!-- 查看检查弹窗 -->
|
||||
<u-popup
|
||||
:show="showViewPopup"
|
||||
mode="bottom"
|
||||
:round="20"
|
||||
closeable
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handleViewPopupClose"
|
||||
>
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">
|
||||
{{ currentViewItem ? currentViewItem.roomName : '' }} - 查看检查
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-scroll" :style="{ height: popupHeight + 'px' }">
|
||||
<SingleRoomCheck
|
||||
v-if="showViewPopup && currentViewItem"
|
||||
:room-info="currentViewRoomInfo"
|
||||
:check-info="currentViewCheckInfo"
|
||||
:existing-data="currentViewData"
|
||||
:is-view-mode="true"
|
||||
@cancel="handleViewPopupClose"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 修改检查弹窗 -->
|
||||
<u-popup
|
||||
:show="showEditPopup"
|
||||
mode="bottom"
|
||||
:round="20"
|
||||
closeable
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handleEditPopupClose"
|
||||
>
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">
|
||||
{{ currentEditItem ? currentEditItem.roomName : '' }} - 修改检查
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-scroll" :style="{ height: popupHeight + 'px' }">
|
||||
<SingleRoomCheck
|
||||
v-if="showEditPopup && currentEditItem"
|
||||
:room-info="currentRoomInfo"
|
||||
:check-info="currentCheckInfo"
|
||||
:existing-data="currentEditData"
|
||||
@submit="handleEditSubmit"
|
||||
@cancel="handleEditPopupClose"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
import SingleRoomCheck from './components/SingleRoomCheck.vue';
|
||||
|
||||
export default {
|
||||
name: 'DormHygieneCheck',
|
||||
components: {
|
||||
SingleRoomCheck,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
@@ -236,6 +298,21 @@ export default {
|
||||
xg_dorm_hygiene_check_type: [],
|
||||
xg_dorm_hygiene_check_level: [],
|
||||
xg_dorm_hygiene_grade: [],
|
||||
|
||||
// 修改弹窗相关
|
||||
showEditPopup: false,
|
||||
currentEditItem: null,
|
||||
currentEditData: null,
|
||||
currentRoomInfo: null,
|
||||
currentCheckInfo: null,
|
||||
popupHeight: 600,
|
||||
|
||||
// 查看弹窗相关
|
||||
showViewPopup: false,
|
||||
currentViewItem: null,
|
||||
currentViewData: null,
|
||||
currentViewRoomInfo: null,
|
||||
currentViewCheckInfo: null,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -254,6 +331,12 @@ export default {
|
||||
uni.$on('refreshCheckList', this.handleRefreshEvent);
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 计算弹窗高度(屏幕高度的70%)
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.popupHeight = systemInfo.windowHeight * 0.7;
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// 移除事件监听
|
||||
uni.$off('refreshCheckList', this.handleRefreshEvent);
|
||||
@@ -450,22 +533,162 @@ export default {
|
||||
// 新增检查
|
||||
handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xgss/wsjc/wsjcAdd?type=add',
|
||||
url: '/pages/xgss/wsjc/wsjcMainAdd',
|
||||
});
|
||||
},
|
||||
|
||||
// 查看检查
|
||||
handleView(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/xgss/wsjc/wsjcDetail?id=${item.id}&type=view`,
|
||||
async handleView(item) {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
// 加载检查记录详情
|
||||
const res = await request({
|
||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
this.currentViewItem = item;
|
||||
this.currentViewData = res.data;
|
||||
|
||||
// 构造房间信息
|
||||
this.currentViewRoomInfo = {
|
||||
id: item.roomId,
|
||||
roomName: item.roomName,
|
||||
roomCode: item.roomCode,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
areaId: item.areaId,
|
||||
areaName: item.areaName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
// 构造检查基础信息
|
||||
this.currentViewCheckInfo = {
|
||||
checkTitle: item.checkTitle,
|
||||
checkDate: item.checkDate,
|
||||
checkerId: item.checkerId,
|
||||
checkerName: item.checkerName,
|
||||
checkType: item.checkType,
|
||||
checkLevel: item.checkLevel,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
this.showViewPopup = true;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('❌ 加载检查记录失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 修改检查
|
||||
handleUpdate(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/xgss/wsjc/wsjcAdd?id=${item.id}&type=update`,
|
||||
async handleUpdate(item) {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
// 加载检查记录详情
|
||||
const res = await request({
|
||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
this.currentEditItem = item;
|
||||
this.currentEditData = res.data;
|
||||
|
||||
// 构造房间信息
|
||||
this.currentRoomInfo = {
|
||||
id: item.roomId,
|
||||
roomName: item.roomName,
|
||||
roomCode: item.roomCode,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
areaId: item.areaId,
|
||||
areaName: item.areaName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
// 构造检查基础信息
|
||||
this.currentCheckInfo = {
|
||||
checkTitle: item.checkTitle,
|
||||
checkDate: item.checkDate,
|
||||
checkerId: item.checkerId,
|
||||
checkerName: item.checkerName,
|
||||
checkType: item.checkType,
|
||||
checkLevel: item.checkLevel,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
this.showEditPopup = true;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('❌ 加载检查记录失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 修改检查提交处理
|
||||
handleEditSubmit(data) {
|
||||
uni.showToast({
|
||||
title: '修改成功',
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
// 关闭弹窗
|
||||
this.handleEditPopupClose();
|
||||
|
||||
// 刷新列表
|
||||
this.loadCheckList();
|
||||
},
|
||||
|
||||
// 修改检查弹窗关闭处理
|
||||
handleEditPopupClose() {
|
||||
this.showEditPopup = false;
|
||||
this.currentEditItem = null;
|
||||
this.currentEditData = null;
|
||||
this.currentRoomInfo = null;
|
||||
this.currentCheckInfo = null;
|
||||
},
|
||||
|
||||
// 查看检查弹窗关闭处理
|
||||
handleViewPopupClose() {
|
||||
this.showViewPopup = false;
|
||||
this.currentViewItem = null;
|
||||
this.currentViewData = null;
|
||||
this.currentViewRoomInfo = null;
|
||||
this.currentViewCheckInfo = null;
|
||||
},
|
||||
|
||||
// 删除检查
|
||||
@@ -858,4 +1081,25 @@ export default {
|
||||
::v-deep .u-button {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
// 修改弹窗样式
|
||||
.popup-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 30rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,971 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar
|
||||
title="宿舍卫生检查"
|
||||
:border-bottom="false"
|
||||
bg-color="#0051d9"
|
||||
:titleStyle="{ color: '#fff' }"
|
||||
>
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page :loading="pageLoading" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view v-if="!pageLoading" class="content-container">
|
||||
<!-- 基础信息卡片 -->
|
||||
<view class="info-card">
|
||||
<view class="card-title">检查基础信息</view>
|
||||
<u-form ref="formRef" :model="formData" :rules="rules" label-width="100">
|
||||
<!-- 检查标题 -->
|
||||
<u-form-item label="检查标题" prop="checkTitle" required>
|
||||
<u-input v-model="formData.checkTitle" placeholder="请输入检查标题" />
|
||||
</u-form-item>
|
||||
|
||||
<!-- 检查日期 -->
|
||||
<u-form-item label="检查日期" prop="checkDate" required>
|
||||
<view @click="showDatePicker = true" style="width: 100%">
|
||||
<u-input
|
||||
v-model="checkDateDisplay"
|
||||
placeholder="请选择检查日期"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-datetime-picker
|
||||
:show="showDatePicker"
|
||||
v-model="checkDateValue"
|
||||
mode="datetime"
|
||||
@confirm="handleDateConfirm"
|
||||
@cancel="showDatePicker = false"
|
||||
></u-datetime-picker>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 检查员 -->
|
||||
<u-form-item label="检查员" prop="checkerName" required>
|
||||
<u-input v-model="formData.checkerName" placeholder="检查员姓名" readonly />
|
||||
</u-form-item>
|
||||
|
||||
<!-- 检查类型 -->
|
||||
<u-form-item label="检查类型" prop="checkType" required>
|
||||
<view @click="showCheckTypePicker = true" style="width: 100%">
|
||||
<u-input
|
||||
v-model="checkTypeDisplay"
|
||||
placeholder="请选择检查类型"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-picker
|
||||
:show="showCheckTypePicker && xg_dorm_hygiene_check_type.length > 0"
|
||||
:columns="checkTypeColumns"
|
||||
:defaultIndex="checkTypeDefaultIndex"
|
||||
keyName="text"
|
||||
@confirm="handleCheckTypeConfirm"
|
||||
@cancel="showCheckTypePicker = false"
|
||||
></u-picker>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 检查级别 -->
|
||||
<u-form-item label="检查级别" prop="checkLevel" required>
|
||||
<view @click="showCheckLevelPicker = true" style="width: 100%">
|
||||
<u-input
|
||||
v-model="checkLevelDisplay"
|
||||
placeholder="请选择检查级别"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-picker
|
||||
:show="showCheckLevelPicker && xg_dorm_hygiene_check_level.length > 0"
|
||||
:columns="checkLevelColumns"
|
||||
:defaultIndex="checkLevelDefaultIndex"
|
||||
keyName="text"
|
||||
@confirm="handleCheckLevelConfirm"
|
||||
@cancel="showCheckLevelPicker = false"
|
||||
></u-picker>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 校区 -->
|
||||
<u-form-item label="校区" prop="campusId" required>
|
||||
<view @click="showCampusPicker = true" style="width: 100%">
|
||||
<u-input
|
||||
v-model="formData.campusName"
|
||||
placeholder="请选择校区"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-picker
|
||||
:show="showCampusPicker"
|
||||
:columns="campusColumns"
|
||||
keyName="text"
|
||||
@confirm="handleCampusConfirm"
|
||||
@cancel="showCampusPicker = false"
|
||||
></u-picker>
|
||||
</u-form-item>
|
||||
|
||||
<!-- 宿舍楼 -->
|
||||
<u-form-item label="宿舍楼" prop="buildingId" required>
|
||||
<view @click="handleBuildingClick" style="width: 100%">
|
||||
<u-input
|
||||
v-model="formData.buildingName"
|
||||
placeholder="请选择宿舍楼"
|
||||
readonly
|
||||
border="none"
|
||||
suffix-icon="arrow-down"
|
||||
suffix-icon-style="color: #c8c9cc;"
|
||||
:disabled="!formData.campusId"
|
||||
style="pointer-events: none"
|
||||
/>
|
||||
</view>
|
||||
<u-picker
|
||||
:show="showBuildingPicker"
|
||||
:columns="buildingColumns"
|
||||
keyName="text"
|
||||
@confirm="handleBuildingConfirm"
|
||||
@cancel="showBuildingPicker = false"
|
||||
></u-picker>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
</view>
|
||||
|
||||
<!-- 房间列表区域 -->
|
||||
<view v-if="formData.buildingId && floorRooms.length > 0" class="rooms-section">
|
||||
<view class="rooms-header">
|
||||
<text class="rooms-title">宿舍房间列表</text>
|
||||
<view class="rooms-stats">
|
||||
<text>总房间: {{ totalRoomsCount }}</text>
|
||||
<text style="margin-left: 20rpx">已检查: {{ completedRoomsCount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="hint-text">
|
||||
<u-icon name="info-circle" color="#ff9900" size="16"></u-icon>
|
||||
<text style="margin-left: 10rpx; color: #ff9900">点击房间卡片开始检查</text>
|
||||
</view>
|
||||
|
||||
<!-- 楼层和房间展示 -->
|
||||
<view class="floors-container">
|
||||
<view
|
||||
v-for="floorData in floorRooms"
|
||||
:key="floorData.floor"
|
||||
class="floor-section"
|
||||
>
|
||||
<view class="floor-header">
|
||||
<view class="floor-name">{{ floorData.floor }}楼</view>
|
||||
<view class="floor-stats">
|
||||
已检查: {{ getFloorCompletedCount(floorData.floor) }} /
|
||||
{{ floorData.rooms.length }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="rooms-grid">
|
||||
<view
|
||||
v-for="room in floorData.rooms"
|
||||
:key="room.id"
|
||||
class="room-card"
|
||||
:class="{ completed: completedRooms.has(room.id) }"
|
||||
@click="handleRoomClick(room)"
|
||||
>
|
||||
<view class="room-card-content">
|
||||
<view class="room-name">{{ room.roomName }}</view>
|
||||
<view class="room-code">
|
||||
{{ room.roomCode || room.roomName }}
|
||||
</view>
|
||||
<view class="room-status">
|
||||
<u-icon
|
||||
v-if="completedRooms.has(room.id)"
|
||||
name="checkmark-circle-fill"
|
||||
color="#19be6b"
|
||||
size="18"
|
||||
></u-icon>
|
||||
<u-icon
|
||||
v-else
|
||||
name="calendar"
|
||||
color="#c8c9cc"
|
||||
size="18"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="room-hint">
|
||||
<text v-if="completedRooms.has(room.id)" class="completed-text">
|
||||
已检查
|
||||
</text>
|
||||
<text v-else class="pending-text">点击检查</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view
|
||||
v-if="formData.buildingId && floorRooms.length === 0 && !roomsLoading"
|
||||
class="empty-container"
|
||||
>
|
||||
<u-empty text="该宿舍楼暂无房间信息" mode="data"></u-empty>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 单个房间检查弹窗 -->
|
||||
<u-popup
|
||||
:show="showRoomCheckPopup"
|
||||
mode="bottom"
|
||||
:round="20"
|
||||
closeable
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handlePopupClose"
|
||||
>
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">
|
||||
{{ currentRoom ? currentRoom.roomName : '' }} - 卫生检查
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-scroll" :style="{ height: popupHeight + 'px' }">
|
||||
<SingleRoomCheck
|
||||
v-if="showRoomCheckPopup && currentRoom"
|
||||
:room-info="currentRoom"
|
||||
:check-info="formData"
|
||||
:existing-data="currentEditData"
|
||||
@submit="handleSingleCheckSubmit"
|
||||
@cancel="handlePopupClose"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
import { request } from '@/common/request.js';
|
||||
import dayjs from 'dayjs';
|
||||
import SingleRoomCheck from './components/SingleRoomCheck.vue';
|
||||
|
||||
export default {
|
||||
name: 'WsjcMainAdd',
|
||||
components: {
|
||||
SingleRoomCheck,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageLoading: true,
|
||||
roomsLoading: false,
|
||||
|
||||
// 表单数据
|
||||
formData: {
|
||||
checkTitle: '',
|
||||
checkDate: '',
|
||||
checkerId: '',
|
||||
checkerName: '',
|
||||
checkType: '1',
|
||||
checkLevel: '1',
|
||||
campusId: '',
|
||||
campusName: '',
|
||||
buildingId: '',
|
||||
buildingName: '',
|
||||
},
|
||||
|
||||
// 验证规则
|
||||
rules: {
|
||||
checkTitle: [{ required: true, message: '请输入检查标题', trigger: 'blur' }],
|
||||
checkDate: [{ required: true, message: '请选择检查日期', trigger: 'change' }],
|
||||
checkerName: [{ required: true, message: '请选择检查员', trigger: 'change' }],
|
||||
checkType: [{ required: true, message: '请选择检查类型', trigger: 'change' }],
|
||||
checkLevel: [{ required: true, message: '请选择检查级别', trigger: 'change' }],
|
||||
campusId: [{ required: true, message: '请选择校区', trigger: 'change' }],
|
||||
buildingId: [{ required: true, message: '请选择宿舍楼', trigger: 'change' }],
|
||||
},
|
||||
|
||||
// Picker显示状态
|
||||
showDatePicker: false,
|
||||
showCheckTypePicker: false,
|
||||
showCheckLevelPicker: false,
|
||||
showCampusPicker: false,
|
||||
showBuildingPicker: false,
|
||||
showRoomCheckPopup: false,
|
||||
|
||||
// 日期时间
|
||||
checkDateValue: Date.now(),
|
||||
checkDateDisplay: '',
|
||||
|
||||
// 字典数据
|
||||
xg_dorm_hygiene_check_type: [],
|
||||
xg_dorm_hygiene_check_level: [],
|
||||
|
||||
// 选项数据
|
||||
campusOptions: [],
|
||||
buildingOptions: [],
|
||||
|
||||
// 房间相关数据
|
||||
floorRooms: [],
|
||||
allRooms: [],
|
||||
completedRooms: new Set(),
|
||||
roomCheckIds: new Map(),
|
||||
|
||||
// 当前操作的房间
|
||||
currentRoom: null,
|
||||
currentEditData: null,
|
||||
|
||||
// 弹窗高度
|
||||
popupHeight: 600,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['userInfo']),
|
||||
|
||||
// 检查类型列
|
||||
checkTypeColumns() {
|
||||
return [
|
||||
this.xg_dorm_hygiene_check_type.map((item) => ({
|
||||
text: item.label,
|
||||
value: item.value,
|
||||
})),
|
||||
];
|
||||
},
|
||||
|
||||
// 检查类型默认索引
|
||||
checkTypeDefaultIndex() {
|
||||
const index = this.xg_dorm_hygiene_check_type.findIndex(
|
||||
(item) => item.value === this.formData.checkType,
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
|
||||
// 检查类型显示文本
|
||||
checkTypeDisplay() {
|
||||
const item = this.xg_dorm_hygiene_check_type.find(
|
||||
(d) => d.value === this.formData.checkType,
|
||||
);
|
||||
return item ? item.label : '';
|
||||
},
|
||||
|
||||
// 检查级别列
|
||||
checkLevelColumns() {
|
||||
return [
|
||||
this.xg_dorm_hygiene_check_level.map((item) => ({
|
||||
text: item.label,
|
||||
value: item.value,
|
||||
})),
|
||||
];
|
||||
},
|
||||
|
||||
// 检查级别默认索引
|
||||
checkLevelDefaultIndex() {
|
||||
const index = this.xg_dorm_hygiene_check_level.findIndex(
|
||||
(item) => item.value === this.formData.checkLevel,
|
||||
);
|
||||
return [index >= 0 ? index : 0];
|
||||
},
|
||||
|
||||
// 检查级别显示文本
|
||||
checkLevelDisplay() {
|
||||
const item = this.xg_dorm_hygiene_check_level.find(
|
||||
(d) => d.value === this.formData.checkLevel,
|
||||
);
|
||||
return item ? item.label : '';
|
||||
},
|
||||
|
||||
// 校区列
|
||||
campusColumns() {
|
||||
return [this.campusOptions.map((item) => ({ text: item.campusName, value: item.id }))];
|
||||
},
|
||||
|
||||
// 宿舍楼列
|
||||
buildingColumns() {
|
||||
return [
|
||||
this.buildingOptions.map((item) => ({
|
||||
text: this.getBuildingDisplayName(item),
|
||||
value: item.id,
|
||||
})),
|
||||
];
|
||||
},
|
||||
|
||||
// 总房间数
|
||||
totalRoomsCount() {
|
||||
return this.allRooms.length;
|
||||
},
|
||||
|
||||
// 已完成房间数
|
||||
completedRoomsCount() {
|
||||
return this.completedRooms.size;
|
||||
},
|
||||
},
|
||||
onLoad() {
|
||||
this.initPage();
|
||||
},
|
||||
onReady() {
|
||||
// 计算弹窗高度(屏幕高度的70%)
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.popupHeight = systemInfo.windowHeight * 0.7;
|
||||
},
|
||||
methods: {
|
||||
// 初始化页面
|
||||
async initPage() {
|
||||
try {
|
||||
this.pageLoading = true;
|
||||
|
||||
// 初始化检查标题和日期
|
||||
this.generateDefaultCheckTitle();
|
||||
this.formData.checkDate = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.checkDateDisplay = this.formData.checkDate;
|
||||
this.checkDateValue = Date.now();
|
||||
|
||||
// 获取当前用户信息
|
||||
await this.fetchCurrentUserInfo();
|
||||
|
||||
// 加载字典数据
|
||||
await this.loadDicts();
|
||||
|
||||
// 加载校区选项
|
||||
await this.loadCampusOptions();
|
||||
} catch (error) {
|
||||
console.error('初始化页面失败:', error);
|
||||
this.$u.toast('初始化页面失败');
|
||||
} finally {
|
||||
this.pageLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 生成默认检查标题
|
||||
generateDefaultCheckTitle() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
this.formData.checkTitle = `${year}${month}${day}宿舍卫生检查`;
|
||||
},
|
||||
|
||||
// 获取当前用户信息
|
||||
async fetchCurrentUserInfo() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/system/user/getInfo',
|
||||
method: 'GET',
|
||||
});
|
||||
if (res.code === 200 && res.data?.user) {
|
||||
const user = res.data.user;
|
||||
this.formData.checkerId = user.userId;
|
||||
this.formData.checkerName = user.nickName || user.userName;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取用户信息失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 加载字典数据
|
||||
async loadDicts() {
|
||||
try {
|
||||
const [checkTypeData, checkLevelData] = await Promise.all([
|
||||
getDict('xg_dorm_hygiene_check_type'),
|
||||
getDict('xg_dorm_hygiene_check_level'),
|
||||
]);
|
||||
|
||||
this.xg_dorm_hygiene_check_type = checkTypeData || [];
|
||||
this.xg_dorm_hygiene_check_level = checkLevelData || [];
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 加载校区选项
|
||||
async loadCampusOptions() {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/xqxx/dormCampus/list',
|
||||
method: 'GET',
|
||||
});
|
||||
this.campusOptions = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('加载校区选项失败:', error);
|
||||
uni.showToast({ title: '加载校区选项失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
// 加载宿舍楼选项
|
||||
async loadBuildingOptions(campusId) {
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/sslxx/dormBuilding/list',
|
||||
method: 'GET',
|
||||
params: { campusId },
|
||||
});
|
||||
this.buildingOptions = res.rows || [];
|
||||
} catch (error) {
|
||||
console.error('加载宿舍楼选项失败:', error);
|
||||
uni.showToast({ title: '加载宿舍楼选项失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
// 加载房间列表
|
||||
async loadRoomsByFloor(buildingId) {
|
||||
try {
|
||||
this.roomsLoading = true;
|
||||
const res = await request({
|
||||
url: '/xgss/dormRoom/list',
|
||||
method: 'GET',
|
||||
params: { buildingId },
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.rows) {
|
||||
this.allRooms = res.rows;
|
||||
|
||||
// 按楼层分组
|
||||
const floorMap = new Map();
|
||||
res.rows.forEach((room) => {
|
||||
const floor = room.floorNum || '1';
|
||||
if (!floorMap.has(floor)) {
|
||||
floorMap.set(floor, []);
|
||||
}
|
||||
floorMap.get(floor).push(room);
|
||||
});
|
||||
|
||||
// 转换为数组并排序
|
||||
this.floorRooms = Array.from(floorMap.entries())
|
||||
.map(([floor, rooms]) => ({ floor, rooms }))
|
||||
.sort((a, b) => parseInt(a.floor) - parseInt(b.floor));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载房间失败:', error);
|
||||
uni.showToast({ title: '加载房间失败', icon: 'none' });
|
||||
} finally {
|
||||
this.roomsLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 日期确认处理
|
||||
handleDateConfirm(e) {
|
||||
this.formData.checkDate = dayjs(e.value).format('YYYY-MM-DD HH:mm:ss');
|
||||
this.checkDateDisplay = this.formData.checkDate;
|
||||
this.showDatePicker = false;
|
||||
},
|
||||
|
||||
// 检查类型确认处理
|
||||
handleCheckTypeConfirm(e) {
|
||||
if (e && e.value && e.value.length > 0) {
|
||||
this.formData.checkType = e.value[0].value;
|
||||
}
|
||||
this.showCheckTypePicker = false;
|
||||
},
|
||||
|
||||
// 检查级别确认处理
|
||||
handleCheckLevelConfirm(e) {
|
||||
if (e && e.value && e.value.length > 0) {
|
||||
this.formData.checkLevel = e.value[0].value;
|
||||
}
|
||||
this.showCheckLevelPicker = false;
|
||||
},
|
||||
|
||||
// 校区确认处理
|
||||
async handleCampusConfirm(e) {
|
||||
if (e && e.value && e.value.length > 0) {
|
||||
const campus = this.campusOptions.find((item) => item.id === e.value[0].value);
|
||||
if (campus) {
|
||||
this.formData.campusId = campus.id;
|
||||
this.formData.campusName = campus.campusName;
|
||||
|
||||
// 清空下级选项
|
||||
this.formData.buildingId = '';
|
||||
this.formData.buildingName = '';
|
||||
this.buildingOptions = [];
|
||||
this.floorRooms = [];
|
||||
this.allRooms = [];
|
||||
this.completedRooms.clear();
|
||||
this.roomCheckIds.clear();
|
||||
|
||||
await this.loadBuildingOptions(campus.id);
|
||||
}
|
||||
}
|
||||
this.showCampusPicker = false;
|
||||
},
|
||||
|
||||
// 宿舍楼点击处理
|
||||
handleBuildingClick() {
|
||||
if (!this.formData.campusId) {
|
||||
this.$u.toast('请先选择校区');
|
||||
return;
|
||||
}
|
||||
this.showBuildingPicker = true;
|
||||
},
|
||||
|
||||
// 宿舍楼确认处理
|
||||
async handleBuildingConfirm(e) {
|
||||
if (e && e.value && e.value.length > 0) {
|
||||
const building = this.buildingOptions.find((item) => item.id === e.value[0].value);
|
||||
if (building) {
|
||||
this.formData.buildingId = building.id;
|
||||
this.formData.buildingName = building.buildingName;
|
||||
|
||||
// 清空房间数据
|
||||
this.floorRooms = [];
|
||||
this.allRooms = [];
|
||||
this.completedRooms.clear();
|
||||
this.roomCheckIds.clear();
|
||||
|
||||
await this.loadRoomsByFloor(building.id);
|
||||
}
|
||||
}
|
||||
this.showBuildingPicker = false;
|
||||
},
|
||||
|
||||
// 房间点击处理
|
||||
async handleRoomClick(room) {
|
||||
console.log('🔍 handleRoomClick - 点击的房间数据:', room);
|
||||
console.log(
|
||||
'🔍 房间字段 - roomName:',
|
||||
room.roomName,
|
||||
', roomCode:',
|
||||
room.roomCode,
|
||||
', name:',
|
||||
room.name,
|
||||
);
|
||||
|
||||
// 验证基础信息是否完整
|
||||
if (
|
||||
!this.formData.checkTitle ||
|
||||
!this.formData.checkDate ||
|
||||
!this.formData.checkerName ||
|
||||
!this.formData.checkType ||
|
||||
!this.formData.checkLevel
|
||||
) {
|
||||
uni.showToast({ title: '请先完善检查基础信息', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentRoom = room;
|
||||
console.log('✅ currentRoom已赋值:', this.currentRoom);
|
||||
|
||||
// 检查是否已有记录ID
|
||||
const checkId = this.roomCheckIds.get(room.id);
|
||||
|
||||
if (checkId) {
|
||||
// 已检查过,加载数据用于编辑
|
||||
try {
|
||||
const response = await request({
|
||||
url: `/dormHygieneCheck/dormHygieneCheck/${checkId}`,
|
||||
method: 'GET',
|
||||
});
|
||||
if (response.code === 200 && response.data) {
|
||||
this.currentEditData = response.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询检查记录详情失败:', error);
|
||||
uni.showToast({ title: '查询检查记录失败', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.currentEditData = null;
|
||||
}
|
||||
|
||||
this.showRoomCheckPopup = true;
|
||||
},
|
||||
|
||||
// 单个房间检查提交处理
|
||||
handleSingleCheckSubmit(data) {
|
||||
// 标记房间为已完成
|
||||
this.completedRooms.add(this.currentRoom.id);
|
||||
|
||||
// 保存检查记录ID
|
||||
if (data.id) {
|
||||
this.roomCheckIds.set(this.currentRoom.id, data.id);
|
||||
}
|
||||
|
||||
uni.showToast({ title: `${this.currentRoom.roomName} 检查完成`, icon: 'success' });
|
||||
this.showRoomCheckPopup = false;
|
||||
this.currentRoom = null;
|
||||
this.currentEditData = null;
|
||||
},
|
||||
|
||||
// 弹窗关闭处理
|
||||
handlePopupClose() {
|
||||
this.showRoomCheckPopup = false;
|
||||
this.currentRoom = null;
|
||||
this.currentEditData = null;
|
||||
},
|
||||
|
||||
// 获取楼层已完成数量
|
||||
getFloorCompletedCount(floor) {
|
||||
const floorData = this.floorRooms.find((f) => f.floor === floor);
|
||||
if (!floorData) return 0;
|
||||
return floorData.rooms.filter((room) => this.completedRooms.has(room.id)).length;
|
||||
},
|
||||
|
||||
// 宿舍楼显示名称
|
||||
getBuildingDisplayName(building) {
|
||||
let displayName = building.buildingName;
|
||||
if (building.buildingType) {
|
||||
if (building.buildingType === '1') {
|
||||
displayName = `(男)${displayName}`;
|
||||
} else if (building.buildingType === '2') {
|
||||
displayName = `(女)${displayName}`;
|
||||
}
|
||||
}
|
||||
return displayName;
|
||||
},
|
||||
|
||||
// 返回
|
||||
goBack() {
|
||||
console.log('🔙 goBack - 点击返回按钮');
|
||||
|
||||
// 检查是否有未保存的数据
|
||||
if (this.completedRoomsCount > 0) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `已检查 ${this.completedRoomsCount} 个房间,确定要返回吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: (err) => {
|
||||
console.error('❌ navigateBack失败:', err);
|
||||
// 如果navigateBack失败,尝试使用redirectTo
|
||||
uni.redirectTo({
|
||||
url: '/pages/xgss/wsjc/wsjc',
|
||||
fail: (err2) => {
|
||||
console.error('❌ redirectTo也失败:', err2);
|
||||
uni.showToast({ title: '返回失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail: (err) => {
|
||||
console.error('❌ navigateBack失败:', err);
|
||||
// 如果navigateBack失败,尝试使用redirectTo
|
||||
uni.redirectTo({
|
||||
url: '/pages/xgss/wsjc/wsjc',
|
||||
fail: (err2) => {
|
||||
console.error('❌ redirectTo也失败:', err2);
|
||||
uni.showToast({ title: '返回失败', icon: 'none' });
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
padding: 20rpx;
|
||||
padding-top: 100rpx; /* 为导航栏预留空间 */
|
||||
}
|
||||
|
||||
.info-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 20rpx;
|
||||
padding-left: 20rpx;
|
||||
border-left: 6rpx solid #0051d9;
|
||||
}
|
||||
|
||||
.rooms-section {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.rooms-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.rooms-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.rooms-stats {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
background-color: #fff8e6;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.floors-container {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.floor-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.floor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 2rpx solid #3c9cff;
|
||||
margin-bottom: 20rpx;
|
||||
min-height: 60rpx;
|
||||
}
|
||||
|
||||
.floor-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #3c9cff;
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.floor-stats {
|
||||
font-size: 24rpx;
|
||||
color: #909399;
|
||||
min-width: 240rpx;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.rooms-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.room-card {
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #e4e7ed;
|
||||
border-radius: 12rpx;
|
||||
padding: 20rpx;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.room-card.completed {
|
||||
border-color: #19be6b;
|
||||
background-color: #f0f9ff;
|
||||
}
|
||||
|
||||
.room-card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
min-height: 80rpx;
|
||||
}
|
||||
|
||||
.room-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
padding-right: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 50rpx;
|
||||
}
|
||||
|
||||
.room-code {
|
||||
font-size: 22rpx;
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.room-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 50rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.room-hint {
|
||||
text-align: center;
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.completed-text {
|
||||
color: #19be6b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pending-text {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 30rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -117,16 +117,12 @@
|
||||
<view class="info-section">
|
||||
<text class="section-title">宿舍信息</text>
|
||||
<view class="dorm-info-grid">
|
||||
<!-- 第一行:校区和宿舍区 -->
|
||||
<!-- 第一行:校区 -->
|
||||
<view class="dorm-row">
|
||||
<view class="info-item-half">
|
||||
<text class="label">校区:</text>
|
||||
<text class="value">{{ item.campusName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item-half">
|
||||
<text class="label">宿舍区:</text>
|
||||
<text class="value">{{ item.areaName || '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 第二行:宿舍楼和房间 -->
|
||||
<view class="dorm-row">
|
||||
@@ -207,6 +203,35 @@
|
||||
<!-- <view class="fab-button" @click="handleAdd">
|
||||
<u-icon name="plus" size="24" color="#ffffff" />
|
||||
</view> -->
|
||||
|
||||
<!-- 查看检查弹窗 -->
|
||||
<u-popup
|
||||
:show="showViewPopup"
|
||||
mode="bottom"
|
||||
:round="20"
|
||||
closeable
|
||||
:safe-area-inset-bottom="true"
|
||||
@close="handleViewPopupClose"
|
||||
>
|
||||
<view class="popup-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">
|
||||
{{ currentViewItem ? currentViewItem.roomName : '' }} - 查看检查
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="popup-scroll" :style="{ height: popupHeight + 'px' }">
|
||||
<SingleRoomCheck
|
||||
v-if="showViewPopup && currentViewItem"
|
||||
:room-info="currentViewRoomInfo"
|
||||
:check-info="currentViewCheckInfo"
|
||||
:existing-data="currentViewData"
|
||||
:is-view-mode="true"
|
||||
@cancel="handleViewPopupClose"
|
||||
/>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -214,9 +239,13 @@
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
|
||||
import { userManager } from '@/utils/user.js';
|
||||
import SingleRoomCheck from '../wsjc/components/SingleRoomCheck.vue';
|
||||
|
||||
export default {
|
||||
name: 'DormHygieneCheck',
|
||||
components: {
|
||||
SingleRoomCheck,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
@@ -239,6 +268,14 @@ export default {
|
||||
xg_dorm_hygiene_check_type: [],
|
||||
xg_dorm_hygiene_check_level: [],
|
||||
xg_dorm_hygiene_grade: [],
|
||||
|
||||
// 查看弹窗相关
|
||||
showViewPopup: false,
|
||||
currentViewItem: null,
|
||||
currentViewData: null,
|
||||
currentViewRoomInfo: null,
|
||||
currentViewCheckInfo: null,
|
||||
popupHeight: 600,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -257,6 +294,12 @@ export default {
|
||||
uni.$on('refreshCheckList', this.handleRefreshEvent);
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 计算弹窗高度(屏幕高度的70%)
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
this.popupHeight = systemInfo.windowHeight * 0.7;
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// 移除事件监听
|
||||
uni.$off('refreshCheckList', this.handleRefreshEvent);
|
||||
@@ -487,10 +530,73 @@ export default {
|
||||
},
|
||||
|
||||
// 查看检查
|
||||
handleView(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/xgss/xssswsjc/xssswsjcDetail?id=${item.id}&type=view`,
|
||||
async handleView(item) {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
|
||||
// 加载检查记录详情
|
||||
const res = await request({
|
||||
url: `/dormHygieneCheck/dormHygieneCheck/${item.id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
this.currentViewItem = item;
|
||||
this.currentViewData = res.data;
|
||||
|
||||
// 构造房间信息
|
||||
this.currentViewRoomInfo = {
|
||||
id: item.roomId,
|
||||
roomName: item.roomName,
|
||||
roomCode: item.roomCode,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
areaId: item.areaId,
|
||||
areaName: item.areaName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
// 构造检查基础信息
|
||||
this.currentViewCheckInfo = {
|
||||
checkTitle: item.checkTitle,
|
||||
checkDate: item.checkDate,
|
||||
checkerId: item.checkerId,
|
||||
checkerName: item.checkerName,
|
||||
checkType: item.checkType,
|
||||
checkLevel: item.checkLevel,
|
||||
campusId: item.campusId,
|
||||
campusName: item.campusName,
|
||||
buildingId: item.buildingId,
|
||||
buildingName: item.buildingName,
|
||||
};
|
||||
|
||||
this.showViewPopup = true;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('❌ 加载检查记录失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 查看检查弹窗关闭处理
|
||||
handleViewPopupClose() {
|
||||
this.showViewPopup = false;
|
||||
this.currentViewItem = null;
|
||||
this.currentViewData = null;
|
||||
this.currentViewRoomInfo = null;
|
||||
this.currentViewCheckInfo = null;
|
||||
},
|
||||
|
||||
// 修改检查
|
||||
@@ -903,4 +1009,25 @@ export default {
|
||||
::v-deep .u-button {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
// 查看弹窗样式
|
||||
.popup-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 30rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.popup-scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -114,7 +114,7 @@
|
||||
提交申请
|
||||
</u-button>
|
||||
<u-button
|
||||
type="warning"
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="file-text"
|
||||
shape="round"
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
@click="handleViewRevoke(item)"
|
||||
plain
|
||||
>
|
||||
查看撤销申请
|
||||
撤销查看
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<view class="discipline-record-container">
|
||||
<!-- 顶部导航 -->
|
||||
<u-navbar title="违纪记录录入" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
||||
<view slot="left" @click="goBack">
|
||||
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
||||
</view>
|
||||
</u-navbar>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索学生姓名或学号"
|
||||
:show-action="false"
|
||||
@search="handleSearch"
|
||||
@custom="handleSearch"
|
||||
bg-color="#f6f6f6"
|
||||
shape="round"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<u-loading-page :loading="loading" loading-text="加载中..."></u-loading-page>
|
||||
|
||||
<!-- 违纪记录列表 -->
|
||||
<view v-if="!loading" class="content-wrapper">
|
||||
<!-- 空状态 -->
|
||||
<view v-if="disciplineRecordList.length === 0" class="empty-container">
|
||||
<u-empty text="暂无违纪记录" mode="data"></u-empty>
|
||||
</view>
|
||||
|
||||
<!-- 记录卡片列表 -->
|
||||
<view v-else class="record-list">
|
||||
<view
|
||||
v-for="(item, index) in disciplineRecordList"
|
||||
:key="item.id || index"
|
||||
class="record-card"
|
||||
>
|
||||
<!-- 卡片头部 -->
|
||||
<view class="card-header">
|
||||
<view class="student-info">
|
||||
<text class="student-name">{{ item.studentName || '-' }}</text>
|
||||
<text class="student-no">({{ item.studentNo || '-' }})</text>
|
||||
</view>
|
||||
<view class="record-status">
|
||||
<u-tag
|
||||
:text="getDictLabel(item.status)"
|
||||
:type="getStatusType(item.status)"
|
||||
size="mini"
|
||||
></u-tag>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 卡片内容 -->
|
||||
<view class="card-content">
|
||||
<view class="info-item">
|
||||
<text class="label">违纪类型:</text>
|
||||
<text class="value">{{ item.disciplineTypeName || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">违纪日期:</text>
|
||||
<text class="value">{{ item.disciplineDate || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">扣分分值:</text>
|
||||
<text class="value value-score">-{{ item.scoreDeduction || '0' }}</text>
|
||||
</view>
|
||||
<view class="info-item">
|
||||
<text class="label">学院班级:</text>
|
||||
<text class="value">
|
||||
{{ item.collegeName || '-' }} / {{ item.className || '-' }}
|
||||
</text>
|
||||
</view>
|
||||
<view v-if="item.disciplineReason" class="info-item">
|
||||
<text class="label">违纪原因:</text>
|
||||
<text class="value">{{ item.disciplineReason }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="card-actions">
|
||||
<!-- 查看按钮 -->
|
||||
<u-button
|
||||
type="info"
|
||||
size="small"
|
||||
class="action-btn"
|
||||
@click="handleView(item)"
|
||||
>
|
||||
<u-icon name="eye" size="16" color="#323233"></u-icon>
|
||||
<text>查看</text>
|
||||
</u-button>
|
||||
|
||||
<!-- 编辑按钮 -->
|
||||
<u-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="action-btn"
|
||||
@click="handleEdit(item)"
|
||||
>
|
||||
<u-icon name="edit-pen" size="16" color="#ffffff"></u-icon>
|
||||
<text class="btn-text">编辑</text>
|
||||
</u-button>
|
||||
|
||||
<!-- 删除按钮 -->
|
||||
<u-button
|
||||
type="error"
|
||||
size="small"
|
||||
class="action-btn"
|
||||
@click="handleDelete(item)"
|
||||
>
|
||||
<u-icon name="trash" size="16" color="#ffffff"></u-icon>
|
||||
<text class="btn-text">删除</text>
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="disciplineRecordList.length > 0" class="load-more">
|
||||
<u-loadmore :status="loadMoreStatus" @loadmore="loadMore" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 浮动新增按钮 -->
|
||||
<view class="float-add-button" @click="handleAdd">
|
||||
<u-icon name="plus" color="#fff" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { request } from '@/common/request.js';
|
||||
import { getDictLabel, getDictTagType, getDict } from '@/common/dict.js';
|
||||
|
||||
export default {
|
||||
name: 'DisciplineRecordEntry',
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
disciplineRecordList: [],
|
||||
total: 0,
|
||||
|
||||
// 搜索关键词
|
||||
searchKeyword: '',
|
||||
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
},
|
||||
|
||||
// 加载更多状态
|
||||
loadMoreStatus: 'loadmore',
|
||||
|
||||
// 字典数据
|
||||
statusDict: [],
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await this.loadDictData();
|
||||
await this.getList();
|
||||
|
||||
// 监听页面刷新事件
|
||||
uni.$on('refreshDisciplineRecordList', () => {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
});
|
||||
},
|
||||
onUnload() {
|
||||
// 移除监听
|
||||
uni.$off('refreshDisciplineRecordList');
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 加载字典数据
|
||||
async loadDictData() {
|
||||
try {
|
||||
this.statusDict = await getDict('student_discipline_status');
|
||||
} catch (error) {
|
||||
console.error('加载字典数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取字典标签
|
||||
getDictLabel(value) {
|
||||
return getDictLabel(this.statusDict, value) || value;
|
||||
},
|
||||
|
||||
// 获取状态类型
|
||||
getStatusType(status) {
|
||||
const fallbackMap = {
|
||||
1: 'success', // 生效
|
||||
2: 'info', // 已撤销
|
||||
};
|
||||
return getDictTagType(this.statusDict, status, fallbackMap);
|
||||
},
|
||||
|
||||
// 获取违纪记录列表(仅显示当前用户创建的)
|
||||
async getList() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/disciplineRecord/disciplineRecord/myCreated',
|
||||
method: 'GET',
|
||||
params: this.queryParams,
|
||||
});
|
||||
|
||||
if (res && res.code === 200) {
|
||||
const newList = res.rows || [];
|
||||
|
||||
if (this.queryParams.pageNum === 1) {
|
||||
this.disciplineRecordList = newList;
|
||||
} else {
|
||||
this.disciplineRecordList = [...this.disciplineRecordList, ...newList];
|
||||
}
|
||||
|
||||
this.total = res.total || 0;
|
||||
|
||||
// 更新加载更多状态
|
||||
if (this.disciplineRecordList.length >= this.total) {
|
||||
this.loadMoreStatus = 'nomore';
|
||||
} else {
|
||||
this.loadMoreStatus = 'loadmore';
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取违纪记录列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '获取数据失败',
|
||||
icon: 'none',
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 搜索处理
|
||||
handleSearch() {
|
||||
// 解析搜索关键词
|
||||
const keyword = this.searchKeyword.trim();
|
||||
|
||||
// 重置查询参数
|
||||
this.queryParams.pageNum = 1;
|
||||
|
||||
// 如果关键词为空,清空搜索条件
|
||||
if (!keyword) {
|
||||
this.queryParams.studentName = '';
|
||||
this.queryParams.studentNo = '';
|
||||
} else {
|
||||
// 同时按学生姓名和学号搜索
|
||||
this.queryParams.studentName = keyword;
|
||||
this.queryParams.studentNo = keyword;
|
||||
}
|
||||
|
||||
// 重新加载列表
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 加载更多
|
||||
loadMore() {
|
||||
if (this.loadMoreStatus === 'loading' || this.loadMoreStatus === 'nomore') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadMoreStatus = 'loading';
|
||||
this.queryParams.pageNum += 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 刷新列表
|
||||
handleSuccess() {
|
||||
this.queryParams.pageNum = 1;
|
||||
this.getList();
|
||||
},
|
||||
|
||||
// 新增按钮操作
|
||||
handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/xskh/wjjlEntry/disciplineRecordForm?mode=add',
|
||||
});
|
||||
},
|
||||
|
||||
// 查看按钮操作
|
||||
handleView(row) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/xskh/wjjlEntry/disciplineRecordForm?mode=view&id=${row.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑按钮操作
|
||||
handleEdit(row) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/xskh/wjjlEntry/disciplineRecordForm?mode=edit&id=${row.id}`,
|
||||
});
|
||||
},
|
||||
|
||||
// 删除按钮操作
|
||||
handleDelete(row) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确定要删除学生"${row.studentName}"的违纪记录吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({ title: '删除中...' });
|
||||
|
||||
await request({
|
||||
url: `/disciplineRecord/disciplineRecord/${row.id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success',
|
||||
});
|
||||
|
||||
this.handleSuccess();
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('删除失败:', error);
|
||||
uni.showToast({
|
||||
title: '删除失败',
|
||||
icon: 'error',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.discipline-record-container {
|
||||
background-color: #f5f6fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
padding: 100rpx 20rpx;
|
||||
}
|
||||
|
||||
.record-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.record-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.student-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.student-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.student-no {
|
||||
font-size: 26rpx;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.record-status {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 28rpx;
|
||||
color: #606266;
|
||||
min-width: 140rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 28rpx;
|
||||
color: #303133;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.value-score {
|
||||
color: #f56c6c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 20rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
::v-deep .u-button {
|
||||
border-radius: 20rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
min-width: 120rpx;
|
||||
height: auto;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 24rpx;
|
||||
margin-left: 8rpx;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
/* 浮动新增按钮 */
|
||||
.float-add-button {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 100rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #3c9cff, #0051d9);
|
||||
box-shadow: 0 8rpx 24rpx rgba(60, 156, 255, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
// uview组件样式调整
|
||||
::v-deep .u-tag {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
::v-deep .u-button {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,308 @@
|
||||
// 文件上传工具 - 移动端适配
|
||||
import { request } from '@/common/request.js';
|
||||
import { REQUEST_CONFIG } from '@/common/config.js';
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
*/
|
||||
function getToken() {
|
||||
try {
|
||||
const token = uni.getStorageSync('access_token');
|
||||
return token ? `Bearer ${token}` : '';
|
||||
} catch (e) {
|
||||
console.error('❌ 获取Token失败:', e);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完整的上传URL
|
||||
*/
|
||||
function buildUploadUrl(url) {
|
||||
let fullUrl;
|
||||
if (REQUEST_CONFIG.baseURL.startsWith('/')) {
|
||||
// 开发环境,baseURL是'/api',需要使用完整的服务器URL
|
||||
fullUrl = `${REQUEST_CONFIG.serverUrl}${url}`;
|
||||
} else {
|
||||
// 生产环境,直接拼接
|
||||
fullUrl = REQUEST_CONFIG.baseURL + url;
|
||||
}
|
||||
return fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传单个文件
|
||||
* @param {string} url - 上传接口URL
|
||||
* @param {Object} options - 上传选项
|
||||
* @param {string} options.filePath - 文件路径
|
||||
* @param {string} options.name - 文件字段名,默认'file'
|
||||
* @param {Object} options.formData - 额外的表单数据
|
||||
* @param {Function} options.onProgress - 进度回调
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function uploadFile(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { filePath, name = 'file', formData = {}, onProgress } = options;
|
||||
|
||||
if (!filePath) {
|
||||
return reject(new Error('文件路径不能为空'));
|
||||
}
|
||||
|
||||
const token = getToken();
|
||||
const clientid = uni.getStorageSync('clientid') || REQUEST_CONFIG.clientId;
|
||||
const fullUrl = buildUploadUrl(url);
|
||||
|
||||
console.log('🚀 开始上传文件:', {
|
||||
url: fullUrl,
|
||||
filePath,
|
||||
name,
|
||||
});
|
||||
|
||||
const uploadTask = uni.uploadFile({
|
||||
url: fullUrl,
|
||||
filePath,
|
||||
name,
|
||||
formData,
|
||||
header: {
|
||||
authorization: token,
|
||||
clientid: clientid,
|
||||
},
|
||||
success: (res) => {
|
||||
console.log('✅ 文件上传成功:', res);
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.code === 200) {
|
||||
resolve(data);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: data.msg || '上传失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(new Error(data.msg || '上传失败'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 解析上传响应失败:', error);
|
||||
reject(new Error('解析响应数据失败'));
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`上传失败: HTTP ${res.statusCode}`));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 文件上传失败:', err);
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none',
|
||||
});
|
||||
reject(new Error(err.errMsg || '上传失败'));
|
||||
},
|
||||
});
|
||||
|
||||
// 监听上传进度
|
||||
if (onProgress && typeof onProgress === 'function') {
|
||||
uploadTask.onProgressUpdate((res) => {
|
||||
onProgress(res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传多个文件
|
||||
* @param {string} url - 上传接口URL
|
||||
* @param {Array} files - 文件列表
|
||||
* @param {Object} options - 上传选项
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
export function uploadFiles(url, files, options = {}) {
|
||||
const uploadPromises = files.map((file, index) => {
|
||||
return uploadFile(url, {
|
||||
...options,
|
||||
filePath: file.tempFilePath || file.path || file.url,
|
||||
onProgress: (progress) => {
|
||||
if (options.onProgress) {
|
||||
options.onProgress({
|
||||
...progress,
|
||||
fileIndex: index,
|
||||
totalFiles: files.length,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(uploadPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择并上传图片
|
||||
* @param {string} url - 上传接口URL
|
||||
* @param {Object} options - 选择和上传选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function chooseAndUploadImage(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
count = 1,
|
||||
sizeType = ['compressed'],
|
||||
sourceType = ['album', 'camera'],
|
||||
...uploadOptions
|
||||
} = options;
|
||||
|
||||
uni.chooseImage({
|
||||
count,
|
||||
sizeType,
|
||||
sourceType,
|
||||
success: (chooseRes) => {
|
||||
console.log('📷 选择图片成功:', chooseRes);
|
||||
|
||||
if (count === 1) {
|
||||
// 单张图片上传
|
||||
uploadFile(url, {
|
||||
...uploadOptions,
|
||||
filePath: chooseRes.tempFilePaths[0],
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
} else {
|
||||
// 多张图片上传
|
||||
uploadFiles(url, chooseRes.tempFiles, uploadOptions)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 选择图片失败:', err);
|
||||
reject(new Error(err.errMsg || '选择图片失败'));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择并上传文件
|
||||
* @param {string} url - 上传接口URL
|
||||
* @param {Object} options - 选择和上传选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function chooseAndUploadFile(url, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { count = 1, type = 'all', extension = [], ...uploadOptions } = options;
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序使用 wx.chooseMessageFile
|
||||
uni.chooseMessageFile({
|
||||
count,
|
||||
type,
|
||||
extension,
|
||||
success: (chooseRes) => {
|
||||
console.log('📁 选择文件成功:', chooseRes);
|
||||
|
||||
if (count === 1) {
|
||||
uploadFile(url, {
|
||||
...uploadOptions,
|
||||
filePath: chooseRes.tempFiles[0].path,
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
} else {
|
||||
uploadFiles(url, chooseRes.tempFiles, uploadOptions)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 选择文件失败:', err);
|
||||
reject(new Error(err.errMsg || '选择文件失败'));
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
// H5和其他平台使用普通文件选择
|
||||
uni.chooseFile({
|
||||
count,
|
||||
type,
|
||||
extension,
|
||||
success: (chooseRes) => {
|
||||
console.log('📁 选择文件成功:', chooseRes);
|
||||
|
||||
if (count === 1) {
|
||||
uploadFile(url, {
|
||||
...uploadOptions,
|
||||
filePath: chooseRes.tempFilePaths[0],
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
} else {
|
||||
uploadFiles(url, chooseRes.tempFiles, uploadOptions)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 选择文件失败:', err);
|
||||
reject(new Error(err.errMsg || '选择文件失败'));
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩图片
|
||||
* @param {string} src - 图片路径
|
||||
* @param {Object} options - 压缩选项
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function compressImage(src, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { quality = 80, compressedWidth, compressedHeight } = options;
|
||||
|
||||
uni.compressImage({
|
||||
src,
|
||||
quality,
|
||||
compressedWidth,
|
||||
compressedHeight,
|
||||
success: (res) => {
|
||||
console.log('✅ 图片压缩成功:', res);
|
||||
resolve(res.tempFilePath);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('❌ 图片压缩失败:', err);
|
||||
reject(new Error(err.errMsg || '图片压缩失败'));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片信息
|
||||
* @param {string} src - 图片路径
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function getImageInfo(src) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => {
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || '获取图片信息失败'));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
uploadFile,
|
||||
uploadFiles,
|
||||
chooseAndUploadImage,
|
||||
chooseAndUploadFile,
|
||||
compressImage,
|
||||
getImageInfo,
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
org.dromara.system.domain.vo.SysSocialVoToSysSocialMapper
|
||||
org.dromara.system.domain.vo.SysDictDataVoToSysDictDataMapper
|
||||
org.dromara.system.domain.SysOssConfigToSysOssConfigVoMapper
|
||||
org.dromara.system.domain.vo.SysLogininforVoToSysLogininforMapper
|
||||
org.dromara.system.domain.bo.SysConfigBoToSysConfigMapper
|
||||
org.dromara.system.domain.SysTenantPackageToSysTenantPackageVoMapper
|
||||
org.dromara.system.domain.bo.SysPostBoToSysPostMapper
|
||||
org.dromara.system.domain.vo.SysOssConfigVoToSysOssConfigMapper
|
||||
org.dromara.system.domain.SysUserToSysUserVoMapper
|
||||
org.dromara.system.domain.vo.SysDictTypeVoToSysDictTypeMapper
|
||||
org.dromara.system.domain.SysRegionToSysRegionVoMapper
|
||||
org.dromara.system.domain.SysDictDataToSysDictDataVoMapper
|
||||
org.dromara.system.domain.bo.SysMenuBoToSysMenuMapper
|
||||
org.dromara.system.domain.bo.SysWeChatConfigBoToSysWeChatConfigMapper
|
||||
org.dromara.system.domain.vo.SysRoleVoToSysRoleMapper
|
||||
org.dromara.system.domain.SysConfigToSysConfigVoMapper
|
||||
org.dromara.system.domain.bo.SysOperLogBoToOperLogEventMapper
|
||||
org.dromara.system.domain.vo.SysClientVoToSysClientMapper
|
||||
org.dromara.system.domain.bo.SysOssConfigBoToSysOssConfigMapper
|
||||
org.dromara.system.domain.vo.SysNoticeVoToSysNoticeMapper
|
||||
org.dromara.system.domain.bo.SysDeptBoToSysDeptMapper
|
||||
org.dromara.system.domain.bo.SysOssBoToSysOssMapper
|
||||
org.dromara.system.domain.vo.SysMenuVoToSysMenuMapper
|
||||
org.dromara.system.domain.vo.SysWeChatConfigVoToSysWeChatConfigMapper
|
||||
org.dromara.system.domain.SysMenuToSysMenuVoMapper
|
||||
org.dromara.system.domain.vo.SysTenantVoToSysTenantMapper
|
||||
org.dromara.system.domain.bo.SysUserBoToSysUserMapper
|
||||
org.dromara.system.domain.bo.SysRegionBoToSysRegionMapper
|
||||
org.dromara.system.domain.bo.SysClientBoToSysClientMapper
|
||||
org.dromara.system.domain.bo.SysDictDataBoToSysDictDataMapper
|
||||
org.dromara.system.domain.vo.SysUserVoToSysUserMapper
|
||||
org.dromara.system.domain.SysClientToSysClientVoMapper
|
||||
org.dromara.system.domain.SysNoticeToSysNoticeVoMapper
|
||||
org.dromara.system.domain.SysOssToSysOssVoMapper
|
||||
org.dromara.system.domain.SysOperLogToSysOperLogVoMapper
|
||||
org.dromara.system.domain.bo.SysLogininforBoToSysLogininforMapper
|
||||
org.dromara.system.domain.vo.SysDeptVoToSysDeptMapper
|
||||
org.dromara.system.domain.SysTenantToSysTenantVoMapper
|
||||
org.dromara.system.domain.vo.SysConfigVoToSysConfigMapper
|
||||
org.dromara.system.domain.bo.SysSocialBoToSysSocialMapper
|
||||
org.dromara.system.domain.bo.SysRoleBoToSysRoleMapper
|
||||
org.dromara.system.domain.vo.SysOssVoToSysOssMapper
|
||||
org.dromara.system.domain.SysDictTypeToSysDictTypeVoMapper
|
||||
org.dromara.system.domain.SysRoleToSysRoleVoMapper
|
||||
org.dromara.system.domain.vo.SysTenantPackageVoToSysTenantPackageMapper
|
||||
org.dromara.system.domain.bo.SysTenantBoToSysTenantMapper
|
||||
org.dromara.system.domain.bo.SysTenantPackageBoToSysTenantPackageMapper
|
||||
org.dromara.system.domain.vo.SysRegionVoToSysRegionMapper
|
||||
org.dromara.system.domain.SysLogininforToSysLogininforVoMapper
|
||||
org.dromara.system.domain.vo.SysPostVoToSysPostMapper
|
||||
org.dromara.system.domain.SysSocialToSysSocialVoMapper
|
||||
org.dromara.system.domain.bo.SysDictTypeBoToSysDictTypeMapper
|
||||
org.dromara.common.log.event.OperLogEventToSysOperLogBoMapper
|
||||
org.dromara.system.domain.SysWeChatConfigToSysWeChatConfigVoMapper
|
||||
org.dromara.system.domain.bo.SysOperLogBoToSysOperLogMapper
|
||||
org.dromara.system.domain.vo.SysOperLogVoToSysOperLogMapper
|
||||
org.dromara.system.domain.SysDeptToSysDeptVoMapper
|
||||
org.dromara.system.domain.bo.SysNoticeBoToSysNoticeMapper
|
||||
org.dromara.system.domain.SysPostToSysPostVoMapper
|
||||
@@ -0,0 +1,47 @@
|
||||
import request from '@/utils/request';
|
||||
import type { AxiosPromise } from 'axios';
|
||||
import type { CollegeVo, CollegeQuery } from './types';
|
||||
|
||||
// 导出类型
|
||||
export type { CollegeVo, MajorVo, CollegeQuery } from './types';
|
||||
|
||||
/**
|
||||
* 查询学院列表(用于二级学院,parent_id = 101)
|
||||
*/
|
||||
export function list(query?: any): AxiosPromise<CollegeVo[]> {
|
||||
return request({
|
||||
url: '/xg/college/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学院和专业树形结构
|
||||
*/
|
||||
export function collegeTree(): AxiosPromise<CollegeVo[]> {
|
||||
return request({
|
||||
url: '/xg/college/tree',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID获取专业列表
|
||||
*/
|
||||
export function listMajorsByCollegeId(collegeId: string | number): AxiosPromise<any[]> {
|
||||
return request({
|
||||
url: `/xg/college/majors/${collegeId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询学院详情
|
||||
*/
|
||||
export function getCollege(deptId: string | number): AxiosPromise<CollegeVo> {
|
||||
return request({
|
||||
url: `/xg/college/${deptId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 学院信息对象
|
||||
*/
|
||||
export interface CollegeVo {
|
||||
deptId: number | string;
|
||||
deptName: string;
|
||||
parentId?: number | string;
|
||||
orderNum?: number;
|
||||
leader?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
status?: string;
|
||||
children?: CollegeVo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 专业信息对象
|
||||
*/
|
||||
export interface MajorVo {
|
||||
deptId: number | string;
|
||||
deptName: string;
|
||||
parentId: number | string;
|
||||
orderNum?: number;
|
||||
leader?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学院查询参数
|
||||
*/
|
||||
export interface CollegeQuery {
|
||||
deptName?: string;
|
||||
status?: string;
|
||||
parentId?: number | string;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置对象
|
||||
*/
|
||||
export interface CollegeLeaderBo {
|
||||
id?: number | string;
|
||||
collegeId?: number | string;
|
||||
collegeName?: string;
|
||||
leaderId?: number | string;
|
||||
leaderName?: string;
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级学院分管领导配置视图对象
|
||||
*/
|
||||
export interface CollegeLeaderVo {
|
||||
id: number | string;
|
||||
collegeId: number | string;
|
||||
collegeName: string;
|
||||
leaderId: number | string;
|
||||
leaderName: string;
|
||||
remark?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询二级学院分管领导配置列表
|
||||
* @param query 查询参数
|
||||
* @returns 返回二级学院分管领导配置列表
|
||||
*/
|
||||
export function listCollegeLeader(query: any) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学院ID查询分管领导列表
|
||||
* @param collegeId 学院ID
|
||||
* @returns 返回分管领导列表
|
||||
*/
|
||||
export function listCollegeLeaderByCollege(collegeId: number | string) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/listByCollege/${collegeId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级学院分管领导配置详情
|
||||
* @param id 主键ID
|
||||
* @returns 返回二级学院分管领导配置详情
|
||||
*/
|
||||
export function getCollegeLeader(id: number | string) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增二级学院分管领导配置
|
||||
* @param data 二级学院分管领导配置信息
|
||||
* @returns 返回添加结果
|
||||
*/
|
||||
export function addCollegeLeader(data: CollegeLeaderBo) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改二级学院分管领导配置
|
||||
* @param data 二级学院分管领导配置信息
|
||||
* @returns 返回修改结果
|
||||
*/
|
||||
export function updateCollegeLeader(data: CollegeLeaderBo) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除二级学院分管领导配置
|
||||
* @param ids 主键ID数组
|
||||
* @returns 返回删除结果
|
||||
*/
|
||||
export function delCollegeLeader(ids: number | string | (number | string)[]) {
|
||||
return request({
|
||||
url: `/xg/ejxyfgld/${ids instanceof Array ? ids.join(',') : ids}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出二级学院分管领导配置
|
||||
* @param query 查询参数
|
||||
* @returns 返回导出结果
|
||||
*/
|
||||
export function exportCollegeLeader(query: any) {
|
||||
return request({
|
||||
url: '/xg/ejxyfgld/export',
|
||||
method: 'post',
|
||||
params: query,
|
||||
responseType: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
import request from '@/utils/request';
|
||||
import { AxiosPromise } from 'axios';
|
||||
|
||||
/**
|
||||
* 学生动向信息管理(宿管员)API
|
||||
* 这些接口专门用于学生动向Tab页,会自动根据宿管员权限过滤数据
|
||||
*/
|
||||
|
||||
// ==================== 调宿退宿申请相关接口 ====================
|
||||
|
||||
export interface DormApplicationQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
applicationType?: string;
|
||||
applicationStatus?: string;
|
||||
applyTime?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface DormApplicationVO {
|
||||
id: string | number;
|
||||
applicationName: string;
|
||||
applicationType: string;
|
||||
applicationStatus: string;
|
||||
studentId: string | number;
|
||||
studentNo: string;
|
||||
studentName: string;
|
||||
studentPhone: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
currentCampusId: string | number;
|
||||
currentCampusName: string;
|
||||
currentBuildingId: string | number;
|
||||
currentBuildingName: string;
|
||||
currentRoomId: string | number;
|
||||
currentRoomNo: string;
|
||||
currentBedId: string | number;
|
||||
currentBedNo: string;
|
||||
targetCampusId?: string | number;
|
||||
targetCampusName?: string;
|
||||
targetBuildingId?: string | number;
|
||||
targetBuildingName?: string;
|
||||
targetRoomId?: string | number;
|
||||
targetRoomNo?: string;
|
||||
targetBedId?: string | number;
|
||||
targetBedNo?: string;
|
||||
reasonDetail: string;
|
||||
applyTime: string;
|
||||
attachmentOssIds?: string;
|
||||
remark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生调宿退宿申请列表
|
||||
*/
|
||||
export function listDormApplication(query?: DormApplicationQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplicationList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调宿退宿申请详细信息
|
||||
*/
|
||||
export function getDormApplication(id: string | number): AxiosPromise<DormApplicationVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调宿退宿申请详细信息(别名)
|
||||
*/
|
||||
export const getDormApplicationInfo = getDormApplication;
|
||||
|
||||
/**
|
||||
* 修改调宿退宿申请信息
|
||||
*/
|
||||
export function updateDormApplication(data: DormApplicationVO): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除调宿退宿申请
|
||||
*/
|
||||
export function delDormApplication(id: string | number | Array<string | number>): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/dormApplication/' + id,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 假期留宿相关接口 ====================
|
||||
|
||||
export interface HolidayStayQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
holidayType?: string;
|
||||
applyStartDate?: string;
|
||||
applyEndDate?: string;
|
||||
status?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface HolidayStayVO {
|
||||
id: string | number;
|
||||
studentId: string | number;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
campusId: string | number;
|
||||
campusName: string;
|
||||
areaId?: string | number;
|
||||
areaName?: string;
|
||||
buildingId: string | number;
|
||||
buildingName: string;
|
||||
roomId: string | number;
|
||||
roomName: string;
|
||||
bedId: string | number;
|
||||
bedName: string;
|
||||
holidayType: string;
|
||||
applyStartDate: string;
|
||||
applyEndDate: string;
|
||||
stayReason: string;
|
||||
stayReasonType: string;
|
||||
studentPhone: string;
|
||||
emergencyContact: string;
|
||||
emergencyPhone: string;
|
||||
parentConsent: string;
|
||||
attachmentOssIds?: string;
|
||||
remark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生假期留宿信息列表
|
||||
*/
|
||||
export function listHolidayStay(query?: HolidayStayQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/holidayStayList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿详细信息
|
||||
*/
|
||||
export function getHolidayStay(id: string | number): AxiosPromise<HolidayStayVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/holidayStay/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取假期留宿详细信息(别名)
|
||||
*/
|
||||
export const getHolidayStayInfo = getHolidayStay;
|
||||
|
||||
// ==================== 学生请假相关接口 ====================
|
||||
|
||||
export interface StudentLeaveQuery {
|
||||
pageNum?: number;
|
||||
pageSize?: number;
|
||||
studentName?: string;
|
||||
studentNo?: string;
|
||||
collegeName?: string;
|
||||
majorName?: string;
|
||||
className?: string;
|
||||
leaveTypeName?: string;
|
||||
approvalStatus?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface StudentLeaveVO {
|
||||
id: string | number;
|
||||
studentId: string | number;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
classId: string | number;
|
||||
className: string;
|
||||
majorId: string | number;
|
||||
majorName: string;
|
||||
collegeId: string | number;
|
||||
collegeName: string;
|
||||
leaveTypeCode: string;
|
||||
leaveTypeName: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
leaveDuration: number;
|
||||
isLeaveSchool: string;
|
||||
needCancel: string;
|
||||
emergencyContact: string;
|
||||
emergencyPhone: string;
|
||||
leaveReason: string;
|
||||
leaveLocation?: string;
|
||||
attachmentOssIds?: string;
|
||||
approvalStatus: string;
|
||||
approvalOpinion?: string;
|
||||
approvalTime?: string;
|
||||
approvalBy?: string | number;
|
||||
approvalByName?: string;
|
||||
cancelStatus?: string;
|
||||
cancelTime?: string;
|
||||
cancelLocation?: string;
|
||||
cancelPhotoOssIds?: string;
|
||||
cancelRemark?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询宿管员权限内的学生请假信息列表
|
||||
*/
|
||||
export function listStudentLeave(query?: StudentLeaveQuery): AxiosPromise<any> {
|
||||
return request({
|
||||
url: '/studentDynamics/studentLeaveList',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生请假详细信息
|
||||
*/
|
||||
export function getStudentLeave(id: string | number): AxiosPromise<StudentLeaveVO> {
|
||||
return request({
|
||||
url: '/studentDynamics/studentLeave/' + id,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生请假详细信息(别名)
|
||||
*/
|
||||
export const getStudentLeaveInfo = getStudentLeave;
|
||||
|
||||
@@ -6,16 +6,17 @@ import { AxiosPromise } from 'axios';
|
||||
*/
|
||||
export interface XgSysUser {
|
||||
userId: number | string;
|
||||
userName: string;
|
||||
userName?: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
email?: string;
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
status: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
deptId: number;
|
||||
deptName: string;
|
||||
sex?: string;
|
||||
status?: string;
|
||||
accountType?: string;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
deptId?: number;
|
||||
deptName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,3 +28,8 @@ export function getActiveUserList(): AxiosPromise<XgSysUser[]> {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询有效用户列表(别名,用于兼容)
|
||||
*/
|
||||
export const activeList = getActiveUserList;
|
||||
@@ -106,3 +106,14 @@ export const getCollegeLeaveApprovers = (studentId: string | number): AxiosPromi
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据学生ID获取二级学院分管领导ID列表
|
||||
* @param studentId 学生ID
|
||||
*/
|
||||
export const getCollegeLeaders = (studentId: string | number): AxiosPromise<{ data: number[] }> => {
|
||||
return request({
|
||||
url: `/studentMsg/studentMsg/getCollegeLeaders/${studentId}`,
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
@@ -33,6 +33,21 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="message" label="审批意见" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column prop="signature" label="手写签名" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<el-popover v-if="scope.row.signature" placement="left" :width="350" trigger="hover">
|
||||
<template #reference>
|
||||
<div class="signature-thumbnail">
|
||||
<img :src="scope.row.signature" alt="签名" class="signature-thumb-image" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="signature-preview-large">
|
||||
<img :src="scope.row.signature" alt="签名" class="signature-large-image" />
|
||||
</div>
|
||||
</el-popover>
|
||||
<span v-else style="color: #909399; font-size: 12px">未签名</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="开始时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column prop="updateTime" label="结束时间" width="160" :show-overflow-tooltip="true" sortable align="center"></el-table-column>
|
||||
<el-table-column
|
||||
@@ -68,6 +83,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, getCurrentInstance, toRefs, type ComponentInternalInstance } from 'vue';
|
||||
import { flowHisTaskList } from '@/api/workflow/instance';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { listByIds } from '@/api/system/oss';
|
||||
@@ -198,4 +214,48 @@ defineExpose({
|
||||
min-height: calc(100vh - 170px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 列表中的签名缩略图容器
|
||||
.signature-thumbnail {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.signature-thumb-image {
|
||||
max-width: 120px;
|
||||
max-height: 50px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标悬停时的大图预览
|
||||
.signature-preview-large {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 6px;
|
||||
|
||||
.signature-large-image {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
border: 2px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -31,6 +31,9 @@
|
||||
<el-form-item v-if="task.flowStatus === 'waiting'" label="审批意见">
|
||||
<el-input v-model="form.message" type="textarea" resize="none" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="task.flowStatus === 'waiting'" label="">
|
||||
<SignaturePad v-model="form.signature" :width="480" :height="180" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
@@ -107,6 +110,9 @@
|
||||
<el-form-item label="审批意见">
|
||||
<el-input v-model="backForm.message" type="textarea" resize="none" />
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<SignaturePad v-model="backForm.signature" :width="450" :height="180" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer" style="float: right; padding-bottom: 20px">
|
||||
@@ -147,6 +153,7 @@ import {
|
||||
getNextNodeList
|
||||
} from '@/api/workflow/task';
|
||||
import UserSelect from '@/components/UserSelect';
|
||||
import SignaturePad from '@/components/SignaturePad/index.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
import { UserVO } from '@/api/system/user/types';
|
||||
@@ -228,6 +235,7 @@ const deleteSignatureVisible = ref(false);
|
||||
const form = ref<Record<string, any>>({
|
||||
taskId: undefined,
|
||||
message: undefined,
|
||||
signature: undefined,
|
||||
assigneeMap: {},
|
||||
variables: {},
|
||||
messageType: ['1'],
|
||||
@@ -237,6 +245,7 @@ const backForm = ref<Record<string, any>>({
|
||||
taskId: undefined,
|
||||
nodeCode: undefined,
|
||||
message: undefined,
|
||||
signature: undefined,
|
||||
variables: {},
|
||||
messageType: ['1']
|
||||
});
|
||||
@@ -248,6 +257,7 @@ const openDialog = async (id?: string) => {
|
||||
form.value.fileId = undefined;
|
||||
taskId.value = id;
|
||||
form.value.message = undefined;
|
||||
form.value.signature = undefined;
|
||||
dialog.visible = true;
|
||||
loading.value = true;
|
||||
buttonDisabled.value = true;
|
||||
@@ -322,6 +332,7 @@ const handleCompleteTask = async () => {
|
||||
const handleBackProcessOpen = async () => {
|
||||
backForm.value = {};
|
||||
backForm.value.messageType = ['1'];
|
||||
backForm.value.signature = undefined;
|
||||
backVisible.value = true;
|
||||
backLoading.value = true;
|
||||
backButtonDisabled.value = true;
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div class="signature-pad-wrapper">
|
||||
<div class="signature-header">
|
||||
<span class="signature-title">手写签名</span>
|
||||
<el-button v-if="hasSignature" size="small" type="danger" :icon="Delete" @click="handleClear">清空</el-button>
|
||||
</div>
|
||||
<div class="signature-canvas-container">
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
class="signature-canvas"
|
||||
@mousedown="handleMouseDown"
|
||||
@mousemove="handleMouseMove"
|
||||
@mouseup="handleMouseUp"
|
||||
@mouseleave="handleMouseUp"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
></canvas>
|
||||
<div v-if="!hasSignature" class="signature-placeholder">请在此处签名</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||
import { Delete } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
modelValue?: string; // Base64签名数据
|
||||
width?: number;
|
||||
height?: number;
|
||||
lineWidth?: number;
|
||||
lineColor?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
width: 500,
|
||||
height: 200,
|
||||
lineWidth: 2,
|
||||
lineColor: '#000000'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void;
|
||||
}>();
|
||||
|
||||
const canvasRef = ref<HTMLCanvasElement>();
|
||||
const hasSignature = ref(false);
|
||||
const isDrawing = ref(false);
|
||||
let ctx: CanvasRenderingContext2D | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initCanvas();
|
||||
// 如果有初始值,加载到画布上
|
||||
if (props.modelValue) {
|
||||
loadSignature(props.modelValue);
|
||||
}
|
||||
|
||||
// 监听容器大小变化
|
||||
if (canvasRef.value?.parentElement) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
handleResize();
|
||||
});
|
||||
resizeObserver.observe(canvasRef.value.parentElement);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听外部值变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal && !hasSignature.value) {
|
||||
loadSignature(newVal);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const initCanvas = () => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const container = canvas.parentElement;
|
||||
|
||||
// 获取容器的实际宽度
|
||||
const containerWidth = container?.clientWidth || props.width;
|
||||
|
||||
// 设置画布的物理尺寸(实际像素)
|
||||
canvas.width = containerWidth;
|
||||
canvas.height = props.height;
|
||||
|
||||
// 同时设置CSS样式,确保显示尺寸与物理尺寸一致
|
||||
canvas.style.width = `${containerWidth}px`;
|
||||
canvas.style.height = `${props.height}px`;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.strokeStyle = props.lineColor;
|
||||
ctx.lineWidth = props.lineWidth;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
}
|
||||
};
|
||||
|
||||
// 处理容器大小变化
|
||||
const handleResize = () => {
|
||||
if (!canvasRef.value) return;
|
||||
|
||||
// 保存当前签名
|
||||
const currentSignature = hasSignature.value ? canvasRef.value.toDataURL('image/png') : '';
|
||||
|
||||
// 重新初始化画布
|
||||
initCanvas();
|
||||
|
||||
// 恢复签名
|
||||
if (currentSignature && hasSignature.value) {
|
||||
nextTick(() => {
|
||||
loadSignature(currentSignature);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const startDrawing = (x: number, y: number) => {
|
||||
if (!ctx || !canvasRef.value) return;
|
||||
|
||||
isDrawing.value = true;
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x - rect.left, y - rect.top);
|
||||
};
|
||||
|
||||
const draw = (x: number, y: number) => {
|
||||
if (!isDrawing.value || !ctx || !canvasRef.value) return;
|
||||
|
||||
const rect = canvasRef.value.getBoundingClientRect();
|
||||
ctx.lineTo(x - rect.left, y - rect.top);
|
||||
ctx.stroke();
|
||||
hasSignature.value = true;
|
||||
};
|
||||
|
||||
const stopDrawing = () => {
|
||||
if (!isDrawing.value) return;
|
||||
|
||||
isDrawing.value = false;
|
||||
if (ctx) {
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
// 导出签名数据
|
||||
if (hasSignature.value && canvasRef.value) {
|
||||
const signatureData = canvasRef.value.toDataURL('image/png');
|
||||
emit('update:modelValue', signatureData);
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标事件处理
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
startDrawing(e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
draw(e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
stopDrawing();
|
||||
};
|
||||
|
||||
// 触摸事件处理(移动端支持)
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
startDrawing(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
const touch = e.touches[0];
|
||||
draw(touch.clientX, touch.clientY);
|
||||
};
|
||||
|
||||
const handleTouchEnd = (e: TouchEvent) => {
|
||||
e.preventDefault();
|
||||
stopDrawing();
|
||||
};
|
||||
|
||||
// 清空签名
|
||||
const handleClear = () => {
|
||||
if (!canvasRef.value || !ctx) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
|
||||
hasSignature.value = false;
|
||||
emit('update:modelValue', '');
|
||||
};
|
||||
|
||||
// 加载签名
|
||||
const loadSignature = (base64Data: string) => {
|
||||
if (!canvasRef.value || !ctx) return;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx!.clearRect(0, 0, canvasRef.value!.width, canvasRef.value!.height);
|
||||
ctx!.drawImage(img, 0, 0);
|
||||
hasSignature.value = true;
|
||||
};
|
||||
img.src = base64Data;
|
||||
};
|
||||
|
||||
// 获取签名数据
|
||||
const getSignature = (): string => {
|
||||
if (!canvasRef.value || !hasSignature.value) return '';
|
||||
return canvasRef.value.toDataURL('image/png');
|
||||
};
|
||||
|
||||
// 对外暴露方法
|
||||
defineExpose({
|
||||
clear: handleClear,
|
||||
getSignature,
|
||||
hasSignature: () => hasSignature.value
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.signature-pad-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.signature-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.signature-title {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.signature-canvas-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
.signature-canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
cursor: crosshair;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.signature-placeholder {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #c0c4cc;
|
||||
font-size: 14px;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+37
-25
@@ -47,7 +47,7 @@
|
||||
<img :src="menu.logoUrl" :alt="menu.title" class="menu-logo" />
|
||||
</div>
|
||||
<span class="action-title" :class="{ 'disabled': !menu.available }">{{ menu.title }}</span>
|
||||
<span class="action-desc" :class="{ 'disabled': !menu.available }">{{ menu.description }}</span>
|
||||
<!-- <span class="action-desc" :class="{ 'disabled': !menu.available }">{{ menu.description }}</span> -->
|
||||
<!-- 移除层级路径显示 -->
|
||||
<el-tag v-if="!menu.available" type="info" size="small" class="status-tag">开发中</el-tag>
|
||||
</div>
|
||||
@@ -946,24 +946,36 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.menu-card-wrapper {
|
||||
flex: 0 0 calc(20% - 16px); // 每行5个,减去间隔
|
||||
min-width: 180px;
|
||||
flex: 0 0 calc((100% - 140px) / 8); // 每行8个,精确计算:(100% - 7个间隔*20px) / 8
|
||||
min-width: 100px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
flex: 0 0 calc((100% - 120px) / 7); // 中大屏幕每行7个
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
flex: 0 0 calc(25% - 15px); // 中等屏幕每行4个
|
||||
flex: 0 0 calc((100% - 100px) / 6); // 中等屏幕每行6个
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
flex: 0 0 calc(33.333% - 14px); // 小屏幕每行3个
|
||||
flex: 0 0 calc((100% - 80px) / 5); // 小屏幕每行5个
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
flex: 0 0 calc((100% - 60px) / 4); // 平板每行4个
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex: 0 0 calc(50% - 10px); // 移动端每行2个
|
||||
flex: 0 0 calc((100% - 40px) / 3); // 移动端每行3个
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
flex: 0 0 calc((100% - 20px) / 2); // 小手机每行2个
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
flex: 0 0 100%; // 超小屏幕每行1个
|
||||
flex: 0 0 calc(100% - 20px); // 超小屏幕每行1个
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,7 +984,7 @@ onMounted(async () => {
|
||||
border: none;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
height: 140px;
|
||||
height: 110px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -995,7 +1007,7 @@ onMounted(async () => {
|
||||
|
||||
.menu-action {
|
||||
text-align: center;
|
||||
padding: 15px 10px;
|
||||
padding: 10px 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1011,15 +1023,15 @@ onMounted(async () => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
height: 48px;
|
||||
margin-bottom: 8px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.menu-logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.1));
|
||||
|
||||
@@ -1041,16 +1053,16 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
color: #666;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
@@ -1140,29 +1152,29 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.menu-action-card {
|
||||
height: 120px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
padding: 10px 5px;
|
||||
padding: 8px 5px;
|
||||
}
|
||||
|
||||
.menu-logo-wrapper {
|
||||
height: 36px;
|
||||
margin-bottom: 8px;
|
||||
height: 30px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.menu-logo {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.action-title {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.action-desc {
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px" :inline="true">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="领导姓名" prop="leaderName">
|
||||
<el-input v-model="queryParams.leaderName" placeholder="请输入领导姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['xg:ejxyfgld:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['xg:ejxyfgld:remove']"
|
||||
>删除</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['xg:ejxyfgld:export']">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="collegeLeaderList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="学院名称" align="center" prop="collegeName" />
|
||||
<el-table-column label="领导姓名" align="center" prop="leaderName" />
|
||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['xg:ejxyfgld:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['xg:ejxyfgld:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改二级学院分管领导对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="collegeLeaderFormRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-form-item label="二级学院" prop="collegeId">
|
||||
<el-select v-model="form.collegeId" placeholder="请选择二级学院" clearable filterable style="width: 100%" @change="handleCollegeChange">
|
||||
<el-option v-for="college in collegeList" :key="college.deptId" :label="college.deptName" :value="college.deptId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="分管领导" prop="leaderId">
|
||||
<el-select v-model="form.leaderId" placeholder="请选择分管领导" clearable filterable style="width: 100%" @change="handleLeaderChange">
|
||||
<el-option v-for="user in teacherList" :key="user.userId" :label="`${user.nickName}(${user.userName})`" :value="user.userId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CollegeLeader" lang="ts">
|
||||
import { ref, reactive, toRefs, getCurrentInstance } from 'vue';
|
||||
import type { FormInstance } from 'element-plus';
|
||||
import {
|
||||
listCollegeLeader,
|
||||
getCollegeLeader,
|
||||
addCollegeLeader,
|
||||
updateCollegeLeader,
|
||||
delCollegeLeader,
|
||||
exportCollegeLeader,
|
||||
type CollegeLeaderBo,
|
||||
type CollegeLeaderVo
|
||||
} from '@/api/xg/ejxyfgld';
|
||||
import { list as listCollege } from '@/api/xg/college';
|
||||
import { activeList as listTeacher } from '@/api/xg/sysUser';
|
||||
|
||||
interface ComponentInternalInstance {
|
||||
proxy?: any;
|
||||
}
|
||||
|
||||
interface DialogOption {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
collegeName?: string;
|
||||
leaderName?: string;
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const collegeLeaderList = ref<CollegeLeaderVo[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
// 学院列表和教师列表
|
||||
const collegeList = ref<any[]>([]);
|
||||
const teacherList = ref<any[]>([]);
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const collegeLeaderFormRef = ref<FormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const initFormData: CollegeLeaderBo = {
|
||||
id: undefined,
|
||||
collegeId: undefined,
|
||||
collegeName: undefined,
|
||||
leaderId: undefined,
|
||||
leaderName: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
collegeName: undefined,
|
||||
leaderName: undefined
|
||||
} as QueryParams,
|
||||
rules: {
|
||||
collegeId: [{ required: true, message: '二级学院不能为空', trigger: 'change' }],
|
||||
leaderId: [{ required: true, message: '分管领导不能为空', trigger: 'change' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询二级学院分管领导列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listCollegeLeader(queryParams.value);
|
||||
collegeLeaderList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载学院列表 */
|
||||
const loadCollegeList = async () => {
|
||||
try {
|
||||
const res = await listCollege();
|
||||
collegeList.value = res.data || [];
|
||||
} catch (error) {
|
||||
console.error('加载学院列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 加载教师列表 */
|
||||
const loadTeacherList = async () => {
|
||||
try {
|
||||
const res = await listTeacher();
|
||||
// 后端已经过滤出教职工(account_type = 1),直接使用
|
||||
teacherList.value = res.data || [];
|
||||
console.log('加载教师列表成功,共', teacherList.value.length, '条数据');
|
||||
} catch (error) {
|
||||
console.error('加载教师列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 学院选择变更 */
|
||||
const handleCollegeChange = (val: any) => {
|
||||
const college = collegeList.value.find((c: any) => c.deptId === val);
|
||||
if (college) {
|
||||
form.value.collegeName = college.deptName;
|
||||
}
|
||||
};
|
||||
|
||||
/** 领导选择变更 */
|
||||
const handleLeaderChange = (val: any) => {
|
||||
const teacher = teacherList.value.find((t: any) => t.userId === val);
|
||||
if (teacher) {
|
||||
form.value.leaderName = teacher.nickName;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData };
|
||||
collegeLeaderFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: CollegeLeaderVo[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = async () => {
|
||||
reset();
|
||||
await loadCollegeList();
|
||||
await loadTeacherList();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加二级学院分管领导';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: CollegeLeaderVo) => {
|
||||
reset();
|
||||
await loadCollegeList();
|
||||
await loadTeacherList();
|
||||
const id = row?.id || ids.value[0];
|
||||
const res = await getCollegeLeader(id);
|
||||
Object.assign(form.value, res.data);
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改二级学院分管领导';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
collegeLeaderFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateCollegeLeader(form.value);
|
||||
proxy?.$modal.msgSuccess('修改成功');
|
||||
} else {
|
||||
await addCollegeLeader(form.value);
|
||||
proxy?.$modal.msgSuccess('新增成功');
|
||||
}
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: CollegeLeaderVo) => {
|
||||
const deleteIds = row?.id ? [row.id] : ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的配置?');
|
||||
try {
|
||||
await delCollegeLeader(deleteIds);
|
||||
await getList();
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.$download(
|
||||
'xg/ejxyfgld/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`二级学院分管领导_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
getList();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model="dialogOpen" width="800px" append-to-body @close="cancel">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请名称:">
|
||||
<span>{{ form.applicationName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请类型:">
|
||||
<dict-tag :options="dorm_application_type" :value="form.applicationType" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名:">
|
||||
<span>{{ form.studentName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号:">
|
||||
<span>{{ form.studentNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学院:">
|
||||
<span>{{ form.collegeName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业:">
|
||||
<span>{{ form.majorName }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级:">
|
||||
<span>{{ form.className }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话:">
|
||||
<span>{{ form.studentPhone }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="当前宿舍:">
|
||||
<span>{{ form.currentCampusName }} {{ form.currentBuildingName }} {{ form.currentRoomNo }} {{ form.currentBedNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20" v-if="form.applicationType === '1'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="目标宿舍:">
|
||||
<span>{{ form.targetCampusName }} {{ form.targetBuildingName }} {{ form.targetRoomNo }} {{ form.targetBedNo }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="申请时间:">
|
||||
<span>{{ form.applyTime }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="审批状态:">
|
||||
<dict-tag :options="student_leave_approval_status" :value="form.applicationStatus" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="申请原因:" prop="reasonDetail">
|
||||
<el-input
|
||||
v-if="isEditable"
|
||||
v-model="form.reasonDetail"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入申请原因"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<span v-else>{{ form.reasonDetail || '无' }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注信息:" prop="remark">
|
||||
<el-input
|
||||
v-if="isEditable"
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注信息"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
<span v-else>{{ form.remark || '无' }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancel">{{ isEditable ? '取消' : '关闭' }}</el-button>
|
||||
<el-button v-if="isEditable" type="primary" @click="submitForm" :loading="submitLoading">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormApplicationDetail">
|
||||
import { getCurrentInstance, ref, toRefs, computed, type ComponentInternalInstance } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getDormApplicationInfo, updateDormApplication, type DormApplicationVO } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { dorm_application_type, student_leave_approval_status } = toRefs(
|
||||
proxy?.useDict('dorm_application_type', 'student_leave_approval_status') || {}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
// 弹窗状态
|
||||
const dialogOpen = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = ref<DormApplicationVO>({} as DormApplicationVO);
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref();
|
||||
|
||||
// 表单校验规则
|
||||
const rules = {
|
||||
reasonDetail: [{ required: true, message: '请输入申请原因', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
/**
|
||||
* 是否可编辑
|
||||
* 草稿、已撤销、已退回状态可编辑
|
||||
*/
|
||||
const isEditable = computed(() => {
|
||||
return ['draft', 'cancel', 'back'].includes(form.value.applicationStatus || '');
|
||||
});
|
||||
|
||||
/**
|
||||
* 弹窗标题
|
||||
*/
|
||||
const dialogTitle = computed(() => {
|
||||
return isEditable.value ? '修改调宿/退宿申请' : '调宿/退宿申请详情';
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
const openDialog = (id: number) => {
|
||||
reset();
|
||||
getDetail(id);
|
||||
dialogOpen.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
const getDetail = async (id: number) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getDormApplicationInfo(id);
|
||||
form.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取调宿退宿申请详情失败:', error);
|
||||
ElMessage.error('获取详情失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const reset = () => {
|
||||
form.value = {} as DormApplicationVO;
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return;
|
||||
|
||||
await formRef.value.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
await updateDormApplication(form.value);
|
||||
ElMessage.success('保存成功');
|
||||
dialogOpen.value = false;
|
||||
emit('refresh');
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
ElMessage.error('保存失败');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消操作
|
||||
*/
|
||||
const cancel = () => {
|
||||
dialogOpen.value = false;
|
||||
reset();
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
open: openDialog
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请类型" prop="applicationType">
|
||||
<el-select v-model="queryParams.applicationType" placeholder="请选择申请类型" clearable style="width: 100%">
|
||||
<el-option v-for="dict in dorm_application_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="申请时间" prop="applyTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择申请时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="applicationStatus">
|
||||
<el-select v-model="queryParams.applicationStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in student_leave_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">调宿/退宿申请列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="申请类型" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="dorm_application_type" :value="scope.row.applicationType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前宿舍" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.currentBuildingName }}{{ scope.row.currentRoomNo }}{{ scope.row.currentBedNo }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标宿舍" align="center" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.applicationType === '1'">
|
||||
{{ scope.row.targetBuildingName }}{{ scope.row.targetRoomNo }}{{ scope.row.targetBedNo }}
|
||||
</span>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_leave_approval_status" :value="scope.row.applicationStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="applyTime" width="180" />
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:dormApplication:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="DormApplicationTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs, type ComponentInternalInstance } from 'vue';
|
||||
import { listDormApplication, type DormApplicationQuery } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { dorm_application_type, student_leave_approval_status } = toRefs(
|
||||
proxy?.useDict('dorm_application_type', 'student_leave_approval_status') || {}
|
||||
);
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<DormApplicationQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
applicationType: undefined,
|
||||
applicationStatus: undefined,
|
||||
applyTime: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listDormApplication(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取调宿退宿申请列表失败:', error);
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
// 跳转到调宿退宿申请详情页面
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xssssq/dialog`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<el-dialog title="假期留宿详情" v-model="dialogOpen" width="1000px" append-to-body>
|
||||
<el-form :model="form" label-width="120px" v-loading="loading">
|
||||
<el-row :gutter="20">
|
||||
<!-- 左列:学生基本信息 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名">
|
||||
<el-input v-model="form.studentName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学号">
|
||||
<el-input v-model="form.studentNo" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学生联系电话">
|
||||
<el-input v-model="form.studentPhone" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="学院名称">
|
||||
<el-input v-model="form.collegeName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="专业名称">
|
||||
<el-input v-model="form.majorName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="班级名称">
|
||||
<el-input v-model="form.className" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 右列:宿舍信息 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="校区名称">
|
||||
<el-input v-model="form.campusName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍区名称">
|
||||
<el-input v-model="form.areaName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="宿舍楼名称">
|
||||
<el-input v-model="form.buildingName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="房间名称">
|
||||
<el-input v-model="form.roomName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="床位名称">
|
||||
<el-input v-model="form.bedName" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 假期申请信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="假期类型">
|
||||
<el-input v-model="holidayTypeLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿原因类型">
|
||||
<el-input v-model="stayReasonTypeLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿开始日期">
|
||||
<el-input v-model="form.applyStartDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="留宿结束日期">
|
||||
<el-input v-model="form.applyEndDate" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 联系信息 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急联系人姓名">
|
||||
<el-input v-model="form.emergencyContact" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="紧急联系人电话">
|
||||
<el-input v-model="form.emergencyPhone" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="家长同意">
|
||||
<el-input v-model="parentConsentLabel" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 留宿原因 -->
|
||||
<el-form-item label="留宿原因">
|
||||
<el-input v-model="form.stayReason" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 附件显示 -->
|
||||
<el-form-item label="附件">
|
||||
<div class="attachment-view-container" v-if="attachmentList.length > 0">
|
||||
<div class="attachment-grid">
|
||||
<div v-for="file in attachmentList" :key="file.ossId" class="attachment-item">
|
||||
<!-- 缩略图区域 -->
|
||||
<div class="attachment-thumb">
|
||||
<image-preview v-if="isImageFile(file)" :src="file.ossId" class="thumb-image" :isOssId="true" />
|
||||
<div v-else class="file-placeholder">
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="file-ext">{{ getFileExt(file.name) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文件名称 -->
|
||||
<div class="attachment-name" :title="file.name">
|
||||
{{ getShortFileName(file.name) }}
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="attachment-actions">
|
||||
<el-button type="success" size="small" @click="handleDownload(file)" title="下载">
|
||||
<el-icon><Download /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-attachment">
|
||||
<el-empty description="暂无附件" :image-size="60" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 备注信息 -->
|
||||
<el-form-item label="备注信息">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="dialogOpen = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="HolidayStayDetail">
|
||||
import { ref, computed, toRefs, getCurrentInstance, type ComponentInternalInstance } from 'vue';
|
||||
import ElMessage from 'element-plus/es/components/message/index';
|
||||
import ElLoading from 'element-plus/es/components/loading/index';
|
||||
import { Document, Download } from '@element-plus/icons-vue';
|
||||
import { getHolidayStay, type HolidayStayVO } from '@/api/xg/studentDynamics';
|
||||
import ImagePreview from '@/components/ImagePreview/index.vue';
|
||||
import request from '@/utils/request';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
// 字典数据
|
||||
const { xg_holiday_type, xg_stay_reason_type, sys_yesno } = toRefs<any>(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type', 'sys_yesno'));
|
||||
|
||||
// 对话框状态
|
||||
const dialogOpen = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = ref<Partial<HolidayStayVO>>({
|
||||
id: undefined,
|
||||
studentId: undefined,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
classId: undefined,
|
||||
className: '',
|
||||
majorId: undefined,
|
||||
majorName: '',
|
||||
collegeId: undefined,
|
||||
collegeName: '',
|
||||
campusId: undefined,
|
||||
campusName: '',
|
||||
areaId: undefined,
|
||||
areaName: '',
|
||||
buildingId: undefined,
|
||||
buildingName: '',
|
||||
roomId: undefined,
|
||||
roomName: '',
|
||||
bedId: undefined,
|
||||
bedName: '',
|
||||
holidayType: '',
|
||||
applyStartDate: '',
|
||||
applyEndDate: '',
|
||||
stayReason: '',
|
||||
stayReasonType: '',
|
||||
studentPhone: '',
|
||||
emergencyContact: '',
|
||||
emergencyPhone: '',
|
||||
parentConsent: '',
|
||||
attachmentOssIds: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 附件列表
|
||||
const attachmentList = ref<any[]>([]);
|
||||
|
||||
// 字典标签计算属性
|
||||
const holidayTypeLabel = computed(() => {
|
||||
if (!form.value.holidayType || !xg_holiday_type.value) return '';
|
||||
const dict = xg_holiday_type.value.find((item: any) => item.value === form.value.holidayType);
|
||||
return dict ? dict.label : form.value.holidayType;
|
||||
});
|
||||
|
||||
const stayReasonTypeLabel = computed(() => {
|
||||
if (!form.value.stayReasonType || !xg_stay_reason_type.value) return '';
|
||||
const dict = xg_stay_reason_type.value.find((item: any) => item.value === form.value.stayReasonType);
|
||||
return dict ? dict.label : form.value.stayReasonType;
|
||||
});
|
||||
|
||||
const parentConsentLabel = computed(() => {
|
||||
if (!form.value.parentConsent || !sys_yesno.value) return '';
|
||||
const dict = sys_yesno.value.find((item: any) => item.value === form.value.parentConsent);
|
||||
return dict ? dict.label : form.value.parentConsent;
|
||||
});
|
||||
|
||||
/**
|
||||
* 打开详情弹窗
|
||||
*/
|
||||
const open = async (id: string | number) => {
|
||||
loading.value = true;
|
||||
dialogOpen.value = true;
|
||||
|
||||
try {
|
||||
// 获取详情数据
|
||||
const response = await getHolidayStay(id);
|
||||
form.value = response.data;
|
||||
|
||||
// 处理附件数据
|
||||
await handleAttachmentData();
|
||||
} catch (error) {
|
||||
console.error('获取假期留宿详情失败:', error);
|
||||
ElMessage.error('获取详情失败');
|
||||
dialogOpen.value = false;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理附件数据
|
||||
*/
|
||||
const handleAttachmentData = async () => {
|
||||
try {
|
||||
if (form.value.attachmentOssIds && form.value.attachmentOssIds.toString().trim()) {
|
||||
// 将逗号分隔的字符串转换为数组
|
||||
const ossIdsArray = form.value.attachmentOssIds
|
||||
.toString()
|
||||
.split(',')
|
||||
.map((id) => id.trim())
|
||||
.filter((id) => id);
|
||||
|
||||
// 调用接口获取附件信息
|
||||
const response = await request({
|
||||
url: `/resource/oss/listByIds/${ossIdsArray.join(',')}`,
|
||||
method: 'get'
|
||||
});
|
||||
|
||||
if (response.code === 200 && response.data) {
|
||||
// 将附件信息转换为显示需要的格式
|
||||
attachmentList.value = response.data.map((item: any) => ({
|
||||
ossId: item.ossId,
|
||||
name: item.originalName || '附件',
|
||||
url: item.url,
|
||||
type: item.contentType || ''
|
||||
}));
|
||||
} else {
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} else {
|
||||
attachmentList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理附件数据失败:', error);
|
||||
attachmentList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否为图片文件
|
||||
*/
|
||||
const isImageFile = (file: any) => {
|
||||
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
|
||||
return imageTypes.includes(file.type) || /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(file.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
const getFileExt = (fileName: string) => {
|
||||
const ext = fileName.split('.').pop()?.toUpperCase();
|
||||
return ext || 'FILE';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取缩短的文件名
|
||||
*/
|
||||
const getShortFileName = (fileName: string) => {
|
||||
if (!fileName) return '';
|
||||
|
||||
const lastDotIndex = fileName.lastIndexOf('.');
|
||||
if (lastDotIndex === -1) {
|
||||
return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
|
||||
}
|
||||
|
||||
const name = fileName.substring(0, lastDotIndex);
|
||||
const ext = fileName.substring(lastDotIndex);
|
||||
|
||||
if (name.length <= 8) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
return name.substring(0, 8) + '...' + ext;
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载附件
|
||||
*/
|
||||
const handleDownload = async (file: any) => {
|
||||
try {
|
||||
const loading = ElLoading.service({
|
||||
text: '正在下载文件...',
|
||||
target: document.body
|
||||
});
|
||||
|
||||
const response = await request({
|
||||
url: '/resource/oss/download/' + file.ossId,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 300000
|
||||
});
|
||||
|
||||
// 验证响应数据
|
||||
if (!response) {
|
||||
throw new Error('响应数据为空');
|
||||
}
|
||||
|
||||
// 检查response的结构
|
||||
let blobData: Blob;
|
||||
if (response instanceof Blob) {
|
||||
blobData = response;
|
||||
} else if (response.data instanceof Blob) {
|
||||
blobData = response.data;
|
||||
} else {
|
||||
throw new Error('无效的响应数据格式');
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blobData);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = file.name;
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
loading?.close();
|
||||
ElMessage.success('下载成功');
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
ElMessage.error('下载文件失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 附件查看容器样式 */
|
||||
.attachment-view-container {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.attachment-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.attachment-item {
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.attachment-item:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.attachment-thumb {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
background: #f5f5f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumb-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.file-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-placeholder .el-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.file-ext {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.attachment-name {
|
||||
padding: 6px 8px;
|
||||
font-size: 11px;
|
||||
color: #303133;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
line-height: 1.2;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 6px 8px;
|
||||
background: #fafafa;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.attachment-actions .el-button {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.no-attachment {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="假期类型" prop="holidayType">
|
||||
<el-select v-model="queryParams.holidayType" placeholder="请选择假期类型" clearable style="width: 100%">
|
||||
<el-option v-for="dict in xg_holiday_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="开始日期" prop="applyStartDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyStartDate"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择开始日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="结束日期" prop="applyEndDate">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.applyEndDate"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择结束日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">假期留宿信息列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="宿舍信息" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.buildingName }}{{ scope.row.roomName }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="假期类型" align="center" prop="holidayType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_holiday_type" :value="scope.row.holidayType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="留宿时间" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.applyStartDate }} 至 {{ scope.row.applyEndDate }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="留宿原因类型" align="center" prop="stayReasonType">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_stay_reason_type" :value="scope.row.stayReasonType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="联系电话" align="center" prop="studentPhone" />
|
||||
<el-table-column label="紧急联系人" align="center" prop="emergencyContact" />
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:holidayStay:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<HolidayStayDetail ref="detailRef" @refresh="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="HolidayStayTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { listHolidayStay, type HolidayStayQuery } from '@/api/xg/studentDynamics';
|
||||
import HolidayStayDetail from './HolidayStayDetail.vue';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 字典数据
|
||||
const { xg_holiday_type, xg_stay_reason_type } = toRefs(proxy?.useDict('xg_holiday_type', 'xg_stay_reason_type') || {});
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<HolidayStayQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
holidayType: undefined,
|
||||
applyStartDate: undefined,
|
||||
applyEndDate: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
const detailRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listHolidayStay(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取假期留宿信息列表失败:', error);
|
||||
ElMessage.error('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
detailRef.value?.open(row.id);
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb-[10px] {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<el-dialog title="学生请假详情" v-model="dialogOpen" width="800px" append-to-body>
|
||||
<div>学生请假详情组件待实现</div>
|
||||
<template #footer>
|
||||
<el-button @click="dialogOpen = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentLeaveDetail">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const dialogOpen = ref(false);
|
||||
|
||||
const open = (id: number) => {
|
||||
console.log('打开学生请假详情,ID:', id);
|
||||
dialogOpen.value = true;
|
||||
};
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 搜索表单 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-card shadow="hover">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="学院名称" prop="collegeName">
|
||||
<el-input v-model="queryParams.collegeName" placeholder="请输入学院名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="专业名称" prop="majorName">
|
||||
<el-input v-model="queryParams.majorName" placeholder="请输入专业名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="班级名称" prop="className">
|
||||
<el-input v-model="queryParams.className" placeholder="请输入班级名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="请假类型" prop="leaveTypeName">
|
||||
<el-input v-model="queryParams.leaveTypeName" placeholder="请输入请假类型名称" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="开始时间" prop="startTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.startTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择开始时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="结束时间" prop="endTime">
|
||||
<el-date-picker
|
||||
clearable
|
||||
v-model="queryParams.endTime"
|
||||
type="date"
|
||||
style="width: 100%"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择结束时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="审批状态" prop="approvalStatus">
|
||||
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审批状态" clearable style="width: 100%">
|
||||
<el-option v-for="dict in student_leave_approval_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24" class="search-btn-container">
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<span class="card-title">学生请假信息列表</span>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
|
||||
<el-table-column label="学生姓名" align="center" prop="studentName" />
|
||||
<el-table-column label="学号" align="center" prop="studentNo" />
|
||||
<el-table-column label="学院" align="center" prop="collegeName" show-overflow-tooltip />
|
||||
<el-table-column label="专业" align="center" prop="majorName" show-overflow-tooltip />
|
||||
<el-table-column label="班级" align="center" prop="className" show-overflow-tooltip />
|
||||
<el-table-column label="请假类型" align="center" prop="leaveTypeName" />
|
||||
<el-table-column label="请假时间" align="center" show-overflow-tooltip>
|
||||
<template #default="scope"> {{ scope.row.startTime }} 至 {{ scope.row.endTime }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="请假时长" align="center">
|
||||
<template #default="scope"> {{ scope.row.leaveDuration }}天 </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否离校" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_yesno" :value="scope.row.isLeaveSchool" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审批状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="student_leave_approval_status" :value="scope.row.approvalStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销假状态" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="xg_xsqj_cancel_status" :value="scope.row.cancelStatus" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="View"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['studentDynamics:studentLeave:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentLeaveTab">
|
||||
import { getCurrentInstance, ref, onMounted, toRefs } from 'vue';
|
||||
import { listStudentLeave, type StudentLeaveQuery } from '@/api/xg/studentDynamics';
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_yesno, student_leave_approval_status, xg_xsqj_cancel_status } = toRefs(
|
||||
proxy?.useDict('sys_yesno', 'student_leave_approval_status', 'xg_xsqj_cancel_status') || {}
|
||||
);
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const dataList = ref([]);
|
||||
const total = ref(0);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref<StudentLeaveQuery & any>({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
collegeName: undefined,
|
||||
majorName: undefined,
|
||||
className: undefined,
|
||||
leaveTypeName: undefined,
|
||||
approvalStatus: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const queryFormRef = ref();
|
||||
|
||||
/**
|
||||
* 查询列表数据
|
||||
*/
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await listStudentLeave(queryParams.value);
|
||||
dataList.value = response.rows;
|
||||
total.value = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取学生请假信息列表失败:', error);
|
||||
proxy?.$modal.msgError('获取数据失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 搜索按钮操作
|
||||
*/
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置按钮操作
|
||||
*/
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/**
|
||||
* 多选框选中数据
|
||||
*/
|
||||
const handleSelectionChange = (selection: any) => {
|
||||
ids.value = selection.map((item: any) => item.id);
|
||||
single.value = selection.length !== 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
*/
|
||||
const handleView = (row: any) => {
|
||||
// 跳转到学生请假详情页面
|
||||
proxy.$router.push({
|
||||
path: `/xg/xsjcxx/xsqj/leaveEdit/index`,
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 页面初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面容器样式 */
|
||||
.page-container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* 搜索按钮容器样式 */
|
||||
.search-btn-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-btn-container .el-form-item {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="p-2">
|
||||
<!-- Tab页组件 -->
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick" class="mb-4">
|
||||
<!-- 调宿/退宿信息Tab页 -->
|
||||
<el-tab-pane label="调宿/退宿信息" name="dormApplication">
|
||||
<DormApplicationTab v-if="activeTab === 'dormApplication'" ref="dormApplicationTabRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 假期留宿信息Tab页 -->
|
||||
<el-tab-pane label="假期留宿信息" name="holidayStay">
|
||||
<HolidayStayTab v-if="activeTab === 'holidayStay'" ref="holidayStayTabRef" />
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 请假信息Tab页 -->
|
||||
<el-tab-pane label="请假信息" name="studentLeave">
|
||||
<StudentLeaveTab v-if="activeTab === 'studentLeave'" ref="studentLeaveTabRef" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="StudentDynamics">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import DormApplicationTab from './components/DormApplicationTab.vue';
|
||||
import HolidayStayTab from './components/HolidayStayTab.vue';
|
||||
import StudentLeaveTab from './components/StudentLeaveTab.vue';
|
||||
|
||||
// Tab页状态
|
||||
const activeTab = ref('dormApplication');
|
||||
|
||||
// 子组件引用
|
||||
const dormApplicationTabRef = ref();
|
||||
const holidayStayTabRef = ref();
|
||||
const studentLeaveTabRef = ref();
|
||||
|
||||
/**
|
||||
* Tab页切换处理
|
||||
*/
|
||||
const handleTabClick = (tab: any) => {
|
||||
console.log('切换到Tab页:', tab.name);
|
||||
// 可以在这里添加Tab切换的逻辑
|
||||
};
|
||||
|
||||
/**
|
||||
* 页面初始化
|
||||
*/
|
||||
onMounted(() => {
|
||||
console.log('学生动向信息管理页面已加载');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.p-2 {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* Tab页样式优化 */
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
/* color: #409eff; */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -183,7 +183,7 @@
|
||||
<el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
||||
<el-table-column label="校区" align="center" prop="campusName" />
|
||||
<el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" v-if="false" />
|
||||
<el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
||||
<el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
||||
<el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
<el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
||||
<el-table-column label="校区" align="center" prop="campusName" />
|
||||
<el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" />
|
||||
<el-table-column label="宿舍区" align="center" prop="areaName" v-if="false" />
|
||||
<el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
||||
<el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
||||
<el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
||||
|
||||
@@ -307,7 +307,7 @@ import ApprovalButton from '@/components/Process/approvalButton.vue';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
|
||||
import { addStudentLeave, getStudentLeave, updateStudentLeave } from '@/api/workflow/studentLeave/studentLeave';
|
||||
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { getStudentMsg, getClassTeacherId, getCollegeLeaveApprovers, getCollegeLeaders } from '@/api/xg/xsjcxx/studentMsg/studentMsg';
|
||||
import { startWorkFlow } from '@/api/workflow/task';
|
||||
import { StudentLeaveForm, StudentLeaveVO } from '@/api/workflow/studentLeave/studentLeave/types';
|
||||
import { StudentMsgVO } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
|
||||
@@ -890,7 +890,7 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// 1. 调用接口获取班主任ID
|
||||
let classTeacherId: number | null = null;
|
||||
try {
|
||||
const classTeacherResponse = await getClassTeacherId(data.studentId);
|
||||
const classTeacherResponse: any = await getClassTeacherId(data.studentId);
|
||||
if (classTeacherResponse && classTeacherResponse.data) {
|
||||
classTeacherId = classTeacherResponse.data;
|
||||
console.log('获取到班主任ID:', classTeacherId);
|
||||
@@ -908,7 +908,7 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// 2. 调用接口获取学院请假审批人列表
|
||||
let collegeApprovers: number[] = [];
|
||||
try {
|
||||
const collegeApproversResponse = await getCollegeLeaveApprovers(data.studentId);
|
||||
const collegeApproversResponse: any = await getCollegeLeaveApprovers(data.studentId);
|
||||
if (collegeApproversResponse && collegeApproversResponse.data) {
|
||||
collegeApprovers = collegeApproversResponse.data;
|
||||
console.log('获取到学院请假审批人列表:', collegeApprovers);
|
||||
@@ -923,6 +923,24 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
// 3. 调用接口获取二级学院分管领导ID列表
|
||||
let collegeLeaders: number[] = [];
|
||||
try {
|
||||
const collegeLeadersResponse: any = await getCollegeLeaders(data.studentId);
|
||||
if (collegeLeadersResponse && collegeLeadersResponse.data) {
|
||||
collegeLeaders = collegeLeadersResponse.data;
|
||||
console.log('获取到二级学院分管领导ID列表:', collegeLeaders);
|
||||
} else {
|
||||
console.warn('获取二级学院分管领导ID列表失败');
|
||||
ElMessage.error('获取二级学院分管领导信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用获取二级学院分管领导接口失败:', error);
|
||||
ElMessage.error('获取二级学院分管领导信息失败,请稍后重试');
|
||||
return; // 停止后续操作
|
||||
}
|
||||
|
||||
submitFormData.value.flowCode = flowCode.value;
|
||||
submitFormData.value.businessId = data.id;
|
||||
// 流程变量
|
||||
@@ -935,7 +953,8 @@ const handleStartWorkFlow = async (data: StudentLeaveForm) => {
|
||||
// userList: ['1', '3', '4'],
|
||||
// 新增的流程变量
|
||||
bzrspr: classTeacherId,
|
||||
ejxyspr: collegeApprovers
|
||||
ejxyspr: collegeApprovers,
|
||||
ejxyfgld: collegeLeaders
|
||||
};
|
||||
submitFormData.value.variables = taskVariables.value;
|
||||
const resp = await startWorkFlow(submitFormData.value);
|
||||
|
||||
Reference in new issue
Block a user