|
|
@@ -0,0 +1,3673 @@
|
|
|
+<template>
|
|
|
+ <view class="page-container">
|
|
|
+ <!-- 顶部导航 -->
|
|
|
+ <u-navbar :title="navTitle" :border-bottom="false" bg-color="#3c9cff" title-color="#fff">
|
|
|
+ <view slot="left" @click="goBack">
|
|
|
+ <u-icon name="arrow-left" color="#fff" size="22"></u-icon>
|
|
|
+ </view>
|
|
|
+ </u-navbar>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <u-loading-page :loading="pageLoading" loading-text="加载中..."></u-loading-page>
|
|
|
+
|
|
|
+ <!-- 表单内容 -->
|
|
|
+ <view v-if="!pageLoading" class="form-container">
|
|
|
+ <u-form ref="formRef" :model="formData" :rules="rules" label-width="100">
|
|
|
+ <!-- 基本信息 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="section-title">基本信息</text>
|
|
|
+
|
|
|
+ <u-form-item label="检查标题" prop="checkTitle" required>
|
|
|
+ <u-input
|
|
|
+ v-model="formData.checkTitle"
|
|
|
+ placeholder="请输入检查标题"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ />
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="检查日期" prop="checkDate" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showDatePicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="checkDateDisplay"
|
|
|
+ placeholder="请选择检查日期"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-datetime-picker
|
|
|
+ :show="showDatePicker"
|
|
|
+ v-model="checkDateValue"
|
|
|
+ mode="datetime"
|
|
|
+ @confirm="handleDateConfirm"
|
|
|
+ @cancel="showDatePicker = false"
|
|
|
+ ></u-datetime-picker>
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="检查类型" prop="checkType" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showCheckTypePicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="checkTypeDisplay"
|
|
|
+ placeholder="请选择检查类型"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showCheckTypePicker && xg_dorm_hygiene_check_type.length > 0"
|
|
|
+ :columns="checkTypeColumns"
|
|
|
+ :defaultIndex="checkTypeDefaultIndex"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleCheckTypeConfirm"
|
|
|
+ @cancel="showCheckTypePicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="检查级别" prop="checkLevel" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showCheckLevelPicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="checkLevelDisplay"
|
|
|
+ placeholder="请选择检查级别"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showCheckLevelPicker && xg_dorm_hygiene_check_level.length > 0"
|
|
|
+ :columns="checkLevelColumns"
|
|
|
+ :defaultIndex="checkLevelDefaultIndex"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleCheckLevelConfirm"
|
|
|
+ @cancel="showCheckLevelPicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 位置信息 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="section-title">位置信息</text>
|
|
|
+
|
|
|
+ <u-form-item label="校区" prop="campusId" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showCampusPicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="formData.campusName"
|
|
|
+ placeholder="请选择校区"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showCampusPicker"
|
|
|
+ :columns="campusColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleCampusConfirm"
|
|
|
+ @cancel="showCampusPicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="宿舍区" prop="areaId" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showAreaPicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="formData.areaName"
|
|
|
+ placeholder="请选择宿舍区"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :disabled="!formData.campusId"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showAreaPicker"
|
|
|
+ :columns="areaColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleAreaConfirm"
|
|
|
+ @cancel="showAreaPicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="宿舍楼" prop="buildingId" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showBuildingPicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="formData.buildingName"
|
|
|
+ placeholder="请选择宿舍楼"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :disabled="!formData.areaId"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showBuildingPicker"
|
|
|
+ :columns="buildingColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleBuildingConfirm"
|
|
|
+ @cancel="showBuildingPicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="房间" prop="roomId" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showRoomPicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="formData.roomName"
|
|
|
+ placeholder="请选择房间"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :disabled="!formData.buildingId"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showRoomPicker"
|
|
|
+ :columns="roomColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleRoomConfirm"
|
|
|
+ @cancel="showRoomPicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 检查结果 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="section-title">检查结果</text>
|
|
|
+
|
|
|
+ <u-form-item label="宿舍总分" prop="totalScore" required>
|
|
|
+ <u-input
|
|
|
+ v-model="formData.totalScore"
|
|
|
+ type="digit"
|
|
|
+ placeholder="请输入宿舍总分(0-100分)"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ @input="pageType !== 'view' && handleTotalScoreChange"
|
|
|
+ />
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="卫生等级" prop="hygieneGrade" required>
|
|
|
+ <view
|
|
|
+ @click="pageType !== 'view' && (showHygieneGradePicker = true)"
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ v-model="hygieneGradeDisplay"
|
|
|
+ placeholder="请选择卫生等级"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="pageType === 'view' ? '' : 'arrow-down'"
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="showHygieneGradePicker && xg_dorm_hygiene_grade.length > 0"
|
|
|
+ :columns="hygieneGradeColumns"
|
|
|
+ :defaultIndex="hygieneGradeDefaultIndex"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="handleHygieneGradeConfirm"
|
|
|
+ @cancel="showHygieneGradePicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </u-form-item>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 房间学生信息 -->
|
|
|
+ <view v-if="formData.roomId && roomStudents.length > 0" class="form-section">
|
|
|
+ <text class="section-title">房间学生信息</text>
|
|
|
+
|
|
|
+ <view class="student-list">
|
|
|
+ <view
|
|
|
+ v-for="(student, index) in roomStudents"
|
|
|
+ :key="index"
|
|
|
+ class="student-item"
|
|
|
+ >
|
|
|
+ <view class="student-header">
|
|
|
+ <text class="student-name">{{ student.studentName }}</text>
|
|
|
+ <text class="student-no">{{ student.studentNo }}</text>
|
|
|
+ <text class="student-bed">{{ student.bedName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="student-form">
|
|
|
+ <view class="form-row-flex">
|
|
|
+ <view class="form-col-flex">
|
|
|
+ <text class="form-label">个人得分</text>
|
|
|
+ <u-input
|
|
|
+ v-model="student.student_score"
|
|
|
+ type="digit"
|
|
|
+ placeholder="0-100分"
|
|
|
+ size="small"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ style="
|
|
|
+ height: 60rpx;
|
|
|
+ line-height: 60rpx;
|
|
|
+ padding: 0px 10px !important;
|
|
|
+ "
|
|
|
+ @input="
|
|
|
+ pageType !== 'view' &&
|
|
|
+ handleStudentScoreChange(student)
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <view class="form-col-flex">
|
|
|
+ <text class="form-label">出勤状态</text>
|
|
|
+ <view
|
|
|
+ @click="
|
|
|
+ pageType !== 'view' &&
|
|
|
+ (student.showAttendancePicker = true)
|
|
|
+ "
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ :value="
|
|
|
+ getAttendanceDisplay(student.attendance_status)
|
|
|
+ "
|
|
|
+ placeholder="请选择"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="
|
|
|
+ pageType === 'view' ? '' : 'arrow-down'
|
|
|
+ "
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ size="small"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="
|
|
|
+ student.showAttendancePicker &&
|
|
|
+ xg_student_attendance_status.length > 0
|
|
|
+ "
|
|
|
+ :columns="attendanceStatusColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="(e) => handleAttendanceConfirm(e, student)"
|
|
|
+ @cancel="student.showAttendancePicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </view>
|
|
|
+ <view class="form-col-flex">
|
|
|
+ <text class="form-label">学生评级</text>
|
|
|
+ <view
|
|
|
+ @click="
|
|
|
+ pageType !== 'view' &&
|
|
|
+ (student.showGradePicker = true)
|
|
|
+ "
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <u-input
|
|
|
+ :value="getGradeDisplay(student.student_grade)"
|
|
|
+ placeholder="请选择"
|
|
|
+ readonly
|
|
|
+ border="none"
|
|
|
+ :suffix-icon="
|
|
|
+ pageType === 'view' ? '' : 'arrow-down'
|
|
|
+ "
|
|
|
+ suffix-icon-style="color: #c8c9cc;"
|
|
|
+ size="small"
|
|
|
+ :style="
|
|
|
+ pageType === 'view'
|
|
|
+ ? 'pointer-events: none'
|
|
|
+ : 'pointer-events: none'
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ <u-picker
|
|
|
+ :show="
|
|
|
+ student.showGradePicker &&
|
|
|
+ xg_dorm_hygiene_grade.length > 0
|
|
|
+ "
|
|
|
+ :columns="hygieneGradeColumns"
|
|
|
+ keyName="text"
|
|
|
+ @confirm="(e) => handleStudentGradeConfirm(e, student)"
|
|
|
+ @cancel="student.showGradePicker = false"
|
|
|
+ ></u-picker>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 附件上传 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="section-title">附件上传</text>
|
|
|
+
|
|
|
+ <view class="upload-area">
|
|
|
+ <!-- 图片展示区域 -->
|
|
|
+ <view v-if="imageFiles.length > 0" class="image-section">
|
|
|
+ <view class="image-title">图片附件</view>
|
|
|
+ <view class="image-grid">
|
|
|
+ <view
|
|
|
+ v-for="(image, index) in imageFiles"
|
|
|
+ :key="image.uid || index"
|
|
|
+ class="image-item"
|
|
|
+ @click="handleImageClick(image, index, $event)"
|
|
|
+ >
|
|
|
+ <!-- 图片容器 -->
|
|
|
+ <view class="image-container">
|
|
|
+ <image
|
|
|
+ :src="image.blobUrl || image.url"
|
|
|
+ class="attachment-image"
|
|
|
+ mode="aspectFill"
|
|
|
+ ></image>
|
|
|
+
|
|
|
+ <!-- 上传状态遮罩 -->
|
|
|
+ <view
|
|
|
+ v-if="image.status === 'uploading'"
|
|
|
+ class="upload-overlay"
|
|
|
+ >
|
|
|
+ <u-loading-icon
|
|
|
+ mode="circle"
|
|
|
+ size="24"
|
|
|
+ color="#fff"
|
|
|
+ ></u-loading-icon>
|
|
|
+ <text class="upload-text">
|
|
|
+ {{ image.progress || 0 }}%
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 错误状态遮罩 -->
|
|
|
+ <view v-if="image.status === 'error'" class="error-overlay">
|
|
|
+ <u-icon
|
|
|
+ name="close-circle"
|
|
|
+ size="24"
|
|
|
+ color="#fff"
|
|
|
+ ></u-icon>
|
|
|
+ <text class="error-text">上传失败</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成功状态角标 -->
|
|
|
+ <view
|
|
|
+ v-if="image.status === 'success'"
|
|
|
+ class="success-badge"
|
|
|
+ >
|
|
|
+ <u-icon
|
|
|
+ name="checkmark-circle-fill"
|
|
|
+ size="18"
|
|
|
+ color="#52c41a"
|
|
|
+ ></u-icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 图片操作按钮 -->
|
|
|
+ <view class="image-actions" @click.stop>
|
|
|
+ <u-button
|
|
|
+ v-if="image.status === 'success'"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ plain
|
|
|
+ @tap="handleImageDownload(image)"
|
|
|
+ @click.stop.prevent="
|
|
|
+ handleImageDownloadClick(image, $event)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 下载
|
|
|
+ </u-button>
|
|
|
+ <u-button
|
|
|
+ v-if="image.status === 'error'"
|
|
|
+ size="mini"
|
|
|
+ type="warning"
|
|
|
+ plain
|
|
|
+ @click.stop.prevent="retryUploadFile(image, $event)"
|
|
|
+ >
|
|
|
+ 重试
|
|
|
+ </u-button>
|
|
|
+ <!-- <u-button
|
|
|
+ size="mini"
|
|
|
+ type="error"
|
|
|
+ plain
|
|
|
+ @tap="handleImageDelete(image, index)"
|
|
|
+ @click.stop.prevent="
|
|
|
+ handleImageDeleteClick(image, index, $event)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </u-button> -->
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 文件名显示 -->
|
|
|
+ <view class="image-name">
|
|
|
+ <text>{{ getShortFileName(image.name) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 微信图片上传进度条 -->
|
|
|
+ <view
|
|
|
+ v-if="uploadProgress > 0 && uploadProgress < 100"
|
|
|
+ class="upload-progress"
|
|
|
+ >
|
|
|
+ <u-line-progress
|
|
|
+ :percent="uploadProgress"
|
|
|
+ :show-percent="true"
|
|
|
+ active-color="#2979ff"
|
|
|
+ inactive-color="#f0f0f0"
|
|
|
+ height="8"
|
|
|
+ :striped="true"
|
|
|
+ :striped-active="true"
|
|
|
+ ></u-line-progress>
|
|
|
+ <text class="progress-text">
|
|
|
+ 微信图片上传进度: {{ uploadProgress }}%
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 自定义文件列表(用于显示非图片文件和上传状态) -->
|
|
|
+ <view class="custom-file-list" v-if="nonImageFiles.length > 0">
|
|
|
+ <view class="file-title">文档附件</view>
|
|
|
+ <view class="file-grid">
|
|
|
+ <view
|
|
|
+ class="file-card"
|
|
|
+ v-for="file in nonImageFiles"
|
|
|
+ :key="file.uid"
|
|
|
+ >
|
|
|
+ <!-- 文件图标容器 -->
|
|
|
+ <view class="file-icon-container">
|
|
|
+ <u-icon
|
|
|
+ :name="
|
|
|
+ file.status === 'uploading' ? 'loading' : 'folder'
|
|
|
+ "
|
|
|
+ size="50"
|
|
|
+ :color="
|
|
|
+ file.status === 'error'
|
|
|
+ ? '#fa3534'
|
|
|
+ : file.status === 'success'
|
|
|
+ ? '#19be6b'
|
|
|
+ : '#909399'
|
|
|
+ "
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 上传状态遮罩 -->
|
|
|
+ <view
|
|
|
+ v-if="file.status === 'uploading'"
|
|
|
+ class="upload-overlay"
|
|
|
+ >
|
|
|
+ <u-loading-icon
|
|
|
+ mode="circle"
|
|
|
+ size="24"
|
|
|
+ color="#fff"
|
|
|
+ ></u-loading-icon>
|
|
|
+ <text class="upload-text">
|
|
|
+ {{ file.progress || 0 }}%
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 错误状态遮罩 -->
|
|
|
+ <view v-if="file.status === 'error'" class="error-overlay">
|
|
|
+ <u-icon
|
|
|
+ name="close-circle"
|
|
|
+ size="24"
|
|
|
+ color="#fff"
|
|
|
+ ></u-icon>
|
|
|
+ <text class="error-text">上传失败</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 成功状态角标 -->
|
|
|
+ <view
|
|
|
+ v-if="file.status === 'success'"
|
|
|
+ class="success-badge"
|
|
|
+ >
|
|
|
+ <u-icon
|
|
|
+ name="checkmark-circle-fill"
|
|
|
+ size="18"
|
|
|
+ color="#52c41a"
|
|
|
+ ></u-icon>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 文件操作按钮 -->
|
|
|
+ <view class="image-actions" @click.stop>
|
|
|
+ <u-button
|
|
|
+ v-if="file.status === 'success'"
|
|
|
+ size="mini"
|
|
|
+ type="primary"
|
|
|
+ plain
|
|
|
+ @tap="handleDownloadFile(file)"
|
|
|
+ @click.stop.prevent="
|
|
|
+ handleDownloadFileClick(file, $event)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 下载
|
|
|
+ </u-button>
|
|
|
+ <u-button
|
|
|
+ v-if="file.status === 'error'"
|
|
|
+ size="mini"
|
|
|
+ type="warning"
|
|
|
+ plain
|
|
|
+ @click.stop.prevent="retryUploadFile(file, $event)"
|
|
|
+ >
|
|
|
+ 重试
|
|
|
+ </u-button>
|
|
|
+ <!-- <u-button
|
|
|
+ size="mini"
|
|
|
+ type="error"
|
|
|
+ plain
|
|
|
+ @click.stop.prevent="
|
|
|
+ handleRemoveFile(
|
|
|
+ file,
|
|
|
+ index +
|
|
|
+ attachmentList.filter((f) => isImageFile(f))
|
|
|
+ .length,
|
|
|
+ $event,
|
|
|
+ )
|
|
|
+ "
|
|
|
+ >
|
|
|
+ 删除
|
|
|
+ </u-button> -->
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 文件名显示 -->
|
|
|
+ <view class="file-name-display">
|
|
|
+ <text>{{ getShortFileName(file.name) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 统一附件上传按钮 -->
|
|
|
+ <view v-if="pageType !== 'view'" class="doc-upload-section">
|
|
|
+ <u-button
|
|
|
+ type="primary"
|
|
|
+ icon="plus"
|
|
|
+ plain
|
|
|
+ @click="handleDocUpload"
|
|
|
+ style="margin-top: 20rpx; width: 100%"
|
|
|
+ >
|
|
|
+ 添加附件(图片)
|
|
|
+ </u-button>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 上传提示信息 -->
|
|
|
+ <view class="upload-tips">
|
|
|
+ <text class="tip-item">
|
|
|
+ • 支持格式:JPG、PNG等 • 单个文件不超过10MB • 最多上传15个文件
|
|
|
+ </text>
|
|
|
+ <text class="tip-item">• 图片文件将以图片形式展示,支持预览和下载</text>
|
|
|
+ <text v-if="isWechatBrowser" class="tip-item wechat-tip">
|
|
|
+ • 在微信中使用可获得更好的图片选择体验
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 备注信息 -->
|
|
|
+ <view class="form-section">
|
|
|
+ <text class="section-title">备注信息</text>
|
|
|
+
|
|
|
+ <u-form-item label="检查总结" prop="checkSummary">
|
|
|
+ <u-textarea
|
|
|
+ v-model="formData.checkSummary"
|
|
|
+ placeholder="请输入检查总结"
|
|
|
+ :maxlength="500"
|
|
|
+ count
|
|
|
+ :height="120"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ />
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="整改建议" prop="improvementSuggestions">
|
|
|
+ <u-textarea
|
|
|
+ v-model="formData.improvementSuggestions"
|
|
|
+ placeholder="请输入整改建议"
|
|
|
+ :maxlength="500"
|
|
|
+ count
|
|
|
+ :height="120"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ />
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="检查备注" prop="checkRemarks">
|
|
|
+ <u-textarea
|
|
|
+ v-model="formData.checkRemarks"
|
|
|
+ placeholder="请输入检查备注"
|
|
|
+ :maxlength="500"
|
|
|
+ count
|
|
|
+ :height="120"
|
|
|
+ :readonly="pageType === 'view'"
|
|
|
+ />
|
|
|
+ </u-form-item>
|
|
|
+
|
|
|
+ <u-form-item label="检查员" prop="checkerName" required>
|
|
|
+ <u-input v-model="formData.checkerName" placeholder="检查员姓名" readonly />
|
|
|
+ </u-form-item>
|
|
|
+ </view>
|
|
|
+ </u-form>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 底部操作按钮 -->
|
|
|
+ <view class="bottom-actions">
|
|
|
+ <u-button type="info" @click="goBack" style="margin-right: 20rpx">返回</u-button>
|
|
|
+ <!-- <u-button type="primary" @click="handleSubmit" :loading="submitLoading">保存</u-button> -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { request, smartUploadFile, isWeChat } from '@/common/request.js';
|
|
|
+import { getDict, getDictLabel } from '@/common/dict.js';
|
|
|
+import weChatSDK from '@/utils/wechat.js';
|
|
|
+import { downloadFile } from '@/common/request.js';
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'DormHygieneCheckAdd',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ pageLoading: false,
|
|
|
+ submitLoading: false,
|
|
|
+
|
|
|
+ // 页面参数
|
|
|
+ pageType: 'add', // add | update
|
|
|
+ checkId: null,
|
|
|
+
|
|
|
+ // 表单数据
|
|
|
+ formData: {
|
|
|
+ id: undefined,
|
|
|
+ checkTitle: '',
|
|
|
+ checkDate: '',
|
|
|
+ checkerId: undefined,
|
|
|
+ checkerName: '',
|
|
|
+ checkType: '1',
|
|
|
+ checkLevel: '1',
|
|
|
+ roomId: undefined,
|
|
|
+ roomCode: undefined,
|
|
|
+ roomName: undefined,
|
|
|
+ buildingId: undefined,
|
|
|
+ buildingName: undefined,
|
|
|
+ areaId: undefined,
|
|
|
+ areaName: undefined,
|
|
|
+ campusId: undefined,
|
|
|
+ campusName: undefined,
|
|
|
+ totalScore: '',
|
|
|
+ hygieneGrade: 'A',
|
|
|
+ attachmentIds: undefined,
|
|
|
+ checkSummary: undefined,
|
|
|
+ improvementSuggestions: undefined,
|
|
|
+ checkRemarks: undefined,
|
|
|
+ },
|
|
|
+
|
|
|
+ // 表单验证规则
|
|
|
+ rules: {
|
|
|
+ checkTitle: [{ required: true, message: '请输入检查标题', trigger: 'blur' }],
|
|
|
+ checkDate: [{ required: true, message: '请选择检查日期', trigger: 'change' }],
|
|
|
+ checkerName: [{ required: true, message: '请输入检查员姓名', trigger: 'blur' }],
|
|
|
+ campusId: [{ required: true, message: '请选择校区', trigger: 'change' }],
|
|
|
+ areaId: [{ required: true, message: '请选择宿舍区', trigger: 'change' }],
|
|
|
+ buildingId: [{ required: true, message: '请选择宿舍楼', trigger: 'change' }],
|
|
|
+ roomId: [{ required: true, message: '请选择房间', trigger: 'change' }],
|
|
|
+ checkType: [{ required: true, message: '请选择检查类型', trigger: 'change' }],
|
|
|
+ checkLevel: [{ required: true, message: '请选择检查级别', trigger: 'change' }],
|
|
|
+ hygieneGrade: [{ required: true, message: '请选择卫生等级', trigger: 'change' }],
|
|
|
+ totalScore: [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: '请输入宿舍总分',
|
|
|
+ trigger: 'blur',
|
|
|
+ validator: (rule, value, callback) => {
|
|
|
+ if (value === null || value === undefined || value === '') {
|
|
|
+ callback(new Error('请输入宿舍总分'));
|
|
|
+ } else {
|
|
|
+ const numValue = Number(value);
|
|
|
+ if (isNaN(numValue)) {
|
|
|
+ callback(new Error('宿舍总分必须是数字'));
|
|
|
+ } else if (numValue < 0 || numValue > 100) {
|
|
|
+ callback(new Error('宿舍总分必须在0-100之间'));
|
|
|
+ } else {
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+
|
|
|
+ // 时间选择器
|
|
|
+ showDatePicker: false,
|
|
|
+ checkDateValue: '',
|
|
|
+ checkDateDisplay: '',
|
|
|
+
|
|
|
+ // 级联选择器数据
|
|
|
+ campusOptions: [],
|
|
|
+ areaOptions: [],
|
|
|
+ buildingOptions: [],
|
|
|
+ roomOptions: [],
|
|
|
+
|
|
|
+ // 级联选择器显示状态
|
|
|
+ showCampusPicker: false,
|
|
|
+ showAreaPicker: false,
|
|
|
+ showBuildingPicker: false,
|
|
|
+ showRoomPicker: false,
|
|
|
+
|
|
|
+ // 字典选择器显示状态
|
|
|
+ showCheckTypePicker: false,
|
|
|
+ showCheckLevelPicker: false,
|
|
|
+ showHygieneGradePicker: false,
|
|
|
+
|
|
|
+ // 字典数据
|
|
|
+ xg_dorm_hygiene_check_type: [],
|
|
|
+ xg_dorm_hygiene_check_level: [],
|
|
|
+ xg_dorm_hygiene_grade: [],
|
|
|
+ xg_student_attendance_status: [],
|
|
|
+
|
|
|
+ // 房间学生信息
|
|
|
+ roomStudents: [],
|
|
|
+
|
|
|
+ // 附件数据
|
|
|
+ attachmentList: [], // 用于存储所有附件的原始数据
|
|
|
+
|
|
|
+ // 微信SDK相关
|
|
|
+ wechatSDK: weChatSDK,
|
|
|
+ isWechatBrowser: false,
|
|
|
+ wechatInitializing: false,
|
|
|
+ wechatImageLoading: false,
|
|
|
+
|
|
|
+ // 微信图片相关
|
|
|
+ selectedWechatImages: [],
|
|
|
+ uploadProgress: 0, // 上传进度
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ navTitle() {
|
|
|
+ if (this.pageType === 'view') {
|
|
|
+ return '查看检查';
|
|
|
+ }
|
|
|
+ return this.pageType === 'add' ? '新增检查' : '修改检查';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 级联选择器数据转换
|
|
|
+ campusColumns() {
|
|
|
+ return [this.campusOptions.map((item) => ({ text: item.campusName, value: item.id }))];
|
|
|
+ },
|
|
|
+
|
|
|
+ areaColumns() {
|
|
|
+ return [this.areaOptions.map((item) => ({ text: item.areaName, value: item.id }))];
|
|
|
+ },
|
|
|
+
|
|
|
+ buildingColumns() {
|
|
|
+ return [
|
|
|
+ this.buildingOptions.map((item) => ({ text: item.buildingName, value: item.id })),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ roomColumns() {
|
|
|
+ return [this.roomOptions.map((item) => ({ text: item.roomName, value: item.id }))];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 字典选择器数据转换
|
|
|
+ checkTypeColumns() {
|
|
|
+ return [
|
|
|
+ this.xg_dorm_hygiene_check_type.map((item) => ({
|
|
|
+ text: item.label,
|
|
|
+ value: item.value,
|
|
|
+ })),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查类型默认索引
|
|
|
+ checkTypeDefaultIndex() {
|
|
|
+ if (!this.formData.checkType || this.xg_dorm_hygiene_check_type.length === 0) {
|
|
|
+ return [0];
|
|
|
+ }
|
|
|
+ const index = this.xg_dorm_hygiene_check_type.findIndex(
|
|
|
+ (item) => item.value === this.formData.checkType,
|
|
|
+ );
|
|
|
+ return [index >= 0 ? index : 0];
|
|
|
+ },
|
|
|
+
|
|
|
+ checkLevelColumns() {
|
|
|
+ return [
|
|
|
+ this.xg_dorm_hygiene_check_level.map((item) => ({
|
|
|
+ text: item.label,
|
|
|
+ value: item.value,
|
|
|
+ })),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查级别默认索引
|
|
|
+ checkLevelDefaultIndex() {
|
|
|
+ if (!this.formData.checkLevel || this.xg_dorm_hygiene_check_level.length === 0) {
|
|
|
+ return [0];
|
|
|
+ }
|
|
|
+ const index = this.xg_dorm_hygiene_check_level.findIndex(
|
|
|
+ (item) => item.value === this.formData.checkLevel,
|
|
|
+ );
|
|
|
+ return [index >= 0 ? index : 0];
|
|
|
+ },
|
|
|
+
|
|
|
+ hygieneGradeColumns() {
|
|
|
+ return [
|
|
|
+ this.xg_dorm_hygiene_grade.map((item) => ({ text: item.label, value: item.value })),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 卫生等级默认索引
|
|
|
+ hygieneGradeDefaultIndex() {
|
|
|
+ if (!this.formData.hygieneGrade || this.xg_dorm_hygiene_grade.length === 0) {
|
|
|
+ return [0];
|
|
|
+ }
|
|
|
+ const index = this.xg_dorm_hygiene_grade.findIndex(
|
|
|
+ (item) => item.value === this.formData.hygieneGrade,
|
|
|
+ );
|
|
|
+ return [index >= 0 ? index : 0];
|
|
|
+ },
|
|
|
+
|
|
|
+ attendanceStatusColumns() {
|
|
|
+ return [
|
|
|
+ this.xg_student_attendance_status.map((item) => ({
|
|
|
+ text: item.label,
|
|
|
+ value: item.value,
|
|
|
+ })),
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示字段
|
|
|
+ checkTypeDisplay() {
|
|
|
+ const item = this.xg_dorm_hygiene_check_type.find(
|
|
|
+ (d) => d.value === this.formData.checkType,
|
|
|
+ );
|
|
|
+ return item ? item.label : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ checkLevelDisplay() {
|
|
|
+ const item = this.xg_dorm_hygiene_check_level.find(
|
|
|
+ (d) => d.value === this.formData.checkLevel,
|
|
|
+ );
|
|
|
+ return item ? item.label : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ hygieneGradeDisplay() {
|
|
|
+ const item = this.xg_dorm_hygiene_grade.find(
|
|
|
+ (d) => d.value === this.formData.hygieneGrade,
|
|
|
+ );
|
|
|
+ return item ? item.label : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片文件(用于展示)
|
|
|
+ imageFiles() {
|
|
|
+ return this.attachmentList.filter((file) => this.isImageFile(file));
|
|
|
+ },
|
|
|
+
|
|
|
+ // 非图片文件(用于展示)
|
|
|
+ nonImageFiles() {
|
|
|
+ return this.attachmentList.filter((file) => !this.isImageFile(file));
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ async onLoad(options) {
|
|
|
+ console.log('页面参数:', options);
|
|
|
+
|
|
|
+ this.pageType = options.type || 'add';
|
|
|
+ this.checkId = options.id;
|
|
|
+
|
|
|
+ // 初始化微信环境检查并自动初始化SDK
|
|
|
+ await this.checkWechatEnvironment();
|
|
|
+
|
|
|
+ await this.initPage();
|
|
|
+ },
|
|
|
+ onUnload() {
|
|
|
+ // 清理blob URL,防止内存泄漏
|
|
|
+ this.cleanupBlobUrls();
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // === 微信SDK相关方法 ===
|
|
|
+
|
|
|
+ // 检查微信环境并自动初始化SDK
|
|
|
+ async checkWechatEnvironment() {
|
|
|
+ try {
|
|
|
+ // 检查是否在微信浏览器中
|
|
|
+ this.isWechatBrowser = this.wechatSDK.isWeChatBrowser();
|
|
|
+
|
|
|
+ if (this.isWechatBrowser) {
|
|
|
+ // 在微信环境中,自动初始化SDK
|
|
|
+ await this.initWechatSDK();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('微信环境检查失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化微信SDK
|
|
|
+ async initWechatSDK() {
|
|
|
+ if (this.wechatInitializing) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.wechatInitializing = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 使用微信SDK工具类进行初始化
|
|
|
+ await this.wechatSDK.init({
|
|
|
+ debug: process.env.NODE_ENV === 'development',
|
|
|
+ jsApiList: ['chooseImage', 'previewImage', 'getLocation', 'openLocation'],
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查关键API支持情况
|
|
|
+ try {
|
|
|
+ const apiCheckResult = await this.wechatSDK.checkJsApi([
|
|
|
+ 'chooseImage',
|
|
|
+ 'getLocation',
|
|
|
+ ]);
|
|
|
+ } catch (apiError) {
|
|
|
+ console.warn('API支持检查失败:', apiError.message);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('微信SDK初始化失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.wechatInitializing = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重新初始化微信SDK
|
|
|
+ async reinitWechatSDK() {
|
|
|
+ this.wechatSDK.isInitialized = false;
|
|
|
+ await this.initWechatSDK();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传微信图片到OSS(参考stuMsgDialog.vue的实现)
|
|
|
+ async uploadWechatImageToOSS(localId) {
|
|
|
+ try {
|
|
|
+ this.uploadProgress = 10;
|
|
|
+
|
|
|
+ // 步骤1:上传到微信服务器(20%进度)
|
|
|
+ const wxUploadRes = await this.wechatSDK.uploadImage(localId);
|
|
|
+
|
|
|
+ if (!wxUploadRes.serverId) {
|
|
|
+ throw new Error('微信服务器上传失败,未获取到serverId');
|
|
|
+ }
|
|
|
+
|
|
|
+ const mediaId = wxUploadRes.serverId;
|
|
|
+ this.uploadProgress = 30;
|
|
|
+
|
|
|
+ // 步骤2:保存到OSS(80%进度)
|
|
|
+ this.uploadProgress = 50;
|
|
|
+
|
|
|
+ const ossResponse = await request({
|
|
|
+ url: '/system/wechat/downloadToOss',
|
|
|
+ method: 'GET',
|
|
|
+ params: {
|
|
|
+ mediaId: mediaId,
|
|
|
+ },
|
|
|
+ timeout: 30000, // 30秒超时
|
|
|
+ });
|
|
|
+
|
|
|
+ if (ossResponse.code === 200) {
|
|
|
+ const ossResult = ossResponse.data;
|
|
|
+ this.uploadProgress = 90;
|
|
|
+
|
|
|
+ this.uploadProgress = 100;
|
|
|
+ return {
|
|
|
+ ossId: ossResult.ossId,
|
|
|
+ url: ossResult.url,
|
|
|
+ fileName: ossResult.fileName,
|
|
|
+ fileSize: ossResult.fileSize || 0,
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ throw new Error(ossResponse.msg || '保存图片到云端存储失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('上传微信图片失败:', error);
|
|
|
+
|
|
|
+ // 上传失败时重置进度
|
|
|
+ this.uploadProgress = 0;
|
|
|
+
|
|
|
+ // 显示详细错误信息
|
|
|
+ let errorMsg = '图片上传失败,请重试';
|
|
|
+ if (error.message.includes('timeout')) {
|
|
|
+ errorMsg = '上传超时,请检查网络连接后重试';
|
|
|
+ } else if (
|
|
|
+ error.message.includes('mediaId') ||
|
|
|
+ error.message.includes('serverId')
|
|
|
+ ) {
|
|
|
+ errorMsg = '微信服务器上传失败,请重试';
|
|
|
+ } else if (error.message.includes('OSS')) {
|
|
|
+ errorMsg = '保存到云端存储失败,请重试';
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: errorMsg,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化页面
|
|
|
+ async initPage() {
|
|
|
+ this.pageLoading = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 加载字典数据
|
|
|
+ await this.loadDictionaries();
|
|
|
+ // 加载级联选择器数据
|
|
|
+ await this.loadCampusOptions();
|
|
|
+ // 设置默认值
|
|
|
+ this.setDefaultValues();
|
|
|
+
|
|
|
+ // 编辑模式或查看模式加载数据
|
|
|
+ if ((this.pageType === 'update' || this.pageType === 'view') && this.checkId) {
|
|
|
+ await this.loadCheckData();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('初始化页面失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '页面初始化失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.pageLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载字典数据
|
|
|
+ async loadDictionaries() {
|
|
|
+ try {
|
|
|
+ const dictTypes = [
|
|
|
+ 'xg_dorm_hygiene_check_type',
|
|
|
+ 'xg_dorm_hygiene_check_level',
|
|
|
+ 'xg_dorm_hygiene_grade',
|
|
|
+ 'xg_student_attendance_status',
|
|
|
+ ];
|
|
|
+
|
|
|
+ const results = await Promise.all(dictTypes.map((type) => getDict(type)));
|
|
|
+
|
|
|
+ this.xg_dorm_hygiene_check_type = results[0] || [];
|
|
|
+ this.xg_dorm_hygiene_check_level = results[1] || [];
|
|
|
+ this.xg_dorm_hygiene_grade = results[2] || [];
|
|
|
+ this.xg_student_attendance_status = results[3] || [];
|
|
|
+
|
|
|
+ console.log('字典数据加载完成');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载字典数据失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置默认值
|
|
|
+ setDefaultValues() {
|
|
|
+ const now = new Date();
|
|
|
+ this.checkDateValue = now.getTime();
|
|
|
+ this.checkDateDisplay = this.formatDateTime(now);
|
|
|
+ this.formData.checkDate = this.formatDateTime(now);
|
|
|
+
|
|
|
+ // 设置默认检查标题
|
|
|
+ const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
|
+ this.formData.checkTitle = `${dateStr}宿舍卫生检查`;
|
|
|
+
|
|
|
+ // 获取当前用户信息
|
|
|
+ this.getCurrentUserInfo();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取当前用户信息
|
|
|
+ async getCurrentUserInfo() {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/system/user/getInfo',
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res && res.code === 200 && res.data && res.data.user) {
|
|
|
+ const user = res.data.user;
|
|
|
+ this.formData.checkerId = user.userId;
|
|
|
+ this.formData.checkerName = user.nickName || user.userName;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('获取用户信息失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载检查数据(编辑模式)
|
|
|
+ async loadCheckData() {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: `/dormHygieneCheck/dormHygieneCheck/${this.checkId}`,
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ Object.assign(this.formData, res.data);
|
|
|
+
|
|
|
+ // 确保totalScore为字符串类型,避免验证问题
|
|
|
+ if (
|
|
|
+ this.formData.totalScore !== null &&
|
|
|
+ this.formData.totalScore !== undefined
|
|
|
+ ) {
|
|
|
+ this.formData.totalScore = String(this.formData.totalScore);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置时间显示
|
|
|
+ if (this.formData.checkDate) {
|
|
|
+ const date = new Date(this.formData.checkDate);
|
|
|
+ this.checkDateValue = date.getTime();
|
|
|
+ this.checkDateDisplay = this.formatDateTime(date);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载级联数据
|
|
|
+ await this.loadCascadeData();
|
|
|
+ // 加载房间学生数据
|
|
|
+ await this.loadRoomStudents();
|
|
|
+ // 加载附件数据
|
|
|
+ await this.loadAttachmentData();
|
|
|
+ // 为图片文件获取blob数据以确保正确显示
|
|
|
+ await this.processImageFiles();
|
|
|
+
|
|
|
+ // 强制刷新组件显示
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // 预加载图片以确保能正确显示
|
|
|
+ this.preloadImages();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载检查数据失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '加载数据失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载级联数据
|
|
|
+ async loadCascadeData() {
|
|
|
+ try {
|
|
|
+ const tasks = [];
|
|
|
+
|
|
|
+ if (this.formData.campusId) {
|
|
|
+ tasks.push(this.loadAreaOptions(this.formData.campusId));
|
|
|
+ }
|
|
|
+ if (this.formData.areaId) {
|
|
|
+ tasks.push(this.loadBuildingOptions(this.formData.areaId));
|
|
|
+ }
|
|
|
+ if (this.formData.buildingId) {
|
|
|
+ tasks.push(this.loadRoomOptions(this.formData.buildingId));
|
|
|
+ }
|
|
|
+
|
|
|
+ await Promise.all(tasks);
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载级联数据失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载校区选项
|
|
|
+ async loadCampusOptions() {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/xqxx/dormCampus/list',
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+ this.campusOptions = res.rows || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载校区选项失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载宿舍区选项
|
|
|
+ async loadAreaOptions(campusId) {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/ssqxx/dormArea/list',
|
|
|
+ method: 'GET',
|
|
|
+ params: { campusId },
|
|
|
+ });
|
|
|
+ this.areaOptions = res.rows || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载宿舍区选项失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载宿舍楼选项
|
|
|
+ async loadBuildingOptions(areaId) {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/sslxx/dormBuilding/list',
|
|
|
+ method: 'GET',
|
|
|
+ params: { areaId },
|
|
|
+ });
|
|
|
+ this.buildingOptions = res.rows || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载宿舍楼选项失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载房间选项
|
|
|
+ async loadRoomOptions(buildingId) {
|
|
|
+ try {
|
|
|
+ const res = await request({
|
|
|
+ url: '/xgss/dormRoom/list',
|
|
|
+ method: 'GET',
|
|
|
+ params: { buildingId },
|
|
|
+ });
|
|
|
+ this.roomOptions = res.rows || [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载房间选项失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载房间学生信息
|
|
|
+ async loadRoomStudents() {
|
|
|
+ if (!this.formData.roomId) {
|
|
|
+ this.roomStudents = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 如果是编辑模式或查看模式,先尝试获取历史评分数据
|
|
|
+ if ((this.pageType === 'update' || this.pageType === 'view') && this.formData.id) {
|
|
|
+ const studentScoresRes = await request({
|
|
|
+ url: `/dormHygieneStudentScore/dormHygieneStudentScore/getByCheckId/${this.formData.id}`,
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (
|
|
|
+ studentScoresRes.code === 200 &&
|
|
|
+ studentScoresRes.data &&
|
|
|
+ studentScoresRes.data.length > 0
|
|
|
+ ) {
|
|
|
+ this.roomStudents = studentScoresRes.data.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ student_score: item.studentScore || null,
|
|
|
+ attendance_status: item.attendanceStatus || '1',
|
|
|
+ student_grade: item.studentGrade || 'A',
|
|
|
+ showAttendancePicker: false,
|
|
|
+ showGradePicker: false,
|
|
|
+ bedCode: item.bedCode,
|
|
|
+ bedName: item.bedCode,
|
|
|
+ }));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取房间学生基础数据
|
|
|
+ const res = await request({
|
|
|
+ url: '/dormBedStudent/dormBedStudent/getRoomStudents',
|
|
|
+ method: 'GET',
|
|
|
+ params: { roomId: this.formData.roomId },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.roomStudents = res.data.map((item) => ({
|
|
|
+ ...item,
|
|
|
+ student_score: null,
|
|
|
+ attendance_status: '1',
|
|
|
+ student_grade: 'A',
|
|
|
+ showAttendancePicker: false,
|
|
|
+ showGradePicker: false,
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载房间学生信息失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载附件数据
|
|
|
+ async loadAttachmentData() {
|
|
|
+ if (!this.formData.attachmentIds) {
|
|
|
+ this.attachmentList = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const ossIds = this.formData.attachmentIds.split(',').filter((id) => id.trim());
|
|
|
+ if (ossIds.length === 0) return;
|
|
|
+
|
|
|
+ const res = await request({
|
|
|
+ url: `/resource/oss/listByIds/${ossIds.join(',')}`,
|
|
|
+ method: 'GET',
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const attachmentFiles = res.data.map((item, index) => {
|
|
|
+ // 使用fileSuffix字段判断文件类型
|
|
|
+ const fileType = this.getFileTypeBySuffix(item.fileSuffix);
|
|
|
+ const isImage = this.isImageFile({
|
|
|
+ name: item.originalName,
|
|
|
+ type: fileType,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 处理图片URL - 确保有正确的API前缀
|
|
|
+ let processedUrl = item.url;
|
|
|
+ if (isImage && item.url && item.url.startsWith('/resource/')) {
|
|
|
+ // 图片文件需要添加/api前缀
|
|
|
+ processedUrl = `/api${item.url}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构造完整的文件名(包含扩展名)
|
|
|
+ let fullFileName = item.originalName || `附件${index + 1}`;
|
|
|
+ if (
|
|
|
+ item.fileSuffix &&
|
|
|
+ !fullFileName
|
|
|
+ .toLowerCase()
|
|
|
+ .endsWith(`.${item.fileSuffix.toLowerCase()}`)
|
|
|
+ ) {
|
|
|
+ fullFileName = `${fullFileName}.${item.fileSuffix}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const fileObj = {
|
|
|
+ uid: `existing_${index}_${Date.now()}`,
|
|
|
+ name: fullFileName,
|
|
|
+ url: processedUrl,
|
|
|
+ ossId: item.ossId,
|
|
|
+ status: 'success',
|
|
|
+ type: fileType,
|
|
|
+ // 确保图片文件有正确的显示属性
|
|
|
+ size: item.fileSize || 0,
|
|
|
+ // 为uview2 upload组件添加必要的属性
|
|
|
+ file: {
|
|
|
+ name: fullFileName,
|
|
|
+ type: fileType,
|
|
|
+ size: item.fileSize || 0,
|
|
|
+ path: processedUrl,
|
|
|
+ url: processedUrl,
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ return fileObj;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.attachmentList = attachmentFiles;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载附件数据失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 根据文件后缀获取文件类型(新增方法)
|
|
|
+ getFileTypeBySuffix(fileSuffix) {
|
|
|
+ if (!fileSuffix) return '';
|
|
|
+
|
|
|
+ // 移除点号,转换为小写
|
|
|
+ const ext = fileSuffix.replace('.', '').toLowerCase();
|
|
|
+
|
|
|
+ // 图片类型判断
|
|
|
+ const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
|
|
+ if (imageTypes.includes(ext)) {
|
|
|
+ // 修正MIME类型映射
|
|
|
+ const mimeTypeMap = {
|
|
|
+ jpg: 'image/jpeg',
|
|
|
+ jpeg: 'image/jpeg',
|
|
|
+ png: 'image/png',
|
|
|
+ gif: 'image/gif',
|
|
|
+ bmp: 'image/bmp',
|
|
|
+ webp: 'image/webp',
|
|
|
+ };
|
|
|
+ return mimeTypeMap[ext] || `image/${ext}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文档类型判断
|
|
|
+ const documentTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'];
|
|
|
+ if (documentTypes.includes(ext)) {
|
|
|
+ return `file/${ext}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他类型
|
|
|
+ return `file/${ext}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 时间选择确认
|
|
|
+ handleDateConfirm(e) {
|
|
|
+ this.checkDateValue = e.value;
|
|
|
+ const date = new Date(e.value);
|
|
|
+ this.checkDateDisplay = this.formatDateTime(date);
|
|
|
+ this.formData.checkDate = this.formatDateTime(date);
|
|
|
+ this.showDatePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 校区选择确认
|
|
|
+ async handleCampusConfirm(e) {
|
|
|
+ console.log('校区选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ const campus = this.campusOptions.find((item) => item.id === e.value[0].value);
|
|
|
+ if (campus) {
|
|
|
+ this.formData.campusId = campus.id;
|
|
|
+ this.formData.campusName = campus.campusName;
|
|
|
+
|
|
|
+ // 清空下级选项
|
|
|
+ this.clearSubOptions('campus');
|
|
|
+ await this.loadAreaOptions(campus.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showCampusPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 宿舍区选择确认
|
|
|
+ async handleAreaConfirm(e) {
|
|
|
+ console.log('宿舍区选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ const area = this.areaOptions.find((item) => item.id === e.value[0].value);
|
|
|
+ if (area) {
|
|
|
+ this.formData.areaId = area.id;
|
|
|
+ this.formData.areaName = area.areaName;
|
|
|
+
|
|
|
+ // 清空下级选项
|
|
|
+ this.clearSubOptions('area');
|
|
|
+ await this.loadBuildingOptions(area.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showAreaPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 宿舍楼选择确认
|
|
|
+ async handleBuildingConfirm(e) {
|
|
|
+ console.log('宿舍楼选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ const building = this.buildingOptions.find((item) => item.id === e.value[0].value);
|
|
|
+ if (building) {
|
|
|
+ this.formData.buildingId = building.id;
|
|
|
+ this.formData.buildingName = building.buildingName;
|
|
|
+
|
|
|
+ // 清空下级选项
|
|
|
+ this.clearSubOptions('building');
|
|
|
+ await this.loadRoomOptions(building.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showBuildingPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 房间选择确认
|
|
|
+ async handleRoomConfirm(e) {
|
|
|
+ console.log('房间选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ const room = this.roomOptions.find((item) => item.id === e.value[0].value);
|
|
|
+ if (room) {
|
|
|
+ this.formData.roomId = room.id;
|
|
|
+ this.formData.roomName = room.roomName;
|
|
|
+ this.formData.roomCode = room.roomCode || room.roomName;
|
|
|
+
|
|
|
+ // 加载房间学生信息
|
|
|
+ await this.loadRoomStudents();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showRoomPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清空下级选项
|
|
|
+ clearSubOptions(level) {
|
|
|
+ switch (level) {
|
|
|
+ case 'campus':
|
|
|
+ this.formData.areaId = undefined;
|
|
|
+ this.formData.areaName = undefined;
|
|
|
+ this.areaOptions = [];
|
|
|
+ case 'area':
|
|
|
+ this.formData.buildingId = undefined;
|
|
|
+ this.formData.buildingName = undefined;
|
|
|
+ this.buildingOptions = [];
|
|
|
+ case 'building':
|
|
|
+ this.formData.roomId = undefined;
|
|
|
+ this.formData.roomName = undefined;
|
|
|
+ this.formData.roomCode = undefined;
|
|
|
+ this.roomOptions = [];
|
|
|
+ this.roomStudents = [];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查类型选择确认
|
|
|
+ handleCheckTypeConfirm(e) {
|
|
|
+ console.log('检查类型选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ this.formData.checkType = e.value[0].value;
|
|
|
+ }
|
|
|
+ this.showCheckTypePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查级别选择确认
|
|
|
+ handleCheckLevelConfirm(e) {
|
|
|
+ console.log('检查级别选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ this.formData.checkLevel = e.value[0].value;
|
|
|
+ }
|
|
|
+ this.showCheckLevelPicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 卫生等级选择确认
|
|
|
+ handleHygieneGradeConfirm(e) {
|
|
|
+ console.log('卫生等级选择确认:', e);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ this.formData.hygieneGrade = e.value[0].value;
|
|
|
+
|
|
|
+ // 自动为所有学生设置相同评级
|
|
|
+ if (this.roomStudents.length > 0) {
|
|
|
+ this.roomStudents.forEach((student) => {
|
|
|
+ student.student_grade = e.value[0].value;
|
|
|
+ });
|
|
|
+
|
|
|
+ const gradeItem = this.xg_dorm_hygiene_grade.find(
|
|
|
+ (d) => d.value === e.value[0].value,
|
|
|
+ );
|
|
|
+ if (gradeItem) {
|
|
|
+ uni.showToast({
|
|
|
+ title: `已为${this.roomStudents.length}名学生设置评级:${gradeItem.label}`,
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.showHygieneGradePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 总分变化处理
|
|
|
+ handleTotalScoreChange() {
|
|
|
+ // 确保totalScore为字符串类型,避免验证问题
|
|
|
+ if (this.formData.totalScore !== null && this.formData.totalScore !== undefined) {
|
|
|
+ this.formData.totalScore = String(this.formData.totalScore);
|
|
|
+ }
|
|
|
+
|
|
|
+ const score = parseFloat(this.formData.totalScore);
|
|
|
+
|
|
|
+ // 验证分数范围
|
|
|
+ if (!isNaN(score) && (score < 0 || score > 100)) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '宿舍总分必须在0-100分之间',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ this.formData.totalScore = '';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isNaN(score) && score >= 0 && score <= 100 && this.roomStudents.length > 0) {
|
|
|
+ this.roomStudents.forEach((student) => {
|
|
|
+ student.student_score = score;
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: `已为${this.roomStudents.length}名学生设置个人得分:${score}分`,
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 学生分数变化处理
|
|
|
+ handleStudentScoreChange(student) {
|
|
|
+ const score = parseFloat(student.student_score);
|
|
|
+ if (!isNaN(score) && (score < 0 || score > 100)) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '个人得分必须在0-100分之间',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ student.student_score = '';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 出勤状态选择确认
|
|
|
+ handleAttendanceConfirm(e, student) {
|
|
|
+ console.log('出勤状态选择确认:', e, student);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ student.attendance_status = e.value[0].value;
|
|
|
+ }
|
|
|
+ student.showAttendancePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 学生评级选择确认
|
|
|
+ handleStudentGradeConfirm(e, student) {
|
|
|
+ console.log('学生评级选择确认:', e, student);
|
|
|
+ if (e && e.value && e.value.length > 0) {
|
|
|
+ student.student_grade = e.value[0].value;
|
|
|
+ }
|
|
|
+ student.showGradePicker = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取出勤状态显示文本
|
|
|
+ getAttendanceDisplay(value) {
|
|
|
+ const item = this.xg_student_attendance_status.find((d) => d.value === value);
|
|
|
+ return item ? item.label : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取评级显示文本
|
|
|
+ getGradeDisplay(value) {
|
|
|
+ const item = this.xg_dorm_hygiene_grade.find((d) => d.value === value);
|
|
|
+ return item ? item.label : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择图片
|
|
|
+ handleImageClick(image, index, event) {
|
|
|
+ // 检查点击目标是否是操作按钮区域
|
|
|
+ const target = event.target;
|
|
|
+ const isActionButton =
|
|
|
+ target.closest('.image-actions') ||
|
|
|
+ target.closest('u-button') ||
|
|
|
+ target.tagName === 'BUTTON';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 统一附件上传方法(支持图片和文档)
|
|
|
+ handleDocUpload() {
|
|
|
+ console.log('=== 统一附件上传方法 ===');
|
|
|
+
|
|
|
+ // 检查文件数量限制
|
|
|
+ if (this.attachmentList.length >= 15) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '最多只能上传15个文件',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示选择文件类型的操作菜单
|
|
|
+ uni.showActionSheet({
|
|
|
+ itemList: ['选择图片'],
|
|
|
+ success: (res) => {
|
|
|
+ switch (res.tapIndex) {
|
|
|
+ case 0:
|
|
|
+ this.chooseDocuments();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: () => {
|
|
|
+ console.log('用户取消选择');
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 选择文档文件
|
|
|
+ chooseDocuments() {
|
|
|
+ // 使用uni.chooseFile选择文档
|
|
|
+ if (typeof uni.chooseFile === 'function') {
|
|
|
+ uni.chooseFile({
|
|
|
+ count: Math.min(5, 15 - this.attachmentList.length),
|
|
|
+ success: (res) => {
|
|
|
+ console.log('选择文档成功:', res);
|
|
|
+ if (res.tempFiles && res.tempFiles.length > 0) {
|
|
|
+ this.handleFileUpload({
|
|
|
+ tempFiles: res.tempFiles,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('选择文档失败:', err);
|
|
|
+ uni.showToast({
|
|
|
+ title: '选择文档失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 降级方案:跳转到备用上传方式
|
|
|
+ this.handleBackupUpload();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 备用上传方法(用户可见)
|
|
|
+ handleBackupUpload() {
|
|
|
+ console.log('=== 备用上传方法 ===');
|
|
|
+
|
|
|
+ // 先检查平台支持情况
|
|
|
+ uni.showActionSheet({
|
|
|
+ itemList: ['选择图片', '选择视频', '选择文档'],
|
|
|
+ success: (res) => {
|
|
|
+ if (res.tapIndex === 0) {
|
|
|
+ // 选择图片
|
|
|
+ uni.chooseImage({
|
|
|
+ count: 5,
|
|
|
+ sizeType: ['original', 'compressed'],
|
|
|
+ sourceType: ['album', 'camera'],
|
|
|
+ success: (imageRes) => {
|
|
|
+ console.log('选择图片成功 - 完整响应:', imageRes);
|
|
|
+ console.log('tempFilePaths:', imageRes.tempFilePaths);
|
|
|
+ console.log('tempFiles:', imageRes.tempFiles);
|
|
|
+
|
|
|
+ const files = imageRes.tempFilePaths.map((path, index) => {
|
|
|
+ const fileName =
|
|
|
+ path.split('/').pop() || `image_${Date.now()}_${index}.jpg`;
|
|
|
+ const tempFile = imageRes.tempFiles?.[index];
|
|
|
+ const fileObj = {
|
|
|
+ name: fileName,
|
|
|
+ path: path,
|
|
|
+ url: path,
|
|
|
+ tempFilePath: path,
|
|
|
+ size: tempFile?.size || 0,
|
|
|
+ type: tempFile?.type || 'image/jpeg',
|
|
|
+ };
|
|
|
+ console.log(`创建图片文件对象 ${index}:`, fileObj);
|
|
|
+ console.log(`对应的tempFile:`, tempFile);
|
|
|
+ return fileObj;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查文件大小
|
|
|
+ const validFiles = files.filter((file) => {
|
|
|
+ if (!this.checkFileSize(file)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (validFiles.length > 0) {
|
|
|
+ this.handleFileUpload({ tempFiles: validFiles });
|
|
|
+ } else {
|
|
|
+ console.log('所有文件都超过大小限制,取消上传');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('选择图片失败:', err);
|
|
|
+ uni.showToast({
|
|
|
+ title: '选择图片失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } else if (res.tapIndex === 1) {
|
|
|
+ // 选择视频
|
|
|
+ uni.chooseVideo({
|
|
|
+ sourceType: ['album', 'camera'],
|
|
|
+ maxDuration: 60,
|
|
|
+ success: (videoRes) => {
|
|
|
+ console.log('选择视频成功:', videoRes);
|
|
|
+ const file = {
|
|
|
+ name: `video_${Date.now()}.mp4`,
|
|
|
+ path: videoRes.tempFilePath,
|
|
|
+ url: videoRes.tempFilePath,
|
|
|
+ tempFilePath: videoRes.tempFilePath,
|
|
|
+ size: videoRes.size || 0,
|
|
|
+ type: 'video/mp4',
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查文件大小
|
|
|
+ if (this.checkFileSize(file)) {
|
|
|
+ this.handleFileUpload({ tempFiles: [file] });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('选择视频失败:', err);
|
|
|
+ uni.showToast({
|
|
|
+ title: '选择视频失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } else if (res.tapIndex === 2) {
|
|
|
+ // 选择文档
|
|
|
+ this.handleDocUpload();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 文件上传
|
|
|
+ handleFileUpload(event) {
|
|
|
+ console.log('文件上传事件触发:', event);
|
|
|
+
|
|
|
+ // 显示用户提示
|
|
|
+ uni.showToast({
|
|
|
+ title: '文件选择成功,开始上传...',
|
|
|
+ icon: 'loading',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 兼容不同的事件格式
|
|
|
+ let files = [];
|
|
|
+
|
|
|
+ // uview2 upload组件的after-read事件格式
|
|
|
+ if (event.file) {
|
|
|
+ files = Array.isArray(event.file) ? event.file : [event.file];
|
|
|
+ console.log('✅ uview2 upload格式解析结果:', files);
|
|
|
+ }
|
|
|
+ // 直接传递的tempFiles格式(文档上传等)
|
|
|
+ else if (event.tempFiles) {
|
|
|
+ files = event.tempFiles;
|
|
|
+ console.log('✅ tempFiles格式解析结果:', files);
|
|
|
+ }
|
|
|
+ // tempFilePaths格式(兼容旧版本)
|
|
|
+ else if (event.tempFilePaths) {
|
|
|
+ files = event.tempFilePaths.map((path, index) => ({
|
|
|
+ url: path,
|
|
|
+ path: path,
|
|
|
+ tempFilePath: path,
|
|
|
+ name: `文件${index + 1}`,
|
|
|
+ type: '',
|
|
|
+ }));
|
|
|
+ console.log('✅ tempFilePaths格式解析结果:', files);
|
|
|
+ }
|
|
|
+ // 单文件格式处理
|
|
|
+ else if (event.url || event.path || event.tempFilePath) {
|
|
|
+ files = [event];
|
|
|
+ console.log('✅ 单文件格式解析结果:', files);
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('📋 最终解析得到的文件列表:', files);
|
|
|
+
|
|
|
+ if (files.length === 0) {
|
|
|
+ console.error('❌ 没有获取到文件数据');
|
|
|
+ console.error('可能的原因:');
|
|
|
+ console.error('1. u-upload组件事件参数结构发生变化');
|
|
|
+ console.error('2. 选择文件时发生错误');
|
|
|
+ console.error('3. 事件触发异常');
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '未获取到文件,请重试',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文件数量限制
|
|
|
+ if (this.attachmentList.length + files.length > 15) {
|
|
|
+ console.warn('⚠️ 文件数量超过限制');
|
|
|
+ uni.showToast({
|
|
|
+ title: '最多只能上传15个文件',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查文件大小限制
|
|
|
+ const validFiles = files.filter((file) => {
|
|
|
+ const isValid = this.checkFileSize(file);
|
|
|
+ console.log(
|
|
|
+ `文件大小检查 - ${file.name || '未知文件'}:`,
|
|
|
+ isValid ? '✅ 通过' : '❌ 超限',
|
|
|
+ );
|
|
|
+ return isValid;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (validFiles.length === 0) {
|
|
|
+ console.log('❌ 所有文件都超过大小限制,取消上传');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (validFiles.length < files.length) {
|
|
|
+ const filteredCount = files.length - validFiles.length;
|
|
|
+ console.log(`⚠️ 已过滤 ${filteredCount} 个超限文件`);
|
|
|
+ uni.showToast({
|
|
|
+ title: `已过滤 ${filteredCount} 个超限文件`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理有效文件
|
|
|
+ const newFiles = validFiles.map((file, index) => {
|
|
|
+ console.log(`🔍 处理文件 ${index + 1}:`, file);
|
|
|
+ console.log(`🔍 文件对象属性:`, Object.keys(file));
|
|
|
+
|
|
|
+ // uview2 upload组件返回的文件通常有url属性作为临时路径
|
|
|
+ const filePath = file.url || file.tempFilePath || file.path;
|
|
|
+ console.log(`📂 文件路径:`, filePath);
|
|
|
+
|
|
|
+ if (!filePath) {
|
|
|
+ console.error(`❌ 文件 ${index + 1} 缺少有效路径`);
|
|
|
+ }
|
|
|
+
|
|
|
+ const fileName =
|
|
|
+ file.name || filePath?.split('/').pop() || `图片${Date.now()}_${index}.jpg`;
|
|
|
+ console.log(`🏷️ 文件名:`, fileName);
|
|
|
+
|
|
|
+ const processedFile = {
|
|
|
+ ...file,
|
|
|
+ uid: `upload_${Date.now()}_${index}`,
|
|
|
+ name: fileName,
|
|
|
+ url: filePath,
|
|
|
+ path: filePath, // 确保path属性存在
|
|
|
+ tempFilePath: filePath, // 确保tempFilePath属性存在
|
|
|
+ status: 'uploading',
|
|
|
+ ossId: null,
|
|
|
+ type: file.type || this.getFileTypeByName(fileName),
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log(`✅ 文件 ${index + 1} 处理完成:`, processedFile);
|
|
|
+ return processedFile;
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('📊 处理后的新文件数量:', newFiles.length);
|
|
|
+ console.log('📊 处理后的新文件列表:', newFiles);
|
|
|
+
|
|
|
+ // 添加到附件列表
|
|
|
+ console.log('📋 当前attachmentList长度:', this.attachmentList.length);
|
|
|
+ this.attachmentList.push(...newFiles);
|
|
|
+ console.log('📋 添加后attachmentList长度:', this.attachmentList.length);
|
|
|
+
|
|
|
+ // 显示上传提示
|
|
|
+ uni.showToast({
|
|
|
+ title: `开始上传 ${newFiles.length} 个文件`,
|
|
|
+ icon: 'loading',
|
|
|
+ duration: 1500,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 🚀 关键步骤:上传文件到服务器
|
|
|
+ console.log('🚀 开始逐个上传文件到服务器...');
|
|
|
+ newFiles.forEach((file, index) => {
|
|
|
+ console.log(`🚀 准备上传第 ${index + 1} 个文件: ${file.name}`);
|
|
|
+ console.log(`🚀 文件路径: ${file.path || file.url}`);
|
|
|
+
|
|
|
+ // 立即调用上传方法
|
|
|
+ this.uploadFileToServer(file)
|
|
|
+ .then(() => {
|
|
|
+ console.log(`✅ 文件 ${file.name} 上传完成`);
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error(`❌ 文件 ${file.name} 上传失败:`, error);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 检查文件大小是否超过限制
|
|
|
+ checkFileSize(file) {
|
|
|
+ const maxSize = 10 * 1024 * 1024; // 10MB
|
|
|
+ const fileSize = file.size || 0;
|
|
|
+
|
|
|
+ console.log('=== 检查文件大小 ===');
|
|
|
+ console.log('文件大小:', fileSize, '字节');
|
|
|
+ console.log('最大限制:', maxSize, '字节');
|
|
|
+ console.log('是否超过限制:', fileSize > maxSize);
|
|
|
+
|
|
|
+ if (fileSize > maxSize) {
|
|
|
+ const fileSizeMB = (fileSize / (1024 * 1024)).toFixed(2);
|
|
|
+ uni.showToast({
|
|
|
+ title: `文件大小 ${fileSizeMB}MB,超过10MB限制`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000,
|
|
|
+ });
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 上传文件到服务器
|
|
|
+ async uploadFileToServer(file) {
|
|
|
+ try {
|
|
|
+ console.log('🚀 === 开始上传文件到服务器 ===');
|
|
|
+ console.log('📁 待上传文件对象:', file);
|
|
|
+ console.log('📁 文件对象属性:', Object.keys(file));
|
|
|
+
|
|
|
+ // 更新文件状态
|
|
|
+ file.status = 'uploading';
|
|
|
+
|
|
|
+ // 确定正确的文件路径 - uview2通常使用url属性
|
|
|
+ const filePath = file.url || file.tempFilePath || file.path;
|
|
|
+ console.log('🎯 文件路径优先级选择:', {
|
|
|
+ url: file.url,
|
|
|
+ tempFilePath: file.tempFilePath,
|
|
|
+ path: file.path,
|
|
|
+ selected: filePath,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!filePath) {
|
|
|
+ const error = new Error('文件路径不存在');
|
|
|
+ console.error('❌ 上传失败 - 文件路径为空:', file);
|
|
|
+ file.status = 'error';
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证文件路径格式
|
|
|
+ console.log('🔍 验证文件路径格式:', filePath);
|
|
|
+ if (
|
|
|
+ !filePath.startsWith('http') &&
|
|
|
+ !filePath.startsWith('/') &&
|
|
|
+ !filePath.startsWith('wxfile://') &&
|
|
|
+ !filePath.startsWith('file://') &&
|
|
|
+ !filePath.startsWith('blob:')
|
|
|
+ ) {
|
|
|
+ console.warn('⚠️ 文件路径格式可能不正确:', filePath);
|
|
|
+ } else {
|
|
|
+ console.log('✅ 文件路径格式验证通过');
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示上传进度
|
|
|
+ uni.showLoading({
|
|
|
+ title: `上传${file.name}...`,
|
|
|
+ mask: true,
|
|
|
+ });
|
|
|
+ console.log(`💫 开始上传文件: ${file.name}`);
|
|
|
+
|
|
|
+ // 调用智能上传API(自动适配微信环境)
|
|
|
+ console.log('📡 调用智能上传API...');
|
|
|
+ console.log('📡 API参数:', {
|
|
|
+ url: '/resource/oss/upload',
|
|
|
+ file: file,
|
|
|
+ formData: { bizType: 'dorm_hygiene_check_attachment' },
|
|
|
+ 环境检测: {
|
|
|
+ isWeChat: isWeChat(),
|
|
|
+ hasBlob: !!file._blob,
|
|
|
+ isBlobUrl: filePath.startsWith('blob:'),
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 🚀 使用智能上传方法,自动处理微信环境兼容性问题
|
|
|
+ const response = await smartUploadFile('/resource/oss/upload', file, {
|
|
|
+ bizType: 'dorm_hygiene_check_attachment',
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('📡 上传API响应:', response);
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ if (response && response.code === 200) {
|
|
|
+ // 上传成功
|
|
|
+ file.url = response.data.url;
|
|
|
+ file.ossId = response.data.ossId;
|
|
|
+ file.status = 'success';
|
|
|
+
|
|
|
+ console.log('✅ 文件上传成功:', {
|
|
|
+ fileName: file.name,
|
|
|
+ ossId: file.ossId,
|
|
|
+ url: file.url,
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '上传成功',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 1500,
|
|
|
+ });
|
|
|
+
|
|
|
+ return response;
|
|
|
+ } else {
|
|
|
+ // 上传失败
|
|
|
+ file.status = 'error';
|
|
|
+ const errorMsg = response?.msg || '上传失败';
|
|
|
+ console.error('❌ 上传失败 - API返回错误:', {
|
|
|
+ response: response,
|
|
|
+ fileName: file.name,
|
|
|
+ errorMsg: errorMsg,
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: errorMsg,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000,
|
|
|
+ });
|
|
|
+
|
|
|
+ throw new Error(errorMsg);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('❌ uploadFileToServer执行失败:', error);
|
|
|
+ console.error('❌ 错误详情:', {
|
|
|
+ fileName: file.name,
|
|
|
+ filePath: file.url || file.tempFilePath || file.path,
|
|
|
+ errorMessage: error.message,
|
|
|
+ errorStack: error.stack,
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.hideLoading();
|
|
|
+ file.status = 'error';
|
|
|
+
|
|
|
+ // 更详细的错误提示
|
|
|
+ let errorMessage = '上传失败';
|
|
|
+ if (error.message) {
|
|
|
+ if (error.message.includes('file is not present')) {
|
|
|
+ errorMessage = '文件格式不支持或文件损坏';
|
|
|
+ } else if (error.message.includes('timeout')) {
|
|
|
+ errorMessage = '上传超时,请检查网络连接';
|
|
|
+ } else if (error.message.includes('网络')) {
|
|
|
+ errorMessage = '网络连接失败,请检查网络';
|
|
|
+ } else if (error.message.includes('文件路径')) {
|
|
|
+ errorMessage = '文件选择异常,请重新选择';
|
|
|
+ } else {
|
|
|
+ errorMessage = error.message;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.error('❌ 最终错误消息:', errorMessage);
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '上传失败',
|
|
|
+ content: `文件"${file.name}"上传失败:${errorMessage}`,
|
|
|
+ showCancel: true,
|
|
|
+ cancelText: '取消',
|
|
|
+ confirmText: '重试',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ console.log('🔄 用户选择重试上传');
|
|
|
+ // 重置状态并重试
|
|
|
+ file.status = 'pending';
|
|
|
+ this.uploadFileToServer(file);
|
|
|
+ } else {
|
|
|
+ console.log('🚫 用户取消重试');
|
|
|
+ // 从列表中移除失败的文件
|
|
|
+ const index = this.attachmentList.findIndex((f) => f.uid === file.uid);
|
|
|
+ if (index > -1) {
|
|
|
+ this.attachmentList.splice(index, 1);
|
|
|
+ console.log('🗑️ 已从附件列表中移除失败文件');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片预览
|
|
|
+ handleImagePreview(image, index) {
|
|
|
+ if (image.status !== 'success') {
|
|
|
+ uni.showToast({
|
|
|
+ title: '图片尚未上传完成',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有成功上传的图片URL
|
|
|
+ const imageUrls = this.imageFiles
|
|
|
+ .filter((file) => file.status === 'success')
|
|
|
+ .map((file) => file.blobUrl || file.url);
|
|
|
+
|
|
|
+ if (imageUrls.length === 0) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '暂无可预览的图片',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 找到当前图片在成功列表中的索引
|
|
|
+ const currentUrl = image.blobUrl || image.url;
|
|
|
+ const currentIndex = imageUrls.findIndex((url) => url === currentUrl);
|
|
|
+
|
|
|
+ uni.previewImage({
|
|
|
+ urls: imageUrls,
|
|
|
+ current: currentIndex >= 0 ? currentIndex : 0,
|
|
|
+ success: () => {
|
|
|
+ // 预览成功
|
|
|
+ },
|
|
|
+ fail: (error) => {
|
|
|
+ console.error('图片预览失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: '预览失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片下载(带重试机制)
|
|
|
+ async handleImageDownload(image, event, retryCount = 0) {
|
|
|
+ // 事件阻止已由handleImageDownloadClick方法处理
|
|
|
+
|
|
|
+ if (image.status !== 'success') {
|
|
|
+ uni.showToast({
|
|
|
+ title: '图片尚未上传完成',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 显示下载进度提示
|
|
|
+ uni.showLoading({
|
|
|
+ title: '正在下载图片...',
|
|
|
+ mask: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 如果有ossId,通过API下载
|
|
|
+ if (image.ossId) {
|
|
|
+ const downloadUrl = `/resource/oss/download/${image.ossId}`;
|
|
|
+
|
|
|
+ // 调用下载接口并等待完成
|
|
|
+ const response = await downloadFile(downloadUrl, image.name);
|
|
|
+
|
|
|
+ if (response.statusCode === 200) {
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.saveFile({
|
|
|
+ tempFilePath: response.tempFilePath,
|
|
|
+ success: function (res) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '下载成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ fail: function () {
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('图片下载过程出错:', error);
|
|
|
+
|
|
|
+ // 确保隐藏loading状态
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ // 重试机制(最多重试2次)
|
|
|
+ if (retryCount < 2) {
|
|
|
+ // 显示重试提示
|
|
|
+ uni.showToast({
|
|
|
+ title: `下载失败,正在重试(${retryCount + 1}/2)...`,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 1500,
|
|
|
+ });
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ this.handleImageDownload(image, event, retryCount + 1);
|
|
|
+ }, 1500); // 延迟1.5秒后重试
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 超过重试次数,显示最终错误信息
|
|
|
+ const errorMsg = error.message?.includes('网络')
|
|
|
+ ? '网络连接失败,请检查网络后重试'
|
|
|
+ : error.message?.includes('timeout')
|
|
|
+ ? '下载超时,请重试'
|
|
|
+ : '下载失败,已重试2次,请检查网络或稍后重试';
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: errorMsg,
|
|
|
+ icon: 'none',
|
|
|
+ duration: 3000,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片点击处理(智能判断是预览还是操作按钮)
|
|
|
+ handleImageClick(image, index, event) {
|
|
|
+ // 检查点击目标是否是操作按钮区域
|
|
|
+ const target = event.target;
|
|
|
+ const isActionButton =
|
|
|
+ target.closest('.image-actions') ||
|
|
|
+ target.closest('u-button') ||
|
|
|
+ target.tagName === 'BUTTON';
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除按钮点击处理(更强的事件处理)
|
|
|
+ handleImageDeleteClick(image, index, event) {
|
|
|
+ console.log('删除按钮被点击:', image, index);
|
|
|
+
|
|
|
+ // 强制阻止事件冒泡和默认行为
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ event.stopImmediatePropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用实际的删除方法
|
|
|
+ this.handleImageDelete(image, index);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片下载按钮点击处理(更强的事件处理)
|
|
|
+ handleImageDownloadClick(image, event) {
|
|
|
+ // 强制阻止事件冒泡和默认行为
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ event.stopImmediatePropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用实际的下载方法
|
|
|
+ this.handleImageDownload(image, event);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 图片删除
|
|
|
+ handleImageDelete(image, index, event) {
|
|
|
+ // 强制阻止事件冒泡和默认行为
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认删除',
|
|
|
+ content: '确定要删除这张图片吗?删除后无法恢复。',
|
|
|
+ confirmColor: '#ff5722',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ // 从附件列表中删除
|
|
|
+ const attachmentIndex = this.attachmentList.findIndex(
|
|
|
+ (file) =>
|
|
|
+ file.uid === image.uid ||
|
|
|
+ (file.ossId && file.ossId === image.ossId) ||
|
|
|
+ (file.url && file.url === image.url),
|
|
|
+ );
|
|
|
+
|
|
|
+ if (attachmentIndex >= 0) {
|
|
|
+ this.attachmentList.splice(attachmentIndex, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理blob URL,防止内存泄漏
|
|
|
+ if (image.blobUrl && image.blobUrl.startsWith('blob:')) {
|
|
|
+ URL.revokeObjectURL(image.blobUrl);
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '删除成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('删除确认对话框显示失败:', err);
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 文件下载(微信小程序专用版本)
|
|
|
+ async handleDownloadFile(file, event) {
|
|
|
+ // 事件阻止已由handleDownloadFileClick方法处理
|
|
|
+
|
|
|
+ if (!file.ossId) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '文件未上传完成',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ uni.showLoading({
|
|
|
+ title: `正在下载${file.name}...`,
|
|
|
+ mask: true,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建下载URL
|
|
|
+ const downloadUrl = `/resource/oss/download/${file.ossId}`;
|
|
|
+
|
|
|
+ if (this.isImageFile(file)) {
|
|
|
+ // 图片文件:下载并保存到相册
|
|
|
+ await this.downloadAndSaveImage(downloadUrl, file.name);
|
|
|
+ } else {
|
|
|
+ // 非图片文件:下载并直接打开预览
|
|
|
+ await this.downloadAndOpenDocument(downloadUrl, file.name);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('下载过程发生异常:', error);
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ let errorMsg = '下载失败,请重试';
|
|
|
+ if (error.message.includes('timeout')) {
|
|
|
+ errorMsg = '下载超时,请检查网络后重试';
|
|
|
+ } else if (error.message.includes('network')) {
|
|
|
+ errorMsg = '网络错误,请检查网络连接';
|
|
|
+ } else if (error.message.includes('404')) {
|
|
|
+ errorMsg = '文件不存在,请联系管理员';
|
|
|
+ } else if (error.message.includes('500')) {
|
|
|
+ errorMsg = '服务器错误,请稍后重试';
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showModal({
|
|
|
+ title: '下载失败',
|
|
|
+ content: errorMsg,
|
|
|
+ showCancel: true,
|
|
|
+ cancelText: '取消',
|
|
|
+ confirmText: '重试',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ this.handleDownloadFile(file);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 下载图片并保存到相册
|
|
|
+ async downloadAndSaveImage(downloadUrl, fileName) {
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ // 调用下载接口并等待完成
|
|
|
+ const response = await downloadFile(downloadUrl, fileName);
|
|
|
+
|
|
|
+ if (response.statusCode === 200) {
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.saveFile({
|
|
|
+ tempFilePath: response.tempFilePath,
|
|
|
+ success: function (res) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '下载成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ fail: function () {
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ uni.hideLoading();
|
|
|
+ resolve();
|
|
|
+ // #endif
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 下载文档并打开预览
|
|
|
+ async downloadAndOpenDocument(downloadUrl, fileName) {
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ // 调用下载接口并等待完成
|
|
|
+ const response = await downloadFile(downloadUrl, fileName);
|
|
|
+
|
|
|
+ if (response.statusCode === 200) {
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.saveFile({
|
|
|
+ tempFilePath: response.tempFilePath,
|
|
|
+ success: function (res) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '下载成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ fail: function () {
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ }
|
|
|
+ uni.hideLoading();
|
|
|
+ resolve();
|
|
|
+ // #endif
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 文件下载按钮点击处理(更强的事件处理)
|
|
|
+ handleDownloadFileClick(file, event) {
|
|
|
+ // 强制阻止事件冒泡和默认行为
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ event.stopImmediatePropagation();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用实际的下载方法
|
|
|
+ this.handleDownloadFile(file, event);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 删除文档
|
|
|
+ handleRemoveFile(file, index, event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ uni.showModal({
|
|
|
+ title: '确认删除',
|
|
|
+ content: '确定要删除这个文件吗?',
|
|
|
+ success: (res) => {
|
|
|
+ if (res.confirm) {
|
|
|
+ const fileIndex = this.attachmentList.findIndex((f) => f.uid === file.uid);
|
|
|
+ if (fileIndex > -1) {
|
|
|
+ this.attachmentList.splice(fileIndex, 1);
|
|
|
+ uni.showToast({
|
|
|
+ title: '文件删除成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 重试上传
|
|
|
+ async retryUploadFile(file, event) {
|
|
|
+ console.log('=== 重试上传文件 ===', file);
|
|
|
+
|
|
|
+ // 强制阻止事件冒泡和默认行为
|
|
|
+ if (event) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 重置文件状态
|
|
|
+ file.status = 'uploading';
|
|
|
+ file.progress = 0;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 根据文件来源选择不同的上传方式
|
|
|
+ if (file.source === 'wechat' && file.localId) {
|
|
|
+ // 微信图片重新上传
|
|
|
+ const uploadResult = await this.uploadWechatImageToOSS(file.localId);
|
|
|
+
|
|
|
+ // 更新文件信息
|
|
|
+ file.status = 'success';
|
|
|
+ file.ossId = uploadResult.ossId;
|
|
|
+ file.url = uploadResult.url;
|
|
|
+ file.name = uploadResult.fileName || file.name;
|
|
|
+ file.size = uploadResult.fileSize || 0;
|
|
|
+ file.progress = 100;
|
|
|
+ } else {
|
|
|
+ // 普通文件重新上传
|
|
|
+ await this.uploadFileToServer(file);
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '重新上传成功',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 1500,
|
|
|
+ });
|
|
|
+ } catch (error) {
|
|
|
+ console.error('重试上传失败:', error);
|
|
|
+ file.status = 'error';
|
|
|
+ file.progress = 0;
|
|
|
+
|
|
|
+ uni.showToast({
|
|
|
+ title: '重试失败,请稍后再试',
|
|
|
+ icon: 'none',
|
|
|
+ duration: 2000,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 格式化日期时间
|
|
|
+ formatDateTime(date) {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ const hours = String(date.getHours()).padStart(2, '0');
|
|
|
+ const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
+ const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 表单提交
|
|
|
+ async handleSubmit() {
|
|
|
+ try {
|
|
|
+ // 表单验证
|
|
|
+ const valid = await this.$refs.formRef.validate();
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ this.submitLoading = true;
|
|
|
+
|
|
|
+ // 处理附件ID
|
|
|
+ const allAttachments = this.attachmentList.filter(
|
|
|
+ (item) => item.status === 'success' && item.ossId,
|
|
|
+ );
|
|
|
+ this.formData.attachmentIds = allAttachments.map((item) => item.ossId).join(',');
|
|
|
+
|
|
|
+ // 处理学生评分数据
|
|
|
+ const studentScores = this.roomStudents.map((student) => ({
|
|
|
+ id: student.id, // 编辑时包含ID
|
|
|
+ studentId: student.studentId,
|
|
|
+ studentName: student.studentName,
|
|
|
+ studentNo: student.studentNo,
|
|
|
+ className: student.className,
|
|
|
+ majorName: student.majorName,
|
|
|
+ collegeName: student.collegeName,
|
|
|
+ bedId: student.bedId,
|
|
|
+ bedCode: student.bedCode,
|
|
|
+ bedName: student.bedName,
|
|
|
+ studentScore: student.student_score ? Number(student.student_score) : undefined,
|
|
|
+ attendanceStatus: student.attendance_status,
|
|
|
+ studentGrade: student.student_grade,
|
|
|
+ isResponsiblePerson: student.isResponsiblePerson
|
|
|
+ ? Number(student.isResponsiblePerson)
|
|
|
+ : 0,
|
|
|
+ scoreRemarks: student.scoreRemarks || '',
|
|
|
+ improvementNotes: student.improvementNotes || '',
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 构建提交数据
|
|
|
+ const submitData = {
|
|
|
+ ...this.formData,
|
|
|
+ // 确保totalScore为数字类型
|
|
|
+ totalScore: this.formData.totalScore
|
|
|
+ ? Number(this.formData.totalScore)
|
|
|
+ : undefined,
|
|
|
+ studentScores: studentScores,
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log('提交数据:', submitData);
|
|
|
+
|
|
|
+ // 调用API
|
|
|
+ const url =
|
|
|
+ this.pageType === 'add'
|
|
|
+ ? '/dormHygieneCheck/dormHygieneCheck/addWithStudentScores'
|
|
|
+ : `/dormHygieneCheck/dormHygieneCheck/updateWithStudentScores`;
|
|
|
+
|
|
|
+ const res = await request({
|
|
|
+ url: url,
|
|
|
+ method: this.pageType === 'add' ? 'POST' : 'PUT',
|
|
|
+ data: submitData,
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res && res.code === 200) {
|
|
|
+ uni.showToast({
|
|
|
+ title: '保存成功',
|
|
|
+ icon: 'success',
|
|
|
+ });
|
|
|
+
|
|
|
+ // 发送刷新事件
|
|
|
+ uni.$emit('refreshCheckList');
|
|
|
+
|
|
|
+ // 返回上一页
|
|
|
+ setTimeout(() => {
|
|
|
+ uni.navigateBack();
|
|
|
+ }, 1500);
|
|
|
+ } else {
|
|
|
+ throw new Error(res?.msg || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交失败:', error);
|
|
|
+ uni.showToast({
|
|
|
+ title: error.message || '保存失败',
|
|
|
+ icon: 'none',
|
|
|
+ });
|
|
|
+ } finally {
|
|
|
+ this.submitLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 返回上一页
|
|
|
+ goBack() {
|
|
|
+ uni.navigateBack();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取文件名短格式
|
|
|
+ getShortFileName(fileName) {
|
|
|
+ if (!fileName) return '';
|
|
|
+
|
|
|
+ const lastDotIndex = fileName.lastIndexOf('.');
|
|
|
+ if (lastDotIndex === -1) {
|
|
|
+ // 没有后缀的文件名
|
|
|
+ return fileName.length > 12 ? fileName.substring(0, 12) + '...' : fileName;
|
|
|
+ }
|
|
|
+
|
|
|
+ const name = fileName.substring(0, lastDotIndex);
|
|
|
+ const ext = fileName.substring(lastDotIndex); // 包含点号的后缀
|
|
|
+
|
|
|
+ if (name.length <= 8) {
|
|
|
+ return fileName; // 文件名较短,直接返回
|
|
|
+ }
|
|
|
+
|
|
|
+ // 文件名较长,缩写中间部分,保留后缀
|
|
|
+ return name.substring(0, 8) + '...' + ext;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 判断是否为图片文件
|
|
|
+ isImageFile(file) {
|
|
|
+ const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp'];
|
|
|
+
|
|
|
+ // 优先使用文件MIME类型判断
|
|
|
+ if (file.type) {
|
|
|
+ return imageTypes.includes(file.type);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 通过文件名判断
|
|
|
+ const fileName = file.name || file.url || '';
|
|
|
+ return /\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(fileName);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 根据文件名获取文件类型
|
|
|
+ getFileTypeByName(fileName) {
|
|
|
+ if (!fileName) return '';
|
|
|
+ const ext = fileName.split('.').pop()?.toLowerCase();
|
|
|
+ const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
|
|
|
+ if (imageTypes.includes(ext)) {
|
|
|
+ // 修正MIME类型映射
|
|
|
+ const mimeTypeMap = {
|
|
|
+ jpg: 'image/jpeg',
|
|
|
+ jpeg: 'image/jpeg',
|
|
|
+ png: 'image/png',
|
|
|
+ gif: 'image/gif',
|
|
|
+ bmp: 'image/bmp',
|
|
|
+ webp: 'image/webp',
|
|
|
+ };
|
|
|
+ return mimeTypeMap[ext] || `image/${ext}`;
|
|
|
+ }
|
|
|
+ return `file/${ext}`;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 预加载图片以确保能正确显示
|
|
|
+ async preloadImages() {
|
|
|
+ console.log('=== 预加载图片 ===');
|
|
|
+ const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file));
|
|
|
+
|
|
|
+ for (const file of imageFiles) {
|
|
|
+ if (file.url) {
|
|
|
+ try {
|
|
|
+ console.log(`预加载图片: ${file.name} - ${file.url}`);
|
|
|
+ // 创建一个Image对象来预加载图片
|
|
|
+ const img = new Image();
|
|
|
+ img.onload = () => {
|
|
|
+ console.log(`图片预加载成功: ${file.name}`);
|
|
|
+ };
|
|
|
+ img.onerror = () => {
|
|
|
+ console.error(`图片预加载失败: ${file.name} - ${file.url}`);
|
|
|
+ };
|
|
|
+ img.src = file.url;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`预加载图片出错: ${file.name}`, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取图片的blob数据(用于uview2 upload组件显示)
|
|
|
+ async getImageBlobUrl(imageUrl) {
|
|
|
+ try {
|
|
|
+ console.log(`获取图片blob数据: ${imageUrl}`);
|
|
|
+
|
|
|
+ // 处理图片URL路径
|
|
|
+ let apiUrl = imageUrl;
|
|
|
+ if (imageUrl.startsWith('/api/')) {
|
|
|
+ // 移除/api前缀,因为request会自动添加baseURL
|
|
|
+ apiUrl = imageUrl.replace('/api', '');
|
|
|
+ } else if (imageUrl.startsWith('/resource/')) {
|
|
|
+ // 直接使用相对路径
|
|
|
+ apiUrl = imageUrl;
|
|
|
+ } else if (imageUrl.startsWith('http')) {
|
|
|
+ // 完整URL,需要特殊处理
|
|
|
+ console.log('检测到完整URL,直接返回原URL');
|
|
|
+ return imageUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`API请求URL: ${apiUrl}`);
|
|
|
+
|
|
|
+ // 使用request方法获取图片数据
|
|
|
+ const response = await request({
|
|
|
+ url: apiUrl,
|
|
|
+ method: 'GET',
|
|
|
+ responseType: 'arraybuffer', // 设置响应类型为arraybuffer
|
|
|
+ header: {
|
|
|
+ isToken: true, // 图片请求可能不需要token验证
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 将arraybuffer转换为blob
|
|
|
+ const blob = new Blob([response], { type: 'image/*' });
|
|
|
+ const blobUrl = URL.createObjectURL(blob);
|
|
|
+
|
|
|
+ console.log(`成功创建blob URL: ${blobUrl}`);
|
|
|
+ return blobUrl;
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`获取图片blob数据失败: ${imageUrl}`, error);
|
|
|
+ // 如果请求失败,返回原始URL作为备用方案
|
|
|
+ return imageUrl;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理图片文件,获取blob数据以确保正确显示
|
|
|
+ async processImageFiles() {
|
|
|
+ console.log('=== 处理图片文件 ===');
|
|
|
+ const imageFiles = this.attachmentList.filter((file) => this.isImageFile(file));
|
|
|
+
|
|
|
+ for (const file of imageFiles) {
|
|
|
+ if (file.url && !file.blobUrl) {
|
|
|
+ try {
|
|
|
+ console.log(`处理图片文件: ${file.name}`);
|
|
|
+ const blobUrl = await this.getImageBlobUrl(file.url);
|
|
|
+ if (blobUrl) {
|
|
|
+ // 更新文件对象,添加blob URL,并标记为图片
|
|
|
+ file.blobUrl = blobUrl;
|
|
|
+ file.url = blobUrl; // 使用blob URL作为显示URL
|
|
|
+ file.fileType = 'image';
|
|
|
+ file.type =
|
|
|
+ file.type && file.type.startsWith('image') ? file.type : 'image';
|
|
|
+ file.isImage = true;
|
|
|
+ console.log(`图片文件处理成功: ${file.name} - ${blobUrl}`);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`处理图片文件失败: ${file.name}`, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 清理blob URL,防止内存泄漏
|
|
|
+ cleanupBlobUrls() {
|
|
|
+ console.log('=== 清理blob URL ===');
|
|
|
+ this.attachmentList.forEach((file) => {
|
|
|
+ if (file.blobUrl && file.blobUrl.startsWith('blob:')) {
|
|
|
+ try {
|
|
|
+ URL.revokeObjectURL(file.blobUrl);
|
|
|
+ console.log(`已清理blob URL: ${file.name}`);
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`清理blob URL失败: ${file.name}`, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #f5f6fa;
|
|
|
+ padding-bottom: 120rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-container {
|
|
|
+ padding: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-section {
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 32rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+ padding-bottom: 16rpx;
|
|
|
+ border-bottom: 2rpx solid #e4e7ed;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+// 学生信息样式
|
|
|
+.student-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.student-item {
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+ border: 1px solid #e9ecef;
|
|
|
+}
|
|
|
+
|
|
|
+.student-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding-bottom: 16rpx;
|
|
|
+ border-bottom: 1px solid #e9ecef;
|
|
|
+}
|
|
|
+
|
|
|
+.student-name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.student-no {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #606266;
|
|
|
+ background-color: #e9ecef;
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
+ border-radius: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.student-bed {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #909399;
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
+ border-radius: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.student-form {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-col {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+// 新增:支持三个字段自适应布局
|
|
|
+.form-row-flex {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.form-col-flex {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ min-width: calc(33.33% - 12rpx); // 每行最多3个,考虑gap
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+// 当屏幕宽度不够时,自适应为两列布局
|
|
|
+@media screen and (max-width: 600rpx) {
|
|
|
+ .form-col-flex {
|
|
|
+ min-width: calc(50% - 8rpx); // 变为2列布局
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 当屏幕宽度很小时,自适应为单列布局
|
|
|
+@media screen and (max-width: 400rpx) {
|
|
|
+ .form-col-flex {
|
|
|
+ min-width: 100%; // 变为1列布局
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.form-label {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #606266;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+// 附件样式
|
|
|
+.upload-area {
|
|
|
+ padding: 20rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.doc-upload-section {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ padding: 20rpx 0;
|
|
|
+ border-top: 2rpx dashed #e0e0e0;
|
|
|
+}
|
|
|
+
|
|
|
+// 微信上传区域样式
|
|
|
+.wechat-upload-section {
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ background: #f0f9ff;
|
|
|
+ border: 2rpx dashed #2979ff;
|
|
|
+ border-radius: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-progress {
|
|
|
+ margin-top: 15rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.progress-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ text-align: center;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+// 完善的上传进度条样式
|
|
|
+.upload-progress {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
|
+ border-radius: 12rpx;
|
|
|
+ border: 1px solid #e1e8ed;
|
|
|
+ box-shadow: 0 2px 8px rgba(41, 121, 255, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.progress-text {
|
|
|
+ display: block;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 26rpx !important;
|
|
|
+ color: #2979ff !important;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ animation: pulse 2s infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes pulse {
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 图片展示区域样式
|
|
|
+.image-section {
|
|
|
+ margin-bottom: 30rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.image-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding-left: 10rpx;
|
|
|
+ border-left: 4rpx solid #2979ff;
|
|
|
+}
|
|
|
+
|
|
|
+.image-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(280rpx, 1fr));
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.image-item {
|
|
|
+ position: relative;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 2rpx solid #f0f0f0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ /* 确保图片容器的点击事件不影响按钮 */
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.image-item:active {
|
|
|
+ transform: scale(0.98);
|
|
|
+ border-color: #2979ff;
|
|
|
+}
|
|
|
+
|
|
|
+.image-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 200rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+
|
|
|
+.attachment-image {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ object-fit: cover;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-overlay {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background-color: rgba(0, 0, 0, 0.6);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.error-overlay {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background-color: rgba(250, 53, 52, 0.8);
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.error-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ margin-top: 8rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.success-badge {
|
|
|
+ position: absolute;
|
|
|
+ top: 8rpx;
|
|
|
+ right: 8rpx;
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+ border-radius: 50%;
|
|
|
+ padding: 4rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.image-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 6rpx;
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ background: #fafafa;
|
|
|
+ border-top: 1rpx solid #f0f0f0;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ justify-content: space-between;
|
|
|
+ /* 确保按钮区域能正确处理点击事件 */
|
|
|
+ position: relative;
|
|
|
+ z-index: 10;
|
|
|
+ pointer-events: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.image-actions .u-button {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 60rpx;
|
|
|
+ font-size: 24rpx !important;
|
|
|
+ /* 确保按钮可点击 */
|
|
|
+ pointer-events: auto;
|
|
|
+ position: relative;
|
|
|
+ z-index: 11;
|
|
|
+}
|
|
|
+
|
|
|
+.image-name {
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ background: #fff;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #666;
|
|
|
+ text-align: center;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+// 自定义文件列表样式
|
|
|
+.custom-file-list {
|
|
|
+ margin-top: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.file-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ padding-left: 10rpx;
|
|
|
+ border-left: 4rpx solid #2979ff;
|
|
|
+}
|
|
|
+
|
|
|
+.file-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(280rpx, 1fr));
|
|
|
+ gap: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.file-card {
|
|
|
+ position: relative;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ border: 2rpx solid #f0f0f0;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.file-card:active {
|
|
|
+ transform: scale(0.98);
|
|
|
+ border-color: #2979ff;
|
|
|
+}
|
|
|
+
|
|
|
+.file-icon-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 200rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #f8f9fa;
|
|
|
+}
|
|
|
+
|
|
|
+.file-info-card {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 8rpx;
|
|
|
+ left: 8rpx;
|
|
|
+ right: 8rpx;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.file-ext-badge {
|
|
|
+ background: rgba(255, 255, 255, 0.9);
|
|
|
+ color: #333;
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
+ border-radius: 4rpx;
|
|
|
+ backdrop-filter: blur(4rpx);
|
|
|
+}
|
|
|
+
|
|
|
+.file-size {
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ color: #fff;
|
|
|
+ font-size: 20rpx;
|
|
|
+ padding: 2rpx 6rpx;
|
|
|
+ border-radius: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.file-actions {
|
|
|
+ display: flex;
|
|
|
+ gap: 6rpx;
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ background: #fafafa;
|
|
|
+ border-top: 1rpx solid #f0f0f0;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.file-actions .u-button {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 60rpx;
|
|
|
+ font-size: 24rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+.file-name-display {
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ background: #fff;
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #666;
|
|
|
+ text-align: center;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-tips {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ padding: 15rpx;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ border-left: 4rpx solid #17a2b8;
|
|
|
+}
|
|
|
+
|
|
|
+.tip-item {
|
|
|
+ display: block;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.wechat-tip {
|
|
|
+ color: #2979ff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+/* 上传状态样式 */
|
|
|
+.upload-status {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 15rpx;
|
|
|
+ padding: 10rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-status.success {
|
|
|
+ color: #19be6b;
|
|
|
+ background-color: rgba(25, 190, 107, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-status.error {
|
|
|
+ color: #fa3534;
|
|
|
+ background-color: rgba(250, 53, 52, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.upload-progress-small {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-text {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #2979ff;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+// 底部操作按钮
|
|
|
+.bottom-actions {
|
|
|
+ position: fixed;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ padding: 20rpx;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ display: flex;
|
|
|
+ gap: 20rpx;
|
|
|
+ z-index: 999;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-actions .u-button {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式设计
|
|
|
+@media (max-width: 375px) {
|
|
|
+ .form-container {
|
|
|
+ padding: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-section {
|
|
|
+ padding: 24rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .student-item {
|
|
|
+ padding: 20rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-row {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-row-flex {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-col-flex {
|
|
|
+ min-width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// uview组件样式调整
|
|
|
+::v-deep .u-form-item {
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-input {
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-textarea {
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-button {
|
|
|
+ border-radius: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+// 确保表单项有足够的高度和宽度
|
|
|
+::v-deep .u-form-item__body {
|
|
|
+ // min-height: 60rpx;
|
|
|
+ padding: 0px !important;
|
|
|
+}
|
|
|
+
|
|
|
+// 统一学生信息卡片中输入框的高度
|
|
|
+::v-deep .student-form .u-input {
|
|
|
+ height: 60rpx !important;
|
|
|
+ line-height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+ padding: 0px 10px !important;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .student-form .u-input__content {
|
|
|
+ height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .student-form .u-input__content__field-wrapper {
|
|
|
+ height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .student-form .u-input__content__field-wrapper__field {
|
|
|
+ height: 60rpx !important;
|
|
|
+ line-height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+// 强制统一所有输入框高度
|
|
|
+::v-deep .form-col-flex .u-input {
|
|
|
+ height: 60rpx !important;
|
|
|
+ line-height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .form-col-flex .u-input__content {
|
|
|
+ height: 60rpx !important;
|
|
|
+ min-height: 60rpx !important;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-form-item__body__right {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-form-item__body__right__content {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-form-item__body__right__content .u-input {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+// 修复选择器样式
|
|
|
+::v-deep .u-picker {
|
|
|
+ z-index: 999999;
|
|
|
+}
|
|
|
+
|
|
|
+::v-deep .u-picker__view {
|
|
|
+ z-index: 999999;
|
|
|
+}
|
|
|
+
|
|
|
+// 响应式适配
|
|
|
+@media screen and (max-width: 750rpx) {
|
|
|
+ .file-grid {
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr));
|
|
|
+ gap: 15rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-icon-container {
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-grid {
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(240rpx, 1fr));
|
|
|
+ gap: 15rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-container {
|
|
|
+ height: 160rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ .file-actions .u-button,
|
|
|
+ .image-actions .u-button {
|
|
|
+ font-size: 22rpx !important;
|
|
|
+ padding: 6rpx 12rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 微信SDK调试样式 */
|
|
|
+.wechat-debug-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 30rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+ border: 2rpx solid #2979ff;
|
|
|
+}
|
|
|
+
|
|
|
+.debug-controls {
|
|
|
+ display: flex;
|
|
|
+ gap: 10rpx;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 10rpx;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.sdk-status {
|
|
|
+ margin: 20rpx 0;
|
|
|
+ padding: 15rpx;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.status-item {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.status-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #666;
|
|
|
+}
|
|
|
+
|
|
|
+.status-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.status-value.success {
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.status-value.error {
|
|
|
+ color: #f5222d;
|
|
|
+}
|
|
|
+
|
|
|
+.debug-logs {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ border: 2rpx solid #e8e8e8;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ background: #fafafa;
|
|
|
+}
|
|
|
+
|
|
|
+.log-content {
|
|
|
+ max-height: 400rpx;
|
|
|
+ padding: 15rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.log-item {
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+ padding: 8rpx 12rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+.log-item.info {
|
|
|
+ background: #e6f7ff;
|
|
|
+ border-left: 4rpx solid #1890ff;
|
|
|
+}
|
|
|
+
|
|
|
+.log-item.success {
|
|
|
+ background: #f6ffed;
|
|
|
+ border-left: 4rpx solid #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.log-item.warning {
|
|
|
+ background: #fffbe6;
|
|
|
+ border-left: 4rpx solid #faad14;
|
|
|
+}
|
|
|
+
|
|
|
+.log-item.error {
|
|
|
+ background: #fff2f0;
|
|
|
+ border-left: 4rpx solid #f5222d;
|
|
|
+}
|
|
|
+
|
|
|
+.log-time {
|
|
|
+ color: #999;
|
|
|
+ font-size: 22rpx;
|
|
|
+ margin-right: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.log-text {
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.log-text.info {
|
|
|
+ color: #1890ff;
|
|
|
+}
|
|
|
+
|
|
|
+.log-text.success {
|
|
|
+ color: #52c41a;
|
|
|
+}
|
|
|
+
|
|
|
+.log-text.warning {
|
|
|
+ color: #faad14;
|
|
|
+}
|
|
|
+
|
|
|
+.log-text.error {
|
|
|
+ color: #f5222d;
|
|
|
+}
|
|
|
+
|
|
|
+.log-detail {
|
|
|
+ margin-top: 5rpx;
|
|
|
+ padding: 8rpx;
|
|
|
+ background: rgba(0, 0, 0, 0.03);
|
|
|
+ border-radius: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-text {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #666;
|
|
|
+ font-family: monospace;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-logs {
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+ padding: 40rpx 0;
|
|
|
+ font-size: 26rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* 浮动调试按钮 */
|
|
|
+.debug-fab {
|
|
|
+ position: fixed;
|
|
|
+ right: 30rpx;
|
|
|
+ bottom: 200rpx;
|
|
|
+ width: 80rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ background: #2979ff;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.3);
|
|
|
+ z-index: 999;
|
|
|
+}
|
|
|
+
|
|
|
+.debug-fab:active {
|
|
|
+ background: #1976d2;
|
|
|
+ transform: scale(0.95);
|
|
|
+}
|
|
|
+</style>
|