| | |
| | | <template> |
| | | <view class="inspection-upload-page"> |
| | | <!-- 页面头部 --> |
| | | <PageHeader title="上传巡检记录" @back="goBack" /> |
| | | |
| | | <PageHeader title="上传巡检记录" |
| | | @back="goBack" /> |
| | | <!-- 页面内容 --> |
| | | <view class="upload-content"> |
| | | <!-- 任务信息卡片 --> |
| | | <view class="task-info-card" v-if="taskInfo"> |
| | | <view class="task-info-card" |
| | | v-if="taskInfo"> |
| | | <view class="task-info-header"> |
| | | <text class="task-name">{{ taskInfo.taskName }}</text> |
| | | </view> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 异常状态选择 --> |
| | | <view class="section-card"> |
| | | <view class="section-title">巡检状态</view> |
| | | <view class="exception-options"> |
| | | <view |
| | | class="exception-option" |
| | | :class="{ active: hasException === false }" |
| | | @click="setExceptionStatus(false)" |
| | | > |
| | | <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon> |
| | | <view class="exception-option" |
| | | :class="{ active: hasException === false }" |
| | | @click="setExceptionStatus(false)"> |
| | | <u-icon name="checkmark-circle" |
| | | size="20" |
| | | color="#52c41a"></u-icon> |
| | | <text class="option-text">正常</text> |
| | | </view> |
| | | <view |
| | | class="exception-option" |
| | | :class="{ active: hasException === true }" |
| | | @click="setExceptionStatus(true)" |
| | | > |
| | | <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> |
| | | <view class="exception-option" |
| | | :class="{ active: hasException === true }" |
| | | @click="setExceptionStatus(true)"> |
| | | <u-icon name="close-circle" |
| | | size="20" |
| | | color="#ff4d4f"></u-icon> |
| | | <text class="option-text">存在异常</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 异常描述(仅在异常时显示) --> |
| | | <view class="section-card" v-if="hasException === true"> |
| | | <view class="section-card" |
| | | v-if="hasException === true"> |
| | | <view class="section-title">异常描述</view> |
| | | <textarea |
| | | v-model="abnormalDescription" |
| | | class="exception-textarea" |
| | | maxlength="500" |
| | | placeholder="请描述异常情况..." |
| | | /> |
| | | <textarea v-model="abnormalDescription" |
| | | class="exception-textarea" |
| | | maxlength="500" |
| | | placeholder="请描述异常情况..." /> |
| | | </view> |
| | | |
| | | <!-- 分类标签页(仅在异常时显示) --> |
| | | <view class="section-card" v-if="hasException === true"> |
| | | <view class="section-card" |
| | | v-if="hasException === true"> |
| | | <view class="upload-tabs"> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentUploadType === 'before' }" |
| | | @click="switchUploadType('before')" |
| | | > |
| | | <view class="tab-item" |
| | | :class="{ active: currentUploadType === 'before' }" |
| | | @click="switchUploadType('before')"> |
| | | 生产前 |
| | | </view> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentUploadType === 'after' }" |
| | | @click="switchUploadType('after')" |
| | | > |
| | | <view class="tab-item" |
| | | :class="{ active: currentUploadType === 'after' }" |
| | | @click="switchUploadType('after')"> |
| | | 生产中 |
| | | </view> |
| | | <view |
| | | class="tab-item" |
| | | :class="{ active: currentUploadType === 'issue' }" |
| | | @click="switchUploadType('issue')" |
| | | > |
| | | <view class="tab-item" |
| | | :class="{ active: currentUploadType === 'issue' }" |
| | | @click="switchUploadType('issue')"> |
| | | 生产后 |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 当前分类的上传区域 --> |
| | | <view class="upload-area"> |
| | | <view class="upload-buttons"> |
| | | <u-button |
| | | type="primary" |
| | | @click="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ marginRight: '10px', flex: 1 }" |
| | | > |
| | | <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px"></u-icon> |
| | | <u-button type="primary" |
| | | @click="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ marginRight: '10px', flex: 1 }"> |
| | | <u-icon name="camera" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></u-icon> |
| | | {{ uploading ? '上传中...' : '拍照' }} |
| | | </u-button> |
| | | <u-button |
| | | type="success" |
| | | @click="chooseMedia('video')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }" |
| | | > |
| | | <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons> |
| | | <u-button type="success" |
| | | @click="chooseMedia('video')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }"> |
| | | <uni-icons type="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></uni-icons> |
| | | {{ uploading ? '上传中...' : '拍视频' }} |
| | | </u-button> |
| | | </view> |
| | | |
| | | <!-- 上传进度 --> |
| | | <view v-if="uploading" class="upload-progress"> |
| | | <u-line-progress :percentage="uploadProgress" :showText="true" activeColor="#409eff"></u-line-progress> |
| | | <view v-if="uploading" |
| | | class="upload-progress"> |
| | | <u-line-progress :percentage="uploadProgress" |
| | | :showText="true" |
| | | activeColor="#409eff"></u-line-progress> |
| | | </view> |
| | | |
| | | <!-- 当前分类的文件列表 --> |
| | | <view v-if="getCurrentFiles().length > 0" class="file-list"> |
| | | <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item"> |
| | | <view v-if="getCurrentFiles().length > 0" |
| | | class="file-list"> |
| | | <view v-for="(file, index) in getCurrentFiles()" |
| | | :key="index" |
| | | class="file-item"> |
| | | <view class="file-preview-container"> |
| | | <image |
| | | v-if="file.type === 'image' || (file.type !== 'video' && !file.type)" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | class="file-preview" |
| | | mode="aspectFill" |
| | | /> |
| | | <view v-else-if="file.type === 'video'" class="video-preview"> |
| | | <uni-icons type="videocam" size="18" color="#fff" style="margin-right: 5px"></uni-icons> |
| | | <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | class="file-preview" |
| | | mode="aspectFill" /> |
| | | <view v-else-if="file.type === 'video'" |
| | | class="video-preview"> |
| | | <uni-icons type="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></uni-icons> |
| | | <text class="video-text">视频</text> |
| | | </view> |
| | | <!-- 删除按钮 --> |
| | | <view class="delete-btn" @click="removeFile(index)"> |
| | | <u-icon name="close" size="12" color="#fff"></u-icon> |
| | | <view class="delete-btn" |
| | | @click="removeFile(index)"> |
| | | <u-icon name="close" |
| | | size="12" |
| | | color="#fff"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="file-info"> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="getCurrentFiles().length === 0" class="empty-state"> |
| | | <view v-if="getCurrentFiles().length === 0" |
| | | class="empty-state"> |
| | | <text>请选择要上传的{{ getUploadTypeText() }}图片或视频</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 统计信息 --> |
| | | <view class="upload-summary"> |
| | | <text class="summary-text"> |
| | |
| | | </text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 正常状态提示 --> |
| | | <view class="normal-tip-card" v-if="hasException === false"> |
| | | <u-icon name="info-circle" size="60" color="#52c41a"></u-icon> |
| | | <view class="normal-tip-card" |
| | | v-if="hasException === false"> |
| | | <u-icon name="info-circle" |
| | | size="60" |
| | | color="#52c41a"></u-icon> |
| | | <text class="tip-text">设备运行正常,无需上传照片</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 底部按钮 --> |
| | | <view class="footer-buttons"> |
| | | <u-button @click="goBack" :customStyle="{ marginRight: '10px' }">取消</u-button> |
| | | <u-button v-if="hasException === true" type="warning" @click="goToRepair" :customStyle="{ marginRight: '10px' }"> |
| | | <u-button @click="goBack" |
| | | :customStyle="{ marginRight: '10px' }">取消</u-button> |
| | | <u-button v-if="hasException === true" |
| | | type="warning" |
| | | @click="goToRepair" |
| | | :customStyle="{ marginRight: '10px' }"> |
| | | 新增报修 |
| | | </u-button> |
| | | <u-button type="primary" @click="submitUpload">提交</u-button> |
| | | <u-button type="primary" |
| | | @click="submitUpload">提交</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue'; |
| | | import { onLoad } from '@dcloudio/uni-app'; |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import { uploadInspectionTask } from '@/api/inspectionManagement'; |
| | | import { getToken } from '@/utils/auth'; |
| | | import config from '@/config'; |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { uploadInspectionTask } from "@/api/inspectionManagement"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import config from "@/config"; |
| | | |
| | | // 任务信息 |
| | | const taskInfo = ref(null); |
| | | // 任务信息 |
| | | const taskInfo = ref(null); |
| | | |
| | | // 上传相关状态 |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | // 上传相关状态 |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | |
| | | // 三个分类的上传状态 |
| | | const beforeModelValue = ref([]); // 生产前 |
| | | const afterModelValue = ref([]); // 生产中 |
| | | const issueModelValue = ref([]); // 生产后 |
| | | // 三个分类的上传状态 |
| | | const beforeModelValue = ref([]); // 生产前 |
| | | const afterModelValue = ref([]); // 生产中 |
| | | const issueModelValue = ref([]); // 生产后 |
| | | |
| | | // 当前激活的上传类型 |
| | | const currentUploadType = ref('before'); // 'before', 'after', 'issue' |
| | | // 当前激活的上传类型 |
| | | const currentUploadType = ref("before"); // 'before', 'after', 'issue' |
| | | |
| | | // 异常状态 |
| | | const hasException = ref(null); // null: 未选择, true: 存在异常, false: 正常 |
| | | // 异常描述 |
| | | const abnormalDescription = ref(''); |
| | | // 异常状态 |
| | | const hasException = ref(null); // null: 未选择, true: 存在异常, false: 正常 |
| | | // 异常描述 |
| | | const abnormalDescription = ref(""); |
| | | |
| | | // 上传配置 |
| | | const uploadConfig = { |
| | | action: '/file/upload', |
| | | limit: 10, |
| | | fileSize: 50, // MB |
| | | fileType: ['jpg', 'jpeg', 'png', 'mp4', 'mov'], |
| | | maxVideoDuration: 60, // 秒 |
| | | }; |
| | | // 上传配置 |
| | | const uploadConfig = { |
| | | action: "/common/upload", |
| | | limit: 10, |
| | | fileSize: 50, // MB |
| | | fileType: ["jpg", "jpeg", "png", "mp4", "mov"], |
| | | maxVideoDuration: 60, // 秒 |
| | | }; |
| | | |
| | | // 计算上传URL |
| | | const uploadFileUrl = computed(() => { |
| | | const baseUrl = config.baseUrl; |
| | | return baseUrl + uploadConfig.action; |
| | | }); |
| | | // 计算上传URL |
| | | const uploadFileUrl = computed(() => { |
| | | const baseUrl = config.baseUrl; |
| | | return baseUrl + uploadConfig.action; |
| | | }); |
| | | |
| | | // 页面加载 |
| | | onLoad((options) => { |
| | | if (options.taskInfo) { |
| | | try { |
| | | taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo)); |
| | | } catch (e) { |
| | | console.error('解析任务信息失败:', e); |
| | | // 页面加载 |
| | | onLoad(options => { |
| | | if (options.taskInfo) { |
| | | try { |
| | | const info = JSON.parse(decodeURIComponent(options.taskInfo)); |
| | | taskInfo.value = info; |
| | | |
| | | // 回显逻辑:从 taskInfo 中恢复已上传的文件 |
| | | const mapFiles = list => { |
| | | if (!list || !Array.isArray(list)) return []; |
| | | return list.map(item => { |
| | | // 处理 URL,去除可能的空格 |
| | | const finalUrl = (item.url || item.previewURL || "").trim(); |
| | | // 自动推断文件类型 |
| | | let fileType = item.type; |
| | | if (!fileType && item.contentType) { |
| | | fileType = item.contentType.startsWith("video") ? "video" : "image"; |
| | | } else if (!fileType) { |
| | | fileType = "image"; // 默认图片 |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | url: finalUrl, |
| | | name: item.name || item.originalFilename, |
| | | tempId: item.tempId || item.id || item.tempFileId, |
| | | size: item.size || item.byteSize || 0, // 映射大小字段 |
| | | type: fileType, |
| | | status: "success", |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | // 根据用户要求映射:AfterDTO(生产前), DTO(生产中), BeforeDTO(生产后) |
| | | if ( |
| | | info.commonFileListAfterVO && |
| | | Array.isArray(info.commonFileListAfterVO) |
| | | ) { |
| | | beforeModelValue.value = mapFiles(info.commonFileListAfterVO); |
| | | } |
| | | console.log(beforeModelValue.value, "beforeModelValue"); |
| | | |
| | | if (info.commonFileListVO && Array.isArray(info.commonFileListVO)) { |
| | | afterModelValue.value = mapFiles(info.commonFileListVO); |
| | | } |
| | | if ( |
| | | info.commonFileListBeforeVO && |
| | | Array.isArray(info.commonFileListBeforeVO) |
| | | ) { |
| | | issueModelValue.value = mapFiles(info.commonFileListBeforeVO); |
| | | } |
| | | |
| | | // 如果有异常描述,也恢复 |
| | | if (info.abnormalDescription) { |
| | | abnormalDescription.value = info.abnormalDescription; |
| | | } |
| | | // 如果有异常状态,也恢复 |
| | | if (info.hasException !== undefined && info.hasException !== null) { |
| | | hasException.value = info.hasException; |
| | | } else if ( |
| | | info.inspectionResult !== undefined && |
| | | info.inspectionResult !== null |
| | | ) { |
| | | // 0-异常,1-正常 |
| | | hasException.value = String(info.inspectionResult) === "0"; |
| | | } |
| | | |
| | | // 自动兜底:如果存在已上传文件,则必然是异常状态,确保 UI 正常显示 |
| | | if ( |
| | | !hasException.value && |
| | | (beforeModelValue.value.length > 0 || |
| | | afterModelValue.value.length > 0 || |
| | | issueModelValue.value.length > 0) |
| | | ) { |
| | | hasException.value = true; |
| | | } |
| | | } catch (e) { |
| | | console.error("解析任务信息失败:", e); |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // 切换上传类型 |
| | | const switchUploadType = (type) => { |
| | | currentUploadType.value = type; |
| | | }; |
| | | // 切换上传类型 |
| | | const switchUploadType = type => { |
| | | currentUploadType.value = type; |
| | | }; |
| | | |
| | | // 获取当前分类的文件列表 |
| | | const getCurrentFiles = () => { |
| | | switch (currentUploadType.value) { |
| | | case 'before': |
| | | return beforeModelValue.value || []; |
| | | case 'after': |
| | | return afterModelValue.value || []; |
| | | case 'issue': |
| | | return issueModelValue.value || []; |
| | | default: |
| | | return []; |
| | | } |
| | | }; |
| | | // 获取当前分类的文件列表 |
| | | const getCurrentFiles = () => { |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | return beforeModelValue.value || []; |
| | | case "after": |
| | | return afterModelValue.value || []; |
| | | case "issue": |
| | | return issueModelValue.value || []; |
| | | default: |
| | | return []; |
| | | } |
| | | }; |
| | | |
| | | // 获取上传类型文本 |
| | | const getUploadTypeText = () => { |
| | | switch (currentUploadType.value) { |
| | | case 'before': |
| | | return '生产前'; |
| | | case 'after': |
| | | return '生产中'; |
| | | case 'issue': |
| | | return '生产后'; |
| | | default: |
| | | return ''; |
| | | } |
| | | }; |
| | | // 获取上传类型文本 |
| | | const getUploadTypeText = () => { |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | return "生产前"; |
| | | case "after": |
| | | return "生产中"; |
| | | case "issue": |
| | | return "生产后"; |
| | | default: |
| | | return ""; |
| | | } |
| | | }; |
| | | |
| | | // 设置异常状态 |
| | | const setExceptionStatus = (status) => { |
| | | hasException.value = status; |
| | | }; |
| | | // 设置异常状态 |
| | | const setExceptionStatus = status => { |
| | | hasException.value = status; |
| | | }; |
| | | |
| | | // 跳转到新增报修页面 |
| | | const goToRepair = () => { |
| | | try { |
| | | const taskData = { |
| | | taskId: taskInfo.value?.taskId || taskInfo.value?.id, |
| | | taskName: taskInfo.value?.taskName, |
| | | inspectionLocation: taskInfo.value?.inspectionLocation, |
| | | inspector: taskInfo.value?.inspector, |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: afterModelValue.value, |
| | | issue: issueModelValue.value, |
| | | }, |
| | | }; |
| | | // 跳转到新增报修页面 |
| | | const goToRepair = () => { |
| | | try { |
| | | const taskData = { |
| | | taskId: taskInfo.value?.taskId || taskInfo.value?.id, |
| | | taskName: taskInfo.value?.taskName, |
| | | inspectionLocation: taskInfo.value?.inspectionLocation, |
| | | inspector: taskInfo.value?.inspector, |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, // 0-异常,1-正常 |
| | | commonFileListAfterDTO: beforeModelValue.value, |
| | | commonFileListDTO: afterModelValue.value, |
| | | commonFileListBeforeDTO: issueModelValue.value, |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: afterModelValue.value, |
| | | issue: issueModelValue.value, |
| | | }, |
| | | }; |
| | | |
| | | uni.setStorageSync('repairTaskInfo', JSON.stringify(taskData)); |
| | | uni.setStorageSync("repairTaskInfo", JSON.stringify(taskData)); |
| | | |
| | | uni.navigateTo({ |
| | | url: '/pages/equipmentManagement/repair/add', |
| | | }); |
| | | } catch (error) { |
| | | console.error('跳转报修页面失败:', error); |
| | | uni.showToast({ |
| | | title: '跳转失败,请重试', |
| | | icon: 'error', |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 提交上传 |
| | | const submitUpload = async () => { |
| | | try { |
| | | // 检查是否选择了异常状态 |
| | | if (hasException.value === null) { |
| | | uni.navigateTo({ |
| | | url: "/pages/equipmentManagement/repair/add", |
| | | }); |
| | | } catch (error) { |
| | | console.error("跳转报修页面失败:", error); |
| | | uni.showToast({ |
| | | title: '请选择巡检状态', |
| | | icon: 'none', |
| | | title: "跳转失败,请重试", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 提交上传 |
| | | const submitUpload = async () => { |
| | | try { |
| | | // 检查是否选择了异常状态 |
| | | if (hasException.value === null) { |
| | | uni.showToast({ |
| | | title: "请选择巡检状态", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 如果是异常状态,检查是否有上传文件和描述 |
| | | if (hasException.value === true) { |
| | | const totalFiles = |
| | | beforeModelValue.value.length + |
| | | afterModelValue.value.length + |
| | | issueModelValue.value.length; |
| | | if (totalFiles === 0) { |
| | | uni.showToast({ |
| | | title: "请上传异常照片", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | // 检查是否填写了异常描述 |
| | | if (!abnormalDescription.value.trim()) { |
| | | uni.showToast({ |
| | | title: "请填写异常描述", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 显示提交中的加载提示 |
| | | uni.showLoading({ |
| | | title: "提交中...", |
| | | mask: true, |
| | | }); |
| | | |
| | | // 按照逻辑合并所有分类的文件用于提取ID |
| | | const allFiles = [ |
| | | ...beforeModelValue.value, |
| | | ...afterModelValue.value, |
| | | ...issueModelValue.value, |
| | | ]; |
| | | |
| | | // 传给后端的临时文件ID列表 |
| | | let tempFileIds = []; |
| | | if (allFiles.length > 0) { |
| | | tempFileIds = allFiles |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(v => v !== undefined && v !== null && v !== ""); |
| | | } |
| | | |
| | | // 提交数据 |
| | | const submitData = { |
| | | ...taskInfo.value, |
| | | commonFileListAfterDTO: beforeModelValue.value, // 生产前 |
| | | commonFileListDTO: afterModelValue.value, // 生产中 |
| | | commonFileListBeforeDTO: issueModelValue.value, // 生产后 |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, // 0-异常,1-正常 |
| | | abnormalDescription: abnormalDescription.value, |
| | | tempFileIds: tempFileIds, |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(submitData); |
| | | |
| | | // 检查提交结果 |
| | | if (result && (result.code === 200 || result.success)) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: "提交成功", |
| | | icon: "success", |
| | | }); |
| | | |
| | | // 返回列表页并刷新 |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 500); |
| | | } else { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: result?.msg || result?.message || "提交失败", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("提交上传失败:", error); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: error?.message || "提交失败", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 格式化文件大小 |
| | | const formatFileSize = size => { |
| | | if (!size) return "0 B"; |
| | | const units = ["B", "KB", "MB", "GB"]; |
| | | let index = 0; |
| | | let fileSize = size; |
| | | while (fileSize >= 1024 && index < units.length - 1) { |
| | | fileSize /= 1024; |
| | | index++; |
| | | } |
| | | return `${fileSize.toFixed(2)} ${units[index]}`; |
| | | }; |
| | | |
| | | // 拍照/拍视频 |
| | | const chooseMedia = type => { |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `最多只能选择${uploadConfig.limit}个文件`, |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 如果是异常状态,检查是否有上传文件和描述 |
| | | if (hasException.value === true) { |
| | | const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length; |
| | | if (totalFiles === 0) { |
| | | uni.showToast({ |
| | | title: '请上传异常照片', |
| | | icon: 'none', |
| | | }); |
| | | return; |
| | | } |
| | | // 检查是否填写了异常描述 |
| | | if (!abnormalDescription.value.trim()) { |
| | | uni.showToast({ |
| | | title: '请填写异常描述', |
| | | icon: 'none', |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | const remaining = uploadConfig.limit - getCurrentFiles().length; |
| | | |
| | | // 显示提交中的加载提示 |
| | | uni.showLoading({ |
| | | title: '提交中...', |
| | | mask: true, |
| | | }); |
| | | // 优先使用 chooseMedia |
| | | if (typeof uni.chooseMedia === "function") { |
| | | uni.chooseMedia({ |
| | | count: Math.min(remaining, 1), |
| | | mediaType: [type || "image"], |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | try { |
| | | const files = res?.tempFiles || []; |
| | | if (!files.length) throw new Error("未获取到文件"); |
| | | |
| | | // 按照逻辑合并所有分类的文件 |
| | | let arr = []; |
| | | if (beforeModelValue.value.length > 0) { |
| | | arr.push(...beforeModelValue.value); |
| | | } |
| | | if (afterModelValue.value.length > 0) { |
| | | arr.push(...afterModelValue.value); |
| | | } |
| | | if (issueModelValue.value.length > 0) { |
| | | arr.push(...issueModelValue.value); |
| | | } |
| | | |
| | | // 传给后端的临时文件ID列表 |
| | | let tempFileIds = []; |
| | | if (arr !== null && arr.length > 0) { |
| | | tempFileIds = arr |
| | | .map((item) => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter((v) => v !== undefined && v !== null && v !== ''); |
| | | } |
| | | |
| | | // 提交数据 |
| | | const submitData = { |
| | | ...taskInfo.value, |
| | | storageBlobDTO: arr, |
| | | hasException: hasException.value, |
| | | abnormalDescription: abnormalDescription.value, |
| | | tempFileIds: tempFileIds, |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(submitData); |
| | | |
| | | // 检查提交结果 |
| | | if (result && (result.code === 200 || result.success)) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '提交成功', |
| | | icon: 'success', |
| | | }); |
| | | |
| | | // 返回列表页并刷新 |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 500); |
| | | } else { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: result?.msg || result?.message || '提交失败', |
| | | icon: 'error', |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error('提交上传失败:', error); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: error?.message || '提交失败', |
| | | icon: 'error', |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 格式化文件大小 |
| | | const formatFileSize = (size) => { |
| | | if (!size) return '0 B'; |
| | | const units = ['B', 'KB', 'MB', 'GB']; |
| | | let index = 0; |
| | | let fileSize = size; |
| | | while (fileSize >= 1024 && index < units.length - 1) { |
| | | fileSize /= 1024; |
| | | index++; |
| | | } |
| | | return `${fileSize.toFixed(2)} ${units[index]}`; |
| | | }; |
| | | |
| | | // 拍照/拍视频 |
| | | const chooseMedia = (type) => { |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `最多只能选择${uploadConfig.limit}个文件`, |
| | | icon: 'none', |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const remaining = uploadConfig.limit - getCurrentFiles().length; |
| | | |
| | | // 优先使用 chooseMedia |
| | | if (typeof uni.chooseMedia === 'function') { |
| | | uni.chooseMedia({ |
| | | count: Math.min(remaining, 1), |
| | | mediaType: [type || 'image'], |
| | | sizeType: ['compressed', 'original'], |
| | | sourceType: ['camera'], |
| | | success: (res) => { |
| | | try { |
| | | const files = res?.tempFiles || []; |
| | | if (!files.length) throw new Error('未获取到文件'); |
| | | |
| | | files.forEach((tf, idx) => { |
| | | const filePath = tf.tempFilePath || tf.path || ''; |
| | | const fileType = tf.fileType || type || 'image'; |
| | | const ext = fileType === 'video' ? 'mp4' : 'jpg'; |
| | | const file = { |
| | | tempFilePath: filePath, |
| | | path: filePath, |
| | | type: fileType, |
| | | name: `${fileType}_${Date.now()}_${idx}.${ext}`, |
| | | size: tf.size || 0, |
| | | duration: tf.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | } catch (err) { |
| | | uni.showToast({ title: err.message || '处理文件失败', icon: 'none' }); |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | console.error('选择媒体失败:', err); |
| | | uni.showToast({ title: '选择失败', icon: 'none' }); |
| | | }, |
| | | }); |
| | | } else { |
| | | // 降级方案 |
| | | if (type === 'video') { |
| | | uni.chooseVideo({ |
| | | sourceType: ['camera'], |
| | | success: (res) => { |
| | | const file = { |
| | | tempFilePath: res.tempFilePath, |
| | | path: res.tempFilePath, |
| | | type: 'video', |
| | | name: `video_${Date.now()}.mp4`, |
| | | size: res.size || 0, |
| | | duration: res.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: '选择视频失败', icon: 'none' }); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.chooseImage({ |
| | | count: Math.min(remaining, 9), |
| | | sizeType: ['compressed'], |
| | | sourceType: ['camera'], |
| | | success: (res) => { |
| | | const list = res.tempFilePaths || res.tempFiles || []; |
| | | list.forEach((src, idx) => { |
| | | const path = typeof src === 'string' ? src : src.path; |
| | | const file = { |
| | | tempFilePath: path, |
| | | path: path, |
| | | type: 'image', |
| | | name: `image_${Date.now()}_${idx}.jpg`, |
| | | size: 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: '选择图片失败', icon: 'none' }); |
| | | }, |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 上传单个文件 |
| | | const uploadFile = (file) => { |
| | | const token = getToken(); |
| | | if (!token) { |
| | | uni.showToast({ title: '用户未登录', icon: 'none' }); |
| | | return; |
| | | } |
| | | |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | |
| | | const uploadTask = uni.uploadFile({ |
| | | url: uploadFileUrl.value, |
| | | filePath: file.tempFilePath, |
| | | name: 'file', |
| | | header: { |
| | | Authorization: `Bearer ${token}`, |
| | | }, |
| | | formData: { |
| | | type: getTabType(), |
| | | }, |
| | | success: (res) => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | const uploadedFile = { |
| | | ...file, |
| | | url: data.data.url, |
| | | tempId: data.data.tempId || data.data.id, |
| | | status: 'success', |
| | | }; |
| | | |
| | | // 根据当前类型添加到对应数组 |
| | | if (currentUploadType.value === 'before') { |
| | | beforeModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === 'after') { |
| | | afterModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === 'issue') { |
| | | issueModelValue.value.push(uploadedFile); |
| | | files.forEach((tf, idx) => { |
| | | const filePath = tf.tempFilePath || tf.path || ""; |
| | | const fileType = tf.fileType || type || "image"; |
| | | const ext = fileType === "video" ? "mp4" : "jpg"; |
| | | const file = { |
| | | tempFilePath: filePath, |
| | | path: filePath, |
| | | type: fileType, |
| | | name: `${fileType}_${Date.now()}_${idx}.${ext}`, |
| | | size: tf.size || 0, |
| | | duration: tf.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | } catch (err) { |
| | | uni.showToast({ title: err.message || "处理文件失败", icon: "none" }); |
| | | } |
| | | |
| | | uni.showToast({ title: '上传成功', icon: 'success' }); |
| | | } else { |
| | | uni.showToast({ title: data.msg || '上传失败', icon: 'none' }); |
| | | } |
| | | } catch (e) { |
| | | uni.showToast({ title: '解析响应失败', icon: 'none' }); |
| | | }, |
| | | fail: err => { |
| | | console.error("选择媒体失败:", err); |
| | | uni.showToast({ title: "选择失败", icon: "none" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | // 降级方案 |
| | | if (type === "video") { |
| | | uni.chooseVideo({ |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | const file = { |
| | | tempFilePath: res.tempFilePath, |
| | | path: res.tempFilePath, |
| | | type: "video", |
| | | name: `video_${Date.now()}.mp4`, |
| | | size: res.size || 0, |
| | | duration: res.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: "选择视频失败", icon: "none" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.chooseImage({ |
| | | count: Math.min(remaining, 9), |
| | | sizeType: ["compressed"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | const list = res.tempFilePaths || res.tempFiles || []; |
| | | list.forEach((src, idx) => { |
| | | const path = typeof src === "string" ? src : src.path; |
| | | const file = { |
| | | tempFilePath: path, |
| | | path: path, |
| | | type: "image", |
| | | name: `image_${Date.now()}_${idx}.jpg`, |
| | | size: 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: "选择图片失败", icon: "none" }); |
| | | }, |
| | | }); |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | console.error('上传失败:', err); |
| | | uni.showToast({ title: '上传失败', icon: 'none' }); |
| | | }, |
| | | complete: () => { |
| | | uploading.value = false; |
| | | }, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 监听上传进度 |
| | | uploadTask.onProgressUpdate((res) => { |
| | | uploadProgress.value = res.progress; |
| | | }); |
| | | }; |
| | | // 上传单个文件 |
| | | const uploadFile = file => { |
| | | const token = getToken(); |
| | | if (!token) { |
| | | uni.showToast({ title: "用户未登录", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | // 获取type值 |
| | | const getTabType = () => { |
| | | switch (currentUploadType.value) { |
| | | case 'before': |
| | | return 10; |
| | | case 'after': |
| | | return 11; |
| | | case 'issue': |
| | | return 12; |
| | | default: |
| | | return 10; |
| | | } |
| | | }; |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | |
| | | // 删除文件 |
| | | const removeFile = (index) => { |
| | | const files = getCurrentFiles(); |
| | | files.splice(index, 1); |
| | | }; |
| | | const uploadTask = uni.uploadFile({ |
| | | url: uploadFileUrl.value, |
| | | filePath: file.tempFilePath, |
| | | name: "files", |
| | | header: { |
| | | Authorization: `Bearer ${token}`, |
| | | }, |
| | | formData: { |
| | | type: getTabType(), |
| | | }, |
| | | success: res => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | // 兼容 CommonUpload.vue 的处理逻辑 |
| | | const resultData = Array.isArray(data.data) |
| | | ? data.data[0] |
| | | : data.data; |
| | | |
| | | // 处理 url 和 name 赋值 |
| | | const finalUrl = resultData.url || resultData.previewURL; |
| | | const finalName = resultData.name || resultData.originalFilename; |
| | | const finalId = |
| | | resultData.tempId || resultData.id || resultData.tempFileId; |
| | | |
| | | const uploadedFile = { |
| | | ...file, |
| | | ...resultData, // 包含后端返回的所有字段 |
| | | url: finalUrl, |
| | | name: finalName, |
| | | tempId: finalId, |
| | | status: "success", |
| | | }; |
| | | |
| | | // 根据当前类型添加到对应数组 |
| | | if (currentUploadType.value === "before") { |
| | | beforeModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === "after") { |
| | | afterModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === "issue") { |
| | | issueModelValue.value.push(uploadedFile); |
| | | } |
| | | |
| | | uni.showToast({ title: "上传成功", icon: "success" }); |
| | | } else { |
| | | uni.showToast({ title: data.msg || "上传失败", icon: "none" }); |
| | | } |
| | | } catch (e) { |
| | | uni.showToast({ title: "解析响应失败", icon: "none" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("上传失败:", err); |
| | | uni.showToast({ title: "上传失败", icon: "none" }); |
| | | }, |
| | | complete: () => { |
| | | uploading.value = false; |
| | | }, |
| | | }); |
| | | |
| | | // 监听上传进度 |
| | | uploadTask.onProgressUpdate(res => { |
| | | uploadProgress.value = res.progress; |
| | | }); |
| | | }; |
| | | |
| | | // 获取type值 |
| | | const getTabType = () => { |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | return 10; |
| | | case "after": |
| | | return 11; |
| | | case "issue": |
| | | return 12; |
| | | default: |
| | | return 10; |
| | | } |
| | | }; |
| | | |
| | | // 删除文件 |
| | | const removeFile = index => { |
| | | const files = getCurrentFiles(); |
| | | files.splice(index, 1); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inspection-upload-page { |
| | | min-height: 100vh; |
| | | background-color: #f5f5f5; |
| | | padding-bottom: 80px; |
| | | } |
| | | .inspection-upload-page { |
| | | min-height: 100vh; |
| | | background-color: #f5f5f5; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .upload-content { |
| | | padding: 15px; |
| | | } |
| | | .upload-content { |
| | | padding: 15px; |
| | | } |
| | | |
| | | /* 任务信息卡片 */ |
| | | .task-info-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | /* 任务信息卡片 */ |
| | | .task-info-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .task-info-header { |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | .task-info-header { |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .task-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .task-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .task-info-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | .task-info-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .info-label { |
| | | font-size: 13px; |
| | | color: #999; |
| | | } |
| | | .info-label { |
| | | font-size: 13px; |
| | | color: #999; |
| | | } |
| | | |
| | | .info-value { |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | .info-value { |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* 通用卡片样式 */ |
| | | .section-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | /* 通用卡片样式 */ |
| | | .section-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 12px; |
| | | } |
| | | .section-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | /* 异常状态选择 */ |
| | | .exception-options { |
| | | display: flex; |
| | | gap: 12px; |
| | | } |
| | | /* 异常状态选择 */ |
| | | .exception-options { |
| | | display: flex; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .exception-option { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 14px 16px; |
| | | background: #f8f9fa; |
| | | border: 2px solid #e9ecef; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | } |
| | | .exception-option { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 14px 16px; |
| | | background: #f8f9fa; |
| | | border: 2px solid #e9ecef; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .exception-option.active { |
| | | border-color: #409eff; |
| | | background: #f0f8ff; |
| | | } |
| | | .exception-option.active { |
| | | border-color: #409eff; |
| | | background: #f0f8ff; |
| | | } |
| | | |
| | | .option-text { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | .option-text { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* 异常描述 */ |
| | | .exception-textarea { |
| | | width: 100%; |
| | | min-height: 100px; |
| | | padding: 12px; |
| | | background: #f8f9fa; |
| | | border: 1px solid #e9ecef; |
| | | border-radius: 8px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | resize: none; |
| | | box-sizing: border-box; |
| | | } |
| | | /* 异常描述 */ |
| | | .exception-textarea { |
| | | width: 100%; |
| | | min-height: 100px; |
| | | padding: 12px; |
| | | background: #f8f9fa; |
| | | border: 1px solid #e9ecef; |
| | | border-radius: 8px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | resize: none; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .exception-textarea:focus { |
| | | outline: none; |
| | | border-color: #409eff; |
| | | background: #fff; |
| | | } |
| | | .exception-textarea:focus { |
| | | outline: none; |
| | | border-color: #409eff; |
| | | background: #fff; |
| | | } |
| | | |
| | | /* 分类标签页 */ |
| | | .upload-tabs { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | /* 分类标签页 */ |
| | | .upload-tabs { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | padding: 10px; |
| | | text-align: center; |
| | | background: #f5f5f5; |
| | | border-radius: 6px; |
| | | font-size: 13px; |
| | | color: #666; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | .tab-item { |
| | | flex: 1; |
| | | padding: 10px; |
| | | text-align: center; |
| | | background: #f5f5f5; |
| | | border-radius: 6px; |
| | | font-size: 13px; |
| | | color: #666; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background: #409eff; |
| | | color: #fff; |
| | | } |
| | | .tab-item.active { |
| | | background: #409eff; |
| | | color: #fff; |
| | | } |
| | | |
| | | /* 上传区域 */ |
| | | .upload-area { |
| | | padding: 10px 0; |
| | | } |
| | | /* 上传区域 */ |
| | | .upload-area { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .upload-progress { |
| | | margin-bottom: 15px; |
| | | } |
| | | .upload-progress { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | /* 文件列表 */ |
| | | .file-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | /* 文件列表 */ |
| | | .file-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .file-item { |
| | | width: calc(33.33% - 7px); |
| | | } |
| | | .file-item { |
| | | width: calc(33.33% - 7px); |
| | | } |
| | | |
| | | .file-preview-container { |
| | | position: relative; |
| | | width: 100%; |
| | | aspect-ratio: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f5f5f5; |
| | | } |
| | | .file-preview-container { |
| | | position: relative; |
| | | width: 100%; |
| | | aspect-ratio: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f5f5f5; |
| | | } |
| | | |
| | | .file-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | } |
| | | .file-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .video-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #333; |
| | | } |
| | | .video-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #333; |
| | | } |
| | | |
| | | .video-text { |
| | | font-size: 12px; |
| | | color: #fff; |
| | | margin-top: 5px; |
| | | } |
| | | .video-text { |
| | | font-size: 12px; |
| | | color: #fff; |
| | | margin-top: 5px; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 5px; |
| | | right: 5px; |
| | | width: 22px; |
| | | height: 22px; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 5px; |
| | | right: 5px; |
| | | width: 22px; |
| | | height: 22px; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .file-info { |
| | | margin-top: 5px; |
| | | } |
| | | .file-info { |
| | | margin-top: 5px; |
| | | } |
| | | |
| | | .file-name { |
| | | display: block; |
| | | font-size: 11px; |
| | | color: #666; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .file-name { |
| | | display: block; |
| | | font-size: 11px; |
| | | color: #666; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .file-size { |
| | | display: block; |
| | | font-size: 10px; |
| | | color: #999; |
| | | margin-top: 2px; |
| | | } |
| | | .file-size { |
| | | display: block; |
| | | font-size: 10px; |
| | | color: #999; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .empty-state { |
| | | text-align: center; |
| | | padding: 30px; |
| | | color: #999; |
| | | font-size: 13px; |
| | | } |
| | | .empty-state { |
| | | text-align: center; |
| | | padding: 30px; |
| | | color: #999; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* 统计信息 */ |
| | | .upload-summary { |
| | | margin-top: 15px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | /* 统计信息 */ |
| | | .upload-summary { |
| | | margin-top: 15px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | |
| | | .summary-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | .summary-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* 正常状态提示 */ |
| | | .normal-tip-card { |
| | | background: #f6ffed; |
| | | border: 2px dashed #b7eb8f; |
| | | border-radius: 12px; |
| | | padding: 50px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | /* 正常状态提示 */ |
| | | .normal-tip-card { |
| | | background: #f6ffed; |
| | | border: 2px dashed #b7eb8f; |
| | | border-radius: 12px; |
| | | padding: 50px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .normal-tip-card .tip-text { |
| | | margin-top: 15px; |
| | | font-size: 16px; |
| | | color: #52c41a; |
| | | font-weight: 500; |
| | | } |
| | | .normal-tip-card .tip-text { |
| | | margin-top: 15px; |
| | | font-size: 16px; |
| | | color: #52c41a; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* 底部按钮 */ |
| | | .footer-buttons { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | display: flex; |
| | | padding: 15px; |
| | | background: #fff; |
| | | border-top: 1px solid #f0f0f0; |
| | | box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); |
| | | } |
| | | /* 底部按钮 */ |
| | | .footer-buttons { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | display: flex; |
| | | padding: 15px; |
| | | background: #fff; |
| | | border-top: 1px solid #f0f0f0; |
| | | box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); |
| | | } |
| | | </style> |