Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project
This commit is contained in:
commit
14d53f60b0
23 files changed
+4260
-72
No files matched your search
@@ -163,9 +163,9 @@ mybatis-plus:
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 实体扫描,多个package用逗号或者分号分隔
|
||||
typeAliasesPackage: org.dromara.**.domain
|
||||
# 临时禁用SQL日志(减少系统负担)
|
||||
# configuration:
|
||||
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
# 启用SQL日志输出,用于调试数据库插入问题
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
dbConfig:
|
||||
# 主键类型
|
||||
|
||||
@@ -104,6 +104,12 @@
|
||||
<artifactId>ruoyi-workflow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 学工管理模块 - 用于获取学生基础信息 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-xg</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package org.dromara.zsjy.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyAddBo;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
|
||||
import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
|
||||
import org.dromara.zsjy.service.IZsjyInternshipDailyService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 实习日常管理Controller
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/zsjy/internship/daily")
|
||||
@Tag(name = "实习日常管理", description = "学生实习日常打卡签到管理")
|
||||
public class ZsjyInternshipDailyController extends BaseController {
|
||||
|
||||
private final IZsjyInternshipDailyService dailyService;
|
||||
|
||||
/**
|
||||
* 查询实习日常记录列表
|
||||
*/
|
||||
@Operation(summary = "查询实习日常记录列表", description = "班主任查看学生实习日常记录")
|
||||
@SaCheckPermission("zsjy:internshipdaily:list")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<ZsjyInternshipDailyVo> list(@Validated ZsjyInternshipDailyQueryBo queryBo, PageQuery pageQuery) {
|
||||
log.info("=== Controller层调试 ===");
|
||||
log.info("接口被调用: /zsjy/internship/daily/list");
|
||||
log.info("查询参数: {}", queryBo);
|
||||
log.info("分页参数: {}", pageQuery);
|
||||
|
||||
return dailyService.queryPageList(queryBo, pageQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生签到打卡
|
||||
*/
|
||||
@Operation(summary = "学生签到打卡", description = "学生进行实习日常签到打卡")
|
||||
@SaCheckPermission("zsjy:internshipdaily:checkin")
|
||||
@PostMapping("/checkin")
|
||||
public R<Void> checkIn(@Validated(AddGroup.class) @RequestBody ZsjyInternshipDailyAddBo addBo) {
|
||||
log.info("=== Controller层调试 ===");
|
||||
log.info("接口被调用: /zsjy/internship/daily/checkin");
|
||||
log.info("签到参数: {}", addBo);
|
||||
|
||||
return dailyService.checkIn(addBo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取学生考勤日历
|
||||
*/
|
||||
@Operation(summary = "获取学生考勤日历", description = "查看指定学生的考勤日历数据")
|
||||
@SaCheckPermission("zsjy:internshipdaily:calendar")
|
||||
@GetMapping("/calendar/{studentId}")
|
||||
public R<List<Map<String, Object>>> getCalendar(
|
||||
@Parameter(description = "学生ID", required = true) @PathVariable Long studentId,
|
||||
@Parameter(description = "年份") @RequestParam(required = false) Integer year,
|
||||
@Parameter(description = "月份") @RequestParam(required = false) Integer month) {
|
||||
|
||||
log.info("=== Controller层调试 ===");
|
||||
log.info("接口被调用: /zsjy/internship/daily/calendar/{}", studentId);
|
||||
log.info("查询年月: {}-{}", year, month);
|
||||
|
||||
return dailyService.getCalendarData(studentId, year, month);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取考勤统计数据
|
||||
*/
|
||||
@Operation(summary = "获取考勤统计数据", description = "查看实习生考勤统计分析")
|
||||
@SaCheckPermission("zsjy:internshipdaily:statistics")
|
||||
@GetMapping("/statistics")
|
||||
public R<Map<String, Object>> getStatistics(@Validated ZsjyInternshipDailyQueryBo queryBo) {
|
||||
log.info("=== Controller层调试 ===");
|
||||
log.info("接口被调用: /zsjy/internship/daily/statistics");
|
||||
log.info("统计参数: {}", queryBo);
|
||||
|
||||
return dailyService.getStatistics(queryBo);
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package org.dromara.zsjy.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Time;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习日常管理实体对象 zsjy_internship_daily
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("zsjy_internship_daily")
|
||||
public class ZsjyInternshipDaily extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日常记录ID
|
||||
*/
|
||||
@TableId(value = "daily_id")
|
||||
private Long dailyId;
|
||||
|
||||
/**
|
||||
* 实习ID
|
||||
*/
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 打卡日期
|
||||
*/
|
||||
private Date checkDate;
|
||||
|
||||
/**
|
||||
* 签到时间
|
||||
*/
|
||||
private Time checkInTime;
|
||||
|
||||
/**
|
||||
* 签到位置
|
||||
*/
|
||||
private String checkInLocation;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 日总结
|
||||
*/
|
||||
private String dailySummary;
|
||||
|
||||
/**
|
||||
* 图片附件(JSON格式)
|
||||
*/
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 是否异常(0正常 1异常)
|
||||
*/
|
||||
private String isException;
|
||||
|
||||
/**
|
||||
* 异常原因
|
||||
*/
|
||||
private String exceptionReason;
|
||||
|
||||
/**
|
||||
* 异常处理状态(0待处理 1已处理)
|
||||
*/
|
||||
private String exceptionStatus;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package org.dromara.zsjy.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习信息实体对象 zsjy_internship_info
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("zsjy_internship_info")
|
||||
public class ZsjyInternshipInfo extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实习信息ID
|
||||
*/
|
||||
@TableId(value = "internship_id")
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
private String gender;
|
||||
|
||||
/**
|
||||
* 学院ID
|
||||
*/
|
||||
private Long collegeId;
|
||||
|
||||
/**
|
||||
* 专业ID
|
||||
*/
|
||||
private Long majorId;
|
||||
|
||||
/**
|
||||
* 班级ID
|
||||
*/
|
||||
private Long classId;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 实习单位名称
|
||||
*/
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* 实习单位地址
|
||||
*/
|
||||
private String companyAddress;
|
||||
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
private String companyIndustry;
|
||||
|
||||
/**
|
||||
* 单位规模
|
||||
*/
|
||||
private String companyScale;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
private String companyContact;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
private String companyPhone;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
private String positionName;
|
||||
|
||||
/**
|
||||
* 岗位职责
|
||||
*/
|
||||
private String positionDuties;
|
||||
|
||||
/**
|
||||
* 实习津贴
|
||||
*/
|
||||
private BigDecimal internshipAllowance;
|
||||
|
||||
/**
|
||||
* 工作时长
|
||||
*/
|
||||
private String workHours;
|
||||
|
||||
/**
|
||||
* 住宿地址
|
||||
*/
|
||||
private String accommodationAddress;
|
||||
|
||||
/**
|
||||
* 住宿类型
|
||||
*/
|
||||
private String accommodationType;
|
||||
|
||||
/**
|
||||
* 住宿费用
|
||||
*/
|
||||
private BigDecimal accommodationCost;
|
||||
|
||||
/**
|
||||
* 实习开始日期
|
||||
*/
|
||||
private Date startDate;
|
||||
|
||||
/**
|
||||
* 实习结束日期
|
||||
*/
|
||||
private Date endDate;
|
||||
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private Date actualEndDate;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 实习状态(1进行中 2已完成 3中断)
|
||||
*/
|
||||
private String internshipStatus;
|
||||
}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package org.dromara.zsjy.domain.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习信息新增业务对象 InternshipInfoAddBO
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "实习信息新增业务对象")
|
||||
public class InternshipInfoAddBO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@Schema(description = "学生ID")
|
||||
@NotNull(message = "学生ID不能为空", groups = {AddGroup.class})
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 实习状态:1-进行中,2-已完成,3-已暂停,4-已终止
|
||||
*/
|
||||
@Schema(description = "实习状态:1-进行中,2-已完成,3-已暂停,4-已终止")
|
||||
@NotBlank(message = "实习状态不能为空", groups = {AddGroup.class})
|
||||
@Pattern(regexp = "^[1-4]$", message = "实习状态只能是1-进行中,2-已完成,3-已暂停,4-已终止", groups = {AddGroup.class})
|
||||
private String internshipStatus;
|
||||
|
||||
/**
|
||||
* 实习单位名称
|
||||
*/
|
||||
@Schema(description = "实习单位名称")
|
||||
@NotBlank(message = "实习单位名称不能为空", groups = {AddGroup.class})
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* 实习单位地址
|
||||
*/
|
||||
@Schema(description = "实习单位地址")
|
||||
private String companyAddress;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
@Schema(description = "单位联系人")
|
||||
private String companyContact;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
@Schema(description = "联系人电话")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系人电话格式不正确", groups = {AddGroup.class})
|
||||
private String companyPhone;
|
||||
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
@Schema(description = "行业类型")
|
||||
private String companyIndustry;
|
||||
|
||||
/**
|
||||
* 单位规模
|
||||
*/
|
||||
@Schema(description = "单位规模")
|
||||
private String companyScale;
|
||||
|
||||
/**
|
||||
* 实习岗位
|
||||
*/
|
||||
@Schema(description = "实习岗位")
|
||||
@NotBlank(message = "实习岗位不能为空", groups = {AddGroup.class})
|
||||
private String internshipPosition;
|
||||
|
||||
/**
|
||||
* 实习薪资
|
||||
*/
|
||||
@Schema(description = "实习薪资")
|
||||
@Positive(message = "实习薪资必须大于0", groups = {AddGroup.class})
|
||||
private BigDecimal internshipAllowance;
|
||||
|
||||
/**
|
||||
* 实习开始时间
|
||||
*/
|
||||
@Schema(description = "实习开始时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@NotNull(message = "实习开始时间不能为空", groups = {AddGroup.class})
|
||||
private Date internshipStartDate;
|
||||
|
||||
/**
|
||||
* 实习结束时间
|
||||
*/
|
||||
@Schema(description = "实习结束时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date internshipEndDate;
|
||||
|
||||
/**
|
||||
* 联系人姓名
|
||||
*/
|
||||
@Schema(description = "联系人姓名")
|
||||
private String contactPersonName;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
@Schema(description = "联系人电话")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系人电话格式不正确", groups = {AddGroup.class})
|
||||
private String contactPersonPhone;
|
||||
|
||||
/**
|
||||
* 岗位职责
|
||||
*/
|
||||
@Schema(description = "岗位职责")
|
||||
private String positionDuties;
|
||||
|
||||
/**
|
||||
* 工作时长
|
||||
*/
|
||||
@Schema(description = "工作时长")
|
||||
private String workHours;
|
||||
|
||||
/**
|
||||
* 住宿地址
|
||||
*/
|
||||
@Schema(description = "住宿地址")
|
||||
private String accommodationAddress;
|
||||
|
||||
/**
|
||||
* 住宿类型
|
||||
*/
|
||||
@Schema(description = "住宿类型")
|
||||
private String accommodationType;
|
||||
|
||||
/**
|
||||
* 住宿费用
|
||||
*/
|
||||
@Schema(description = "住宿费用")
|
||||
private BigDecimal accommodationCost;
|
||||
|
||||
/**
|
||||
* 实习协议状态:1-已签署,2-未签署
|
||||
*/
|
||||
@Schema(description = "实习协议状态:1-已签署,2-未签署")
|
||||
@Pattern(regexp = "^[1-2]$", message = "实习协议状态只能是1-已签署,2-未签署", groups = {AddGroup.class})
|
||||
private String agreementStatus;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
@Schema(description = "备注信息")
|
||||
private String remark;
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
package org.dromara.zsjy.domain.bo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习信息修改业务对象 InternshipInfoEditBO
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "实习信息修改业务对象")
|
||||
public class InternshipInfoEditBO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实习信息ID
|
||||
*/
|
||||
@Schema(description = "实习信息ID")
|
||||
@NotNull(message = "实习信息ID不能为空", groups = {EditGroup.class})
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@Schema(description = "学生ID")
|
||||
@NotNull(message = "学生ID不能为空", groups = {EditGroup.class})
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 实习状态:1-进行中,2-已完成,3-已暂停,4-已终止
|
||||
*/
|
||||
@Schema(description = "实习状态:1-进行中,2-已完成,3-已暂停,4-已终止")
|
||||
@NotBlank(message = "实习状态不能为空", groups = {EditGroup.class})
|
||||
@Pattern(regexp = "^[1-4]$", message = "实习状态只能是1-进行中,2-已完成,3-已暂停,4-已终止", groups = {EditGroup.class})
|
||||
private String internshipStatus;
|
||||
|
||||
/**
|
||||
* 实习单位名称
|
||||
*/
|
||||
@Schema(description = "实习单位名称")
|
||||
@NotBlank(message = "实习单位名称不能为空", groups = {EditGroup.class})
|
||||
private String companyName;
|
||||
|
||||
/**
|
||||
* 实习单位地址
|
||||
*/
|
||||
@Schema(description = "实习单位地址")
|
||||
private String companyAddress;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
@Schema(description = "单位联系人")
|
||||
private String companyContact;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
@Schema(description = "联系人电话")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系人电话格式不正确", groups = {EditGroup.class})
|
||||
private String companyPhone;
|
||||
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
@Schema(description = "行业类型")
|
||||
private String companyIndustry;
|
||||
|
||||
/**
|
||||
* 单位规模
|
||||
*/
|
||||
@Schema(description = "单位规模")
|
||||
private String companyScale;
|
||||
|
||||
/**
|
||||
* 实习岗位
|
||||
*/
|
||||
@Schema(description = "实习岗位")
|
||||
@NotBlank(message = "实习岗位不能为空", groups = {EditGroup.class})
|
||||
private String internshipPosition;
|
||||
|
||||
/**
|
||||
* 实习薪资
|
||||
*/
|
||||
@Schema(description = "实习薪资")
|
||||
@Positive(message = "实习薪资必须大于0", groups = {EditGroup.class})
|
||||
private BigDecimal internshipAllowance;
|
||||
|
||||
/**
|
||||
* 实习开始时间
|
||||
*/
|
||||
@Schema(description = "实习开始时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
@NotNull(message = "实习开始时间不能为空", groups = {EditGroup.class})
|
||||
private Date internshipStartDate;
|
||||
|
||||
/**
|
||||
* 实习结束时间
|
||||
*/
|
||||
@Schema(description = "实习结束时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date internshipEndDate;
|
||||
|
||||
/**
|
||||
* 联系人姓名
|
||||
*/
|
||||
@Schema(description = "联系人姓名")
|
||||
private String contactPersonName;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
@Schema(description = "联系人电话")
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系人电话格式不正确", groups = {EditGroup.class})
|
||||
private String contactPersonPhone;
|
||||
|
||||
/**
|
||||
* 岗位职责
|
||||
*/
|
||||
@Schema(description = "岗位职责")
|
||||
private String positionDuties;
|
||||
|
||||
/**
|
||||
* 工作时长
|
||||
*/
|
||||
@Schema(description = "工作时长")
|
||||
private String workHours;
|
||||
|
||||
/**
|
||||
* 住宿地址
|
||||
*/
|
||||
@Schema(description = "住宿地址")
|
||||
private String accommodationAddress;
|
||||
|
||||
/**
|
||||
* 住宿类型
|
||||
*/
|
||||
@Schema(description = "住宿类型")
|
||||
private String accommodationType;
|
||||
|
||||
/**
|
||||
* 住宿费用
|
||||
*/
|
||||
@Schema(description = "住宿费用")
|
||||
private BigDecimal accommodationCost;
|
||||
|
||||
/**
|
||||
* 实习协议状态:1-已签署,2-未签署
|
||||
*/
|
||||
@Schema(description = "实习协议状态:1-已签署,2-未签署")
|
||||
@Pattern(regexp = "^[1-2]$", message = "实习协议状态只能是1-已签署,2-未签署", groups = {EditGroup.class})
|
||||
private String agreementStatus;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
@Schema(description = "备注信息")
|
||||
private String remark;
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package org.dromara.zsjy.domain.bo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Time;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习日常管理新增业务对象 ZsjyInternshipDailyAddBo
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "实习日常管理新增业务对象")
|
||||
public class ZsjyInternshipDailyAddBo extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实习ID
|
||||
*/
|
||||
@Schema(description = "实习ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "实习ID不能为空", groups = {AddGroup.class})
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@Schema(description = "学生ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "学生ID不能为空", groups = {AddGroup.class})
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 打卡日期
|
||||
*/
|
||||
@Schema(description = "打卡日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "打卡日期不能为空", groups = {AddGroup.class})
|
||||
private Date checkDate;
|
||||
|
||||
/**
|
||||
* 签到时间
|
||||
*/
|
||||
@Schema(description = "签到时间")
|
||||
private Time checkInTime;
|
||||
|
||||
/**
|
||||
* 签到位置
|
||||
*/
|
||||
@Schema(description = "签到位置")
|
||||
@Size(max = 200, message = "签到位置长度不能超过200个字符")
|
||||
private String checkInLocation;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@Schema(description = "纬度")
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@Schema(description = "经度")
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 日总结
|
||||
*/
|
||||
@Schema(description = "日总结")
|
||||
private String dailySummary;
|
||||
|
||||
/**
|
||||
* 图片附件(JSON格式)
|
||||
*/
|
||||
@Schema(description = "图片附件")
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 是否异常(0正常 1异常)
|
||||
*/
|
||||
@Schema(description = "是否异常")
|
||||
private String isException;
|
||||
|
||||
/**
|
||||
* 异常原因
|
||||
*/
|
||||
@Schema(description = "异常原因")
|
||||
@Size(max = 500, message = "异常原因长度不能超过500个字符")
|
||||
private String exceptionReason;
|
||||
|
||||
/**
|
||||
* 异常处理状态(0待处理 1已处理)
|
||||
*/
|
||||
@Schema(description = "异常处理状态")
|
||||
private String exceptionStatus;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package org.dromara.zsjy.domain.bo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习日常管理查询业务对象 ZsjyInternshipDailyQueryBo
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "实习日常管理查询业务对象")
|
||||
public class ZsjyInternshipDailyQueryBo extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@Schema(description = "学生ID")
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
@Schema(description = "学号")
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
@Schema(description = "学生姓名")
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 实习ID
|
||||
*/
|
||||
@Schema(description = "实习ID")
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 打卡开始日期
|
||||
*/
|
||||
@Schema(description = "打卡开始日期")
|
||||
private Date checkDateStart;
|
||||
|
||||
/**
|
||||
* 打卡结束日期
|
||||
*/
|
||||
@Schema(description = "打卡结束日期")
|
||||
private Date checkDateEnd;
|
||||
|
||||
/**
|
||||
* 是否异常(0正常 1异常)
|
||||
*/
|
||||
@Schema(description = "是否异常")
|
||||
private String isException;
|
||||
|
||||
/**
|
||||
* 异常处理状态(0待处理 1已处理)
|
||||
*/
|
||||
@Schema(description = "异常处理状态")
|
||||
private String exceptionStatus;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 公司名称
|
||||
*/
|
||||
@Schema(description = "公司名称")
|
||||
private String companyName;
|
||||
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package org.dromara.zsjy.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.Time;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 实习日常管理视图对象 ZsjyInternshipDailyVo
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "实习日常管理视图对象")
|
||||
public class ZsjyInternshipDailyVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 日常记录ID
|
||||
*/
|
||||
@Schema(description = "日常记录ID")
|
||||
private Long dailyId;
|
||||
|
||||
/**
|
||||
* 实习ID
|
||||
*/
|
||||
@Schema(description = "实习ID")
|
||||
private Long internshipId;
|
||||
|
||||
/**
|
||||
* 学生ID
|
||||
*/
|
||||
@Schema(description = "学生ID")
|
||||
private Long studentId;
|
||||
|
||||
/**
|
||||
* 打卡日期
|
||||
*/
|
||||
@Schema(description = "打卡日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date checkDate;
|
||||
|
||||
/**
|
||||
* 签到时间
|
||||
*/
|
||||
@Schema(description = "签到时间")
|
||||
@JsonFormat(pattern = "HH:mm:ss")
|
||||
private Time checkInTime;
|
||||
|
||||
/**
|
||||
* 签到位置
|
||||
*/
|
||||
@Schema(description = "签到位置")
|
||||
private String checkInLocation;
|
||||
|
||||
/**
|
||||
* 纬度
|
||||
*/
|
||||
@Schema(description = "纬度")
|
||||
private BigDecimal latitude;
|
||||
|
||||
/**
|
||||
* 经度
|
||||
*/
|
||||
@Schema(description = "经度")
|
||||
private BigDecimal longitude;
|
||||
|
||||
/**
|
||||
* 日总结
|
||||
*/
|
||||
@Schema(description = "日总结")
|
||||
private String dailySummary;
|
||||
|
||||
/**
|
||||
* 图片附件(JSON格式)
|
||||
*/
|
||||
@Schema(description = "图片附件")
|
||||
private String images;
|
||||
|
||||
/**
|
||||
* 是否异常(0正常 1异常)
|
||||
*/
|
||||
@Schema(description = "是否异常")
|
||||
private String isException;
|
||||
|
||||
/**
|
||||
* 异常原因
|
||||
*/
|
||||
@Schema(description = "异常原因")
|
||||
private String exceptionReason;
|
||||
|
||||
/**
|
||||
* 异常处理状态(0待处理 1已处理)
|
||||
*/
|
||||
@Schema(description = "异常处理状态")
|
||||
private String exceptionStatus;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
// ============ VO类基础字段规范(强制性) ============
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@Schema(description = "创建者")
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@Schema(description = "更新者")
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date updateTime;
|
||||
|
||||
// ============ 扩展字段(用于页面显示) ============
|
||||
/**
|
||||
* 学生姓名
|
||||
*/
|
||||
@Schema(description = "学生姓名")
|
||||
private String studentName;
|
||||
|
||||
/**
|
||||
* 学号
|
||||
*/
|
||||
@Schema(description = "学号")
|
||||
private String studentNo;
|
||||
|
||||
/**
|
||||
* 公司名称
|
||||
*/
|
||||
@Schema(description = "公司名称")
|
||||
private String companyName;
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.dromara.zsjy.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.dromara.zsjy.domain.ZsjyInternshipDaily;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
|
||||
import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 实习日常管理Mapper接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Mapper
|
||||
public interface ZsjyInternshipDailyMapper extends BaseMapper<ZsjyInternshipDaily> {
|
||||
|
||||
/**
|
||||
* 查询实习日常记录列表
|
||||
*
|
||||
* @param page 分页参数(可选)
|
||||
* @param queryBo 查询条件
|
||||
* @return 实习日常记录列表
|
||||
*/
|
||||
List<ZsjyInternshipDailyVo> selectDailyList(@Param("page") Page<ZsjyInternshipDailyVo> page,
|
||||
@Param("queryBo") ZsjyInternshipDailyQueryBo queryBo);
|
||||
|
||||
/**
|
||||
* 根据学生ID获取考勤日历数据
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @return 考勤日历数据
|
||||
*/
|
||||
List<Map<String, Object>> selectCalendarData(@Param("studentId") Long studentId,
|
||||
@Param("startDate") Date startDate,
|
||||
@Param("endDate") Date endDate);
|
||||
|
||||
/**
|
||||
* 获取考勤统计数据
|
||||
*
|
||||
* @param queryBo 查询条件
|
||||
* @return 统计数据
|
||||
*/
|
||||
Map<String, Object> selectStatistics(@Param("queryBo") ZsjyInternshipDailyQueryBo queryBo);
|
||||
|
||||
/**
|
||||
* 检查学生当日是否已签到
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @param checkDate 打卡日期
|
||||
* @return 是否已签到
|
||||
*/
|
||||
boolean existsByStudentAndDate(@Param("studentId") Long studentId,
|
||||
@Param("checkDate") Date checkDate);
|
||||
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package org.dromara.zsjy.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.dromara.zsjy.domain.ZsjyInternshipInfo;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 实习信息Mapper接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Mapper
|
||||
public interface ZsjyInternshipInfoMapper extends BaseMapper<ZsjyInternshipInfo> {
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package org.dromara.zsjy.service;
|
||||
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyAddBo;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
|
||||
import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 实习日常管理Service接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
public interface IZsjyInternshipDailyService {
|
||||
|
||||
/**
|
||||
* 查询实习日常记录分页列表
|
||||
*
|
||||
* @param queryBo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 实习日常记录分页列表
|
||||
*/
|
||||
TableDataInfo<ZsjyInternshipDailyVo> queryPageList(ZsjyInternshipDailyQueryBo queryBo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询实习日常记录列表(不分页)
|
||||
*
|
||||
* @param queryBo 查询条件
|
||||
* @return 实习日常记录列表
|
||||
*/
|
||||
List<ZsjyInternshipDailyVo> queryList(ZsjyInternshipDailyQueryBo queryBo);
|
||||
|
||||
/**
|
||||
* 学生签到打卡
|
||||
*
|
||||
* @param addBo 签到信息
|
||||
* @return 签到结果
|
||||
*/
|
||||
R<Void> checkIn(ZsjyInternshipDailyAddBo addBo);
|
||||
|
||||
/**
|
||||
* 根据学生ID获取考勤日历数据
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @param year 年份
|
||||
* @param month 月份
|
||||
* @return 考勤日历数据
|
||||
*/
|
||||
R<List<Map<String, Object>>> getCalendarData(Long studentId, Integer year, Integer month);
|
||||
|
||||
/**
|
||||
* 获取考勤统计数据
|
||||
*
|
||||
* @param queryBo 查询条件
|
||||
* @return 统计数据
|
||||
*/
|
||||
R<Map<String, Object>> getStatistics(ZsjyInternshipDailyQueryBo queryBo);
|
||||
|
||||
}
|
||||
+116
-13
@@ -14,6 +14,9 @@ import org.dromara.common.excel.utils.ExcelUtil;
|
||||
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.XgStudentMsgVo;
|
||||
import org.dromara.xg.service.IXgStudentMsgService;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.zsjy.domain.bo.InternshipInfoQueryBO;
|
||||
import org.dromara.zsjy.domain.bo.InternshipInfoAddBO;
|
||||
import org.dromara.zsjy.domain.bo.InternshipInfoEditBO;
|
||||
@@ -41,6 +44,7 @@ public class InternshipInfoServiceImpl implements IInternshipInfoService {
|
||||
|
||||
private final InternshipInfoMapper internshipInfoMapper;
|
||||
private final ZsjyInternshipInfoMapper zsjyInternshipInfoMapper;
|
||||
private final IXgStudentMsgService xgStudentMsgService;
|
||||
|
||||
/**
|
||||
* 查询学生实习信息分页列表(班主任专用)
|
||||
@@ -266,35 +270,104 @@ public class InternshipInfoServiceImpl implements IInternshipInfoService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean insertInternshipInfo(InternshipInfoAddBO addBO) {
|
||||
log.info("新增实习信息,参数:{}", addBO);
|
||||
log.info("=== 新增实习信息开始 ===");
|
||||
log.info("1. 接收到的参数:{}", addBO);
|
||||
|
||||
// 检查该学生是否已经有实习信息
|
||||
LambdaQueryWrapper<ZsjyInternshipInfo> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ZsjyInternshipInfo::getStudentId, addBO.getStudentId());
|
||||
ZsjyInternshipInfo existingInfo = zsjyInternshipInfoMapper.selectOne(queryWrapper);
|
||||
log.info("2. 检查学生是否已有实习信息,结果:{}", existingInfo != null ? "已存在" : "不存在");
|
||||
|
||||
if (ObjectUtil.isNotNull(existingInfo)) {
|
||||
log.warn("学生ID:{} 已存在实习信息,不能重复添加", addBO.getStudentId());
|
||||
throw new RuntimeException("该学生已存在实习信息,不能重复添加");
|
||||
}
|
||||
|
||||
// 根据学生ID查询学生基础信息
|
||||
log.info("3. 查询学生基础信息...");
|
||||
XgStudentMsgVo studentInfo = getStudentInfo(addBO.getStudentId());
|
||||
log.info("4. 学生信息查询结果:学号={}, 姓名={}, 学院ID={}, 专业ID={}, 班级ID={}, 手机号={}",
|
||||
studentInfo.getStudentNo(), studentInfo.getName(),
|
||||
studentInfo.getCollegeId(), studentInfo.getMajorId(),
|
||||
studentInfo.getClassId(), studentInfo.getPhone());
|
||||
|
||||
// 构建实习信息实体
|
||||
ZsjyInternshipInfo internshipInfo = new ZsjyInternshipInfo();
|
||||
BeanUtil.copyProperties(addBO, internshipInfo);
|
||||
log.info("5. 开始字段映射...");
|
||||
|
||||
// 映射字段名称差异
|
||||
internshipInfo.setPositionName(addBO.getInternshipPosition());
|
||||
internshipInfo.setStartDate(addBO.getInternshipStartDate());
|
||||
internshipInfo.setEndDate(addBO.getInternshipEndDate());
|
||||
internshipInfo.setCompanyContact(addBO.getContactPersonName());
|
||||
internshipInfo.setCompanyPhone(addBO.getContactPersonPhone());
|
||||
internshipInfo.setStatus(addBO.getAgreementStatus());
|
||||
// 学生基础信息映射(数据库必填字段)
|
||||
internshipInfo.setStudentId(addBO.getStudentId());
|
||||
internshipInfo.setStudentNo(studentInfo.getStudentNo()); // 从学生信息获取
|
||||
internshipInfo.setStudentName(studentInfo.getName()); // 从学生信息获取
|
||||
internshipInfo.setGender(studentInfo.getGender()); // 从学生信息获取
|
||||
|
||||
// 类型转换:String -> Long(处理可能的空值)
|
||||
internshipInfo.setCollegeId(StringUtils.isNotBlank(studentInfo.getCollegeId()) ?
|
||||
Long.parseLong(studentInfo.getCollegeId()) : null); // 从学生信息获取
|
||||
internshipInfo.setMajorId(StringUtils.isNotBlank(studentInfo.getMajorId()) ?
|
||||
Long.parseLong(studentInfo.getMajorId()) : null); // 从学生信息获取
|
||||
internshipInfo.setClassId(StringUtils.isNotBlank(studentInfo.getClassId()) ?
|
||||
Long.parseLong(studentInfo.getClassId()) : null); // 从学生信息获取
|
||||
|
||||
internshipInfo.setPhone(studentInfo.getPhone()); // 从学生信息获取
|
||||
|
||||
// 实习相关信息映射(来自BO)
|
||||
internshipInfo.setInternshipStatus(addBO.getInternshipStatus());
|
||||
internshipInfo.setCompanyName(addBO.getCompanyName());
|
||||
internshipInfo.setCompanyAddress(addBO.getCompanyAddress());
|
||||
internshipInfo.setInternshipAllowance(addBO.getInternshipAllowance());
|
||||
internshipInfo.setPositionName(addBO.getInternshipPosition()); // internshipPosition -> positionName
|
||||
internshipInfo.setStartDate(addBO.getInternshipStartDate()); // internshipStartDate -> startDate
|
||||
internshipInfo.setEndDate(addBO.getInternshipEndDate()); // internshipEndDate -> endDate
|
||||
|
||||
// 公司联系信息映射(优先使用BO中的新字段,兼容旧字段)
|
||||
internshipInfo.setCompanyContact(StringUtils.isNotBlank(addBO.getCompanyContact()) ?
|
||||
addBO.getCompanyContact() : addBO.getContactPersonName()); // 优先使用companyContact
|
||||
internshipInfo.setCompanyPhone(StringUtils.isNotBlank(addBO.getCompanyPhone()) ?
|
||||
addBO.getCompanyPhone() : addBO.getContactPersonPhone()); // 优先使用companyPhone
|
||||
|
||||
// 新增字段映射(从BO中获取真实值)
|
||||
internshipInfo.setCompanyIndustry(addBO.getCompanyIndustry()); // 行业类型
|
||||
internshipInfo.setCompanyScale(addBO.getCompanyScale()); // 单位规模
|
||||
internshipInfo.setPositionDuties(addBO.getPositionDuties()); // 岗位职责
|
||||
internshipInfo.setWorkHours(addBO.getWorkHours()); // 工作时长
|
||||
internshipInfo.setAccommodationAddress(addBO.getAccommodationAddress()); // 住宿地址
|
||||
internshipInfo.setAccommodationType(addBO.getAccommodationType()); // 住宿类型
|
||||
internshipInfo.setAccommodationCost(addBO.getAccommodationCost()); // 住宿费用
|
||||
|
||||
internshipInfo.setStatus(addBO.getAgreementStatus()); // agreementStatus -> status
|
||||
// actualEndDate保持null,因为实习还未结束
|
||||
|
||||
log.info("6. 映射后的实体对象:");
|
||||
log.info(" - studentId: {}", internshipInfo.getStudentId());
|
||||
log.info(" - studentNo: {}", internshipInfo.getStudentNo());
|
||||
log.info(" - studentName: {}", internshipInfo.getStudentName());
|
||||
log.info(" - collegeId: {}", internshipInfo.getCollegeId());
|
||||
log.info(" - majorId: {}", internshipInfo.getMajorId());
|
||||
log.info(" - classId: {}", internshipInfo.getClassId());
|
||||
log.info(" - phone: {}", internshipInfo.getPhone());
|
||||
log.info(" - internshipStatus: {}", internshipInfo.getInternshipStatus());
|
||||
log.info(" - companyName: {}", internshipInfo.getCompanyName());
|
||||
log.info(" - companyAddress: {}", internshipInfo.getCompanyAddress());
|
||||
log.info(" - positionName: {}", internshipInfo.getPositionName());
|
||||
log.info(" - internshipAllowance: {}", internshipInfo.getInternshipAllowance());
|
||||
log.info(" - startDate: {}", internshipInfo.getStartDate());
|
||||
log.info(" - endDate: {}", internshipInfo.getEndDate());
|
||||
log.info(" - companyContact: {}", internshipInfo.getCompanyContact());
|
||||
log.info(" - companyPhone: {}", internshipInfo.getCompanyPhone());
|
||||
log.info(" - status: {}", internshipInfo.getStatus());
|
||||
|
||||
// 插入数据
|
||||
log.info("7. 开始执行数据库插入操作...");
|
||||
int result = zsjyInternshipInfoMapper.insert(internshipInfo);
|
||||
log.info("新增实习信息结果:{}", result > 0 ? "成功" : "失败");
|
||||
log.info("8. 数据库插入结果:{} (1表示成功,0表示失败)", result);
|
||||
log.info("9. 插入后的主键ID:{}", internshipInfo.getInternshipId());
|
||||
|
||||
return result > 0;
|
||||
boolean success = result > 0;
|
||||
log.info("=== 新增实习信息结束,结果:{} ===", success ? "成功" : "失败");
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,8 +396,22 @@ public class InternshipInfoServiceImpl implements IInternshipInfoService {
|
||||
internshipInfo.setPositionName(editBO.getInternshipPosition());
|
||||
internshipInfo.setStartDate(editBO.getInternshipStartDate());
|
||||
internshipInfo.setEndDate(editBO.getInternshipEndDate());
|
||||
internshipInfo.setCompanyContact(editBO.getContactPersonName());
|
||||
internshipInfo.setCompanyPhone(editBO.getContactPersonPhone());
|
||||
|
||||
// 公司联系信息映射(优先使用BO中的新字段,兼容旧字段)
|
||||
internshipInfo.setCompanyContact(StringUtils.isNotBlank(editBO.getCompanyContact()) ?
|
||||
editBO.getCompanyContact() : editBO.getContactPersonName()); // 优先使用companyContact
|
||||
internshipInfo.setCompanyPhone(StringUtils.isNotBlank(editBO.getCompanyPhone()) ?
|
||||
editBO.getCompanyPhone() : editBO.getContactPersonPhone()); // 优先使用companyPhone
|
||||
|
||||
// 新增字段映射(从BO中获取真实值)
|
||||
internshipInfo.setCompanyIndustry(editBO.getCompanyIndustry()); // 行业类型
|
||||
internshipInfo.setCompanyScale(editBO.getCompanyScale()); // 单位规模
|
||||
internshipInfo.setPositionDuties(editBO.getPositionDuties()); // 岗位职责
|
||||
internshipInfo.setWorkHours(editBO.getWorkHours()); // 工作时长
|
||||
internshipInfo.setAccommodationAddress(editBO.getAccommodationAddress()); // 住宿地址
|
||||
internshipInfo.setAccommodationType(editBO.getAccommodationType()); // 住宿类型
|
||||
internshipInfo.setAccommodationCost(editBO.getAccommodationCost()); // 住宿费用
|
||||
|
||||
internshipInfo.setStatus(editBO.getAgreementStatus());
|
||||
|
||||
// 更新数据
|
||||
@@ -356,4 +443,20 @@ public class InternshipInfoServiceImpl implements IInternshipInfoService {
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID查询学生基础信息
|
||||
*
|
||||
* @param studentId 学生ID
|
||||
* @return 学生信息
|
||||
*/
|
||||
private XgStudentMsgVo getStudentInfo(Long studentId) {
|
||||
log.info("根据学生ID查询学生信息:{}", studentId);
|
||||
XgStudentMsgVo studentInfo = xgStudentMsgService.queryByUserId(studentId);
|
||||
if (studentInfo == null) {
|
||||
log.error("未找到学生信息,学生ID:{}", studentId);
|
||||
throw new ServiceException("未找到学生信息,学生ID:" + studentId);
|
||||
}
|
||||
return studentInfo;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package org.dromara.zsjy.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
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.zsjy.domain.ZsjyInternshipDaily;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyAddBo;
|
||||
import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
|
||||
import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
|
||||
import org.dromara.zsjy.mapper.ZsjyInternshipDailyMapper;
|
||||
import org.dromara.zsjy.service.IZsjyInternshipDailyService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.sql.Time;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.YearMonth;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 实习日常管理Service业务层处理
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyService {
|
||||
|
||||
private final ZsjyInternshipDailyMapper baseMapper;
|
||||
|
||||
/**
|
||||
* 查询实习日常记录分页列表
|
||||
*/
|
||||
@Override
|
||||
public TableDataInfo<ZsjyInternshipDailyVo> queryPageList(ZsjyInternshipDailyQueryBo queryBo, PageQuery pageQuery) {
|
||||
Page<ZsjyInternshipDailyVo> page = pageQuery.build();
|
||||
List<ZsjyInternshipDailyVo> list = baseMapper.selectDailyList(page, queryBo);
|
||||
return TableDataInfo.build(page);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询实习日常记录列表(不分页)
|
||||
*/
|
||||
@Override
|
||||
public List<ZsjyInternshipDailyVo> queryList(ZsjyInternshipDailyQueryBo queryBo) {
|
||||
return baseMapper.selectDailyList(null, queryBo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生签到打卡
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public R<Void> checkIn(ZsjyInternshipDailyAddBo addBo) {
|
||||
try {
|
||||
// 1. 验证学生当日是否已签到
|
||||
boolean exists = baseMapper.existsByStudentAndDate(addBo.getStudentId(), addBo.getCheckDate());
|
||||
if (exists) {
|
||||
return R.fail("今日已签到,请勿重复签到");
|
||||
}
|
||||
|
||||
// 2. 构建实体对象
|
||||
ZsjyInternshipDaily entity = MapstructUtils.convert(addBo, ZsjyInternshipDaily.class);
|
||||
|
||||
// 3. 设置签到时间为当前时间
|
||||
if (ObjectUtil.isNull(entity.getCheckInTime())) {
|
||||
entity.setCheckInTime(Time.valueOf(DateUtil.format(new Date(), "HH:mm:ss")));
|
||||
}
|
||||
|
||||
// 4. 设置默认值
|
||||
if (ObjectUtil.isNull(entity.getIsException())) {
|
||||
entity.setIsException("0"); // 默认正常
|
||||
}
|
||||
if (ObjectUtil.isNull(entity.getExceptionStatus())) {
|
||||
entity.setExceptionStatus("0"); // 默认待处理
|
||||
}
|
||||
if (ObjectUtil.isNull(entity.getStatus())) {
|
||||
entity.setStatus("0"); // 默认正常
|
||||
}
|
||||
|
||||
// 5. 保存签到记录
|
||||
boolean result = baseMapper.insert(entity) > 0;
|
||||
if (!result) {
|
||||
return R.fail("签到失败,请稍后重试");
|
||||
}
|
||||
|
||||
log.info("学生[{}]于{}成功签到", addBo.getStudentId(), addBo.getCheckDate());
|
||||
return R.ok("签到成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("学生签到失败: {}", e.getMessage(), e);
|
||||
throw new ServiceException("签到失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据学生ID获取考勤日历数据
|
||||
*/
|
||||
@Override
|
||||
public R<List<Map<String, Object>>> getCalendarData(Long studentId, Integer year, Integer month) {
|
||||
try {
|
||||
// 1. 参数校验
|
||||
if (ObjectUtil.isNull(studentId)) {
|
||||
return R.fail("学生ID不能为空");
|
||||
}
|
||||
if (ObjectUtil.isNull(year) || ObjectUtil.isNull(month)) {
|
||||
return R.fail("年份和月份不能为空");
|
||||
}
|
||||
|
||||
// 2. 计算日期范围
|
||||
YearMonth yearMonth = YearMonth.of(year, month);
|
||||
Date startDate = DateUtil.beginOfMonth(DateUtil.parse(yearMonth + "-01"));
|
||||
Date endDate = DateUtil.endOfMonth(startDate);
|
||||
|
||||
// 3. 查询考勤数据
|
||||
List<Map<String, Object>> calendarData = baseMapper.selectCalendarData(studentId, startDate, endDate);
|
||||
|
||||
return R.ok(calendarData);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取考勤日历数据失败: {}", e.getMessage(), e);
|
||||
return R.fail("获取考勤日历数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取考勤统计数据
|
||||
*/
|
||||
@Override
|
||||
public R<Map<String, Object>> getStatistics(ZsjyInternshipDailyQueryBo queryBo) {
|
||||
try {
|
||||
// 1. 查询统计数据
|
||||
Map<String, Object> statistics = baseMapper.selectStatistics(queryBo);
|
||||
|
||||
// 2. 补充统计信息
|
||||
if (ObjectUtil.isNull(statistics)) {
|
||||
statistics = new HashMap<>();
|
||||
statistics.put("totalDays", 0);
|
||||
statistics.put("attendanceDays", 0);
|
||||
statistics.put("exceptionDays", 0);
|
||||
statistics.put("pendingExceptions", 0);
|
||||
statistics.put("attendanceRate", 0.0);
|
||||
}
|
||||
|
||||
return R.ok(statistics);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取考勤统计数据失败: {}", e.getMessage(), e);
|
||||
return R.fail("获取考勤统计数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?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.zsjy.mapper.ZsjyInternshipDailyMapper">
|
||||
|
||||
<!-- 实习日常记录结果映射 -->
|
||||
<resultMap type="org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo" id="ZsjyInternshipDailyVoResult">
|
||||
<id property="dailyId" column="daily_id" />
|
||||
<result property="internshipId" column="internship_id" />
|
||||
<result property="studentId" column="student_id" />
|
||||
<result property="checkDate" column="check_date" />
|
||||
<result property="checkInTime" column="check_in_time" />
|
||||
<result property="checkInLocation" column="check_in_location" />
|
||||
<result property="latitude" column="latitude" />
|
||||
<result property="longitude" column="longitude" />
|
||||
<result property="dailySummary" column="daily_summary" />
|
||||
<result property="images" column="images" />
|
||||
<result property="isException" column="is_exception" />
|
||||
<result property="exceptionReason" column="exception_reason" />
|
||||
<result property="exceptionStatus" column="exception_status" />
|
||||
<result property="status" column="status" />
|
||||
<result property="remark" column="remark" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<!-- 扩展字段 -->
|
||||
<result property="studentName" column="student_name" />
|
||||
<result property="studentNo" column="student_no" />
|
||||
<result property="companyName" column="company_name" />
|
||||
</resultMap>
|
||||
|
||||
<!-- 基础SQL片段 -->
|
||||
<sql id="selectZsjyInternshipDailyVo">
|
||||
SELECT
|
||||
d.daily_id,
|
||||
d.internship_id,
|
||||
d.student_id,
|
||||
d.check_date,
|
||||
d.check_in_time,
|
||||
d.check_in_location,
|
||||
d.latitude,
|
||||
d.longitude,
|
||||
d.daily_summary,
|
||||
d.images,
|
||||
d.is_exception,
|
||||
d.exception_reason,
|
||||
d.exception_status,
|
||||
d.status,
|
||||
d.remark,
|
||||
d.create_by,
|
||||
d.create_time,
|
||||
d.update_by,
|
||||
d.update_time,
|
||||
-- 关联查询扩展字段
|
||||
i.student_name,
|
||||
i.student_no,
|
||||
i.company_name
|
||||
FROM zsjy_internship_daily d
|
||||
LEFT JOIN zsjy_internship_info i ON d.internship_id = i.internship_id
|
||||
</sql>
|
||||
|
||||
<!-- 查询实习日常记录列表 -->
|
||||
<select id="selectDailyList" parameterType="org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo" resultMap="ZsjyInternshipDailyVoResult">
|
||||
<include refid="selectZsjyInternshipDailyVo"/>
|
||||
<where>
|
||||
d.deleted = 0
|
||||
<if test="queryBo.studentId != null">
|
||||
AND d.student_id = #{queryBo.studentId}
|
||||
</if>
|
||||
<if test="queryBo.studentNo != null and queryBo.studentNo != ''">
|
||||
AND i.student_no LIKE CONCAT('%', #{queryBo.studentNo}, '%')
|
||||
</if>
|
||||
<if test="queryBo.studentName != null and queryBo.studentName != ''">
|
||||
AND i.student_name LIKE CONCAT('%', #{queryBo.studentName}, '%')
|
||||
</if>
|
||||
<if test="queryBo.internshipId != null">
|
||||
AND d.internship_id = #{queryBo.internshipId}
|
||||
</if>
|
||||
<if test="queryBo.checkDateStart != null">
|
||||
AND d.check_date >= #{queryBo.checkDateStart}
|
||||
</if>
|
||||
<if test="queryBo.checkDateEnd != null">
|
||||
AND d.check_date <= #{queryBo.checkDateEnd}
|
||||
</if>
|
||||
<if test="queryBo.isException != null and queryBo.isException != ''">
|
||||
AND d.is_exception = #{queryBo.isException}
|
||||
</if>
|
||||
<if test="queryBo.exceptionStatus != null and queryBo.exceptionStatus != ''">
|
||||
AND d.exception_status = #{queryBo.exceptionStatus}
|
||||
</if>
|
||||
<if test="queryBo.status != null and queryBo.status != ''">
|
||||
AND d.status = #{queryBo.status}
|
||||
</if>
|
||||
<if test="queryBo.companyName != null and queryBo.companyName != ''">
|
||||
AND i.company_name LIKE CONCAT('%', #{queryBo.companyName}, '%')
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY d.check_date DESC, d.create_time DESC
|
||||
</select>
|
||||
|
||||
<!-- 根据学生ID获取考勤日历数据 -->
|
||||
<select id="selectCalendarData" resultType="java.util.Map">
|
||||
SELECT
|
||||
DATE_FORMAT(check_date, '%Y-%m-%d') as date,
|
||||
CASE
|
||||
WHEN check_in_time IS NOT NULL THEN '1'
|
||||
WHEN is_exception = '1' THEN '2'
|
||||
ELSE '0'
|
||||
END as status,
|
||||
check_in_time,
|
||||
is_exception,
|
||||
exception_reason,
|
||||
daily_summary
|
||||
FROM zsjy_internship_daily
|
||||
WHERE student_id = #{studentId}
|
||||
AND deleted = 0
|
||||
<if test="startDate != null">
|
||||
AND check_date >= #{startDate}
|
||||
</if>
|
||||
<if test="endDate != null">
|
||||
AND check_date <= #{endDate}
|
||||
</if>
|
||||
ORDER BY check_date ASC
|
||||
</select>
|
||||
|
||||
<!-- 获取考勤统计数据 -->
|
||||
<select id="selectStatistics" resultType="java.util.Map">
|
||||
SELECT
|
||||
COUNT(*) as totalDays,
|
||||
SUM(CASE WHEN check_in_time IS NOT NULL THEN 1 ELSE 0 END) as attendanceDays,
|
||||
SUM(CASE WHEN is_exception = '1' THEN 1 ELSE 0 END) as exceptionDays,
|
||||
SUM(CASE WHEN is_exception = '1' AND exception_status = '0' THEN 1 ELSE 0 END) as pendingExceptions,
|
||||
ROUND(
|
||||
SUM(CASE WHEN check_in_time IS NOT NULL THEN 1 ELSE 0 END) * 100.0 /
|
||||
NULLIF(COUNT(*), 0), 2
|
||||
) as attendanceRate
|
||||
FROM zsjy_internship_daily d
|
||||
LEFT JOIN zsjy_internship_info i ON d.internship_id = i.internship_id
|
||||
<where>
|
||||
d.deleted = 0
|
||||
<if test="queryBo.studentId != null">
|
||||
AND d.student_id = #{queryBo.studentId}
|
||||
</if>
|
||||
<if test="queryBo.internshipId != null">
|
||||
AND d.internship_id = #{queryBo.internshipId}
|
||||
</if>
|
||||
<if test="queryBo.checkDateStart != null">
|
||||
AND d.check_date >= #{queryBo.checkDateStart}
|
||||
</if>
|
||||
<if test="queryBo.checkDateEnd != null">
|
||||
AND d.check_date <= #{queryBo.checkDateEnd}
|
||||
</if>
|
||||
<if test="queryBo.companyName != null and queryBo.companyName != ''">
|
||||
AND i.company_name LIKE CONCAT('%', #{queryBo.companyName}, '%')
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<!-- 检查学生当日是否已签到 -->
|
||||
<select id="existsByStudentAndDate" resultType="boolean">
|
||||
SELECT COUNT(*) > 0
|
||||
FROM zsjy_internship_daily
|
||||
WHERE student_id = #{studentId}
|
||||
AND check_date = #{checkDate}
|
||||
AND deleted = 0
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询实习日常记录列表
|
||||
export function listInternshipDaily(query) {
|
||||
return request({
|
||||
url: '/zsjy/internship/daily/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 学生签到打卡
|
||||
export function checkIn(data) {
|
||||
return request({
|
||||
url: '/zsjy/internship/daily/checkin',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增实习日常记录
|
||||
export function addInternshipDaily(data) {
|
||||
return request({
|
||||
url: '/zsjy/internship/daily',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取学生考勤日历
|
||||
export function getCalendar(studentId, params) {
|
||||
return request({
|
||||
url: `/zsjy/internship/daily/calendar/${studentId}`,
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取考勤统计数据
|
||||
export function getStatistics(query) {
|
||||
return request({
|
||||
url: '/zsjy/internship/daily/statistics',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
+115
-45
@@ -841,6 +841,31 @@ export interface InternshipInfoVO {
|
||||
*/
|
||||
companyAddress?: string;
|
||||
|
||||
/**
|
||||
* 实习津贴
|
||||
*/
|
||||
internshipAllowance?: number;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
contactPersonName?: string;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
contactPersonPhone?: string;
|
||||
|
||||
/**
|
||||
* 协议状态
|
||||
*/
|
||||
agreementStatus?: string;
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@@ -871,6 +896,16 @@ export interface InternshipInfoAddBO {
|
||||
*/
|
||||
studentId: string | number;
|
||||
|
||||
/**
|
||||
* 学生姓名(前端显示用)
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 学生学号(前端显示用)
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 实习状态(1进行中 2已完成 3中断)
|
||||
*/
|
||||
@@ -886,6 +921,16 @@ export interface InternshipInfoAddBO {
|
||||
*/
|
||||
companyAddress: string;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
companyContact?: string;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
companyPhone?: string;
|
||||
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
@@ -897,30 +942,40 @@ export interface InternshipInfoAddBO {
|
||||
companyScale?: string;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
* 实习岗位
|
||||
*/
|
||||
companyContact: string;
|
||||
internshipPosition: string;
|
||||
|
||||
/**
|
||||
* 实习薪资
|
||||
*/
|
||||
internshipAllowance?: number;
|
||||
|
||||
/**
|
||||
* 实习开始时间
|
||||
*/
|
||||
internshipStartDate: string;
|
||||
|
||||
/**
|
||||
* 实习结束时间
|
||||
*/
|
||||
internshipEndDate: string;
|
||||
|
||||
/**
|
||||
* 联系人姓名
|
||||
*/
|
||||
contactPersonName: string;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
companyPhone: string;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
positionName: string;
|
||||
contactPersonPhone: string;
|
||||
|
||||
/**
|
||||
* 岗位职责
|
||||
*/
|
||||
positionDuties?: string;
|
||||
|
||||
/**
|
||||
* 实习津贴
|
||||
*/
|
||||
internshipAllowance?: number;
|
||||
|
||||
/**
|
||||
* 工作时长
|
||||
*/
|
||||
@@ -942,19 +997,9 @@ export interface InternshipInfoAddBO {
|
||||
accommodationCost?: number;
|
||||
|
||||
/**
|
||||
* 实习开始日期
|
||||
* 实习协议状态(1已签署 2未签署)
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* 实习结束日期
|
||||
*/
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
actualEndDate?: string;
|
||||
agreementStatus: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
@@ -976,6 +1021,16 @@ export interface InternshipInfoEditBO {
|
||||
*/
|
||||
studentId: string | number;
|
||||
|
||||
/**
|
||||
* 学生姓名(前端显示用)
|
||||
*/
|
||||
studentName?: string;
|
||||
|
||||
/**
|
||||
* 学生学号(前端显示用)
|
||||
*/
|
||||
studentNo?: string;
|
||||
|
||||
/**
|
||||
* 实习状态(1进行中 2已完成 3中断)
|
||||
*/
|
||||
@@ -991,6 +1046,16 @@ export interface InternshipInfoEditBO {
|
||||
*/
|
||||
companyAddress: string;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
*/
|
||||
companyContact?: string;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
companyPhone?: string;
|
||||
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
@@ -1002,30 +1067,40 @@ export interface InternshipInfoEditBO {
|
||||
companyScale?: string;
|
||||
|
||||
/**
|
||||
* 单位联系人
|
||||
* 实习岗位
|
||||
*/
|
||||
companyContact: string;
|
||||
internshipPosition: string;
|
||||
|
||||
/**
|
||||
* 实习薪资
|
||||
*/
|
||||
internshipAllowance?: number;
|
||||
|
||||
/**
|
||||
* 实习开始时间
|
||||
*/
|
||||
internshipStartDate: string;
|
||||
|
||||
/**
|
||||
* 实习结束时间
|
||||
*/
|
||||
internshipEndDate: string;
|
||||
|
||||
/**
|
||||
* 联系人姓名
|
||||
*/
|
||||
contactPersonName: string;
|
||||
|
||||
/**
|
||||
* 联系人电话
|
||||
*/
|
||||
companyPhone: string;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
positionName: string;
|
||||
contactPersonPhone: string;
|
||||
|
||||
/**
|
||||
* 岗位职责
|
||||
*/
|
||||
positionDuties?: string;
|
||||
|
||||
/**
|
||||
* 实习津贴
|
||||
*/
|
||||
internshipAllowance?: number;
|
||||
|
||||
/**
|
||||
* 工作时长
|
||||
*/
|
||||
@@ -1047,14 +1122,9 @@ export interface InternshipInfoEditBO {
|
||||
accommodationCost?: number;
|
||||
|
||||
/**
|
||||
* 实习开始日期
|
||||
* 实习协议状态(1已签署 2未签署)
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* 实习结束日期
|
||||
*/
|
||||
endDate: string;
|
||||
agreementStatus: string;
|
||||
|
||||
/**
|
||||
* 实际结束日期
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
<template>
|
||||
<div class="checkin-container">
|
||||
<!-- 顶部状态卡片 -->
|
||||
<el-card class="status-card" shadow="hover">
|
||||
<div class="status-header">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="60" :src="userInfo.avatar" icon="UserFilled" />
|
||||
<div class="info-text">
|
||||
<h3>{{ userInfo.studentName || userInfo.nickName }}</h3>
|
||||
<p>学号:{{ userInfo.studentNumber || '未设置' }}</p>
|
||||
<p>班级:{{ userInfo.className || '未设置' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="time-info">
|
||||
<div class="current-time">{{ currentTime }}</div>
|
||||
<div class="current-date">{{ currentDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 签到区域 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-card class="checkin-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><Clock /></el-icon>
|
||||
<span>签到打卡</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="checkin-content">
|
||||
<!-- 签到状态显示 -->
|
||||
<div class="status-display">
|
||||
<div v-if="todayRecord.checkInTime" class="checked-in">
|
||||
<el-icon class="status-icon success"><SuccessFilled /></el-icon>
|
||||
<div class="status-text">
|
||||
<h4>已签到</h4>
|
||||
<p>{{ parseTime(todayRecord.checkInTime, '{h}:{i}') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="not-checked-in">
|
||||
<el-icon class="status-icon waiting"><Clock /></el-icon>
|
||||
<div class="status-text">
|
||||
<h4>未签到</h4>
|
||||
<p>请点击下方按钮进行签到</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 签到按钮 -->
|
||||
<div class="checkin-actions">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:disabled="todayRecord.checkInTime || loading"
|
||||
:loading="loading"
|
||||
@click="handleCheckIn"
|
||||
class="checkin-btn"
|
||||
>
|
||||
<el-icon><Location /></el-icon>
|
||||
{{ todayRecord.checkInTime ? '已签到' : '立即签到' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 位置信息 -->
|
||||
<div class="location-info" v-if="locationInfo.address">
|
||||
<el-icon><MapPin /></el-icon>
|
||||
<span>{{ locationInfo.address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-card class="checkout-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><Clock /></el-icon>
|
||||
<span>签退打卡</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="checkout-content">
|
||||
<!-- 签退状态显示 -->
|
||||
<div class="status-display">
|
||||
<div v-if="todayRecord.checkOutTime" class="checked-out">
|
||||
<el-icon class="status-icon success"><SuccessFilled /></el-icon>
|
||||
<div class="status-text">
|
||||
<h4>已签退</h4>
|
||||
<p>{{ parseTime(todayRecord.checkOutTime, '{h}:{i}') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="not-checked-out">
|
||||
<el-icon class="status-icon waiting"><Clock /></el-icon>
|
||||
<div class="status-text">
|
||||
<h4>未签退</h4>
|
||||
<p>{{ todayRecord.checkInTime ? '可以进行签退' : '请先签到' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 签退按钮 -->
|
||||
<div class="checkout-actions">
|
||||
<el-button
|
||||
type="success"
|
||||
size="large"
|
||||
:disabled="!todayRecord.checkInTime || todayRecord.checkOutTime || loading"
|
||||
:loading="loading"
|
||||
@click="handleCheckOut"
|
||||
class="checkout-btn"
|
||||
>
|
||||
<el-icon><Location /></el-icon>
|
||||
{{ todayRecord.checkOutTime ? '已签退' : '立即签退' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 工作时长 -->
|
||||
<div class="duration-info" v-if="todayRecord.workDuration">
|
||||
<el-icon><Timer /></el-icon>
|
||||
<span>工作时长:{{ todayRecord.workDuration }}小时</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 工作内容填写 -->
|
||||
<el-card class="work-content-card" shadow="hover" v-if="todayRecord.checkInTime">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><Edit /></el-icon>
|
||||
<span>今日工作内容</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="work-content">
|
||||
<el-form ref="workFormRef" :model="workForm" label-width="100px">
|
||||
<el-form-item label="工作内容" prop="workContent">
|
||||
<el-input
|
||||
v-model="workForm.workContent"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请详细描述今日的工作内容..."
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="workForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可填写工作心得、遇到的问题等..."
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveWorkContent" :loading="saving">
|
||||
<el-icon><DocumentAdd /></el-icon>
|
||||
保存工作内容
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 最近记录 -->
|
||||
<el-card class="recent-records-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<el-icon class="header-icon"><List /></el-icon>
|
||||
<span>最近考勤记录</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="recent-records">
|
||||
<el-table :data="recentRecords" style="width: 100%">
|
||||
<el-table-column label="日期" width="120">
|
||||
<template #default="scope">
|
||||
{{ parseTime(scope.row.checkDate, '{m}-{d}') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签到时间" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.checkInTime ? parseTime(scope.row.checkInTime, '{h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签退时间" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.checkOutTime ? parseTime(scope.row.checkOutTime, '{h}:{i}') : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工作时长" width="100">
|
||||
<template #default="scope">
|
||||
{{ scope.row.workDuration ? scope.row.workDuration + 'h' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)" size="small">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工作内容" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.workContent || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="InternshipCheckin">
|
||||
import { checkIn } from "@/api/zsjy/internshipDaily";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const currentTime = ref('');
|
||||
const currentDate = ref('');
|
||||
const userInfo = ref({});
|
||||
const todayRecord = ref({});
|
||||
const recentRecords = ref([]);
|
||||
const locationInfo = ref({});
|
||||
|
||||
// 工作内容表单
|
||||
const workForm = ref({
|
||||
workContent: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 获取用户信息
|
||||
const getUserInfo = () => {
|
||||
// 从store或接口获取用户信息
|
||||
userInfo.value = {
|
||||
studentName: '张三',
|
||||
studentNumber: '20230001',
|
||||
className: '计算机科学与技术2023-1班',
|
||||
avatar: ''
|
||||
};
|
||||
};
|
||||
|
||||
// 获取今日考勤记录
|
||||
const getTodayRecord = () => {
|
||||
// 调用接口获取今日考勤记录
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
// 这里应该调用实际的API
|
||||
todayRecord.value = {
|
||||
checkDate: today,
|
||||
checkInTime: null,
|
||||
checkOutTime: null,
|
||||
workDuration: null,
|
||||
status: '0'
|
||||
};
|
||||
};
|
||||
|
||||
// 获取最近考勤记录
|
||||
const getRecentRecords = () => {
|
||||
// 调用接口获取最近7天的考勤记录
|
||||
recentRecords.value = [
|
||||
{
|
||||
checkDate: '2024-01-15',
|
||||
checkInTime: '2024-01-15 09:00:00',
|
||||
checkOutTime: '2024-01-15 18:00:00',
|
||||
workDuration: 8,
|
||||
status: '1',
|
||||
workContent: '参与项目开发,完成用户模块设计'
|
||||
},
|
||||
// 更多记录...
|
||||
];
|
||||
};
|
||||
|
||||
// 获取地理位置
|
||||
const getLocation = () => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(position) => {
|
||||
const { latitude, longitude } = position.coords;
|
||||
locationInfo.value = {
|
||||
latitude,
|
||||
longitude,
|
||||
address: '获取位置中...'
|
||||
};
|
||||
// 调用地图API获取详细地址
|
||||
getAddressFromCoords(latitude, longitude);
|
||||
},
|
||||
(error) => {
|
||||
console.error('获取位置失败:', error);
|
||||
proxy.$modal.msgError('获取位置失败,请检查浏览器权限设置');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
proxy.$modal.msgError('浏览器不支持地理位置功能');
|
||||
}
|
||||
};
|
||||
|
||||
// 根据坐标获取地址
|
||||
const getAddressFromCoords = (lat, lng) => {
|
||||
// 这里可以调用高德地图、百度地图等API
|
||||
// 模拟地址信息
|
||||
setTimeout(() => {
|
||||
locationInfo.value.address = '浙江省杭州市西湖区某某大厦';
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 处理签到
|
||||
const handleCheckIn = async () => {
|
||||
if (!locationInfo.value.latitude) {
|
||||
proxy.$modal.msgError('请先获取位置信息');
|
||||
getLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const checkInData = {
|
||||
checkInLocation: locationInfo.value.address,
|
||||
latitude: locationInfo.value.latitude,
|
||||
longitude: locationInfo.value.longitude,
|
||||
checkInTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
await checkIn(checkInData);
|
||||
|
||||
proxy.$modal.msgSuccess('签到成功');
|
||||
getTodayRecord(); // 刷新今日记录
|
||||
getRecentRecords(); // 刷新最近记录
|
||||
} catch (error) {
|
||||
console.error('签到失败:', error);
|
||||
proxy.$modal.msgError('签到失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理签退
|
||||
const handleCheckOut = async () => {
|
||||
if (!locationInfo.value.latitude) {
|
||||
proxy.$modal.msgError('请先获取位置信息');
|
||||
getLocation();
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const checkOutData = {
|
||||
checkOutLocation: locationInfo.value.address,
|
||||
latitude: locationInfo.value.latitude,
|
||||
longitude: locationInfo.value.longitude,
|
||||
checkOutTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 调用签退接口
|
||||
// await checkOut(checkOutData);
|
||||
|
||||
proxy.$modal.msgSuccess('签退成功');
|
||||
getTodayRecord(); // 刷新今日记录
|
||||
getRecentRecords(); // 刷新最近记录
|
||||
} catch (error) {
|
||||
console.error('签退失败:', error);
|
||||
proxy.$modal.msgError('签退失败,请重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存工作内容
|
||||
const saveWorkContent = async () => {
|
||||
if (!workForm.value.workContent.trim()) {
|
||||
proxy.$modal.msgError('请填写工作内容');
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
// 调用保存工作内容的接口
|
||||
// await updateWorkContent(workForm.value);
|
||||
|
||||
proxy.$modal.msgSuccess('工作内容保存成功');
|
||||
getTodayRecord(); // 刷新今日记录
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
proxy.$modal.msgError('保存失败,请重试');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态类型
|
||||
const getStatusType = (status) => {
|
||||
const statusMap = {
|
||||
'0': 'info',
|
||||
'1': 'success',
|
||||
'2': 'warning',
|
||||
'3': 'danger'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'0': '未签到',
|
||||
'1': '正常',
|
||||
'2': '迟到',
|
||||
'3': '早退'
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
};
|
||||
|
||||
// 更新时间
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
currentTime.value = now.toLocaleTimeString('zh-CN', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
currentDate.value = now.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
weekday: 'long'
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getUserInfo();
|
||||
getTodayRecord();
|
||||
getRecentRecords();
|
||||
getLocation();
|
||||
updateTime();
|
||||
|
||||
// 每秒更新时间
|
||||
setInterval(updateTime, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.checkin-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
||||
.status-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.info-text {
|
||||
h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 2px 0;
|
||||
opacity: 0.9;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.time-info {
|
||||
text-align: right;
|
||||
|
||||
.current-time {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.current-date {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-card,
|
||||
.checkout-card {
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
|
||||
.header-icon {
|
||||
font-size: 18px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-content,
|
||||
.checkout-content {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
|
||||
.status-display {
|
||||
margin-bottom: 24px;
|
||||
|
||||
.checked-in,
|
||||
.checked-out,
|
||||
.not-checked-in,
|
||||
.not-checked-out {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.status-icon {
|
||||
font-size: 48px;
|
||||
|
||||
&.success {
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
&.waiting {
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-actions,
|
||||
.checkout-actions {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.checkin-btn,
|
||||
.checkout-btn {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
border-radius: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.location-info,
|
||||
.duration-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.work-content-card,
|
||||
.recent-records-card {
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
|
||||
.header-icon {
|
||||
font-size: 18px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.work-content {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.recent-records {
|
||||
:deep(.el-table) {
|
||||
.el-table__header-wrapper {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.checkin-container {
|
||||
padding: 12px;
|
||||
|
||||
.status-header {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 16px;
|
||||
|
||||
.user-info {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
text-align: center;
|
||||
|
||||
.current-time {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-btn,
|
||||
.checkout-btn {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.checkin-card,
|
||||
.checkout-card,
|
||||
.work-content-card,
|
||||
.recent-records-card {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.checkin-btn,
|
||||
.checkout-btn {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,899 @@
|
||||
<template>
|
||||
<div class="p-2 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">
|
||||
<!-- 第一行:4个搜索条件 -->
|
||||
<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="studentNumber">
|
||||
<el-input v-model="queryParams.studentNumber" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="实习单位" prop="companyName">
|
||||
<el-input v-model="queryParams.companyName" placeholder="请输入实习单位" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-form-item label="签到状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择签到状态" clearable style="width: 100%">
|
||||
<el-option label="已签到" value="1" />
|
||||
<el-option label="未签到" value="0" />
|
||||
<el-option label="迟到" value="2" />
|
||||
<el-option label="早退" value="3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第二行:日期筛选 + 操作按钮 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签到日期">
|
||||
<el-date-picker
|
||||
v-model="daterangeCheckTime"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleQuery"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" 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>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="mb-[10px]">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-card-primary">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.totalStudents || 0 }}</div>
|
||||
<div class="stat-label">实习学生总数</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-card-success">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><SuccessFilled /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.todayCheckedIn || 0 }}</div>
|
||||
<div class="stat-label">今日已签到</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-card-warning">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><WarningFilled /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.todayLate || 0 }}</div>
|
||||
<div class="stat-label">今日迟到</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-card shadow="hover" class="stat-card stat-card-info">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon">
|
||||
<el-icon><Clock /></el-icon>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ statistics.attendanceRate || '0%' }}</div>
|
||||
<div class="stat-label">本月出勤率</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<el-card shadow="never" class="button-card">
|
||||
<div class="card-header-vertical">
|
||||
<div class="toolbar-title">实习日常管理</div>
|
||||
<div class="button-group">
|
||||
<el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internship:daily:add']">新增签到</el-button>
|
||||
<el-button plain icon="Search" @click="showSearch = !showSearch">搜索</el-button>
|
||||
<el-button type="primary" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internship:daily:export']">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-card shadow="never" class="table-card">
|
||||
<div class="table-container">
|
||||
<el-table
|
||||
ref="internshipDailyTableRef"
|
||||
v-loading="loading"
|
||||
:data="internshipDailyList"
|
||||
row-key="id"
|
||||
class="scroll-table"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
|
||||
<el-table-column label="学生信息" min-width="180" align="left">
|
||||
<template #default="scope">
|
||||
<div class="student-info">
|
||||
<div class="student-name">{{ scope.row.studentName }}</div>
|
||||
<div class="student-number">学号:{{ scope.row.studentNumber }}</div>
|
||||
<div class="student-class">班级:{{ scope.row.className }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="实习单位" prop="companyName" min-width="150" align="left" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="签到时间" min-width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.checkInTime">
|
||||
<div>{{ parseTime(scope.row.checkInTime, '{y}-{m}-{d}') }}</div>
|
||||
<div class="time-detail">{{ parseTime(scope.row.checkInTime, '{h}:{i}') }}</div>
|
||||
</div>
|
||||
<span v-else class="text-danger">未签到</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="签退时间" min-width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.checkOutTime">
|
||||
<div>{{ parseTime(scope.row.checkOutTime, '{y}-{m}-{d}') }}</div>
|
||||
<div class="time-detail">{{ parseTime(scope.row.checkOutTime, '{h}:{i}') }}</div>
|
||||
</div>
|
||||
<span v-else class="text-muted">未签退</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="工作时长" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.workDuration">{{ scope.row.workDuration }}小时</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="签到状态" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="attendanceStatusOptions" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="签到地点" prop="checkInLocation" min-width="150" align="left" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="工作内容" prop="workContent" min-width="200" align="left" show-overflow-tooltip />
|
||||
|
||||
<el-table-column label="操作" width="120" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<div class="action-buttons">
|
||||
<el-tooltip content="查看详情" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="查看日历" placement="top">
|
||||
<el-button link type="success" icon="Calendar" @click="handleCalendar(scope.row)" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div class="pagination-container">
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
:page="queryParams.pageNum"
|
||||
:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog title="考勤详情" v-model="viewDialogVisible" width="600px" append-to-body>
|
||||
<div v-if="currentRecord" class="detail-container">
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学生姓名">{{ currentRecord.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学号">{{ currentRecord.studentNumber }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ currentRecord.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习单位">{{ currentRecord.companyName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="签到时间">
|
||||
{{ currentRecord.checkInTime ? parseTime(currentRecord.checkInTime) : '未签到' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="签退时间">
|
||||
{{ currentRecord.checkOutTime ? parseTime(currentRecord.checkOutTime) : '未签退' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工作时长">
|
||||
{{ currentRecord.workDuration ? currentRecord.workDuration + '小时' : '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="签到状态">
|
||||
<dict-tag :options="attendanceStatusOptions" :value="currentRecord.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="签到地点" :span="2">{{ currentRecord.checkInLocation || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作内容" :span="2">
|
||||
<div style="white-space: pre-wrap;">{{ currentRecord.workContent || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">
|
||||
<div style="white-space: pre-wrap;">{{ currentRecord.remark || '-' }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button @click="viewDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 考勤日历对话框 -->
|
||||
<el-dialog title="考勤日历" v-model="calendarDialogVisible" width="800px" append-to-body>
|
||||
<div v-if="currentStudent" class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<h4>{{ currentStudent.studentName }}({{ currentStudent.studentNumber }})的考勤记录</h4>
|
||||
<el-date-picker
|
||||
v-model="calendarMonth"
|
||||
type="month"
|
||||
placeholder="选择月份"
|
||||
format="YYYY年MM月"
|
||||
value-format="YYYY-MM"
|
||||
@change="handleMonthChange"
|
||||
/>
|
||||
</div>
|
||||
<el-calendar v-model="calendarDate">
|
||||
<template #date-cell="{ data }">
|
||||
<div class="calendar-day">
|
||||
<div class="day-number">{{ data.day.split('-').slice(2).join('') }}</div>
|
||||
<div v-if="getAttendanceStatus(data.day)" class="attendance-status">
|
||||
<el-tag
|
||||
:type="getStatusTagType(getAttendanceStatus(data.day))"
|
||||
size="small"
|
||||
>
|
||||
{{ getStatusText(getAttendanceStatus(data.day)) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button @click="calendarDialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 新增签到对话框 -->
|
||||
<el-dialog title="新增签到记录" v-model="addDialogVisible" width="600px" append-to-body>
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="addFormRules" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="addForm.studentName" placeholder="请输入学生姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNumber">
|
||||
<el-input v-model="addForm.studentNumber" placeholder="请输入学号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="addForm.className" placeholder="请输入班级" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习单位" prop="companyName">
|
||||
<el-input v-model="addForm.companyName" placeholder="请输入实习单位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签到时间" prop="checkInTime">
|
||||
<el-date-picker
|
||||
v-model="addForm.checkInTime"
|
||||
type="datetime"
|
||||
placeholder="选择签到时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签退时间" prop="checkOutTime">
|
||||
<el-date-picker
|
||||
v-model="addForm.checkOutTime"
|
||||
type="datetime"
|
||||
placeholder="选择签退时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="签到状态" prop="status">
|
||||
<el-select v-model="addForm.status" placeholder="请选择签到状态" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in attendanceStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作时长" prop="workDuration">
|
||||
<el-input-number
|
||||
v-model="addForm.workDuration"
|
||||
:min="0"
|
||||
:max="24"
|
||||
:precision="1"
|
||||
placeholder="工作时长(小时)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="签到地点" prop="checkInLocation">
|
||||
<el-input v-model="addForm.checkInLocation" placeholder="请输入签到地点" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工作内容" prop="workContent">
|
||||
<el-input
|
||||
v-model="addForm.workContent"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入工作内容"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="addForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer-right">
|
||||
<el-button @click="cancelAdd">取消</el-button>
|
||||
<el-button type="primary" @click="submitAdd" :loading="addLoading">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="InternshipDaily">
|
||||
import { listInternshipDaily, getCalendar, getStatistics, addInternshipDaily } from "@/api/zsjy/internshipDaily";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
// 响应式数据
|
||||
const internshipDailyList = ref([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const total = ref(0);
|
||||
const daterangeCheckTime = ref([]);
|
||||
const viewDialogVisible = ref(false);
|
||||
const calendarDialogVisible = ref(false);
|
||||
const currentRecord = ref(null);
|
||||
const currentStudent = ref(null);
|
||||
const calendarMonth = ref('');
|
||||
const calendarDate = ref(new Date());
|
||||
const calendarData = ref([]);
|
||||
const statistics = ref({});
|
||||
const addDialogVisible = ref(false);
|
||||
const addLoading = ref(false);
|
||||
|
||||
// 新增表单数据
|
||||
const addForm = ref({
|
||||
studentName: '',
|
||||
studentNumber: '',
|
||||
className: '',
|
||||
companyName: '',
|
||||
checkInTime: '',
|
||||
checkOutTime: '',
|
||||
workDuration: null,
|
||||
status: '1',
|
||||
checkInLocation: '',
|
||||
workContent: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 新增表单验证规则
|
||||
const addFormRules = ref({
|
||||
studentName: [
|
||||
{ required: true, message: '请输入学生姓名', trigger: 'blur' }
|
||||
],
|
||||
studentNumber: [
|
||||
{ required: true, message: '请输入学号', trigger: 'blur' }
|
||||
],
|
||||
className: [
|
||||
{ required: true, message: '请输入班级', trigger: 'blur' }
|
||||
],
|
||||
companyName: [
|
||||
{ required: true, message: '请输入实习单位', trigger: 'blur' }
|
||||
],
|
||||
checkInTime: [
|
||||
{ required: true, message: '请选择签到时间', trigger: 'change' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择签到状态', trigger: 'change' }
|
||||
]
|
||||
});
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNumber: '',
|
||||
companyName: '',
|
||||
status: '',
|
||||
checkTimeStart: '',
|
||||
checkTimeEnd: ''
|
||||
});
|
||||
|
||||
// 字典数据
|
||||
const attendanceStatusOptions = ref([
|
||||
{ label: '未签到', value: '0', elTagType: 'info' },
|
||||
{ label: '已签到', value: '1', elTagType: 'success' },
|
||||
{ label: '迟到', value: '2', elTagType: 'warning' },
|
||||
{ label: '早退', value: '3', elTagType: 'danger' }
|
||||
]);
|
||||
|
||||
// 查询列表
|
||||
const getList = () => {
|
||||
loading.value = true;
|
||||
|
||||
// 处理日期范围
|
||||
if (daterangeCheckTime.value && daterangeCheckTime.value.length === 2) {
|
||||
queryParams.value.checkTimeStart = daterangeCheckTime.value[0];
|
||||
queryParams.value.checkTimeEnd = daterangeCheckTime.value[1];
|
||||
} else {
|
||||
queryParams.value.checkTimeStart = '';
|
||||
queryParams.value.checkTimeEnd = '';
|
||||
}
|
||||
|
||||
listInternshipDaily(queryParams.value).then(response => {
|
||||
internshipDailyList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 获取统计数据
|
||||
const getStatisticsData = () => {
|
||||
getStatistics().then(response => {
|
||||
statistics.value = response.data || {};
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索按钮操作
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
// 重置按钮操作
|
||||
const resetQuery = () => {
|
||||
daterangeCheckTime.value = [];
|
||||
proxy.resetForm("queryFormRef");
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
studentName: '',
|
||||
studentNumber: '',
|
||||
companyName: '',
|
||||
status: '',
|
||||
checkTimeStart: '',
|
||||
checkTimeEnd: ''
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
// 多选框选中数据
|
||||
const handleSelectionChange = (selection) => {
|
||||
// 多选处理逻辑
|
||||
};
|
||||
|
||||
// 新增按钮操作
|
||||
const handleAdd = () => {
|
||||
resetAddForm();
|
||||
addDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 重置新增表单
|
||||
const resetAddForm = () => {
|
||||
addForm.value = {
|
||||
studentName: '',
|
||||
studentNumber: '',
|
||||
className: '',
|
||||
companyName: '',
|
||||
checkInTime: '',
|
||||
checkOutTime: '',
|
||||
workDuration: null,
|
||||
status: '1',
|
||||
checkInLocation: '',
|
||||
workContent: '',
|
||||
remark: ''
|
||||
};
|
||||
if (proxy.$refs.addFormRef) {
|
||||
proxy.$refs.addFormRef.resetFields();
|
||||
}
|
||||
};
|
||||
|
||||
// 取消新增
|
||||
const cancelAdd = () => {
|
||||
addDialogVisible.value = false;
|
||||
resetAddForm();
|
||||
};
|
||||
|
||||
// 提交新增
|
||||
const submitAdd = () => {
|
||||
proxy.$refs.addFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
addLoading.value = true;
|
||||
|
||||
addInternshipDaily(addForm.value).then(response => {
|
||||
proxy.$modal.msgSuccess("新增签到记录成功");
|
||||
addDialogVisible.value = false;
|
||||
getList();
|
||||
}).finally(() => {
|
||||
addLoading.value = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row) => {
|
||||
currentRecord.value = { ...row };
|
||||
viewDialogVisible.value = true;
|
||||
};
|
||||
|
||||
// 查看考勤日历
|
||||
const handleCalendar = (row) => {
|
||||
currentStudent.value = { ...row };
|
||||
calendarMonth.value = new Date().toISOString().slice(0, 7); // 当前月份
|
||||
calendarDate.value = new Date();
|
||||
calendarDialogVisible.value = true;
|
||||
loadCalendarData();
|
||||
};
|
||||
|
||||
// 月份变化处理
|
||||
const handleMonthChange = (month) => {
|
||||
calendarMonth.value = month;
|
||||
loadCalendarData();
|
||||
};
|
||||
|
||||
// 加载日历数据
|
||||
const loadCalendarData = () => {
|
||||
if (!currentStudent.value || !calendarMonth.value) return;
|
||||
|
||||
const params = {
|
||||
month: calendarMonth.value
|
||||
};
|
||||
|
||||
getCalendar(currentStudent.value.studentId, params).then(response => {
|
||||
calendarData.value = response.data || [];
|
||||
});
|
||||
};
|
||||
|
||||
// 获取考勤状态
|
||||
const getAttendanceStatus = (date) => {
|
||||
const record = calendarData.value.find(item =>
|
||||
item.checkDate === date
|
||||
);
|
||||
return record ? record.status : null;
|
||||
};
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status) => {
|
||||
const statusMap = {
|
||||
'0': 'info',
|
||||
'1': 'success',
|
||||
'2': 'warning',
|
||||
'3': 'danger'
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'0': '未签到',
|
||||
'1': '正常',
|
||||
'2': '迟到',
|
||||
'3': '早退'
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
};
|
||||
|
||||
// 导出按钮操作
|
||||
const handleExport = () => {
|
||||
proxy.download('zsjy/internship/daily/export', {
|
||||
...queryParams.value
|
||||
}, `实习日常记录_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
getList();
|
||||
getStatisticsData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 继承后勤管理的样式
|
||||
.page-container {
|
||||
.search-btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.button-card {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header-vertical {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 20px 0 0 !important;
|
||||
background-color: #fff;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stat-card {
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
height: 90px;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 12px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.stat-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
|
||||
.stat-value {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 13px;
|
||||
opacity: 0.7;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.stat-card-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-success {
|
||||
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-warning {
|
||||
background: linear-gradient(135deg, #ff9a56 0%, #ffad56 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&.stat-card-info {
|
||||
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.scroll-table {
|
||||
:deep(.el-table__header-wrapper) {
|
||||
.el-table__header {
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
.el-table__row {
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 学生信息样式 */
|
||||
.student-info {
|
||||
.student-name {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.student-number,
|
||||
.student-class {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
/* 时间详情样式 */
|
||||
.time-detail {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
.dialog-footer-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 详情容器样式 */
|
||||
.detail-container {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* 日历相关样式 */
|
||||
.calendar-container {
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
color: #303133;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.day-number {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.attendance-status {
|
||||
.el-tag {
|
||||
font-size: 10px;
|
||||
padding: 0 4px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="新增实习信息"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
draggable
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="form-container">
|
||||
<el-row :gutter="24">
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">基本信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="form.studentName" placeholder="学生姓名" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习状态" prop="internshipStatus">
|
||||
<el-select v-model="form.internshipStatus" placeholder="请选择实习状态" style="width: 100%">
|
||||
<el-option v-for="dict in internship_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习津贴" prop="internshipAllowance">
|
||||
<el-input-number v-model="form.internshipAllowance" :min="0" :precision="2" controls-position="right" placeholder="实习津贴" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习单位信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">实习单位信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习单位名称" prop="companyName">
|
||||
<el-input v-model="form.companyName" placeholder="请输入实习单位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位联系人" prop="companyContact">
|
||||
<el-input v-model="form.companyContact" placeholder="请输入单位联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="单位地址" prop="companyAddress">
|
||||
<el-input v-model="form.companyAddress" placeholder="请输入单位地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人电话" prop="companyPhone">
|
||||
<el-input v-model="form.companyPhone" placeholder="请输入联系人电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="行业类型" prop="companyIndustry">
|
||||
<el-input v-model="form.companyIndustry" placeholder="请输入行业类型" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位规模" prop="companyScale">
|
||||
<el-select v-model="form.companyScale" placeholder="请选择单位规模" clearable style="width: 100%">
|
||||
<el-option label="20人以下" value="1" />
|
||||
<el-option label="20-99人" value="2" />
|
||||
<el-option label="100-499人" value="3" />
|
||||
<el-option label="500-999人" value="4" />
|
||||
<el-option label="1000人以上" value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 岗位信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">岗位信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位名称" prop="internshipPosition">
|
||||
<el-input v-model="form.internshipPosition" placeholder="请输入岗位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作时长" prop="workHours">
|
||||
<el-input v-model="form.workHours" placeholder="请输入工作时长" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位职责" prop="positionDuties">
|
||||
<el-input v-model="form.positionDuties" placeholder="请输入岗位职责" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习周期 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">实习周期</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习开始日期" prop="internshipStartDate">
|
||||
<el-date-picker
|
||||
v-model="form.internshipStartDate"
|
||||
type="date"
|
||||
placeholder="请选择实习开始日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习结束日期" prop="internshipEndDate">
|
||||
<el-date-picker
|
||||
v-model="form.internshipEndDate"
|
||||
type="date"
|
||||
placeholder="请选择实习结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 联系方式和其他信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">联系方式和其他信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位联系人" prop="contactPersonName">
|
||||
<el-input v-model="form.contactPersonName" placeholder="请输入单位联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人电话" prop="contactPersonPhone">
|
||||
<el-input v-model="form.contactPersonPhone" placeholder="请输入联系人电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习协议状态" prop="agreementStatus">
|
||||
<el-select v-model="form.agreementStatus" placeholder="请选择实习协议状态" style="width: 100%">
|
||||
<el-option label="已签署" value="1" />
|
||||
<el-option label="未签署" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="AddDialog" lang="ts">
|
||||
import { ref, reactive, watch, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { addInternshipInfo } from '@/api/zsjy/internship';
|
||||
import { InternshipInfoAddBO } from '@/api/zsjy/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { internship_status } = toRefs<any>(proxy?.useDict('internship_status'));
|
||||
|
||||
// Props定义
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
studentInfo?: {
|
||||
studentId: string;
|
||||
studentName: string;
|
||||
studentNo: string;
|
||||
};
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
studentInfo: () => ({
|
||||
studentId: '',
|
||||
studentName: '',
|
||||
studentNo: ''
|
||||
})
|
||||
});
|
||||
|
||||
// Emits定义
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean];
|
||||
'success': [];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const formRef = ref();
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<InternshipInfoAddBO>({
|
||||
studentId: '',
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
internshipStatus: '1',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
companyIndustry: '',
|
||||
companyScale: '',
|
||||
internshipPosition: '',
|
||||
positionDuties: '',
|
||||
internshipAllowance: 0,
|
||||
workHours: '',
|
||||
accommodationAddress: '',
|
||||
accommodationType: '',
|
||||
accommodationCost: 0,
|
||||
internshipStartDate: '',
|
||||
internshipEndDate: '',
|
||||
contactPersonName: '',
|
||||
contactPersonPhone: '',
|
||||
agreementStatus: '1',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
internshipStatus: [{ required: true, message: '请选择实习状态', trigger: 'change' }],
|
||||
companyName: [{ required: true, message: '请输入实习单位名称', trigger: 'blur' }],
|
||||
companyAddress: [{ required: true, message: '请输入单位地址', trigger: 'blur' }],
|
||||
internshipPosition: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
|
||||
contactPersonName: [{ required: true, message: '请输入单位联系人', trigger: 'blur' }],
|
||||
contactPersonPhone: [
|
||||
{ required: true, message: '请输入联系人电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
internshipStartDate: [{ required: true, message: '请选择实习开始日期', trigger: 'change' }],
|
||||
internshipEndDate: [{ required: true, message: '请选择实习结束日期', trigger: 'change' }],
|
||||
agreementStatus: [{ required: true, message: '请选择实习协议状态', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal && props.studentInfo) {
|
||||
form.studentId = props.studentInfo.studentId;
|
||||
form.studentName = props.studentInfo.studentName;
|
||||
form.studentNo = props.studentInfo.studentNo;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 监听visible变化,同步到父组件
|
||||
watch(
|
||||
() => visible.value,
|
||||
(newVal) => {
|
||||
emit('update:modelValue', newVal);
|
||||
}
|
||||
);
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
studentId: '',
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
internshipStatus: '1',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
companyIndustry: '',
|
||||
companyScale: '',
|
||||
internshipPosition: '',
|
||||
positionDuties: '',
|
||||
internshipAllowance: 0,
|
||||
workHours: '',
|
||||
accommodationAddress: '',
|
||||
accommodationType: '',
|
||||
accommodationCost: 0,
|
||||
internshipStartDate: '',
|
||||
internshipEndDate: '',
|
||||
contactPersonName: '',
|
||||
contactPersonPhone: '',
|
||||
agreementStatus: '1',
|
||||
remark: ''
|
||||
});
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
submitLoading.value = true;
|
||||
|
||||
console.log('=== 前端提交实习信息 ===');
|
||||
console.log('1. 表单验证通过');
|
||||
console.log('2. 提交的数据:', JSON.stringify(form, null, 2));
|
||||
|
||||
const response = await addInternshipInfo(form);
|
||||
console.log('3. 后端响应:', response);
|
||||
console.log('4. 响应状态码:', response.code);
|
||||
console.log('5. 响应消息:', response.msg);
|
||||
|
||||
if (response.code === 200) {
|
||||
console.log('6. 新增成功');
|
||||
ElMessage.success('新增实习信息成功');
|
||||
handleClose();
|
||||
emit('success');
|
||||
} else {
|
||||
console.error('7. 新增失败,后端返回错误:', response.msg);
|
||||
ElMessage.error(response.msg || '新增实习信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('=== 前端提交失败 ===');
|
||||
console.error('错误对象:', error);
|
||||
console.error('错误信息:', error.message);
|
||||
console.error('错误响应:', error.response);
|
||||
ElMessage.error('新增实习信息失败');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
console.log('=== 前端提交流程结束 ===');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-container {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="编辑实习信息"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
draggable
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="form-container" v-loading="loading">
|
||||
<el-row :gutter="24">
|
||||
<!-- 基本信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">基本信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生姓名" prop="studentName">
|
||||
<el-input v-model="form.studentName" placeholder="学生姓名" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习状态" prop="internshipStatus">
|
||||
<el-select v-model="form.internshipStatus" placeholder="请选择实习状态" style="width: 100%">
|
||||
<el-option v-for="dict in internship_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习津贴" prop="internshipAllowance">
|
||||
<el-input-number v-model="form.internshipAllowance" :min="0" :precision="2" controls-position="right" placeholder="实习津贴" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习单位信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">实习单位信息</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习单位名称" prop="companyName">
|
||||
<el-input v-model="form.companyName" placeholder="请输入实习单位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位联系人" prop="companyContact">
|
||||
<el-input v-model="form.companyContact" placeholder="请输入单位联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="单位地址" prop="companyAddress">
|
||||
<el-input v-model="form.companyAddress" placeholder="请输入单位地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人电话" prop="companyPhone">
|
||||
<el-input v-model="form.companyPhone" placeholder="请输入联系人电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="行业类型" prop="companyIndustry">
|
||||
<el-input v-model="form.companyIndustry" placeholder="请输入行业类型" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位规模" prop="companyScale">
|
||||
<el-select v-model="form.companyScale" placeholder="请选择单位规模" clearable style="width: 100%">
|
||||
<el-option label="20人以下" value="1" />
|
||||
<el-option label="20-99人" value="2" />
|
||||
<el-option label="100-499人" value="3" />
|
||||
<el-option label="500-999人" value="4" />
|
||||
<el-option label="1000人以上" value="5" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 岗位信息 -->
|
||||
<el-col :span="24">
|
||||
<div class="form-section-title">岗位信息</div>
|
||||
</el-col>
|
||||
<!-- 岗位名称 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位名称" prop="internshipPosition">
|
||||
<el-input v-model="form.internshipPosition" placeholder="请输入岗位名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 岗位职责 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="岗位职责" prop="positionDuties">
|
||||
<el-input v-model="form.positionDuties" placeholder="请输入岗位职责" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习津贴 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习津贴" prop="internshipAllowance">
|
||||
<el-input-number v-model="form.internshipAllowance" :min="0" :precision="2" placeholder="请输入实习津贴" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 工作时长 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作时长" prop="workHours">
|
||||
<el-input v-model="form.workHours" placeholder="请输入工作时长" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第四分区:实习时间信息 -->
|
||||
<el-divider content-position="left">实习时间信息</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<!-- 实习开始日期 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习开始日期" prop="internshipStartDate">
|
||||
<el-date-picker
|
||||
v-model="form.internshipStartDate"
|
||||
type="date"
|
||||
placeholder="请选择实习开始日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习结束日期 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习结束日期" prop="internshipEndDate">
|
||||
<el-date-picker
|
||||
v-model="form.internshipEndDate"
|
||||
type="date"
|
||||
placeholder="请选择实习结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实际结束日期 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实际结束日期" prop="actualEndDate">
|
||||
<el-date-picker
|
||||
v-model="form.actualEndDate"
|
||||
type="date"
|
||||
placeholder="请选择实际结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第五分区:联系方式和其他信息 -->
|
||||
<el-divider content-position="left">联系方式和其他信息</el-divider>
|
||||
<el-row :gutter="20">
|
||||
<!-- 单位联系人 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="单位联系人" prop="contactPersonName">
|
||||
<el-input v-model="form.contactPersonName" placeholder="请输入单位联系人" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 联系人电话 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人电话" prop="contactPersonPhone">
|
||||
<el-input v-model="form.contactPersonPhone" placeholder="请输入联系人电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 实习协议状态 -->
|
||||
<el-col :span="12">
|
||||
<el-form-item label="实习协议状态" prop="agreementStatus">
|
||||
<el-select v-model="form.agreementStatus" placeholder="请选择实习协议状态" style="width: 100%">
|
||||
<el-option label="已签署" value="1" />
|
||||
<el-option label="未签署" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
|
||||
<el-button type="danger" @click="handleDelete">删除</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="EditDialog" lang="ts">
|
||||
import { ref, reactive, watch, getCurrentInstance, ComponentInternalInstance, toRefs } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { getInternshipInfo, updateInternshipInfo, delInternshipInfo } from '@/api/zsjy/internship';
|
||||
import { InternshipInfoEditBO } from '@/api/zsjy/types';
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { internship_status } = toRefs<any>(proxy?.useDict('internship_status'));
|
||||
|
||||
// Props定义
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
studentId?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: false,
|
||||
studentId: ''
|
||||
});
|
||||
|
||||
// Emits定义
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean];
|
||||
'success': [];
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const formRef = ref();
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<InternshipInfoEditBO>({
|
||||
internshipId: '',
|
||||
studentId: '',
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
internshipStatus: '1',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
companyIndustry: '',
|
||||
companyScale: '',
|
||||
internshipPosition: '',
|
||||
positionDuties: '',
|
||||
internshipAllowance: 0,
|
||||
workHours: '',
|
||||
accommodationAddress: '',
|
||||
accommodationType: '',
|
||||
accommodationCost: 0,
|
||||
internshipStartDate: '',
|
||||
internshipEndDate: '',
|
||||
contactPersonName: '',
|
||||
contactPersonPhone: '',
|
||||
agreementStatus: '1',
|
||||
actualEndDate: '',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
internshipStatus: [{ required: true, message: '请选择实习状态', trigger: 'change' }],
|
||||
companyName: [{ required: true, message: '请输入实习单位名称', trigger: 'blur' }],
|
||||
companyAddress: [{ required: true, message: '请输入单位地址', trigger: 'blur' }],
|
||||
internshipPosition: [{ required: true, message: '请输入岗位名称', trigger: 'blur' }],
|
||||
contactPersonName: [{ required: true, message: '请输入单位联系人', trigger: 'blur' }],
|
||||
contactPersonPhone: [
|
||||
{ required: true, message: '请输入联系人电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
internshipStartDate: [{ required: true, message: '请选择实习开始日期', trigger: 'change' }],
|
||||
internshipEndDate: [{ required: true, message: '请选择实习结束日期', trigger: 'change' }],
|
||||
agreementStatus: [{ required: true, message: '请选择实习协议状态', trigger: 'change' }]
|
||||
});
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal && props.studentId) {
|
||||
getInternshipDetail();
|
||||
}
|
||||
});
|
||||
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:modelValue', newVal);
|
||||
});
|
||||
|
||||
// 获取实习信息详情
|
||||
const getInternshipDetail = async () => {
|
||||
if (!props.studentId) {
|
||||
ElMessage.error('参数错误');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getInternshipInfo(props.studentId);
|
||||
const data = res.data;
|
||||
|
||||
// 填充表单数据
|
||||
Object.assign(form, {
|
||||
internshipId: data.internshipId,
|
||||
studentId: data.studentId,
|
||||
studentName: data.studentName,
|
||||
studentNo: data.studentNo,
|
||||
internshipStatus: data.internshipStatus,
|
||||
companyName: data.companyName,
|
||||
companyAddress: data.companyAddress,
|
||||
companyIndustry: data.companyIndustry,
|
||||
companyScale: data.companyScale,
|
||||
internshipPosition: data.internshipPosition || data.positionName,
|
||||
positionDuties: data.positionDuties,
|
||||
internshipAllowance: data.internshipAllowance,
|
||||
workHours: data.workHours,
|
||||
accommodationAddress: data.accommodationAddress,
|
||||
accommodationType: data.accommodationType,
|
||||
accommodationCost: data.accommodationCost,
|
||||
internshipStartDate: data.internshipStartDate || data.startDate,
|
||||
internshipEndDate: data.internshipEndDate || data.endDate,
|
||||
contactPersonName: data.contactPersonName || data.companyContact,
|
||||
contactPersonPhone: data.contactPersonPhone || data.companyPhone,
|
||||
agreementStatus: data.agreementStatus || '1',
|
||||
actualEndDate: data.actualEndDate,
|
||||
remark: data.remark
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取实习信息失败:', error);
|
||||
ElMessage.error('获取实习信息失败');
|
||||
handleClose();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
internshipId: '',
|
||||
studentId: '',
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
internshipStatus: '1',
|
||||
companyName: '',
|
||||
companyAddress: '',
|
||||
companyIndustry: '',
|
||||
companyScale: '',
|
||||
companyContact: '',
|
||||
companyPhone: '',
|
||||
positionName: '',
|
||||
positionDuties: '',
|
||||
internshipAllowance: 0,
|
||||
workHours: '',
|
||||
accommodationAddress: '',
|
||||
accommodationType: '',
|
||||
accommodationCost: 0,
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
actualEndDate: '',
|
||||
remark: ''
|
||||
});
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
submitLoading.value = true;
|
||||
|
||||
await updateInternshipInfo(form);
|
||||
ElMessage.success('修改实习信息成功');
|
||||
handleClose();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.error('修改实习信息失败:', error);
|
||||
ElMessage.error('修改实习信息失败');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除实习信息
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认删除该实习信息?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
});
|
||||
|
||||
await delInternshipInfo(form.internshipId);
|
||||
ElMessage.success('删除实习信息成功');
|
||||
handleClose();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
if (error === 'cancel') return;
|
||||
console.error('删除实习信息失败:', error);
|
||||
ElMessage.error('删除实习信息失败');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-container {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.form-section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<div class="statistics-card internshiping">
|
||||
<div class="statistics-content">
|
||||
<div class="statistics-number">{{ statisticsData.internshipCount || 0 }}</div>
|
||||
<div class="statistics-label">实习中</div>
|
||||
<div class="statistics-label">已实习</div>
|
||||
</div>
|
||||
<div class="statistics-icon">
|
||||
<el-icon><Loading /></el-icon>
|
||||
@@ -103,9 +103,9 @@
|
||||
<el-col :span="6">
|
||||
<div class="statistics-card completed">
|
||||
<div class="statistics-content">
|
||||
<div class="statistics-number">{{ statisticsData.internshipCount || 0 }}</div>
|
||||
<div class="statistics-label">已完成</div>
|
||||
<div class="statistics-rate">实习率:{{ statisticsData.internshipRate || '0.00%' }}</div>
|
||||
<div class="statistics-number">{{ statisticsData.internshipRate || '0.00%' }}</div>
|
||||
<div class="statistics-label">实习率</div>
|
||||
<div class="statistics-description">已实习学生占比</div>
|
||||
</div>
|
||||
<div class="statistics-icon">
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
@@ -194,17 +194,17 @@
|
||||
|
||||
|
||||
<!-- 学生实习详情对话框 -->
|
||||
<el-dialog title="学生实习详情" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-dialog title="学生实习详情" v-model="dialog.visible" width="900px" append-to-body>
|
||||
<div v-if="studentDetail" class="student-detail">
|
||||
<el-descriptions title="基本信息" :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ studentDetail.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ studentDetail.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ studentDetail.gender }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ studentDetail.mobile }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ studentDetail.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学院">{{ studentDetail.collegeName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ studentDetail.majorName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ studentDetail.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班主任">{{ studentDetail.classMasterName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班主任">{{ studentDetail.classTeacherName }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions title="实习信息" :column="2" border class="mt-4">
|
||||
@@ -212,17 +212,29 @@
|
||||
<span v-if="studentDetail.internshipStatus === 'null' || !studentDetail.internshipStatus" class="status-tag not-started">未实习</span>
|
||||
<dict-tag v-else :options="internship_status" :value="studentDetail.internshipStatus" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实习公司">{{ studentDetail.internshipCompany || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习公司">{{ studentDetail.companyName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习岗位">{{ studentDetail.internshipPosition || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习地点">{{ studentDetail.internshipLocation || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习地点">{{ studentDetail.companyAddress || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习开始时间">
|
||||
<span v-if="studentDetail.internshipStartTime">{{ parseTime(studentDetail.internshipStartTime, '{y}-{m}-{d}') }}</span>
|
||||
<span v-if="studentDetail.internshipStartDate">{{ parseTime(studentDetail.internshipStartDate, '{y}-{m}-{d}') }}</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实习结束时间">
|
||||
<span v-if="studentDetail.internshipEndTime">{{ parseTime(studentDetail.internshipEndTime, '{y}-{m}-{d}') }}</span>
|
||||
<span v-if="studentDetail.internshipEndDate">{{ parseTime(studentDetail.internshipEndDate, '{y}-{m}-{d}') }}</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位联系人">{{ studentDetail.contactPersonName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人电话">{{ studentDetail.contactPersonPhone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="实习津贴">
|
||||
<span v-if="studentDetail.internshipAllowance && studentDetail.internshipAllowance > 0">{{ studentDetail.internshipAllowance }}元/月</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="协议状态">
|
||||
<el-tag v-if="studentDetail.agreementStatus === '1'" type="success">已签署</el-tag>
|
||||
<el-tag v-else-if="studentDetail.agreementStatus === '2'" type="warning">未签署</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ studentDetail.remark || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
<template #footer>
|
||||
@@ -498,6 +510,12 @@ const cancel = () => {
|
||||
opacity: 0.8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.statistics-description {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.statistics-icon {
|
||||
|
||||
Reference in new issue
Block a user