Browse Source

Merge branch 'master' of http://47.99.164.66:3000/hzdyjs-xg/all-project

zlwu 1 year ago
parent
commit
1db2f91af1

+ 32 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/InternshipInfoController.java

@@ -5,6 +5,7 @@ import org.dromara.common.core.domain.R;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.common.web.core.BaseController;
+import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.zsjy.domain.vo.InternshipInfoVO;
 import org.dromara.zsjy.domain.vo.InternshipInfoVO;
 import org.dromara.zsjy.domain.bo.InternshipInfoQueryBO;
 import org.dromara.zsjy.domain.bo.InternshipInfoQueryBO;
 import org.dromara.zsjy.domain.bo.InternshipInfoAddBO;
 import org.dromara.zsjy.domain.bo.InternshipInfoAddBO;
@@ -101,6 +102,37 @@ public class InternshipInfoController extends BaseController {
         return R.ok(internshipInfoService.getInternshipStatistics(queryBO));
         return R.ok(internshipInfoService.getInternshipStatistics(queryBO));
     }
     }
 
 
+    /**
+     * 学生获取自己的实习信息(移动端专用)
+     */
+    @Operation(summary = "学生获取实习信息", description = "学生查看自己的实习信息(移动端)")
+    @GetMapping("/student/info")
+    public R<InternshipInfoVO> getStudentInfo() {
+        log.info("=== 学生获取实习信息 ===");
+        log.info("当前用户ID: {}", LoginHelper.getUserId());
+
+        try {
+            Long currentUserId = LoginHelper.getUserId();
+            if (currentUserId == null) {
+                log.error("用户ID为空,无法获取实习信息");
+                return R.fail("用户未登录");
+            }
+
+            InternshipInfoVO result = internshipInfoService.queryById(currentUserId);
+
+            if (result != null) {
+                log.info("成功获取学生实习信息,学生ID: {}", currentUserId);
+                return R.ok(result);
+            } else {
+                log.info("学生暂无实习信息,学生ID: {}", currentUserId);
+                return R.fail("暂无实习信息");
+            }
+        } catch (Exception e) {
+            log.error("获取学生实习信息异常,用户ID: {},错误信息: {}", LoginHelper.getUserId(), e.getMessage(), e);
+            return R.fail("获取实习信息失败:" + e.getMessage());
+        }
+    }
+
     /**
     /**
      * 新增实习信息
      * 新增实习信息
      */
      */

+ 2 - 1
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/controller/ZsjyInternshipDailyController.java

@@ -6,6 +6,7 @@ import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
 import org.dromara.common.web.core.BaseController;
+import org.dromara.zsjy.domain.bo.CheckInGroup;
 import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyAddBo;
 import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyAddBo;
 import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
 import org.dromara.zsjy.domain.bo.ZsjyInternshipDailyQueryBo;
 import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
 import org.dromara.zsjy.domain.vo.ZsjyInternshipDailyVo;
@@ -58,7 +59,7 @@ public class ZsjyInternshipDailyController extends BaseController {
     @Operation(summary = "学生签到打卡", description = "学生进行实习日常签到打卡")
     @Operation(summary = "学生签到打卡", description = "学生进行实习日常签到打卡")
     @SaCheckPermission("zsjy:internshipdaily:checkin")
     @SaCheckPermission("zsjy:internshipdaily:checkin")
     @PostMapping("/checkin")
     @PostMapping("/checkin")
-    public R<Void> checkIn(@Validated(AddGroup.class) @RequestBody ZsjyInternshipDailyAddBo addBo) {
+    public R<Void> checkIn(@Validated(CheckInGroup.class) @RequestBody ZsjyInternshipDailyAddBo addBo) {
         log.info("=== Controller层调试 ===");
         log.info("=== Controller层调试 ===");
         log.info("接口被调用: /zsjy/internship/daily/checkin");
         log.info("接口被调用: /zsjy/internship/daily/checkin");
         log.info("签到参数: {}", addBo);
         log.info("签到参数: {}", addBo);

+ 106 - 15
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/ZsjyInternshipDaily.java

@@ -1,5 +1,7 @@
 package org.dromara.zsjy.domain;
 package org.dromara.zsjy.domain;
 
 
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.Data;
@@ -9,6 +11,8 @@ import org.dromara.common.mybatis.core.domain.BaseEntity;
 import java.io.Serial;
 import java.io.Serial;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
 import java.sql.Time;
 import java.sql.Time;
+import java.time.LocalDate;
+import java.time.LocalTime;
 import java.util.Date;
 import java.util.Date;
 
 
 /**
 /**
@@ -26,74 +30,161 @@ public class ZsjyInternshipDaily extends BaseEntity {
     private static final long serialVersionUID = 1L;
     private static final long serialVersionUID = 1L;
 
 
     /**
     /**
-     * 日常记录ID
+     * 日常记录ID (对应数据库daily_id字段)
      */
      */
-    @TableId(value = "daily_id")
-    private Long dailyId;
+    @TableId(value = "daily_id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 租户ID
+     */
+    @TableField("tenant_id")
+    private String tenantId;
 
 
     /**
     /**
      * 实习ID
      * 实习ID
      */
      */
+    @TableField("internship_id")
     private Long internshipId;
     private Long internshipId;
 
 
     /**
     /**
      * 学生ID
      * 学生ID
      */
      */
+    @TableField("student_id")
     private Long studentId;
     private Long studentId;
 
 
     /**
     /**
-     * 打卡日期
+     * 签到日期
      */
      */
-    private Date checkDate;
+    @TableField("check_date")
+    private LocalDate checkDate;
 
 
     /**
     /**
      * 签到时间
      * 签到时间
      */
      */
-    private Time checkInTime;
+    @TableField("check_in_time")
+    private LocalTime checkInTime;
 
 
     /**
     /**
-     * 签到位置
+     * 签到地点 (对应数据库check_in_location字段)
      */
      */
-    private String checkInLocation;
+    @TableField("check_in_location")
+    private String checkOutLocation;
 
 
     /**
     /**
-     * 纬度
+     * 签到经度 (对应数据库longitude字段)
      */
      */
-    private BigDecimal latitude;
+    @TableField("longitude")
+    private BigDecimal checkOutLongitude;
 
 
     /**
     /**
-     * 经度
+     * 签到纬度 (对应数据库latitude字段)
      */
      */
-    private BigDecimal longitude;
+    @TableField("latitude")
+    private BigDecimal checkOutLatitude;
 
 
     /**
     /**
-     * 日总结
+     * 工作内容/日总结
      */
      */
-    private String dailySummary;
+    @TableField("daily_summary")
+    private String workContent;
 
 
     /**
     /**
      * 图片附件(JSON格式)
      * 图片附件(JSON格式)
      */
      */
+    @TableField("images")
     private String images;
     private String images;
 
 
     /**
     /**
      * 是否异常(0正常 1异常)
      * 是否异常(0正常 1异常)
      */
      */
+    @TableField("is_exception")
     private String isException;
     private String isException;
 
 
     /**
     /**
      * 异常原因
      * 异常原因
      */
      */
+    @TableField("exception_reason")
     private String exceptionReason;
     private String exceptionReason;
 
 
     /**
     /**
      * 异常处理状态(0待处理 1已处理)
      * 异常处理状态(0待处理 1已处理)
      */
      */
+    @TableField("exception_status")
     private String exceptionStatus;
     private String exceptionStatus;
 
 
     /**
     /**
-     * 状态(0正常 1停用)
+     * 考勤状态(0=未签到 1=正常 2=迟到 3=早退 4=旷工)
      */
      */
+    @TableField("status")
     private String status;
     private String status;
 
 
+    /**
+     * 备注
+     */
+    @TableField("remark")
+    private String remark;
+
+    // ==================== 扩展字段(数据库中不存在,通过关联查询获取) ====================
+
+    /**
+     * 学生姓名 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String studentName;
+
+    /**
+     * 学号 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String studentNumber;
+
+    /**
+     * 班级名称 (通过student_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String className;
+
+    /**
+     * 实习单位名称 (通过internship_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String companyName;
+
+    /**
+     * 实习岗位 (通过internship_id关联查询获取)
+     */
+    @TableField(exist = false)
+    private String position;
+
+    /**
+     * 签到地点 (业务扩展字段,当前只有签退地点)
+     */
+    @TableField(exist = false)
+    private String checkInLocation;
+
+    /**
+     * 签到经度 (业务扩展字段,当前只有签退经度)
+     */
+    @TableField(exist = false)
+    private BigDecimal checkInLongitude;
+
+    /**
+     * 签到纬度 (业务扩展字段,当前只有签退纬度)
+     */
+    @TableField(exist = false)
+    private BigDecimal checkInLatitude;
+
+    /**
+     * 签退时间 (业务扩展字段,当前只有签到时间)
+     */
+    @TableField(exist = false)
+    private LocalTime checkOutTime;
+
+    /**
+     * 工作时长(小时) (业务计算字段)
+     */
+    @TableField(exist = false)
+    private BigDecimal workDuration;
+
 }
 }

+ 11 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/CheckInGroup.java

@@ -0,0 +1,11 @@
+package org.dromara.zsjy.domain.bo;
+
+/**
+ * 签到验证组
+ * 用于签到时的数据验证,不包含签退相关字段的验证
+ *
+ * @author system
+ * @date 2025-01-03
+ */
+public interface CheckInGroup {
+}

+ 150 - 28
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/domain/bo/ZsjyInternshipDailyAddBo.java

@@ -10,7 +10,8 @@ import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 import jakarta.validation.constraints.Size;
 import java.io.Serial;
 import java.io.Serial;
 import java.math.BigDecimal;
 import java.math.BigDecimal;
-import java.sql.Time;
+import java.time.LocalDate;
+import java.time.LocalTime;
 import java.util.Date;
 import java.util.Date;
 
 
 /**
 /**
@@ -31,53 +32,87 @@ public class ZsjyInternshipDailyAddBo extends BaseEntity {
      * 实习ID
      * 实习ID
      */
      */
     @Schema(description = "实习ID", requiredMode = Schema.RequiredMode.REQUIRED)
     @Schema(description = "实习ID", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "实习ID不能为空", groups = {AddGroup.class})
+    @NotNull(message = "实习ID不能为空", groups = {AddGroup.class, CheckInGroup.class})
     private Long internshipId;
     private Long internshipId;
 
 
     /**
     /**
      * 学生ID
      * 学生ID
      */
      */
     @Schema(description = "学生ID", requiredMode = Schema.RequiredMode.REQUIRED)
     @Schema(description = "学生ID", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "学生ID不能为空", groups = {AddGroup.class})
+    @NotNull(message = "学生ID不能为空", groups = {AddGroup.class, CheckInGroup.class})
     private Long studentId;
     private Long studentId;
 
 
     /**
     /**
-     * 打卡日期
+     * 签到日期
      */
      */
-    @Schema(description = "打卡日期", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotNull(message = "打卡日期不能为空", groups = {AddGroup.class})
-    private Date checkDate;
+    @Schema(description = "签到日期", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签到日期不能为空", groups = {AddGroup.class, CheckInGroup.class})
+    private LocalDate checkDate;
 
 
     /**
     /**
      * 签到时间
      * 签到时间
      */
      */
-    @Schema(description = "签到时间")
-    private Time checkInTime;
+    @Schema(description = "签到时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签到时间不能为空", groups = {AddGroup.class, CheckInGroup.class})
+    private LocalTime checkInTime;
 
 
     /**
     /**
-     * 签到位置
+     * 签退时间
      */
      */
-    @Schema(description = "签到位置")
-    @Size(max = 200, message = "签到位置长度不能超过200个字符")
+    @Schema(description = "签退时间")
+    private LocalTime checkOutTime;
+
+    /**
+     * 签到地点 (前端传递字段,映射到数据库check_in_location字段)
+     */
+    @Schema(description = "签到地点", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签到地点不能为空", groups = {CheckInGroup.class})
+    @Size(max = 500, message = "签到地点长度不能超过500个字符")
     private String checkInLocation;
     private String checkInLocation;
 
 
     /**
     /**
-     * 纬度
+     * 签到纬度 (前端传递字段,映射到数据库latitude字段)
      */
      */
-    @Schema(description = "纬度")
+    @Schema(description = "签到纬度", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签到纬度不能为空", groups = {CheckInGroup.class})
     private BigDecimal latitude;
     private BigDecimal latitude;
 
 
     /**
     /**
-     * 经度
+     * 签到经度 (前端传递字段,映射到数据库longitude字段)
      */
      */
-    @Schema(description = "经度")
+    @Schema(description = "签到经度", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签到经度不能为空", groups = {CheckInGroup.class})
     private BigDecimal longitude;
     private BigDecimal longitude;
 
 
     /**
     /**
-     * 日总结
+     * 签退地点 (对应数据库check_in_location字段,实际存储签退地点)
      */
      */
-    @Schema(description = "日总结")
-    private String dailySummary;
+    @Schema(description = "签退地点", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签退地点不能为空", groups = {AddGroup.class})
+    @Size(max = 500, message = "签退地点长度不能超过500个字符")
+    private String checkOutLocation;
+
+    /**
+     * 签退纬度 (对应数据库latitude字段)
+     */
+    @Schema(description = "签退纬度", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签退纬度不能为空", groups = {AddGroup.class})
+    private BigDecimal checkOutLatitude;
+
+    /**
+     * 签退经度 (对应数据库longitude字段)
+     */
+    @Schema(description = "签退经度", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "签退经度不能为空", groups = {AddGroup.class})
+    private BigDecimal checkOutLongitude;
+
+    /**
+     * 工作内容 (对应数据库daily_summary字段)
+     */
+    @Schema(description = "工作内容", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "工作内容不能为空", groups = {AddGroup.class})
+    @Size(max = 1000, message = "工作内容长度不能超过1000个字符")
+    private String workContent;
 
 
     /**
     /**
      * 图片附件(JSON格式)
      * 图片附件(JSON格式)
@@ -95,25 +130,112 @@ public class ZsjyInternshipDailyAddBo extends BaseEntity {
      * 异常原因
      * 异常原因
      */
      */
     @Schema(description = "异常原因")
     @Schema(description = "异常原因")
-    @Size(max = 500, message = "异常原因长度不能超过500个字符")
     private String exceptionReason;
     private String exceptionReason;
 
 
     /**
     /**
-     * 异常处理状态(0待处理 1已处理)
+     * 考勤状态(0=未签到 1=正常 2=迟到 3=早退 4=旷工)
      */
      */
-    @Schema(description = "异常处理状态")
-    private String exceptionStatus;
-
-    /**
-     * 状态(0正常 1停用)
-     */
-    @Schema(description = "状态")
+    @Schema(description = "考勤状态")
     private String status;
     private String status;
 
 
     /**
     /**
      * 备注
      * 备注
      */
      */
     @Schema(description = "备注")
     @Schema(description = "备注")
+    @Size(max = 500, message = "备注长度不能超过500个字符")
     private String remark;
     private String remark;
 
 
+    // ==================== 扩展字段(用于显示,不存储到数据库) ====================
+
+    /**
+     * 学生姓名(扩展字段,用于显示)
+     */
+    @Schema(description = "学生姓名", hidden = true)
+    private String studentName;
+
+    /**
+     * 学生学号(扩展字段,用于显示)
+     */
+    @Schema(description = "学生学号", hidden = true)
+    private String studentNumber;
+
+    /**
+     * 班级名称(扩展字段,用于显示)
+     */
+    @Schema(description = "班级名称", hidden = true)
+    private String className;
+
+    /**
+     * 企业名称(扩展字段,用于显示)
+     */
+    @Schema(description = "企业名称", hidden = true)
+    private String companyName;
+
+    /**
+     * 实习岗位(扩展字段,用于显示)
+     */
+    @Schema(description = "实习岗位", hidden = true)
+    private String position;
+
+    // ==================== 兼容移动端字段(前端可能使用的别名) ====================
+
+    /**
+     * 日总结(兼容移动端,实际映射为workContent)
+     */
+    @Schema(description = "日总结", hidden = true)
+    private String dailySummary;
+
+    // ==================== 数据转换方法(处理前端字段映射) ====================
+
+    /**
+     * 处理移动端兼容字段映射
+     * 将移动端使用的字段名映射到实际的字段
+     */
+    public void processCompatibleFields() {
+        // 处理经纬度映射(签到时映射到签退字段存储)
+        if (this.latitude != null && this.checkOutLatitude == null) {
+            this.checkOutLatitude = this.latitude;
+        }
+        if (this.longitude != null && this.checkOutLongitude == null) {
+            this.checkOutLongitude = this.longitude;
+        }
+
+        // 处理工作内容映射
+        if (this.dailySummary != null && this.workContent == null) {
+            this.workContent = this.dailySummary;
+        }
+
+        // 处理签到位置映射(签到时映射到签退字段存储)
+        if (this.checkInLocation != null && this.checkOutLocation == null) {
+            this.checkOutLocation = this.checkInLocation;
+        }
+    }
+
+    /**
+     * 获取实际的签退地点(优先使用checkOutLocation,否则使用checkInLocation)
+     */
+    public String getActualCheckOutLocation() {
+        return this.checkOutLocation != null ? this.checkOutLocation : this.checkInLocation;
+    }
+
+    /**
+     * 获取实际的签退经度(优先使用checkOutLongitude,否则使用longitude)
+     */
+    public BigDecimal getActualCheckOutLongitude() {
+        return this.checkOutLongitude != null ? this.checkOutLongitude : this.longitude;
+    }
+
+    /**
+     * 获取实际的签退纬度(优先使用checkOutLatitude,否则使用latitude)
+     */
+    public BigDecimal getActualCheckOutLatitude() {
+        return this.checkOutLatitude != null ? this.checkOutLatitude : this.latitude;
+    }
+
+    /**
+     * 获取实际的工作内容(优先使用workContent,否则使用dailySummary)
+     */
+    public String getActualWorkContent() {
+        return this.workContent != null ? this.workContent : this.dailySummary;
+    }
 }
 }

+ 58 - 16
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/service/impl/ZsjyInternshipDailyServiceImpl.java

@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.domain.R;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.satoken.utils.LoginHelper;
@@ -22,8 +23,11 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
 import java.sql.Time;
 import java.sql.Time;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.YearMonth;
 import java.time.YearMonth;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.*;
 
 
 /**
 /**
@@ -64,32 +68,70 @@ public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyServi
     @Transactional(rollbackFor = Exception.class)
     @Transactional(rollbackFor = Exception.class)
     public R<Void> checkIn(ZsjyInternshipDailyAddBo addBo) {
     public R<Void> checkIn(ZsjyInternshipDailyAddBo addBo) {
         try {
         try {
-            // 1. 验证学生当日是否已签到
-            boolean exists = baseMapper.existsByStudentAndDate(addBo.getStudentId(), addBo.getCheckDate());
+            // 1. 验证学生当日是否已签到 - 将LocalDate转换为Date
+            Date checkDateAsDate = Date.from(addBo.getCheckDate().atStartOfDay(ZoneId.systemDefault()).toInstant());
+            boolean exists = baseMapper.existsByStudentAndDate(addBo.getStudentId(), checkDateAsDate);
             if (exists) {
             if (exists) {
                 return R.fail("今日已签到,请勿重复签到");
                 return R.fail("今日已签到,请勿重复签到");
             }
             }
 
 
-            // 2. 构建实体对象
-            ZsjyInternshipDaily entity = MapstructUtils.convert(addBo, ZsjyInternshipDaily.class);
+            // 2. 处理兼容字段映射(移动端可能使用的别名映射到正确的字段)
+            if (addBo.getLatitude() != null && addBo.getCheckOutLatitude() == null) {
+                addBo.setCheckOutLatitude(addBo.getLatitude());
+            }
+            if (addBo.getLongitude() != null && addBo.getCheckOutLongitude() == null) {
+                addBo.setCheckOutLongitude(addBo.getLongitude());
+            }
+            if (addBo.getDailySummary() != null && addBo.getWorkContent() == null) {
+                addBo.setWorkContent(addBo.getDailySummary());
+            }
+            if (addBo.getCheckInLocation() != null && addBo.getCheckOutLocation() == null) {
+                addBo.setCheckOutLocation(addBo.getCheckInLocation());
+            }
 
 
-            // 3. 设置签到时间为当前时间
+            // 3. 构建实体对象 - 手动创建避免MapStruct转换器错误
+            ZsjyInternshipDaily entity = new ZsjyInternshipDaily();
+
+            // 核心必填字段(对应数据库字段)
+            entity.setInternshipId(addBo.getInternshipId());
+            entity.setStudentId(addBo.getStudentId());
+            entity.setCheckDate(addBo.getCheckDate());
+            entity.setCheckInTime(addBo.getCheckInTime());
+            entity.setCheckOutLocation(addBo.getCheckOutLocation()); // 对应数据库check_in_location字段
+            entity.setCheckOutLatitude(addBo.getCheckOutLatitude()); // 对应数据库latitude字段
+            entity.setCheckOutLongitude(addBo.getCheckOutLongitude()); // 对应数据库longitude字段
+            entity.setWorkContent(addBo.getWorkContent()); // 对应数据库daily_summary字段
+            entity.setImages(addBo.getImages());
+            entity.setIsException(addBo.getIsException());
+            entity.setExceptionReason(addBo.getExceptionReason());
+            entity.setStatus(addBo.getStatus());
+            entity.setRemark(addBo.getRemark());
+
+            // 扩展字段(不存储到数据库,仅用于显示)
+            entity.setStudentName(addBo.getStudentName());
+            entity.setStudentNumber(addBo.getStudentNumber());
+            entity.setClassName(addBo.getClassName());
+            entity.setCompanyName(addBo.getCompanyName());
+            entity.setPosition(addBo.getPosition());
+
+            // 继承BaseEntity的字段会自动填充(create_time, create_by等)
+
+            // 4. 设置签到时间为当前时间(如果没有传入)
             if (ObjectUtil.isNull(entity.getCheckInTime())) {
             if (ObjectUtil.isNull(entity.getCheckInTime())) {
-                entity.setCheckInTime(Time.valueOf(DateUtil.format(new Date(), "HH:mm:ss")));
+                entity.setCheckInTime(LocalTime.now());
             }
             }
 
 
-            // 4. 设置默认值
-            if (ObjectUtil.isNull(entity.getIsException())) {
-                entity.setIsException("0"); // 默认正常
-            }
-            if (ObjectUtil.isNull(entity.getExceptionStatus())) {
-                entity.setExceptionStatus("0"); // 默认待处理
-            }
+            // 5. 设置默认状态
             if (ObjectUtil.isNull(entity.getStatus())) {
             if (ObjectUtil.isNull(entity.getStatus())) {
-                entity.setStatus("0"); // 默认正常
+                entity.setStatus("1"); // 正常状态
+            }
+
+            // 6. 设置默认异常状态
+            if (ObjectUtil.isNull(entity.getIsException())) {
+                entity.setIsException("0"); // 正常
             }
             }
 
 
-            // 5. 保存签到记录
+            // 7. 保存签到记录
             boolean result = baseMapper.insert(entity) > 0;
             boolean result = baseMapper.insert(entity) > 0;
             if (!result) {
             if (!result) {
                 return R.fail("签到失败,请稍后重试");
                 return R.fail("签到失败,请稍后重试");
@@ -100,7 +142,7 @@ public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyServi
 
 
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("学生签到失败: {}", e.getMessage(), e);
             log.error("学生签到失败: {}", e.getMessage(), e);
-            throw new ServiceException("签到失败: " + e.getMessage());
+            return R.fail("签到失败: " + e.getMessage());
         }
         }
     }
     }
 
 

+ 83 - 0
mobile/src/api/internship.js

@@ -0,0 +1,83 @@
+/**
+ * 实习管理API接口
+ * 提供学生实习签到、获取实习信息等功能
+ */
+
+import { request } from '@/common/request.js';
+
+/**
+ * 获取学生实习信息(班主任专用)
+ * @param {number} studentId 学生ID
+ * @returns Promise
+ */
+export const getInternshipInfo = (studentId) => {
+    return request({
+        url: `/zsjy/internship/${studentId}`,
+        method: 'GET'
+    });
+};
+
+/**
+ * 学生获取自己的实习信息(移动端专用)
+ * @returns Promise
+ */
+export const getStudentInternshipInfo = () => {
+    return request({
+        url: '/zsjy/internship/student/info',
+        method: 'GET'
+    });
+};
+
+/**
+ * 学生签到打卡
+ * @param {Object} data 签到数据
+ * @returns Promise
+ */
+export const checkIn = (data) => {
+    return request({
+        url: '/zsjy/internship/daily/checkin',
+        method: 'POST',
+        data
+    });
+};
+
+/**
+ * 获取学生考勤日历数据
+ * @param {number} studentId 学生ID
+ * @param {number} year 年份
+ * @param {number} month 月份
+ * @returns Promise
+ */
+export const getCalendarData = (studentId, year, month) => {
+    return request({
+        url: '/zsjy/internship/daily/calendar',
+        method: 'GET',
+        params: { studentId, year, month }
+    });
+};
+
+/**
+ * 获取考勤统计数据
+ * @param {Object} params 查询参数
+ * @returns Promise
+ */
+export const getStatistics = (params) => {
+    return request({
+        url: '/zsjy/internship/daily/statistics',
+        method: 'GET',
+        params
+    });
+};
+
+/**
+ * 查询实习日常记录列表
+ * @param {Object} params 查询参数
+ * @returns Promise
+ */
+export const getDailyList = (params) => {
+    return request({
+        url: '/zsjy/internship/daily/list',
+        method: 'GET',
+        params
+    });
+}; 

+ 8 - 0
mobile/src/pages.json

@@ -119,6 +119,14 @@
 				"navigationBarTitleText": "微信JS-SDK测试",
 				"navigationBarTitleText": "微信JS-SDK测试",
 				"navigationStyle": "custom"
 				"navigationStyle": "custom"
 			}
 			}
+		},
+		{
+			"path": "pages/internship/index",
+			"style": {
+				"navigationBarTitleText": "实习打卡",
+				"navigationBarBackgroundColor": "#0051d9",
+				"navigationBarTextStyle": "white"
+			}
 		}
 		}
 	],
 	],
 	"globalStyle": {
 	"globalStyle": {

+ 23 - 1
mobile/src/pages/index/index.vue

@@ -154,6 +154,20 @@
                             />
                             />
                         </template>
                         </template>
                     </ServiceItem>
                     </ServiceItem>
+
+                    <ServiceItem
+                        v-if="hasPermi(['student:internship:list'])"
+                        title="实习打卡"
+                        @click="gotoInternshipManage"
+                    >
+                        <template #icon>
+                            <image
+                                src="/static/00116.png"
+                                mode="aspectFit"
+                                style="width: 24px; height: 24px"
+                            />
+                        </template>
+                    </ServiceItem>
                 </GridLayout>
                 </GridLayout>
             </Card>
             </Card>
 
 
@@ -575,7 +589,8 @@ export default {
                 this.hasPermi(['student:psychology:list']) ||
                 this.hasPermi(['student:psychology:list']) ||
                 this.hasPermi(['student:club:list']) ||
                 this.hasPermi(['student:club:list']) ||
                 this.hasPermi(['student:counsel:list']) ||
                 this.hasPermi(['student:counsel:list']) ||
-                this.hasPermi(['student:info:edit'])
+                this.hasPermi(['student:info:edit']) ||
+                this.hasPermi(['student:internship:list'])
             );
             );
         },
         },
 
 
@@ -776,6 +791,13 @@ export default {
             });
             });
         },
         },
 
 
+        // 跳转到实习管理页面
+        gotoInternshipManage() {
+            uni.navigateTo({
+                url: '/pages/internship/index',
+            });
+        },
+
         // 图片加载成功处理
         // 图片加载成功处理
         handleImageLoad(event) {
         handleImageLoad(event) {
             console.log('✅ 轮播图片加载成功:', event.detail);
             console.log('✅ 轮播图片加载成功:', event.detail);

+ 1248 - 0
mobile/src/pages/internship/index.vue

@@ -0,0 +1,1248 @@
+<template>
+    <view class="internship-page">
+        <!-- 顶部用户信息卡片 -->
+        <view class="user-card">
+            <view class="user-info">
+                <view class="avatar-section">
+                    <u-avatar 
+                        :src="userInfo.avatar || '/static/default-avatar.png'" 
+                        size="60"
+                        shape="circle"
+                    ></u-avatar>
+                </view>
+                <view class="info-section">
+                    <view class="user-name">{{ userInfo.nickName || userInfo.userName || '学生' }}</view>
+                    <view class="user-detail">学号:{{ userInfo.userName || '暂无' }}</view>
+                    <view class="user-detail">部门:{{ userInfo.deptName || '暂无' }}</view>
+                </view>
+            </view>
+        </view>
+
+        <!-- 实习信息卡片 -->
+        <view class="internship-card" v-if="internshipInfo">
+            <view class="card-header">
+                <u-icon name="bookmark" size="18" color="#409eff"></u-icon>
+                <text class="card-title">实习信息</text>
+            </view>
+            <view class="internship-info">
+                <view class="info-row">
+                    <text class="label">实习单位:</text>
+                    <text class="value">{{ internshipInfo.companyName || '暂无' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">实习岗位:</text>
+                    <text class="value">{{ internshipInfo.internshipPosition || '暂无' }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">实习期间:</text>
+                    <text class="value">{{ formatDateRange(internshipInfo.internshipStartDate, internshipInfo.internshipEndDate) }}</text>
+                </view>
+                <view class="info-row">
+                    <text class="label">状态:</text>
+                    <u-tag :text="getStatusText(internshipInfo.internshipStatus)" :type="getStatusType(internshipInfo.internshipStatus)" size="mini"></u-tag>
+                </view>
+            </view>
+        </view>
+
+        <!-- 今日签到卡片 -->
+        <view class="checkin-card">
+            <view class="card-header">
+                <u-icon name="clock" size="18" color="#67c23a"></u-icon>
+                <text class="card-title">今日签到</text>
+                <text class="date-text">{{ currentDate }}</text>
+            </view>
+            
+            <!-- 签到状态显示 -->
+            <view class="checkin-status" v-if="todayRecord">
+                <view class="status-item">
+                    <u-icon name="checkmark-circle" size="20" color="#67c23a"></u-icon>
+                    <text class="status-text">已签到</text>
+                </view>
+                <view class="time-info">
+                    <text class="time-label">签到时间:</text>
+                    <text class="time-value">{{ todayRecord.checkInTime }}</text>
+                </view>
+                <view class="location-info" v-if="todayRecord.checkInLocation">
+                    <text class="location-label">签到位置:</text>
+                    <text class="location-value">{{ todayRecord.checkInLocation }}</text>
+                </view>
+            </view>
+
+            <!-- 签到按钮 -->
+            <view class="checkin-actions" v-else>
+                <u-button 
+                    type="primary" 
+                    size="large"
+                    :loading="checkinLoading"
+                    :disabled="checkinLoading || !canCheckin"
+                    @click="handleCheckin"
+                    :custom-style="checkinButtonStyle"
+                >
+                    <u-icon name="map" size="20" color="#fff" style="margin-right: 8px;"></u-icon>
+                    {{ checkinLoading ? '签到中...' : '立即签到' }}
+                </u-button>
+                
+                <view class="checkin-tips" v-if="!canCheckin">
+                    <u-icon name="info-circle" size="14" color="#909399"></u-icon>
+                    <text class="tips-text">{{ checkinTips }}</text>
+                </view>
+            </view>
+        </view>
+
+        <!-- 位置信息卡片 -->
+        <view class="location-card" v-if="currentLocation">
+            <view class="card-header">
+                <u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
+                <text class="card-title">当前位置</text>
+                <u-button 
+                    type="info" 
+                    size="mini" 
+                    text="重新定位" 
+                    @click="refreshLocation"
+                    :loading="locationLoading"
+                ></u-button>
+            </view>
+            <view class="location-info">
+                <view class="location-text">{{ currentLocation.address }}</view>
+                <view class="coordinates">
+                    <text class="coord-text">经度:{{ currentLocation.longitude.toFixed(6) }}</text>
+                    <text class="coord-text">纬度:{{ currentLocation.latitude.toFixed(6) }}</text>
+                </view>
+                <view class="location-accuracy" v-if="currentLocation.accuracy">
+                    <text class="accuracy-text">定位精度:{{ currentLocation.accuracy.toFixed(0) }}米</text>
+                    <u-tag 
+                        :text="getAccuracyStatus(currentLocation.accuracy).text"
+                        :type="getAccuracyStatus(currentLocation.accuracy).type"
+                        size="mini"
+                        style="margin-left: 8px;"
+                    ></u-tag>
+                </view>
+                <view class="location-actions">
+                    <u-button 
+                        type="primary" 
+                        size="mini" 
+                        plain
+                        @click="openLocationInMap"
+                        style="margin-top: 8px;"
+                    >
+                        <u-icon name="map" size="16" color="#2979ff" />
+                        <text style="margin-left: 4px;">在地图中查看</text>
+                    </u-button>
+                </view>
+            </view>
+        </view>
+
+        <!-- 位置获取失败提示 -->
+        <view class="location-error-card" v-else-if="locationError">
+            <view class="card-header">
+                <u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
+                <text class="card-title">位置信息</text>
+                <u-button 
+                    type="primary" 
+                    size="mini" 
+                    text="重新定位" 
+                    @click="refreshLocation"
+                    :loading="locationLoading"
+                ></u-button>
+            </view>
+            <view class="error-content">
+                <u-icon name="warning" size="24" color="#e6a23c"></u-icon>
+                <text class="error-text">{{ locationError }}</text>
+                <text class="error-tips">签到需要位置信息,请确保:</text>
+                <text class="error-tips">• 已开启位置权限</text>
+                <text class="error-tips">• GPS/网络定位已开启</text>
+                <text class="error-tips">• 网络连接正常</text>
+            </view>
+        </view>
+
+        <!-- 考勤统计 -->
+        <view class="statistics-card">
+            <view class="card-header">
+                <u-icon name="bar-chart" size="18" color="#e6a23c"></u-icon>
+                <text class="card-title">本月考勤</text>
+            </view>
+            <view class="stats-grid">
+                <view class="stat-item">
+                    <text class="stat-number">{{ monthStats.totalDays || 0 }}</text>
+                    <text class="stat-label">应签到</text>
+                </view>
+                <view class="stat-item">
+                    <text class="stat-number">{{ monthStats.checkedDays || 0 }}</text>
+                    <text class="stat-label">已签到</text>
+                </view>
+                <view class="stat-item">
+                    <text class="stat-number">{{ monthStats.missedDays || 0 }}</text>
+                    <text class="stat-label">未签到</text>
+                </view>
+                <view class="stat-item">
+                    <text class="stat-number">{{ monthStats.exceptionDays || 0 }}</text>
+                    <text class="stat-label">异常</text>
+                </view>
+            </view>
+        </view>
+
+        <!-- 最近签到记录 -->
+        <view class="records-card">
+            <view class="card-header">
+                <u-icon name="list" size="18" color="#909399"></u-icon>
+                <text class="card-title">最近签到</text>
+                <u-button 
+                    type="info" 
+                    size="mini" 
+                    text="查看更多" 
+                    @click="viewMoreRecords"
+                ></u-button>
+            </view>
+            <view class="records-list" v-if="recentRecords.length > 0">
+                <view class="record-item" v-for="record in recentRecords" :key="record.dailyId">
+                    <view class="record-date">{{ formatDate(record.checkDate) }}</view>
+                    <view class="record-time">{{ record.checkInTime || '未签到' }}</view>
+                    <view class="record-status">
+                        <u-tag 
+                            :text="record.checkInTime ? '已签到' : '未签到'" 
+                            :type="record.checkInTime ? 'success' : 'error'" 
+                            size="mini"
+                        ></u-tag>
+                    </view>
+                </view>
+            </view>
+            <view class="empty-records" v-else>
+                <u-icon name="inbox" size="40" color="#c0c4cc"></u-icon>
+                <text class="empty-text">暂无签到记录</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { getInternshipInfo, getStudentInternshipInfo, checkIn, getDailyList, getStatistics } from '@/api/internship.js';
+import { getCurrentLocation, checkLocationPermission, openLocationSettings } from '@/utils/location.js';
+import { userManager } from '@/utils/user.js';
+
+export default {
+    name: 'InternshipIndex',
+    data() {
+        return {
+            // 用户信息
+            userInfo: {},
+            
+            // 实习信息
+            internshipInfo: null,
+            
+            // 签到相关
+            checkinLoading: false,
+            canCheckin: true,
+            checkinTips: '',
+            todayRecord: null,
+            
+            // 位置信息
+            currentLocation: null,
+            locationLoading: false,
+            locationError: '',
+            
+            // 统计数据
+            monthStats: {
+                totalDays: 0,
+                checkedDays: 0,
+                missedDays: 0,
+                exceptionDays: 0
+            },
+            
+            // 签到记录
+            recentRecords: [],
+            
+            // 当前日期
+            currentDate: '',
+            
+            // 样式
+            checkinButtonStyle: {
+                width: '100%',
+                height: '50px',
+                borderRadius: '25px',
+                background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
+                border: 'none',
+                fontSize: '16px',
+                fontWeight: 'bold'
+            }
+        };
+    },
+    
+    onLoad() {
+        this.initPage();
+    },
+    
+    onShow() {
+        // 页面显示时刷新数据
+        this.loadTodayRecord();
+        this.loadStatistics();
+    },
+    
+    methods: {
+        /**
+         * 初始化页面
+         */
+        async initPage() {
+            try {
+                // 设置当前日期
+                this.currentDate = this.formatDate(new Date());
+                
+                // 获取用户信息
+                this.userInfo = userManager.getCurrentUser() || {};
+                
+                if (!this.userInfo.userId) {
+                    uni.showToast({
+                        title: '请先登录',
+                        icon: 'none'
+                    });
+                    return;
+                }
+                
+                // 并行加载数据
+                await Promise.all([
+                    this.loadInternshipInfo(),
+                    this.loadTodayRecord(),
+                    this.loadRecentRecords(),
+                    this.loadStatistics(),
+                    this.initLocation()
+                ]);
+                
+            } catch (error) {
+                console.error('初始化页面失败:', error);
+                uni.showToast({
+                    title: '数据加载失败',
+                    icon: 'none'
+                });
+            }
+        },
+        
+        /**
+         * 加载实习信息
+         */
+        async loadInternshipInfo() {
+            try {
+                console.log('🔍 开始加载学生实习信息...');
+                console.log('👤 当前用户信息:', this.userInfo);
+                
+                const response = await getStudentInternshipInfo(); // 使用新的学生专用API
+                console.log('📡 API原始响应:', JSON.stringify(response, null, 2));
+                
+                if (response.code === 200) {
+                    this.internshipInfo = response.data;
+                    console.log('✅ 实习信息获取成功:', JSON.stringify(this.internshipInfo, null, 2));
+                    
+                    // 详细字段检查
+                    if (this.internshipInfo) {
+                        console.log('🏢 实习单位:', this.internshipInfo.companyName);
+                        console.log('💼 实习岗位:', this.internshipInfo.internshipPosition);
+                        console.log('📅 开始时间:', this.internshipInfo.internshipStartDate);
+                        console.log('📅 结束时间:', this.internshipInfo.internshipEndDate);
+                        console.log('📊 状态:', this.internshipInfo.internshipStatus);
+                    }
+                } else {
+                    console.warn('⚠️ 实习信息获取失败:', response.msg);
+                    this.showInternshipError(response.msg || '获取实习信息失败');
+                }
+            } catch (error) {
+                console.error('❌ 加载实习信息失败:', error);
+                
+                // 根据错误类型显示不同信息
+                if (error.response) {
+                    if (error.response.status === 403) {
+                        this.showInternshipError('暂无权限查看实习信息,请联系管理员');
+                    } else if (error.response.status === 401) {
+                        this.showInternshipError('登录已失效,请重新登录');
+                    } else {
+                        this.showInternshipError(`服务器错误:${error.response.status}`);
+                    }
+                } else if (error.request) {
+                    this.showInternshipError('网络连接失败,请检查网络');
+                } else {
+                    this.showInternshipError('请求发送失败,请重试');
+                }
+            }
+        },
+        
+        /**
+         * 显示实习信息错误提示
+         */
+        showInternshipError(message) {
+            console.log('🚨 显示错误提示:', message);
+            uni.showToast({
+                title: message,
+                icon: 'none',
+                duration: 3000
+            });
+        },
+        
+        /**
+         * 加载今日签到记录
+         */
+        async loadTodayRecord() {
+            try {
+                const today = new Date();
+                const params = {
+                    studentId: this.userInfo.userId,
+                    checkDate: this.formatDate(today),
+                    pageNum: 1,
+                    pageSize: 1
+                };
+                
+                const response = await getDailyList(params);
+                if (response.code === 200 && response.rows && response.rows.length > 0) {
+                    this.todayRecord = response.rows[0];
+                    this.canCheckin = false;
+                    this.checkinTips = '今日已签到';
+                } else {
+                    this.todayRecord = null;
+                    this.canCheckin = true;
+                    this.checkinTips = '';
+                }
+            } catch (error) {
+                console.error('加载今日记录失败:', error);
+                this.canCheckin = true;
+            }
+        },
+        
+        /**
+         * 加载最近签到记录
+         */
+        async loadRecentRecords() {
+            try {
+                const params = {
+                    studentId: this.userInfo.userId,
+                    pageNum: 1,
+                    pageSize: 5
+                };
+                
+                const response = await getDailyList(params);
+                if (response.code === 200) {
+                    this.recentRecords = response.rows || [];
+                }
+            } catch (error) {
+                console.error('加载签到记录失败:', error);
+            }
+        },
+        
+        /**
+         * 加载统计数据
+         */
+        async loadStatistics() {
+            try {
+                const now = new Date();
+                const params = {
+                    studentId: this.userInfo.userId,
+                    year: now.getFullYear(),
+                    month: now.getMonth() + 1
+                };
+                
+                const response = await getStatistics(params);
+                if (response.code === 200) {
+                    this.monthStats = response.data || this.monthStats;
+                }
+            } catch (error) {
+                console.error('加载统计数据失败:', error);
+            }
+        },
+        
+        /**
+         * 初始化位置信息
+         */
+        async initLocation() {
+            try {
+                // 检查位置权限
+                const hasPermission = await checkLocationPermission();
+                if (!hasPermission) {
+                    this.canCheckin = false;
+                    this.checkinTips = '需要位置权限才能签到';
+                    return;
+                }
+                
+                // 获取当前位置
+                await this.getCurrentLocationInfo();
+            } catch (error) {
+                console.error('初始化位置失败:', error);
+                this.canCheckin = false;
+                this.checkinTips = '无法获取位置信息';
+            }
+        },
+        
+        /**
+         * 检查是否为H5环境
+         */
+        isH5Web() {
+            try {
+                return typeof window !== 'undefined' && typeof document !== 'undefined';
+            } catch (_) {
+                return false;
+            }
+        },
+        
+        /**
+         * 检查浏览器是否支持地理定位
+         */
+        isBrowserGeolocationSupported() {
+            return this.isH5Web() && !!navigator.geolocation;
+        },
+        
+        /**
+         * 检查是否为微信浏览器
+         */
+        isWechatBrowser() {
+            if (typeof navigator === 'undefined') return false;
+            const ua = navigator.userAgent.toLowerCase();
+            return ua.includes('micromessenger');
+        },
+        
+        /**
+         * 获取当前位置信息 - 增强版多重定位方案
+         */
+        async getCurrentLocationInfo() {
+            this.locationLoading = true;
+            this.locationError = '';
+            
+            try {
+                // 方案1: 微信定位(微信浏览器环境)
+                if (this.isWechatBrowser()) {
+                    try {
+                        uni.showToast({
+                            title: '正在微信定位...',
+                            icon: 'loading',
+                            duration: 2000
+                        });
+                        const wechatLocation = await this.getWechatLocation();
+                        if (wechatLocation) {
+                            this.currentLocation = wechatLocation;
+                            this.updateLocationSuccess();
+                            uni.showToast({
+                                title: '微信定位成功',
+                                icon: 'success',
+                                duration: 1500
+                            });
+                            return;
+                        }
+                    } catch (error) {
+                        console.log('微信定位失败,尝试其他方案:', error);
+                    }
+                }
+                
+                // 方案2: 浏览器HTML5定位(H5环境)
+                if (this.isH5Web() && this.isBrowserGeolocationSupported()) {
+                    try {
+                        uni.showToast({
+                            title: '正在浏览器定位...',
+                            icon: 'loading',
+                            duration: 2000
+                        });
+                        const browserLocation = await this.getBrowserLocation();
+                        if (browserLocation) {
+                            this.currentLocation = browserLocation;
+                            this.updateLocationSuccess();
+                            uni.showToast({
+                                title: '浏览器定位成功',
+                                icon: 'success',
+                                duration: 1500
+                            });
+                            return;
+                        }
+                    } catch (error) {
+                        console.log('浏览器定位失败,尝试其他方案:', error);
+                    }
+                }
+                
+                // 方案3: uni-app定位(通用回退方案)
+                try {
+                    uni.showToast({
+                        title: '正在系统定位...',
+                        icon: 'loading',
+                        duration: 2000
+                    });
+                    const uniLocation = await this.getUniappLocation();
+                    if (uniLocation) {
+                        this.currentLocation = uniLocation;
+                        this.updateLocationSuccess();
+                        uni.showToast({
+                            title: '定位成功',
+                            icon: 'success',
+                            duration: 1500
+                        });
+                        return;
+                    }
+                } catch (error) {
+                    console.log('uni-app定位失败:', error);
+                }
+                
+                // 所有定位方案都失败
+                this.updateLocationError('定位失败,请检查网络连接和位置权限');
+                
+            } catch (error) {
+                console.error('获取位置失败:', error);
+                this.updateLocationError(error.message || '获取位置失败');
+            } finally {
+                this.locationLoading = false;
+            }
+        },
+        
+        /**
+         * 微信定位
+         */
+        async getWechatLocation() {
+            return new Promise((resolve, reject) => {
+                // 检查微信环境
+                if (!this.isWechatBrowser()) {
+                    reject(new Error('不在微信环境中'));
+                    return;
+                }
+                
+                // 微信JSSDK定位
+                if (typeof wx !== 'undefined' && wx.getLocation) {
+                    wx.getLocation({
+                        type: 'gcj02',
+                        success: (res) => {
+                            if (res.latitude && res.longitude) {
+                                resolve({
+                                    latitude: res.latitude,
+                                    longitude: res.longitude,
+                                    accuracy: res.accuracy || 50,
+                                    address: `${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`
+                                });
+                            } else {
+                                reject(new Error('微信定位返回数据无效'));
+                            }
+                        },
+                        fail: (error) => {
+                            console.error('微信定位失败:', error);
+                            reject(new Error('微信定位失败'));
+                        }
+                    });
+                } else {
+                    reject(new Error('微信JSSDK未初始化'));
+                }
+            });
+        },
+        
+        /**
+         * 浏览器HTML5定位
+         */
+        async getBrowserLocation() {
+            return new Promise((resolve, reject) => {
+                navigator.geolocation.getCurrentPosition(
+                    (position) => {
+                        const { latitude, longitude, accuracy } = position.coords;
+                        resolve({
+                            latitude: latitude,
+                            longitude: longitude,
+                            accuracy: accuracy || 100,
+                            address: `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`
+                        });
+                    },
+                    (error) => {
+                        let errorMsg = '浏览器定位失败';
+                        switch (error.code) {
+                            case error.PERMISSION_DENIED:
+                                errorMsg = '位置权限被拒绝';
+                                break;
+                            case error.POSITION_UNAVAILABLE:
+                                errorMsg = '位置信息不可用';
+                                break;
+                            case error.TIMEOUT:
+                                errorMsg = '定位超时';
+                                break;
+                        }
+                        reject(new Error(errorMsg));
+                    },
+                    {
+                        enableHighAccuracy: true,
+                        timeout: 15000,
+                        maximumAge: 60000
+                    }
+                );
+            });
+        },
+        
+        /**
+         * uni-app定位
+         */
+        async getUniappLocation() {
+            return new Promise((resolve, reject) => {
+                uni.getLocation({
+                    type: 'gcj02',
+                    geocode: true,
+                    success: (res) => {
+                        resolve({
+                            latitude: res.latitude,
+                            longitude: res.longitude,
+                            accuracy: res.accuracy || 100,
+                            address: res.address || `${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`
+                        });
+                    },
+                    fail: (error) => {
+                        let errorMsg = 'uni-app定位失败';
+                        if (error.errMsg) {
+                            if (error.errMsg.includes('auth deny')) {
+                                errorMsg = '位置权限被拒绝';
+                            } else if (error.errMsg.includes('locate fail')) {
+                                errorMsg = '定位失败,请检查GPS设置';
+                            }
+                        }
+                        reject(new Error(errorMsg));
+                    }
+                });
+            });
+        },
+        
+        /**
+         * 更新定位成功状态
+         */
+        updateLocationSuccess() {
+            if (!this.todayRecord) {
+                this.canCheckin = true;
+                this.checkinTips = '';
+            }
+            this.locationError = '';
+        },
+        
+        /**
+         * 更新定位失败状态
+         */
+        updateLocationError(message) {
+            this.currentLocation = null;
+            this.locationError = message;
+            if (!this.todayRecord) {
+                this.canCheckin = false;
+                this.checkinTips = message;
+            }
+        },
+        
+        /**
+         * 刷新位置
+         */
+        refreshLocation() {
+            this.getCurrentLocationInfo();
+        },
+
+        /**
+         * 打开位置信息在地图中查看
+         */
+        openLocationInMap() {
+            if (!this.currentLocation) {
+                uni.showToast({
+                    title: '无法获取位置信息',
+                    icon: 'none'
+                });
+                return;
+            }
+
+            const { latitude, longitude } = this.currentLocation;
+            const name = this.currentLocation.address || '当前位置';
+            
+            // 使用uni.openLocation打开系统地图
+            uni.openLocation({
+                latitude: latitude,
+                longitude: longitude,
+                name: name,
+                address: this.currentLocation.address || `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`,
+                success: () => {
+                    console.log('打开地图成功');
+                },
+                fail: (error) => {
+                    console.error('打开地图失败:', error);
+                    uni.showToast({
+                        title: '打开地图失败',
+                        icon: 'none'
+                    });
+                }
+            });
+        },
+        
+        /**
+         * 处理签到
+         */
+        async handleCheckin() {
+            if (!this.canCheckin || this.checkinLoading) {
+                return;
+            }
+            
+            // 检查实习信息
+            if (!this.internshipInfo) {
+                uni.showToast({
+                    title: '暂无实习信息',
+                    icon: 'none'
+                });
+                return;
+            }
+            
+            // 检查位置信息
+            if (!this.currentLocation) {
+                uni.showModal({
+                    title: '位置信息',
+                    content: '需要获取位置信息才能签到,是否重新获取?',
+                    confirmText: '重新获取',
+                    success: (res) => {
+                        if (res.confirm) {
+                            this.initLocation();
+                        }
+                    }
+                });
+                return;
+            }
+            
+            // 检查定位精度
+            if (this.currentLocation.accuracy && this.currentLocation.accuracy > 500) {
+                const confirmResult = await new Promise((resolve) => {
+                    uni.showModal({
+                        title: '定位精度提醒',
+                        content: `当前定位精度较低(${this.currentLocation.accuracy.toFixed(0)}米),可能影响签到准确性,是否继续签到?`,
+                        confirmText: '继续签到',
+                        cancelText: '重新定位',
+                        success: (res) => resolve(res.confirm),
+                        fail: () => resolve(false)
+                    });
+                });
+                
+                if (!confirmResult) {
+                    // 用户选择重新定位
+                    this.refreshLocation();
+                    return;
+                }
+            }
+            
+            this.checkinLoading = true;
+            
+            try {
+                // 构建签到数据 - 使用后端兼容的字段名
+                const checkinData = {
+                    internshipId: this.internshipInfo.internshipId, // 新增必填字段
+                    studentId: this.userInfo.userId,
+                    checkDate: new Date().toISOString().split('T')[0], // 格式化为 yyyy-MM-dd
+                    checkInTime: new Date().toTimeString().split(' ')[0], // 格式化为 HH:mm:ss
+                    
+                    // 使用兼容字段名(后端Service会自动映射到正确字段)
+                    checkInLocation: this.currentLocation.address,  // 后端映射为 checkOutLocation
+                    latitude: this.currentLocation.latitude,        // 后端映射为 checkOutLatitude
+                    longitude: this.currentLocation.longitude,      // 后端映射为 checkOutLongitude
+                    dailySummary: '移动端签到打卡',                    // 后端映射为 workContent
+                    
+                    status: '1', // 正常状态
+                    isException: '0' // 正常,无异常
+                };
+                
+                console.log('🔍 签到前检查:', {
+                    internshipInfo: this.internshipInfo,
+                    userInfo: this.userInfo, 
+                    currentLocation: this.currentLocation,
+                    canCheckin: this.canCheckin
+                });
+                console.log('📤 发送签到数据:', JSON.stringify(checkinData, null, 2));
+                
+                const response = await checkIn(checkinData);
+                
+                if (response.code === 200) {
+                    uni.showToast({
+                        title: '签到成功',
+                        icon: 'success'
+                    });
+                    
+                    // 刷新今日记录
+                    await this.loadTodayRecord();
+                    await this.loadRecentRecords();
+                    await this.loadStatistics();
+                    
+                } else {
+                    throw new Error(response.msg || '签到失败');
+                }
+                
+            } catch (error) {
+                console.error('❌ 签到详细错误:', {
+                    message: error.message,
+                    response: error.response,
+                    stack: error.stack
+                });
+                uni.showToast({
+                    title: error.message || '签到失败',
+                    icon: 'none'
+                });
+            } finally {
+                this.checkinLoading = false;
+            }
+        },
+        
+        /**
+         * 查看更多记录
+         */
+        viewMoreRecords() {
+            // 可以跳转到详细记录页面
+            uni.showToast({
+                title: '功能开发中',
+                icon: 'none'
+            });
+        },
+        
+        /**
+         * 格式化日期
+         */
+        formatDate(date) {
+            if (!date) return '';
+            const d = new Date(date);
+            const year = d.getFullYear();
+            const month = String(d.getMonth() + 1).padStart(2, '0');
+            const day = String(d.getDate()).padStart(2, '0');
+            return `${year}-${month}-${day}`;
+        },
+        
+        /**
+         * 格式化日期范围
+         */
+        formatDateRange(startDate, endDate) {
+            if (!startDate || !endDate) return '暂无';
+            return `${this.formatDate(startDate)} 至 ${this.formatDate(endDate)}`;
+        },
+        
+        /**
+         * 获取状态文本
+         */
+        getStatusText(status) {
+            const statusMap = {
+                '1': '进行中',
+                '2': '已完成',
+                '3': '已暂停',
+                '4': '已终止'
+            };
+            return statusMap[status] || '未实习';
+        },
+        
+        /**
+         * 获取状态类型
+         */
+        getStatusType(status) {
+            const typeMap = {
+                '1': 'success',  // 进行中
+                '2': 'info',     // 已完成
+                '3': 'warning',  // 已暂停
+                '4': 'error'     // 已终止
+            };
+            return typeMap[status] || 'default';
+        },
+
+        /**
+         * 获取定位精度状态
+         */
+        getAccuracyStatus(accuracy) {
+            if (accuracy < 100) {
+                return { text: '高精度', type: 'success' };
+            } else if (accuracy < 500) {
+                return { text: '中精度', type: 'info' };
+            } else {
+                return { text: '低精度', type: 'warning' };
+            }
+        }
+    }
+};
+</script>
+
+<style scoped>
+.internship-page {
+    min-height: 100vh;
+    background: #f5f7fa;
+    padding: 16px;
+}
+
+/* 用户信息卡片 */
+.user-card {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 16px;
+    padding: 20px;
+    margin-bottom: 16px;
+    color: white;
+    box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
+}
+
+.user-info {
+    display: flex;
+    align-items: center;
+}
+
+.avatar-section {
+    margin-right: 16px;
+}
+
+.info-section {
+    flex: 1;
+}
+
+.user-name {
+    font-size: 18px;
+    font-weight: bold;
+    margin-bottom: 4px;
+}
+
+.user-detail {
+    font-size: 14px;
+    opacity: 0.9;
+    margin-bottom: 2px;
+}
+
+/* 通用卡片样式 */
+.internship-card,
+.checkin-card,
+.location-card,
+.statistics-card,
+.records-card {
+    background: white;
+    border-radius: 12px;
+    padding: 16px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.card-header {
+    display: flex;
+    align-items: center;
+    margin-bottom: 12px;
+    padding-bottom: 8px;
+    border-bottom: 1px solid #f0f0f0;
+}
+
+.card-title {
+    font-size: 16px;
+    font-weight: bold;
+    margin-left: 8px;
+    flex: 1;
+    color: #303133;
+}
+
+.date-text {
+    font-size: 12px;
+    color: #909399;
+}
+
+/* 实习信息 */
+.internship-info {
+    padding-top: 8px;
+}
+
+.info-row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+}
+
+.label {
+    font-size: 14px;
+    color: #606266;
+    width: 80px;
+    flex-shrink: 0;
+}
+
+.value {
+    font-size: 14px;
+    color: #303133;
+    flex: 1;
+}
+
+/* 签到状态 */
+.checkin-status {
+    padding: 16px;
+    background: #f0f9ff;
+    border-radius: 8px;
+    border-left: 4px solid #67c23a;
+}
+
+.status-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+}
+
+.status-text {
+    font-size: 16px;
+    font-weight: bold;
+    color: #67c23a;
+    margin-left: 8px;
+}
+
+.time-info,
+.location-info {
+    display: flex;
+    align-items: center;
+    margin-bottom: 4px;
+}
+
+.time-label,
+.location-label {
+    font-size: 14px;
+    color: #606266;
+    margin-right: 8px;
+}
+
+.time-value,
+.location-value {
+    font-size: 14px;
+    color: #303133;
+}
+
+/* 签到操作 */
+.checkin-actions {
+    padding: 16px 0;
+}
+
+.checkin-tips {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 12px;
+}
+
+.tips-text {
+    font-size: 12px;
+    color: #909399;
+    margin-left: 4px;
+}
+
+/* 位置信息 */
+.location-info {
+    padding-top: 8px;
+}
+
+.location-text {
+    font-size: 14px;
+    color: #303133;
+    margin-bottom: 8px;
+    line-height: 1.5;
+}
+
+.coordinates {
+    display: flex;
+    justify-content: space-between;
+}
+
+.coord-text {
+    font-size: 12px;
+    color: #909399;
+}
+
+.location-accuracy {
+    display: flex;
+    align-items: center;
+    margin-top: 8px;
+    margin-bottom: 8px;
+}
+
+.accuracy-text {
+    font-size: 12px;
+    color: #909399;
+    margin-right: 8px;
+}
+
+.location-actions {
+    display: flex;
+    justify-content: flex-end;
+}
+
+/* 位置获取失败提示 */
+.location-error-card {
+    background: #fffbe6;
+    border: 1px solid #ffe58f;
+    border-radius: 12px;
+    padding: 16px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.error-content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding-top: 12px;
+}
+
+.error-text {
+    font-size: 14px;
+    color: #faad14;
+    margin-top: 8px;
+    text-align: center;
+}
+
+.error-tips {
+    font-size: 12px;
+    color: #faad14;
+    margin-top: 4px;
+    text-align: center;
+}
+
+/* 统计数据 */
+.stats-grid {
+    display: flex;
+    justify-content: space-between;
+    padding-top: 8px;
+}
+
+.stat-item {
+    flex: 1;
+    text-align: center;
+    padding: 12px 8px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    margin: 0 4px;
+}
+
+.stat-number {
+    display: block;
+    font-size: 20px;
+    font-weight: bold;
+    color: #409eff;
+    margin-bottom: 4px;
+}
+
+.stat-label {
+    font-size: 12px;
+    color: #606266;
+}
+
+/* 签到记录 */
+.records-list {
+    padding-top: 8px;
+}
+
+.record-item {
+    display: flex;
+    align-items: center;
+    padding: 12px 0;
+    border-bottom: 1px solid #f0f0f0;
+}
+
+.record-item:last-child {
+    border-bottom: none;
+}
+
+.record-date {
+    font-size: 14px;
+    color: #303133;
+    width: 100px;
+    flex-shrink: 0;
+}
+
+.record-time {
+    font-size: 14px;
+    color: #606266;
+    flex: 1;
+    margin-left: 12px;
+}
+
+.record-status {
+    flex-shrink: 0;
+}
+
+/* 空状态 */
+.empty-records {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 32px 0;
+}
+
+.empty-text {
+    font-size: 14px;
+    color: #909399;
+    margin-top: 8px;
+}
+</style>

+ 140 - 0
mobile/src/utils/location.js

@@ -0,0 +1,140 @@
+/**
+ * 定位服务工具类
+ * 提供获取当前位置、地址解析等功能
+ */
+
+/**
+ * 获取当前位置
+ * @returns Promise<{latitude, longitude, address}>
+ */
+export const getCurrentLocation = () => {
+    return new Promise((resolve, reject) => {
+        uni.showLoading({
+            title: '正在获取位置...'
+        });
+
+        uni.getLocation({
+            type: 'gcj02', // 使用国测局坐标系
+            geocode: true, // 解析地址信息
+            success: (res) => {
+                console.log('📍 获取位置成功:', res);
+                
+                const locationData = {
+                    latitude: res.latitude,
+                    longitude: res.longitude,
+                    address: res.address || '位置信息获取中...'
+                };
+
+                // 如果没有地址信息,尝试逆地理编码
+                if (!res.address) {
+                    reverseGeocode(res.latitude, res.longitude)
+                        .then(address => {
+                            locationData.address = address;
+                            resolve(locationData);
+                        })
+                        .catch(() => {
+                            locationData.address = `${res.latitude.toFixed(6)}, ${res.longitude.toFixed(6)}`;
+                            resolve(locationData);
+                        });
+                } else {
+                    resolve(locationData);
+                }
+            },
+            fail: (err) => {
+                console.error('❌ 获取位置失败:', err);
+                
+                let errorMsg = '获取位置失败';
+                if (err.errMsg) {
+                    if (err.errMsg.includes('auth deny')) {
+                        errorMsg = '位置权限被拒绝,请在设置中开启位置权限';
+                    } else if (err.errMsg.includes('locate fail')) {
+                        errorMsg = '定位失败,请检查网络连接和GPS设置';
+                    }
+                }
+                
+                reject(new Error(errorMsg));
+            },
+            complete: () => {
+                uni.hideLoading();
+            }
+        });
+    });
+};
+
+/**
+ * 逆地理编码 - 根据坐标获取地址
+ * @param {number} latitude 纬度
+ * @param {number} longitude 经度
+ * @returns Promise<string>
+ */
+export const reverseGeocode = (latitude, longitude) => {
+    return new Promise((resolve, reject) => {
+        // 这里可以调用第三方地图服务API进行逆地理编码
+        // 暂时返回坐标信息
+        const address = `${latitude.toFixed(6)}, ${longitude.toFixed(6)}`;
+        resolve(address);
+    });
+};
+
+/**
+ * 检查定位权限
+ * @returns Promise<boolean>
+ */
+export const checkLocationPermission = () => {
+    return new Promise((resolve) => {
+        // #ifdef APP-PLUS
+        const permissions = ['android.permission.ACCESS_FINE_LOCATION'];
+        plus.android.requestPermissions(permissions, (result) => {
+            resolve(result.granted && result.granted.length > 0);
+        }, (error) => {
+            console.error('权限检查失败:', error);
+            resolve(false);
+        });
+        // #endif
+        
+        // #ifndef APP-PLUS
+        resolve(true); // 小程序和H5环境默认有权限
+        // #endif
+    });
+};
+
+/**
+ * 打开位置权限设置
+ */
+export const openLocationSettings = () => {
+    uni.showModal({
+        title: '位置权限',
+        content: '需要位置权限才能进行签到,请在设置中开启位置权限',
+        confirmText: '去设置',
+        success: (res) => {
+            if (res.confirm) {
+                // #ifdef APP-PLUS
+                plus.runtime.openURL('app-settings:');
+                // #endif
+                
+                // #ifdef MP-WEIXIN
+                wx.openSetting();
+                // #endif
+            }
+        }
+    });
+};
+
+/**
+ * 计算两点间距离(米)
+ * @param {number} lat1 纬度1
+ * @param {number} lng1 经度1  
+ * @param {number} lat2 纬度2
+ * @param {number} lng2 经度2
+ * @returns {number} 距离(米)
+ */
+export const calculateDistance = (lat1, lng1, lat2, lng2) => {
+    const R = 6371000; // 地球半径(米)
+    const dLat = (lat2 - lat1) * Math.PI / 180;
+    const dLng = (lng2 - lng1) * Math.PI / 180;
+    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+              Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
+              Math.sin(dLng / 2) * Math.sin(dLng / 2);
+    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+    return R * c;
+}; 

+ 26 - 0
mobile/src/utils/permission.js

@@ -14,6 +14,10 @@ export const checkPermi = (value) => {
     if (value && value instanceof Array && value.length > 0) {
     if (value && value instanceof Array && value.length > 0) {
         const userInfo = userStorage.getUserInfo();
         const userInfo = userStorage.getUserInfo();
         if (!userInfo || !userInfo.permissions) {
         if (!userInfo || !userInfo.permissions) {
+            console.warn('🚫 权限检查失败: 用户信息或权限列表不存在', {
+                hasUserInfo: !!userInfo,
+                hasPermissions: !!(userInfo && userInfo.permissions)
+            });
             return false;
             return false;
         }
         }
 
 
@@ -21,11 +25,33 @@ export const checkPermi = (value) => {
         const permissionDatas = value;
         const permissionDatas = value;
         const all_permission = '*:*:*';
         const all_permission = '*:*:*';
 
 
+        // 调试日志:输出当前用户权限和检查的权限
+        console.log('🔐 权限检查调试信息:', {
+            检查权限: permissionDatas,
+            用户权限数量: permissions.length,
+            是否包含超级权限: permissions.includes(all_permission),
+            权限匹配结果: permissionDatas.some(perm => permissions.includes(perm))
+        });
+
+        // 特别检查实习相关权限
+        if (permissionDatas.some(perm => perm.includes('internship'))) {
+            console.log('📚 实习权限检查详情:', {
+                检查的实习权限: permissionDatas.filter(perm => perm.includes('internship')),
+                用户拥有的实习权限: permissions.filter(perm => perm.includes('internship')),
+                所有权限前10条: permissions.slice(0, 10)
+            });
+        }
+
         const hasPermission = permissions.some((permission) => {
         const hasPermission = permissions.some((permission) => {
             return all_permission === permission || permissionDatas.includes(permission);
             return all_permission === permission || permissionDatas.includes(permission);
         });
         });
 
 
         if (!hasPermission) {
         if (!hasPermission) {
+            console.warn('🚫 权限验证失败:', {
+                需要权限: permissionDatas,
+                用户ID: userInfo.user?.userId,
+                用户名: userInfo.user?.userName
+            });
             return false;
             return false;
         }
         }
         return true;
         return true;

+ 3 - 3
web/src/views/zsjy/internship/daily/index.vue

@@ -125,9 +125,9 @@
       <div class="card-header-vertical">
       <div class="card-header-vertical">
         <div class="toolbar-title">实习日常管理</div>
         <div class="toolbar-title">实习日常管理</div>
         <div class="button-group">
         <div class="button-group">
-          <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internship:daily:add']">新增签到</el-button>
+          <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipdaily:add']">新增签到</el-button>
           <el-button plain icon="Search" @click="showSearch = !showSearch">搜索</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>
+          <el-button type="primary" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipdaily:export']">导出</el-button>
         </div>
         </div>
       </div>
       </div>
     </el-card>
     </el-card>
@@ -578,7 +578,7 @@ const submitAdd = () => {
   proxy.$refs.addFormRef.validate(valid => {
   proxy.$refs.addFormRef.validate(valid => {
     if (valid) {
     if (valid) {
       addLoading.value = true;
       addLoading.value = true;
-      
+
       addInternshipDaily(addForm.value).then(response => {
       addInternshipDaily(addForm.value).then(response => {
         proxy.$modal.msgSuccess("新增签到记录成功");
         proxy.$modal.msgSuccess("新增签到记录成功");
         addDialogVisible.value = false;
         addDialogVisible.value = false;