浏览代码

wuzhen update

wuzhen 1 年之前
父节点
当前提交
b0d47046c4

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

@@ -82,4 +82,10 @@ public class ZsjyInternshipDailyQueryBo extends BaseEntity {
     @Schema(description = "公司名称")
     private String companyName;
 
+    /**
+     * 班主任ID(用于权限控制)
+     */
+    @Schema(description = "班主任ID")
+    private Long headTeacherId;
+
 }

+ 8 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/java/org/dromara/zsjy/mapper/ZsjyInternshipDailyMapper.java

@@ -61,4 +61,12 @@ public interface ZsjyInternshipDailyMapper extends BaseMapper<ZsjyInternshipDail
     boolean existsByStudentAndDate(@Param("studentId") Long studentId,
                                   @Param("checkDate") Date checkDate);
 
+    /**
+     * 统计班主任管理的班级数量
+     *
+     * @param teacherId 班主任ID
+     * @return 班级数量
+     */
+    Long countClassesByTeacherId(@Param("teacherId") Long teacherId);
+
 }

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

@@ -48,8 +48,19 @@ public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyServi
      */
     @Override
     public TableDataInfo<ZsjyInternshipDailyVo> queryPageList(ZsjyInternshipDailyQueryBo queryBo, PageQuery pageQuery) {
+        log.info("=== 实习日常查询后端调试日志 ===");
+        log.info("1. 查询参数: {}", queryBo);
+        log.info("2. 分页参数: pageNum={}, pageSize={}", pageQuery.getPageNum(), pageQuery.getPageSize());
+
+        // 设置数据权限
+        setDataPermission(queryBo);
+        log.info("3. 设置数据权限后的查询参数: {}", queryBo);
+        log.info("3.1. 权限控制参数: headTeacherId={}", queryBo.getHeadTeacherId());
+
         Page<ZsjyInternshipDailyVo> page = pageQuery.build();
         List<ZsjyInternshipDailyVo> list = baseMapper.selectDailyList(page, queryBo);
+        log.info("4. 查询到的数据条数: {}", list != null ? list.size() : "null");
+
         return TableDataInfo.build(list);
     }
 
@@ -58,6 +69,9 @@ public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyServi
      */
     @Override
     public List<ZsjyInternshipDailyVo> queryList(ZsjyInternshipDailyQueryBo queryBo) {
+        // 设置数据权限
+        setDataPermission(queryBo);
+
         return baseMapper.selectDailyList(null, queryBo);
     }
 
@@ -202,4 +216,46 @@ public class ZsjyInternshipDailyServiceImpl implements IZsjyInternshipDailyServi
         }
     }
 
+        /**
+     * 设置数据权限(班主任只能查看自己班级的学生数据)
+     */
+    private void setDataPermission(ZsjyInternshipDailyQueryBo queryBo) {
+        try {
+            // 获取当前登录用户ID
+            Long currentUserId = LoginHelper.getUserId();
+            log.info("=== 权限控制调试 ===");
+            log.info("当前登录用户ID: {}", currentUserId);
+
+            if (ObjectUtil.isNotNull(currentUserId)) {
+                // 检查该用户是否是班主任
+                Long classTeacherCount = baseMapper.countClassesByTeacherId(currentUserId);
+                log.info("用户ID {}作为班主任管理的班级数量: {}", currentUserId, classTeacherCount);
+
+                if (classTeacherCount > 0) {
+                    // 是班主任,设置权限限制
+                    queryBo.setHeadTeacherId(currentUserId);
+                    log.info("✅ 权限判断:用户是班主任,设置headTeacherId={}", currentUserId);
+                } else {
+                    // 不是班主任,设置不存在的班主任ID,使其无法查看任何数据
+                    log.info("❌ 权限判断:用户不是班主任,设置权限限制");
+
+                    // 设置一个不存在的班主任ID(-1),这样SQL查询结果为空
+                    queryBo.setHeadTeacherId(-1L);
+                    log.info("🔒 设置数据权限:非班主任用户无法查看数据,headTeacherId=-1");
+                }
+
+                // 输出最终的权限设置
+                log.info("🎯 最终权限设置:queryBo.headTeacherId = {}", queryBo.getHeadTeacherId());
+            } else {
+                log.warn("⚠️ 未获取到当前用户ID,设置权限限制");
+                queryBo.setHeadTeacherId(-1L);
+            }
+        } catch (Exception e) {
+            log.error("❌ 设置数据权限失败:{}", e.getMessage(), e);
+            // 异常情况下,设置权限限制以确保数据安全
+            queryBo.setHeadTeacherId(-1L);
+            log.warn("🔒 异常情况下设置权限限制,headTeacherId=-1");
+        }
+    }
+
 }

+ 18 - 0
backend/ruoyi-modules/ruoyi-zsjy/src/main/resources/mapper/ZsjyInternshipDailyMapper.xml

@@ -104,6 +104,15 @@
             <if test="queryBo.companyName != null and queryBo.companyName != ''">
                 AND i.company_name LIKE CONCAT('%', #{queryBo.companyName}, '%')
             </if>
+            <!-- 权限控制:班主任只能查看自己班级的学生数据 -->
+            <if test="queryBo.headTeacherId != null">
+                AND EXISTS (
+                    SELECT 1 FROM xg_student_msg s
+                    WHERE s.id = d.student_id
+                      AND s.class_teacher_id = #{queryBo.headTeacherId}
+                      AND s.deleted = 0
+                )
+            </if>
         </where>
         ORDER BY d.check_date DESC, d.create_time DESC
     </select>
@@ -184,4 +193,13 @@
           AND deleted = 0
     </select>
 
+    <!-- 统计班主任管理的班级数量 -->
+    <select id="countClassesByTeacherId" resultType="java.lang.Long">
+        SELECT COUNT(DISTINCT s.class_id)
+        FROM xg_student_msg s
+        WHERE s.class_teacher_id = #{teacherId}
+          AND s.deleted = 0
+          AND s.class_id IS NOT NULL
+    </select>
+
 </mapper>

+ 290 - 56
mobile/src/pages/internship/index.vue

@@ -92,66 +92,112 @@
         <!-- 位置信息卡片 -->
         <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>
+                <view class="header-left">
+                    <u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
+                    <text class="card-title">当前位置</text>
+                </view>
                 <u-button 
                     type="info" 
                     size="mini" 
                     text="重新定位" 
                     @click="refreshLocation"
                     :loading="locationLoading"
+                    class="refresh-btn"
                 ></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 class="location-address">
+                <u-icon name="location" size="14" color="#909399" class="address-icon"></u-icon>
+                <text class="address-text">{{ currentLocation.address }}</text>
+            </view>
+            
+            <!-- 坐标信息 -->
+            <view class="coordinates-section">
+                <view class="coord-row">
+                    <view class="coord-item">
+                        <text class="coord-label">经度</text>
+                        <text class="coord-value">{{ currentLocation.longitude.toFixed(6) }}</text>
+                    </view>
+                    <view class="coord-divider"></view>
+                    <view class="coord-item">
+                        <text class="coord-label">纬度</text>
+                        <text class="coord-value">{{ currentLocation.latitude.toFixed(6) }}</text>
+                    </view>
                 </view>
-                <view class="location-accuracy" v-if="currentLocation.accuracy">
-                    <text class="accuracy-text">定位精度:{{ currentLocation.accuracy.toFixed(0) }}米</text>
+            </view>
+            
+            <!-- 精度信息 -->
+            <view class="accuracy-section" v-if="currentLocation.accuracy">
+                <view class="accuracy-info">
+                    <u-icon name="target" size="14" color="#67c23a" class="accuracy-icon"></u-icon>
+                    <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;"
+                        class="accuracy-tag"
                     ></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 class="location-actions">
+                <u-button 
+                    type="primary" 
+                    size="default" 
+                    plain
+                    @click="openLocationInMap"
+                    class="map-btn"
+                >
+                    <u-icon name="map" size="16" color="#2979ff" />
+                    <text class="btn-text">在地图中查看</text>
+                </u-button>
             </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>
+                <view class="header-left">
+                    <u-icon name="map-pin" size="18" color="#f56c6c"></u-icon>
+                    <text class="card-title">位置信息</text>
+                </view>
                 <u-button 
                     type="primary" 
                     size="mini" 
                     text="重新定位" 
                     @click="refreshLocation"
                     :loading="locationLoading"
+                    class="refresh-btn"
                 ></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 class="error-header">
+                    <u-icon name="warning-circle" size="28" color="#e6a23c" class="error-icon"></u-icon>
+                    <text class="error-title">无法获取位置信息</text>
+                </view>
+                
+                <text class="error-message">{{ locationError }}</text>
+                
+                <view class="error-tips-section">
+                    <text class="tips-title">签到需要位置信息,请确保:</text>
+                    <view class="tips-list">
+                        <view class="tip-item">
+                            <u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
+                            <text class="tip-text">已开启位置权限</text>
+                        </view>
+                        <view class="tip-item">
+                            <u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
+                            <text class="tip-text">GPS/网络定位已开启</text>
+                        </view>
+                        <view class="tip-item">
+                            <u-icon name="checkmark-circle" size="14" color="#67c23a" class="tip-icon"></u-icon>
+                            <text class="tip-text">网络连接正常</text>
+                        </view>
+                    </view>
+                </view>
             </view>
         </view>
 
@@ -997,19 +1043,29 @@ export default {
 .card-header {
     display: flex;
     align-items: center;
-    margin-bottom: 12px;
-    padding-bottom: 8px;
+    justify-content: space-between;
+    margin-bottom: 16px;
+    padding-bottom: 12px;
     border-bottom: 1px solid #f0f0f0;
 }
 
+.header-left {
+    display: flex;
+    align-items: center;
+    flex: 1;
+}
+
 .card-title {
     font-size: 16px;
     font-weight: bold;
     margin-left: 8px;
-    flex: 1;
     color: #303133;
 }
 
+.refresh-btn {
+    flex-shrink: 0;
+}
+
 .date-text {
     font-size: 12px;
     color: #909399;
@@ -1099,43 +1155,112 @@ export default {
 }
 
 /* 位置信息 */
-.location-info {
-    padding-top: 8px;
+.location-address {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 16px;
+    padding: 12px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    border-left: 3px solid #67c23a;
 }
 
-.location-text {
+.address-icon {
+    margin-right: 8px;
+    margin-top: 2px;
+    flex-shrink: 0;
+}
+
+.address-text {
     font-size: 14px;
     color: #303133;
-    margin-bottom: 8px;
     line-height: 1.5;
+    flex: 1;
 }
 
-.coordinates {
+/* 坐标信息 */
+.coordinates-section {
+    margin-bottom: 16px;
+}
+
+.coord-row {
     display: flex;
-    justify-content: space-between;
+    align-items: center;
+    background: #f8f9fa;
+    border-radius: 8px;
+    padding: 12px;
+}
+
+.coord-item {
+    flex: 1;
+    text-align: center;
 }
 
-.coord-text {
+.coord-divider {
+    width: 1px;
+    height: 32px;
+    background: #e4e7ed;
+    margin: 0 16px;
+}
+
+.coord-label {
+    display: block;
     font-size: 12px;
     color: #909399;
+    margin-bottom: 4px;
+}
+
+.coord-value {
+    display: block;
+    font-size: 14px;
+    color: #303133;
+    font-weight: 500;
+    font-family: monospace;
 }
 
-.location-accuracy {
+/* 精度信息 */
+.accuracy-section {
+    margin-bottom: 16px;
+}
+
+.accuracy-info {
     display: flex;
     align-items: center;
-    margin-top: 8px;
-    margin-bottom: 8px;
+    padding: 10px 12px;
+    background: #f0f9ff;
+    border-radius: 8px;
+    border: 1px solid #b3e5fc;
 }
 
-.accuracy-text {
-    font-size: 12px;
-    color: #909399;
+.accuracy-icon {
     margin-right: 8px;
+    flex-shrink: 0;
+}
+
+.accuracy-text {
+    font-size: 13px;
+    color: #67c23a;
+    font-weight: 500;
+    flex: 1;
+}
+
+.accuracy-tag {
+    margin-left: 8px;
+    flex-shrink: 0;
 }
 
+/* 操作按钮 */
 .location-actions {
     display: flex;
-    justify-content: flex-end;
+    justify-content: center;
+}
+
+.map-btn {
+    min-width: 140px;
+}
+
+.btn-text {
+    margin-left: 6px;
 }
 
 /* 位置获取失败提示 */
@@ -1149,24 +1274,73 @@ export default {
 }
 
 .error-content {
+    padding-top: 8px;
+}
+
+.error-header {
     display: flex;
-    flex-direction: column;
     align-items: center;
-    padding-top: 12px;
+    justify-content: center;
+    margin-bottom: 16px;
+}
+
+.error-icon {
+    margin-right: 8px;
 }
 
-.error-text {
+.error-title {
+    font-size: 16px;
+    font-weight: bold;
+    color: #e6a23c;
+}
+
+.error-message {
+    display: block;
     font-size: 14px;
     color: #faad14;
-    margin-top: 8px;
     text-align: center;
+    margin-bottom: 20px;
+    line-height: 1.5;
 }
 
-.error-tips {
-    font-size: 12px;
-    color: #faad14;
-    margin-top: 4px;
-    text-align: center;
+.error-tips-section {
+    background: #fff7e6;
+    border-radius: 8px;
+    padding: 16px;
+    border: 1px solid #ffd591;
+}
+
+.tips-title {
+    display: block;
+    font-size: 14px;
+    font-weight: bold;
+    color: #d48806;
+    margin-bottom: 12px;
+}
+
+.tips-list {
+    padding-left: 0;
+}
+
+.tip-item {
+    display: flex;
+    align-items: center;
+    margin-bottom: 8px;
+}
+
+.tip-item:last-child {
+    margin-bottom: 0;
+}
+
+.tip-icon {
+    margin-right: 8px;
+    flex-shrink: 0;
+}
+
+.tip-text {
+    font-size: 13px;
+    color: #d48806;
+    line-height: 1.4;
 }
 
 /* 统计数据 */
@@ -1245,4 +1419,64 @@ export default {
     color: #909399;
     margin-top: 8px;
 }
+
+/* 响应式设计 */
+@media screen and (max-width: 375px) {
+    .coord-row {
+        padding: 10px;
+    }
+    
+    .coord-divider {
+        margin: 0 12px;
+    }
+    
+    .coord-value {
+        font-size: 13px;
+    }
+    
+    .map-btn {
+        min-width: 120px;
+        font-size: 14px;
+    }
+    
+    .error-tips-section {
+        padding: 12px;
+    }
+}
+
+@media screen and (min-width: 414px) {
+    .location-address {
+        padding: 16px;
+    }
+    
+    .coord-row {
+        padding: 16px;
+    }
+    
+    .accuracy-info {
+        padding: 12px 16px;
+    }
+    
+    .coord-value {
+        font-size: 15px;
+    }
+}
+
+/* 深色模式适配 */
+@media (prefers-color-scheme: dark) {
+    .location-address,
+    .coord-row {
+        background: rgba(255, 255, 255, 0.05);
+    }
+    
+    .accuracy-info {
+        background: rgba(103, 194, 58, 0.1);
+        border-color: rgba(103, 194, 58, 0.3);
+    }
+    
+    .error-tips-section {
+        background: rgba(255, 247, 230, 0.1);
+        border-color: rgba(255, 213, 145, 0.3);
+    }
+}
 </style>

+ 6 - 6
web/src/api/zsjy/internshipDaily.js

@@ -1,4 +1,4 @@
-import request from '@/utils/request'
+import request from '@/utils/request';
 
 // 查询实习日常记录列表
 export function listInternshipDaily(query) {
@@ -6,7 +6,7 @@ export function listInternshipDaily(query) {
     url: '/zsjy/internship/daily/list',
     method: 'get',
     params: query
-  })
+  });
 }
 
 // 学生签到打卡
@@ -15,7 +15,7 @@ export function checkIn(data) {
     url: '/zsjy/internship/daily/checkin',
     method: 'post',
     data: data
-  })
+  });
 }
 
 // 新增实习日常记录
@@ -24,7 +24,7 @@ export function addInternshipDaily(data) {
     url: '/zsjy/internship/daily',
     method: 'post',
     data: data
-  })
+  });
 }
 
 // 获取学生考勤日历
@@ -33,7 +33,7 @@ export function getCalendar(studentId, params) {
     url: `/zsjy/internship/daily/calendar/${studentId}`,
     method: 'get',
     params: params
-  })
+  });
 }
 
 // 获取考勤统计数据
@@ -42,5 +42,5 @@ export function getStatistics(query) {
     url: '/zsjy/internship/daily/statistics',
     method: 'get',
     params: query
-  })
+  });
 }

+ 290 - 0
web/src/api/zsjy/types.ts

@@ -1195,6 +1195,16 @@ export interface InternshipInfoQuery {
    * 实习开始日期-结束
    */
   internshipStartDateEnd?: string;
+
+  /**
+   * 班主任ID(用于数据权限控制)
+   */
+  classTeacherId?: string | number;
+
+  /**
+   * 辅导员ID(用于数据权限控制)
+   */
+  counselorId?: string | number;
 }
 
 /**
@@ -1261,3 +1271,283 @@ export interface InternshipStatisticsVO {
    */
   internshipRate?: string;
 }
+
+/**
+ * 班主任走访记录 VO
+ */
+export interface InternshipVisitVO {
+  /**
+   * 走访记录ID
+   */
+  visitId?: string | number;
+
+  /**
+   * 实习记录ID
+   */
+  internshipId?: string | number;
+
+  /**
+   * 学生ID
+   */
+  studentId?: string | number;
+
+  /**
+   * 学生姓名
+   */
+  studentName?: string;
+
+  /**
+   * 学号
+   */
+  studentNumber?: string;
+
+  /**
+   * 班级名称
+   */
+  className?: string;
+
+  /**
+   * 专业名称
+   */
+  majorName?: string;
+
+  /**
+   * 学院名称
+   */
+  collegeName?: string;
+
+  /**
+   * 企业名称
+   */
+  companyName?: string;
+
+  /**
+   * 企业地址
+   */
+  companyAddress?: string;
+
+  /**
+   * 走访日期
+   */
+  visitDate?: string;
+
+  /**
+   * 走访人员
+   */
+  visitorName?: string;
+
+  /**
+   * 走访人员职务
+   */
+  visitorPosition?: string;
+
+  /**
+   * 走访目的
+   */
+  visitPurpose?: string;
+
+  /**
+   * 企业情况
+   */
+  companySituation?: string;
+
+  /**
+   * 学生表现
+   */
+  studentPerformance?: string;
+
+  /**
+   * 发现问题
+   */
+  problemsFound?: string;
+
+  /**
+   * 改进建议
+   */
+  suggestions?: string;
+
+  /**
+   * 走访总结
+   */
+  visitSummary?: string;
+
+  /**
+   * 现场照片(JSON格式)
+   */
+  visitImages?: string;
+
+  /**
+   * 走访文件附件(JSON格式)
+   */
+  visitFiles?: string;
+
+  /**
+   * 状态(0-草稿 1-已提交 2-已审核)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 创建时间
+   */
+  createTime?: string;
+
+  /**
+   * 创建者
+   */
+  createBy?: number;
+
+  /**
+   * 更新时间
+   */
+  updateTime?: string;
+
+  /**
+   * 更新者
+   */
+  updateBy?: number;
+}
+
+/**
+ * 班主任走访记录查询参数
+ */
+export interface InternshipVisitQuery extends PageQuery {
+  /**
+   * 学生姓名
+   */
+  studentName?: string;
+
+  /**
+   * 学号
+   */
+  studentNumber?: string;
+
+  /**
+   * 企业名称
+   */
+  companyName?: string;
+
+  /**
+   * 走访人员
+   */
+  visitorName?: string;
+
+  /**
+   * 走访日期-开始
+   */
+  visitDateBegin?: string;
+
+  /**
+   * 走访日期-结束
+   */
+  visitDateEnd?: string;
+
+  /**
+   * 状态
+   */
+  status?: string;
+}
+
+/**
+ * 班主任走访记录新增参数
+ */
+export interface InternshipVisitAddBO {
+  /**
+   * 实习记录ID
+   */
+  internshipId?: string | number;
+
+  /**
+   * 学生ID
+   */
+  studentId?: string | number;
+
+  /**
+   * 企业名称
+   */
+  companyName?: string;
+
+  /**
+   * 企业地址
+   */
+  companyAddress?: string;
+
+  /**
+   * 走访日期
+   */
+  visitDate?: string;
+
+  /**
+   * 走访人员
+   */
+  visitorName?: string;
+
+  /**
+   * 走访人员职务
+   */
+  visitorPosition?: string;
+
+  /**
+   * 走访目的
+   */
+  visitPurpose?: string;
+
+  /**
+   * 企业情况
+   */
+  companySituation?: string;
+
+  /**
+   * 学生表现
+   */
+  studentPerformance?: string;
+
+  /**
+   * 发现问题
+   */
+  problemsFound?: string;
+
+  /**
+   * 改进建议
+   */
+  suggestions?: string;
+
+  /**
+   * 走访总结
+   */
+  visitSummary?: string;
+
+  /**
+   * 现场照片(JSON格式)
+   */
+  visitImages?: string;
+
+  /**
+   * 走访文件附件(JSON格式)
+   */
+  visitFiles?: string;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+/**
+ * 班主任走访记录编辑参数
+ */
+export interface InternshipVisitEditBO extends InternshipVisitAddBO {
+  /**
+   * 走访记录ID
+   */
+  visitId?: string | number;
+}

+ 88 - 4
web/src/components/ImageUpload/index.vue

@@ -455,14 +455,38 @@ const handleBeforeUpload = (file: any) => {
     }
   }
 
+  // 设置超时处理 - 如果60秒后仍在loading状态,自动关闭并提示
+  const timeoutId = setTimeout(() => {
+    if (number.value > 0) {
+      console.warn('上传超时,自动关闭loading');
+      (proxy as any)?.$modal.closeLoading();
+      (proxy as any)?.$modal.msgError('上传超时,请检查网络连接或重试');
+      number.value = 0; // 重置计数器
+    }
+  }, 60000); // 60秒超时
+
   //压缩图片,开启压缩并且大于指定的压缩大小时才压缩
   if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
     (proxy as any)?.$modal.loading('正在上传图片,请稍候...');
     number.value++;
-    return compressAccurately(file, props.compressTargetSize);
+
+    // 压缩图片时的错误处理
+    try {
+      return compressAccurately(file, props.compressTargetSize);
+    } catch (compressionError) {
+      console.error('图片压缩失败:', compressionError);
+      clearTimeout(timeoutId);
+      (proxy as any)?.$modal.closeLoading();
+      (proxy as any)?.$modal.msgError('图片压缩失败,请重试');
+      number.value--;
+      return false;
+    }
   } else {
     (proxy as any)?.$modal.loading('正在上传图片,请稍候...');
     number.value++;
+
+    // 保存timeout ID以便在上传完成时清除
+    (file as any).uploadTimeoutId = timeoutId;
   }
 };
 
@@ -476,6 +500,11 @@ const handleUploadSuccess = (res: any, file: any) => {
   console.log('handleUploadSuccess:res', res);
   console.log('handleUploadSuccess:file', file);
 
+  // 清除上传超时处理
+  if ((file as any).uploadTimeoutId) {
+    clearTimeout((file as any).uploadTimeoutId);
+  }
+
   if (res.code === 200) {
     const uploadItem = {
       name: res.data.fileName,
@@ -488,7 +517,9 @@ const handleUploadSuccess = (res: any, file: any) => {
   } else {
     number.value--;
     (proxy as any)?.$modal.closeLoading();
-    (proxy as any)?.$modal.msgError(res.msg);
+    // 提供更详细的错误信息
+    const errorMsg = res.msg || '上传失败,请重试';
+    (proxy as any)?.$modal.msgError(errorMsg);
     imageUploadRef.value?.handleRemove(file);
     uploadedSuccessfully();
   }
@@ -657,9 +688,62 @@ const uploadedSuccessfully = async () => {
 };
 
 // 上传失败
-const handleUploadError = () => {
-  (proxy as any)?.$modal.msgError('上传图片失败');
+const handleUploadError = (error: any, file: any) => {
+  console.error('上传失败详细信息:', error);
+
+  // 清除上传超时处理
+  if (file && (file as any).uploadTimeoutId) {
+    clearTimeout((file as any).uploadTimeoutId);
+  }
+
+  // 确保关闭loading
   (proxy as any)?.$modal.closeLoading();
+
+  // 减少计数器
+  number.value--;
+
+  // 根据错误类型提供不同的错误信息
+  let errorMessage = '上传图片失败';
+
+  if (error.response) {
+    const status = error.response.status;
+    const responseData = error.response.data;
+
+    switch (status) {
+      case 403:
+        errorMessage = '没有文件上传权限,请联系管理员';
+        break;
+      case 413:
+        errorMessage = '文件太大,超过服务器限制';
+        break;
+      case 500:
+        errorMessage = '服务器内部错误,请稍后重试';
+        break;
+      case 504:
+        errorMessage = '上传超时,请检查网络连接';
+        break;
+      default:
+        if (responseData && responseData.msg) {
+          errorMessage = responseData.msg;
+        } else if (responseData && typeof responseData === 'string') {
+          errorMessage = `上传失败: ${responseData}`;
+        } else {
+          errorMessage = `上传失败,错误代码: ${status}`;
+        }
+    }
+  } else if (error.request) {
+    // 网络错误
+    if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) {
+      errorMessage = '上传超时,请检查网络连接或文件大小';
+    } else {
+      errorMessage = '网络连接错误,请检查网络设置';
+    }
+  } else if (error.message) {
+    errorMessage = `上传失败: ${error.message}`;
+  }
+
+  console.error('上传错误信息:', errorMessage);
+  (proxy as any)?.$modal.msgError(errorMessage);
 };
 
 // 预览

+ 15 - 2
web/src/views/jcbt/workstudy/xgreview/README.md

@@ -7,12 +7,14 @@
 ## 功能特性
 
 ### 1. 统计概览
+
 - 待审核申请数量
-- 已通过申请数量  
+- 已通过申请数量
 - 已拒绝申请数量
 - 总处理数量
 
 ### 2. 待审核申请管理
+
 - 查看待审核申请列表
 - 支持多条件搜索(岗位名称、学生姓名、学号、学院名称、申请日期)
 - 单个申请审核(通过/拒绝)
@@ -20,6 +22,7 @@
 - 查看申请详情
 
 ### 3. 所有申请查看
+
 - 查看所有申请记录
 - 支持按状态筛选
 - 导出申请数据
@@ -40,16 +43,19 @@ xgreview/
 ## 技术特点
 
 ### 1. 响应式设计
+
 - 使用Vue 3 Composition API
 - TypeScript类型支持
 - Element Plus UI组件库
 
 ### 2. 数据管理
+
 - 基于学工处审核API
 - 支持分页查询
 - 实时数据更新
 
 ### 3. 用户体验
+
 - 加载状态提示
 - 操作反馈消息
 - 响应式布局
@@ -57,6 +63,7 @@ xgreview/
 ## API接口
 
 ### 主要接口
+
 - `listPendingApplications` - 获取待审核申请列表
 - `listStudentAffairsApplications` - 获取所有申请列表
 - `getStudentAffairsApplication` - 获取申请详情
@@ -68,15 +75,18 @@ xgreview/
 ## 使用说明
 
 ### 1. 访问路径
+
 ```
 /jcbt/workstudy/xgreview
 ```
 
 ### 2. 权限要求
+
 - 需要学工处审核权限
 - 支持批量操作权限
 
 ### 3. 操作流程
+
 1. 查看统计概览,了解待处理申请数量
 2. 在"待审核申请"标签页中查看待处理申请
 3. 使用搜索功能筛选特定申请
@@ -100,16 +110,19 @@ xgreview/
 ## 开发说明
 
 ### 1. 组件复用
+
 本模块复用了学院审核模块的组件结构,但使用不同的API接口和字段。
 
 ### 2. 样式规范
+
 遵循项目的前端开发规范,使用统一的颜色主题和组件样式。
 
 ### 3. 错误处理
+
 包含完整的错误处理机制,提供用户友好的错误提示。
 
 ## 更新日志
 
 - **2025-01-03**:创建学工处审核模块
 - **2025-01-03**:实现基础审核功能
-- **2025-01-03**:添加统计概览和批量操作 
+- **2025-01-03**:添加统计概览和批量操作

+ 35 - 10
web/src/views/jcbt/workstudy/xgreview/开发总结.md

@@ -7,12 +7,14 @@
 ## ✅ 已完成功能
 
 ### 1. 主页面 (index.vue)
+
 - ✅ 统计卡片展示(待审核、已通过、已拒绝、总计)
 - ✅ 标签页切换(待审核申请、所有申请)
 - ✅ 响应式布局和美观的UI设计
 - ✅ 与后勤管理模块保持一致的样式风格
 
 ### 2. 待审核申请组件 (PendingApplications.vue)
+
 - ✅ 多条件搜索表单(岗位名称、学生姓名、学号、学院名称、申请日期)
 - ✅ 批量操作按钮(批量通过、批量拒绝)
 - ✅ 数据表格展示(包含学院审核状态)
@@ -22,6 +24,7 @@
 - ✅ 分页功能
 
 ### 3. 所有申请组件 (AllApplications.vue)
+
 - ✅ 扩展搜索条件(包含学工处审核状态)
 - ✅ 完整申请列表展示
 - ✅ 学院审核和学工处审核状态对比
@@ -29,6 +32,7 @@
 - ✅ 审核操作(针对待审核状态)
 
 ### 4. 申请详情组件 (ApplicationDetail.vue)
+
 - ✅ 完整的申请信息展示
 - ✅ 学院审核信息展示
 - ✅ 学工处审核信息展示
@@ -36,23 +40,27 @@
 - ✅ 立即审核功能
 
 ### 5. 路由配置 (jcbt.ts)
+
 - ✅ 创建jcbt模块路由配置
 - ✅ 包含学工处审核模块路由
 - ✅ 支持懒加载
 
 ### 6. 文档说明
+
 - ✅ README.md - 详细的功能说明和使用指南
 - ✅ 开发总结.md - 开发过程和成果总结
 
 ## 🏗️ 技术架构
 
 ### 前端技术栈
+
 - **Vue 3.5.13** + **TypeScript 5.8.3**
 - **Element Plus** UI组件库
 - **Composition API** 响应式编程
 - **SCSS** 样式预处理器
 
 ### 组件结构
+
 ```
 xgreview/
 ├── index.vue                    # 主页面
@@ -66,6 +74,7 @@ xgreview/
 ```
 
 ### API接口
+
 - 基于 `@/api/jcbt/workStudyApplicationStudentAffairsReview.ts`
 - 支持加密请求(AES+RSA)
 - 完整的CRUD操作接口
@@ -73,16 +82,19 @@ xgreview/
 ## 🎨 设计特点
 
 ### 1. 视觉一致性
+
 - 与学院审核模块保持完全一致的视觉风格
 - 使用相同的颜色主题和组件样式
 - 统一的布局结构和交互模式
 
 ### 2. 用户体验
+
 - 响应式设计,支持不同屏幕尺寸
 - 加载状态提示和操作反馈
 - 直观的操作流程和清晰的界面布局
 
 ### 3. 功能完整性
+
 - 覆盖完整的审核业务流程
 - 支持单个和批量操作
 - 提供详细的数据展示和操作记录
@@ -90,53 +102,61 @@ xgreview/
 ## 🔧 开发规范遵循
 
 ### 1. 前端开发规范
+
 - ✅ 遵循 `.cursor/cursorrules-frontend.md` 规范
 - ✅ 使用Vue 3 Composition API
 - ✅ TypeScript类型定义完整
 - ✅ 组件化开发,代码复用
 
 ### 2. 项目结构规范
+
 - ✅ 页面代码存放在 `web/src/views/jcbt/workstudy/xgreview/`
 - ✅ API代码存放在 `web/src/api/jcbt/`
 - ✅ 路由配置存放在 `web/src/router/`
 
 ### 3. 代码质量
+
 - ✅ 完整的错误处理机制
 - ✅ 清晰的代码注释和文档
 - ✅ 遵循Vue 3最佳实践
 
 ## 📊 功能对比
 
-| 功能模块 | 学院审核 | 学工处审核 | 状态 |
-|---------|---------|-----------|------|
-| 统计概览 | ✅ | ✅ | 完全一致 |
-| 待审核列表 | ✅ | ✅ | 完全一致 |
-| 所有申请 | ✅ | ✅ | 完全一致 |
-| 申请详情 | ✅ | ✅ | 完全一致 |
-| 审核操作 | ✅ | ✅ | 完全一致 |
-| 批量操作 | ✅ | ✅ | 完全一致 |
-| 导出功能 | ✅ | ✅ | 完全一致 |
+| 功能模块   | 学院审核 | 学工处审核 | 状态     |
+| ---------- | -------- | ---------- | -------- |
+| 统计概览   | ✅       | ✅         | 完全一致 |
+| 待审核列表 | ✅       | ✅         | 完全一致 |
+| 所有申请   | ✅       | ✅         | 完全一致 |
+| 申请详情   | ✅       | ✅         | 完全一致 |
+| 审核操作   | ✅       | ✅         | 完全一致 |
+| 批量操作   | ✅       | ✅         | 完全一致 |
+| 导出功能   | ✅       | ✅         | 完全一致 |
 
 ## 🚀 部署说明
 
 ### 1. 文件位置
+
 所有文件已正确放置在指定目录:
+
 - 主页面:`web/src/views/jcbt/workstudy/xgreview/index.vue`
 - 组件:`web/src/views/jcbt/workstudy/xgreview/components/`
 - 路由:`web/src/router/jcbt.ts`
 
 ### 2. 访问路径
+
 ```
 /jcbt/workstudy/xgreview
 ```
 
 ### 3. 权限要求
+
 - 需要学工处审核权限
 - 支持批量操作权限
 
 ## 🔍 测试建议
 
 ### 1. 功能测试
+
 - 统计数据展示
 - 搜索和筛选功能
 - 审核操作流程
@@ -144,11 +164,13 @@ xgreview/
 - 导出功能
 
 ### 2. 兼容性测试
+
 - 不同浏览器兼容性
 - 不同屏幕尺寸响应式
 - 权限控制验证
 
 ### 3. 性能测试
+
 - 大数据量加载性能
 - 操作响应速度
 - 内存使用情况
@@ -156,16 +178,19 @@ xgreview/
 ## 📝 后续优化建议
 
 ### 1. 功能增强
+
 - 添加审核历史记录
 - 支持审核意见模板
 - 增加数据统计图表
 
 ### 2. 性能优化
+
 - 实现虚拟滚动
 - 添加数据缓存
 - 优化API请求
 
 ### 3. 用户体验
+
 - 添加操作引导
 - 支持快捷键操作
 - 增加个性化设置
@@ -186,4 +211,4 @@ xgreview/
 **开发完成时间**:2025-01-03  
 **开发状态**:✅ 完成  
 **代码质量**:⭐⭐⭐⭐⭐  
-**文档完整性**:⭐⭐⭐⭐⭐ 
+**文档完整性**:⭐⭐⭐⭐⭐

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

@@ -216,8 +216,8 @@
 </template>
 
 <script setup name="InternshipCheckin">
-import { checkIn } from "@/api/zsjy/internshipDaily";
-import { parseTime } from "@/utils/ruoyi";
+import { checkIn } from '@/api/zsjy/internshipDaily';
+import { parseTime } from '@/utils/ruoyi';
 
 const { proxy } = getCurrentInstance();
 
@@ -273,7 +273,7 @@ const getRecentRecords = () => {
       workDuration: 8,
       status: '1',
       workContent: '参与项目开发,完成用户模块设计'
-    },
+    }
     // 更多记录...
   ];
 };

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

@@ -212,13 +212,7 @@
 
       <!-- 分页组件 -->
       <div class="pagination-container">
-        <pagination
-          v-show="total > 0"
-          :total="total"
-          :page="queryParams.pageNum"
-          :limit="queryParams.pageSize"
-          @pagination="getList"
-        />
+        <pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @pagination="getList" />
       </div>
     </el-card>
 
@@ -244,10 +238,10 @@
           </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>
+            <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>
+            <div style="white-space: pre-wrap">{{ currentRecord.remark || '-' }}</div>
           </el-descriptions-item>
         </el-descriptions>
       </div>
@@ -277,10 +271,7 @@
             <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"
-                >
+                <el-tag :type="getStatusTagType(getAttendanceStatus(data.day))" size="small">
                   {{ getStatusText(getAttendanceStatus(data.day)) }}
                 </el-tag>
               </div>
@@ -350,25 +341,13 @@
           <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-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-input-number v-model="addForm.workDuration" :min="0" :max="24" :precision="1" placeholder="工作时长(小时)" style="width: 100%" />
             </el-form-item>
           </el-col>
         </el-row>
@@ -376,24 +355,10 @@
           <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-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-input v-model="addForm.remark" type="textarea" :rows="2" placeholder="请输入备注" maxlength="200" show-word-limit />
         </el-form-item>
       </el-form>
       <template #footer>
@@ -447,24 +412,12 @@ const addForm = ref({
 
 // 新增表单验证规则
 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' }
-  ]
+  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' }]
 });
 
 // 查询参数
@@ -500,7 +453,7 @@ const getList = () => {
     queryParams.value.checkTimeEnd = '';
   }
 
-  listInternshipDaily(queryParams.value).then(response => {
+  listInternshipDaily(queryParams.value).then((response) => {
     internshipDailyList.value = response.rows;
     total.value = response.total;
     loading.value = false;
@@ -510,13 +463,15 @@ const getList = () => {
 // 获取统计数据
 const getStatisticsData = () => {
   console.log('正在获取统计数据...');
-  getStatistics().then(response => {
-    console.log('统计数据响应:', response);
-    statistics.value = response.data || {};
-    console.log('设置的统计数据:', statistics.value);
-  }).catch(error => {
-    console.error('获取统计数据失败:', error);
-  });
+  getStatistics()
+    .then((response) => {
+      console.log('统计数据响应:', response);
+      statistics.value = response.data || {};
+      console.log('设置的统计数据:', statistics.value);
+    })
+    .catch((error) => {
+      console.error('获取统计数据失败:', error);
+    });
 };
 
 // 搜索按钮操作
@@ -528,7 +483,7 @@ const handleQuery = () => {
 // 重置按钮操作
 const resetQuery = () => {
   daterangeCheckTime.value = [];
-  proxy.resetForm("queryFormRef");
+  proxy.resetForm('queryFormRef');
   queryParams.value = {
     pageNum: 1,
     pageSize: 10,
@@ -581,17 +536,19 @@ const cancelAdd = () => {
 
 // 提交新增
 const submitAdd = () => {
-  proxy.$refs.addFormRef.validate(valid => {
+  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;
-      });
+      addInternshipDaily(addForm.value)
+        .then((response) => {
+          proxy.$modal.msgSuccess('新增签到记录成功');
+          addDialogVisible.value = false;
+          getList();
+        })
+        .finally(() => {
+          addLoading.value = false;
+        });
     }
   });
 };
@@ -625,16 +582,14 @@ const loadCalendarData = () => {
     month: calendarMonth.value
   };
 
-  getCalendar(currentStudent.value.studentId, params).then(response => {
+  getCalendar(currentStudent.value.studentId, params).then((response) => {
     calendarData.value = response.data || [];
   });
 };
 
 // 获取考勤状态
 const getAttendanceStatus = (date) => {
-  const record = calendarData.value.find(item =>
-    item.checkDate === date
-  );
+  const record = calendarData.value.find((item) => item.checkDate === date);
   return record ? record.status : null;
 };
 
@@ -662,9 +617,13 @@ const getStatusText = (status) => {
 
 // 导出按钮操作
 const handleExport = () => {
-  proxy.download('zsjy/internship/daily/export', {
-    ...queryParams.value
-  }, `实习日常记录_${new Date().getTime()}.xlsx`);
+  proxy.download(
+    'zsjy/internship/daily/export',
+    {
+      ...queryParams.value
+    },
+    `实习日常记录_${new Date().getTime()}.xlsx`
+  );
 };
 
 // 初始化

+ 9 - 10
web/src/views/zsjy/internship/msgmanage/components/AddDialog.vue

@@ -1,12 +1,5 @@
 <template>
-  <el-dialog
-    v-model="visible"
-    title="新增实习信息"
-    width="80%"
-    :before-close="handleClose"
-    draggable
-    destroy-on-close
-  >
+  <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">
         <!-- 基本信息 -->
@@ -32,7 +25,14 @@
         </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-input-number
+              v-model="form.internshipAllowance"
+              :min="0"
+              :precision="2"
+              controls-position="right"
+              placeholder="实习津贴"
+              style="width: 100%"
+            />
           </el-form-item>
         </el-col>
 
@@ -363,4 +363,3 @@ const handleSubmit = async () => {
   padding-bottom: 10px;
 }
 </style>
- 

+ 17 - 15
web/src/views/zsjy/internship/msgmanage/components/EditDialog.vue

@@ -1,12 +1,5 @@
 <template>
-  <el-dialog
-    v-model="visible"
-    title="编辑实习信息"
-    width="80%"
-    :before-close="handleClose"
-    draggable
-    destroy-on-close
-  >
+  <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">
         <!-- 基本信息 -->
@@ -32,7 +25,14 @@
         </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-input-number
+              v-model="form.internshipAllowance"
+              :min="0"
+              :precision="2"
+              controls-position="right"
+              placeholder="实习津贴"
+              style="width: 100%"
+            />
           </el-form-item>
         </el-col>
 
@@ -277,12 +277,15 @@ const rules = reactive({
 });
 
 // 监听弹窗显示状态
-watch(() => props.modelValue, (newVal) => {
-  visible.value = newVal;
-  if (newVal && props.studentId) {
-    getInternshipDetail();
+watch(
+  () => props.modelValue,
+  (newVal) => {
+    visible.value = newVal;
+    if (newVal && props.studentId) {
+      getInternshipDetail();
+    }
   }
-});
+);
 
 watch(visible, (newVal) => {
   emit('update:modelValue', newVal);
@@ -434,4 +437,3 @@ const handleDelete = async () => {
   padding-bottom: 10px;
 }
 </style>
- 

+ 5 - 13
web/src/views/zsjy/internship/msgmanage/index.vue

@@ -191,8 +191,6 @@
       </div>
     </el-card>
 
-
-
     <!-- 学生实习详情对话框 -->
     <el-dialog title="学生实习详情" v-model="dialog.visible" width="900px" append-to-body>
       <div v-if="studentDetail" class="student-detail">
@@ -226,7 +224,9 @@
           <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-if="studentDetail.internshipAllowance && studentDetail.internshipAllowance > 0"
+              >{{ studentDetail.internshipAllowance }}元/月</span
+            >
             <span v-else>-</span>
           </el-descriptions-item>
           <el-descriptions-item label="协议状态">
@@ -245,18 +245,10 @@
     </el-dialog>
 
     <!-- 新增弹窗 -->
-    <AddDialog
-      v-model="showAddDialog"
-      :student-info="selectedStudentInfo"
-      @success="handleDialogSuccess"
-    />
+    <AddDialog v-model="showAddDialog" :student-info="selectedStudentInfo" @success="handleDialogSuccess" />
 
     <!-- 编辑弹窗 -->
-    <EditDialog
-      v-model="showEditDialog"
-      :student-id="selectedStudentId"
-      @success="handleDialogSuccess"
-    />
+    <EditDialog v-model="showEditDialog" :student-id="selectedStudentId" @success="handleDialogSuccess" />
   </div>
 </template>