| | |
| | | <template> |
| | | <view class="upkeep-maintain"> |
| | | <!-- 使用通用页面头部组件 --> |
| | | <PageHeader title="新增保养" @back="goBack" /> |
| | | |
| | | <!-- 表单内容 --> |
| | | <van-form @submit="sendForm" ref="formRef" label-width="110px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center"> |
| | | <!-- 基本信息 --> |
| | | <van-cell-group title="保养信息" inset> |
| | | <van-field |
| | | v-model="form.maintenanceActuallyName" |
| | | label="实际保养人" |
| | | placeholder="请输入实际保养人" |
| | | :rules="formRules.maintenanceActuallyName" |
| | | required |
| | | clearable |
| | | /> |
| | | <van-field |
| | | v-model="form.maintenanceActuallyTime" |
| | | label="实际保养日期" |
| | | placeholder="请选择实际保养日期" |
| | | :rules="formRules.maintenanceActuallyTime" |
| | | required |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable |
| | | /> |
| | | <van-field |
| | | v-model="maintenanceResultText" |
| | | label="保养结果" |
| | | placeholder="请选择保养结果" |
| | | :rules="formRules.maintenanceResult" |
| | | required |
| | | readonly |
| | | @click="showResultPicker" |
| | | clearable |
| | | /> |
| | | </van-cell-group> |
| | | |
| | | <!-- 提交按钮 --> |
| | | <view class="footer-btns"> |
| | | <van-button class="cancel-btn" @click="goBack">取消</van-button> |
| | | <van-button class="save-btn" native-type="submit" form-type="submit" :loading="loading">保存</van-button> |
| | | </view> |
| | | </van-form> |
| | | |
| | | <!-- 日期选择器 --> |
| | | <van-popup v-model:show="showDate" position="bottom"> |
| | | <van-date-picker |
| | | v-model="currentDate" |
| | | title="选择日期" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | /> |
| | | </van-popup> |
| | | |
| | | <!-- 保养结果选择器 --> |
| | | <van-popup v-model:show="showResult" position="bottom"> |
| | | <van-picker |
| | | :model-value="resultPickerValue" |
| | | :columns="resultColumns" |
| | | @confirm="onResultConfirm" |
| | | @cancel="showResult = false" |
| | | /> |
| | | </van-popup> |
| | | </view> |
| | | <view class="upkeep-maintain"> |
| | | <!-- 使用通用页面头部组件 --> |
| | | <PageHeader title="新增保养" |
| | | @back="goBack" /> |
| | | <!-- 表单内容 --> |
| | | <u-form ref="formRef" |
| | | :model="form" |
| | | :rules="formRules" |
| | | label-width="110px" |
| | | :error-type="['message']"> |
| | | <!-- 基本信息 --> |
| | | <u-form-item label="实际保养人" |
| | | prop="maintenanceActuallyName" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.maintenanceActuallyName" |
| | | placeholder="请输入实际保养人" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="实际保养日期" |
| | | prop="maintenanceActuallyTime" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.maintenanceActuallyTime" |
| | | placeholder="请选择实际保养日期" |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" |
| | | @click.stop="showDatePicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="保养结果" |
| | | prop="maintenanceResult" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.maintenanceResult" |
| | | placeholder="请输入保养结果" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="保养状态" |
| | | prop="status" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="maintenancestatusText" |
| | | placeholder="请选择保养状态" |
| | | readonly |
| | | @click="showResultPicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" |
| | | @click="showResultPicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="设备备件" |
| | | prop="sparePartsIds" |
| | | border-bottom> |
| | | <view class="spare-parts-container" |
| | | @click="showSparePartPicker"> |
| | | <view v-if="selectedSpareParts.length > 0" |
| | | class="spare-parts-list"> |
| | | <view v-for="(item, index) in selectedSpareParts" |
| | | :key="index" |
| | | class="spare-part-tag"> |
| | | <text>{{ item.name }}</text> |
| | | <u-icon name="close" |
| | | size="12" |
| | | color="#fff" |
| | | @click="removeSparePart(index)" /> |
| | | </view> |
| | | </view> |
| | | <text v-else |
| | | class="placeholder">请选择设备备件</text> |
| | | </view> |
| | | <template #right> |
| | | <u-icon name="arrow-right" |
| | | @click="showSparePartPicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- 上传附件 --> |
| | | <u-form-item v-if="form.status == '1'" |
| | | label="保养附件" |
| | | border-bottom> |
| | | <view class="simple-upload-area"> |
| | | <view class="upload-buttons"> |
| | | <u-button type="primary" |
| | | @click="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="uploadFiles.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="uploadFiles.length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }"> |
| | | <uni-icons type="videocam" |
| | | name="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> |
| | | <!-- 上传的文件列表 --> |
| | | <view v-if="uploadFiles.length > 0" |
| | | class="file-list"> |
| | | <view v-for="(file, index) in uploadFiles" |
| | | :key="index" |
| | | class="file-item"> |
| | | <view class="file-preview-container"> |
| | | <image v-if="file.type === 'image' || isImageFile(file)" |
| | | :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" |
| | | name="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> |
| | | </view> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? '图片' : '视频') |
| | | }}</text> |
| | | <text class="file-size">{{ formatFileSize(file.size) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="uploadFiles.length === 0" |
| | | class="empty-state"> |
| | | <text>请选择要上传的保养图片</text> |
| | | </view> |
| | | </view> |
| | | </u-form-item> |
| | | <!-- 提交按钮 --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">取消</u-button> |
| | | <u-button class="save-btn" |
| | | @click="sendForm" |
| | | :loading="loading">保存</u-button> |
| | | </view> |
| | | </u-form> |
| | | <!-- 日期选择器 --> |
| | | <u-popup v-model="showDate" |
| | | mode="bottom" |
| | | :closeable="true"> |
| | | <u-datetime-picker v-model="form.maintenanceActuallyTime" |
| | | mode="date" |
| | | title="选择日期" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" /> |
| | | </u-popup> |
| | | <!-- 保养结果选择器 --> |
| | | <up-action-sheet :show="showResult" |
| | | :actions="resultColumns" |
| | | title="选择保养结果" |
| | | @select="onResultConfirm" |
| | | @close="showResult = false" /> |
| | | <!-- 设备备件选择器 --> |
| | | <up-popup :show="showSparePart" |
| | | mode="bottom" |
| | | :closeable="true" |
| | | @close="showSparePart = false"> |
| | | <view class="spare-part-popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">选择设备备件</text> |
| | | </view> |
| | | <view class="spare-part-options"> |
| | | <view v-for="(item, index) in sparePartOptions" |
| | | :key="index" |
| | | class="spare-part-option" |
| | | :class="{ 'selected': isSparePartSelected(item.id) }" |
| | | @click="toggleSparePartSelection(item)"> |
| | | <text>{{ item.name }}</text> |
| | | <u-icon v-if="isSparePartSelected(item.id)" |
| | | name="checkmark" |
| | | color="#2c7be5" /> |
| | | </view> |
| | | </view> |
| | | <up-button type="primary" |
| | | size="small" |
| | | :customStyle="{ borderRadius: '6px', padding: '4px 12px' }" |
| | | @click="confirmSparePartSelection">确定</up-button> |
| | | </view> |
| | | </up-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue'; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import { addMaintenance } from '@/api/equipmentManagement/upkeep'; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | import { showToast } from 'vant'; |
| | | import { ref, onMounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { |
| | | addMaintenance, |
| | | getSparePartsOptions, |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import config from "@/config"; |
| | | |
| | | defineOptions({ |
| | | name: "设备保养表单", |
| | | }); |
| | | // 显示提示信息 |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | const userStore = useUserStore(); |
| | | defineOptions({ |
| | | name: "设备保养表单", |
| | | }); |
| | | |
| | | // 表单引用 |
| | | const formRef = ref(null); |
| | | const loading = ref(false); |
| | | const showDate = ref(false); |
| | | const showResult = ref(false); |
| | | const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]); |
| | | const resultPickerValue = ref([]); |
| | | const maintenanceResultText = ref(''); |
| | | const userStore = useUserStore(); |
| | | |
| | | // 保养结果选项 |
| | | const resultColumns = [ |
| | | { text: '完好', value: 1 }, |
| | | { text: '维修', value: 0 } |
| | | ]; |
| | | // 表单引用 |
| | | const formRef = ref(null); |
| | | const loading = ref(false); |
| | | const showDate = ref(false); |
| | | const showResult = ref(false); |
| | | const showSparePart = ref(false); |
| | | const currentDate = ref([ |
| | | new Date().getFullYear(), |
| | | new Date().getMonth() + 1, |
| | | new Date().getDate(), |
| | | ]); |
| | | const resultPickerValue = ref([]); |
| | | const maintenancestatusText = ref(""); |
| | | const selectedSpareParts = ref([]); |
| | | const tempSelectedSpareParts = ref([]); |
| | | |
| | | // 表单验证规则 |
| | | const formRules = { |
| | | maintenanceActuallyName: [{ required: true, trigger: "blur", message: "请输入实际保养人" }], |
| | | maintenanceActuallyTime: [{ required: true, trigger: "change", message: "请选择实际保养日期" }], |
| | | maintenanceResult: [{ required: true, trigger: "change", message: "请选择保养结果" }], |
| | | }; |
| | | // 文件上传相关 |
| | | const uploadFiles = ref([]); |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | const number = ref(0); |
| | | |
| | | // 使用 ref 声明表单数据 |
| | | const form = ref({ |
| | | maintenanceActuallyName: userStore.nickName || '', // 默认使用当前用户昵称 |
| | | maintenanceResult: undefined, // 保养结果 |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD"), // 实际保养日期(只显示日期) |
| | | }); |
| | | // 上传配置 |
| | | const uploadConfig = { |
| | | limit: 9, |
| | | fileType: ["jpg", "jpeg", "png", "gif", "webp", "mp4", "mov", "avi", "wmv"], |
| | | maxVideoDuration: 60, |
| | | }; |
| | | |
| | | // 清除表单校验状态 |
| | | const clearValidate = () => { |
| | | // Vant4中不需要手动清除验证状态,重置表单时会自动清除 |
| | | // formRef.value?.clearValidate(); // 删除这行 |
| | | }; |
| | | // 上传文件URL |
| | | const uploadFileUrl = ref(`${config.baseUrl}/file/upload`); |
| | | |
| | | // 重置表单数据和校验状态 |
| | | const resetForm = () => { |
| | | form.value = { |
| | | maintenanceActuallyName: userStore.nickName || '', |
| | | maintenanceResult: undefined, |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD"), |
| | | }; |
| | | maintenanceResultText.value = ''; |
| | | }; |
| | | // 保养结果选项 |
| | | const resultColumns = [ |
| | | { name: "完结", value: 1 }, |
| | | { name: "待保养", value: 0 }, |
| | | { name: "失败", value: 2 }, |
| | | ]; |
| | | |
| | | const resetFormAndValidate = () => { |
| | | resetForm(); |
| | | // clearValidate(); // 删除这行,Vant4会自动处理 |
| | | }; |
| | | // 表单验证规则 |
| | | const formRules = { |
| | | maintenanceActuallyName: [ |
| | | { required: true, trigger: "blur", message: "请输入实际保养人" }, |
| | | ], |
| | | maintenanceActuallyTime: [ |
| | | { required: true, trigger: "change", message: "请选择实际保养日期" }, |
| | | ], |
| | | maintenanceResult: [ |
| | | { required: true, trigger: "change", message: "请选择保养结果" }, |
| | | ], |
| | | }; |
| | | |
| | | // 提交表单 |
| | | const sendForm = async () => { |
| | | try { |
| | | // 使用Vant4的正确验证方式 |
| | | formRef.value?.validate().then(() => { |
| | | // 验证通过 |
| | | submitFormData(); |
| | | }).catch((errors) => { |
| | | // 验证失败 |
| | | showToast('请填写完整信息'); |
| | | }); |
| | | } catch (e) { |
| | | showToast('表单验证失败'); |
| | | } |
| | | }; |
| | | // 使用 ref 声明表单数据 |
| | | const form = ref({ |
| | | maintenanceActuallyName: userStore.nickName || "", // 默认使用当前用户昵称 |
| | | maintenanceResult: undefined, // 保养结果 |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // 实际保养日期(只显示日期) |
| | | sparePartsIds: undefined, // 设备备件ID |
| | | }); |
| | | |
| | | // 提交表单数据 |
| | | const submitFormData = async () => { |
| | | try { |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | if (!id) { |
| | | showToast('参数错误'); |
| | | loading.value = false; |
| | | return; |
| | | } |
| | | |
| | | // 准备提交数据,maintenanceActuallyTime 加上当前时分秒 |
| | | const submitData = { ...form.value }; |
| | | if (submitData.maintenanceActuallyTime && !submitData.maintenanceActuallyTime.includes(':')) { |
| | | // 如果 maintenanceActuallyTime 只包含日期,添加当前时分秒 |
| | | submitData.maintenanceActuallyTime = submitData.maintenanceActuallyTime + ' ' + dayjs().format('HH:mm:ss'); |
| | | } |
| | | |
| | | const { code } = await addMaintenance({ id: id, ...submitData }); |
| | | |
| | | if (code == 200) { |
| | | showToast('新增保养成功'); |
| | | resetFormAndValidate(); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast('操作失败'); |
| | | } |
| | | }; |
| | | // 清除表单校验状态 |
| | | const clearValidate = () => { |
| | | // uview-plus不需要手动清除验证状态,重置表单时会自动清除 |
| | | }; |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // 重置表单数据和校验状态 |
| | | const resetForm = () => { |
| | | form.value = { |
| | | maintenanceActuallyName: userStore.nickName || "", |
| | | maintenanceResult: undefined, |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | sparePartsIds: [], |
| | | }; |
| | | maintenancestatusText.value = ""; |
| | | selectedSpareParts.value = []; |
| | | tempSelectedSpareParts.value = []; |
| | | }; |
| | | |
| | | // 获取页面ID |
| | | const getPageId = () => { |
| | | const pages = getCurrentPages(); |
| | | const currentPage = pages[pages.length - 1]; |
| | | const options = currentPage.options; |
| | | return options.id; |
| | | }; |
| | | const resetFormAndValidate = () => { |
| | | resetForm(); |
| | | // clearValidate(); // 删除这行,Vant4会自动处理 |
| | | }; |
| | | // 判断是否为图片文件 |
| | | const isImageFile = file => { |
| | | // 检查contentType字段 |
| | | if (file.contentType && file.contentType.startsWith("image/")) { |
| | | return true; |
| | | } |
| | | |
| | | // 显示日期选择器 |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | // 检查原有的type字段 |
| | | if (file.type === "image") return true; |
| | | |
| | | // 确认日期选择 |
| | | const onDateConfirm = ({ selectedValues }) => { |
| | | // 只保存年月日,不包含时分秒 |
| | | form.value.maintenanceActuallyTime = selectedValues.join('-'); |
| | | currentDate.value = selectedValues; |
| | | showDate.value = false; |
| | | }; |
| | | // 检查文件扩展名 |
| | | const name = file.bucketFilename || file.originalFilename || file.name || ""; |
| | | const ext = name.split(".").pop()?.toLowerCase(); |
| | | return ["jpg", "jpeg", "png", "gif", "webp"].includes(ext); |
| | | }; |
| | | |
| | | // 显示保养结果选择器 |
| | | const showResultPicker = () => { |
| | | showResult.value = true; |
| | | }; |
| | | // 提交表单 |
| | | const sendForm = async () => { |
| | | console.log(form.value.sparePartsIds, "form.value.sparePartsIds"); |
| | | try { |
| | | // 手动验证表单 |
| | | let isValid = true; |
| | | let errorMessage = ""; |
| | | if (!form.value.maintenanceActuallyName) { |
| | | isValid = false; |
| | | errorMessage = "请输入实际保养人"; |
| | | } else if (!form.value.maintenanceActuallyTime) { |
| | | isValid = false; |
| | | errorMessage = "请选择实际保养日期"; |
| | | } else if (form.value.maintenanceResult === undefined) { |
| | | isValid = false; |
| | | errorMessage = "请选择保养结果"; |
| | | } else if (uploadFiles.value.length === 0 && form.value.status == "1") { |
| | | isValid = false; |
| | | errorMessage = "请上传保养照片"; |
| | | } |
| | | |
| | | // 确认保养结果选择 |
| | | const onResultConfirm = ({ selectedValues, selectedOptions }) => { |
| | | form.value.maintenanceResult = selectedOptions[0].value; |
| | | maintenanceResultText.value = selectedOptions[0].text; |
| | | resultPickerValue.value = selectedValues; |
| | | showResult.value = false; |
| | | }; |
| | | if (!isValid) { |
| | | showToast(errorMessage); |
| | | return; |
| | | } |
| | | |
| | | // 初始化表单数据 |
| | | const initForm = () => { |
| | | // 设置保养人为当前用户昵称 |
| | | form.value.maintenanceActuallyName = userStore.nickName || ''; |
| | | // 设置当前日期(只包含年月日) |
| | | form.value.maintenanceActuallyTime = dayjs().format('YYYY-MM-DD'); |
| | | currentDate.value = [new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]; |
| | | }; |
| | | // 验证通过 |
| | | submitFormData(); |
| | | } catch (e) { |
| | | showToast("表单验证失败"); |
| | | } |
| | | }; |
| | | |
| | | onShow(() => { |
| | | // 页面显示时初始化表单 |
| | | initForm(); |
| | | }); |
| | | // 提交表单数据 |
| | | const submitFormData = async () => { |
| | | try { |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | onMounted(() => { |
| | | // 页面加载时初始化表单 |
| | | initForm(); |
| | | }); |
| | | if (!id) { |
| | | showToast("参数错误"); |
| | | loading.value = false; |
| | | return; |
| | | } |
| | | // 准备提交数据,maintenanceActuallyTime 加上当前时分秒 |
| | | const submitData = { |
| | | ...form.value, |
| | | imagesFile: form.value.status == "1" ? uploadFiles.value : [], |
| | | sparePartsIds: form.value.sparePartsIds |
| | | ? form.value.sparePartsIds.join(",") |
| | | : "", |
| | | }; |
| | | const { code } = await addMaintenance({ id: id, ...submitData }); |
| | | |
| | | if (code == 200) { |
| | | showToast("新增保养成功"); |
| | | resetFormAndValidate(); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast("操作失败"); |
| | | } |
| | | }; |
| | | |
| | | // 返回上一页 |
| | | const goBack = () => { |
| | | // 清除存储的id和数据 |
| | | uni.removeStorageSync("repairId"); |
| | | uni.removeStorageSync("upkeepItemData"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // 获取页面ID |
| | | const getPageId = () => { |
| | | // 从本地存储获取id |
| | | return uni.getStorageSync("repairId"); |
| | | }; |
| | | |
| | | const dataform = ref({}); |
| | | // 获取设备信息 |
| | | const getUpkeepItemData = () => { |
| | | try { |
| | | const dataStr = uni.getStorageSync("upkeepItemData"); |
| | | if (!dataStr) { |
| | | return null; |
| | | } |
| | | dataform.value = JSON.parse(dataStr); |
| | | fetchSparePartOptions(dataform.value.deviceLedgerId); |
| | | return JSON.parse(dataStr); |
| | | } catch (e) { |
| | | console.error("解析设备数据失败:", e); |
| | | return null; |
| | | } |
| | | }; |
| | | |
| | | // 显示日期选择器 |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // 确认日期选择 |
| | | const onDateConfirm = e => { |
| | | // 只保存年月日,不包含时分秒 |
| | | form.value.maintenanceActuallyTime = dayjs(e.value).format( |
| | | "YYYY-MM-DD HH:mm:ss" |
| | | ); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // 显示保养结果选择器 |
| | | const showResultPicker = () => { |
| | | showResult.value = true; |
| | | }; |
| | | |
| | | // 确认保养结果选择 |
| | | const onResultConfirm = selected => { |
| | | form.value.status = selected.value; |
| | | maintenancestatusText.value = selected.name; |
| | | showResult.value = false; |
| | | }; |
| | | |
| | | // 显示设备备件选择器 |
| | | const showSparePartPicker = () => { |
| | | tempSelectedSpareParts.value = [...selectedSpareParts.value]; |
| | | showSparePart.value = true; |
| | | }; |
| | | |
| | | // 检查备件是否已选中 |
| | | const isSparePartSelected = id => { |
| | | return tempSelectedSpareParts.value.some( |
| | | item => item.id === id || item.value === id |
| | | ); |
| | | }; |
| | | |
| | | // 切换备件选中状态 |
| | | const toggleSparePartSelection = item => { |
| | | const itemId = item.id || item.value; |
| | | const index = tempSelectedSpareParts.value.findIndex( |
| | | selected => selected.id === itemId || selected.value === itemId |
| | | ); |
| | | if (index > -1) { |
| | | tempSelectedSpareParts.value.splice(index, 1); |
| | | } else { |
| | | tempSelectedSpareParts.value.push(item); |
| | | } |
| | | }; |
| | | |
| | | // 确认备件选择 |
| | | const confirmSparePartSelection = () => { |
| | | selectedSpareParts.value = [...tempSelectedSpareParts.value]; |
| | | form.value.sparePartsIds = selectedSpareParts.value.map(item => item.id); |
| | | showSparePart.value = false; |
| | | }; |
| | | |
| | | // 移除已选备件 |
| | | const removeSparePart = index => { |
| | | selectedSpareParts.value.splice(index, 1); |
| | | form.value.sparePartsIds = selectedSpareParts.value.map(item => item.id); |
| | | }; |
| | | const sparePartsIds = ref([]); |
| | | // 初始化表单数据 |
| | | const initForm = () => { |
| | | // 获取设备信息 |
| | | const itemData = getUpkeepItemData(); |
| | | |
| | | // 重置选择的备件 |
| | | selectedSpareParts.value = []; |
| | | // 重置上传的文件 |
| | | uploadFiles.value = []; |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | maintenancestatusText.value = ""; |
| | | |
| | | // 设置保养人为当前用户昵称 |
| | | form.value.maintenanceActuallyName = userStore.nickName || ""; |
| | | // 设置当前日期(只包含年月日) |
| | | form.value.maintenanceActuallyTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | currentDate.value = [ |
| | | new Date().getFullYear(), |
| | | new Date().getMonth() + 1, |
| | | new Date().getDate(), |
| | | ]; |
| | | |
| | | // 如果有设备信息,填充已有的保养数据 |
| | | if (itemData) { |
| | | // 填充实际保养人 |
| | | if (itemData.maintenanceActuallyName) { |
| | | form.value.maintenanceActuallyName = itemData.maintenanceActuallyName; |
| | | } |
| | | // 填充保养结果 |
| | | if ( |
| | | itemData.maintenanceResult !== undefined && |
| | | itemData.maintenanceResult !== null |
| | | ) { |
| | | form.value.maintenanceResult = itemData.maintenanceResult; |
| | | } |
| | | // 填充实际保养日期 |
| | | if (itemData.maintenanceActuallyTime) { |
| | | form.value.maintenanceActuallyTime = itemData.maintenanceActuallyTime; |
| | | // 解析日期设置到日期选择器 |
| | | const date = dayjs(itemData.maintenanceActuallyTime); |
| | | currentDate.value = [date.year(), date.month() + 1, date.date()]; |
| | | } |
| | | // 填充保养状态 |
| | | if (itemData.status) { |
| | | const statusMap = { |
| | | 0: "待保养", |
| | | 1: "完结", |
| | | 2: "失败", |
| | | }; |
| | | maintenancestatusText.value = statusMap[itemData.status] || ""; |
| | | } |
| | | // 填充备件数据 |
| | | |
| | | // 处理字符串格式的备件IDs |
| | | sparePartsIds.value = itemData.sparePartsIds; |
| | | |
| | | // 填充附件数据 |
| | | if (itemData.files && itemData.files.length > 0) { |
| | | uploadFiles.value = itemData.files.map(file => ({ |
| | | id: file.id, |
| | | name: file.name || file.bucketFilename || file.originalFilename, |
| | | url: file.url || file.downloadUrl, |
| | | type: |
| | | file.type || |
| | | (file.contentType && file.contentType.startsWith("image/") |
| | | ? "image" |
| | | : "video"), |
| | | size: file.size || file.byteSize, |
| | | })); |
| | | } else if (itemData.uploadFiles && itemData.uploadFiles.length > 0) { |
| | | uploadFiles.value = itemData.uploadFiles.map(file => ({ |
| | | id: file.id, |
| | | name: file.name || file.bucketFilename || file.originalFilename, |
| | | url: file.url || file.downloadUrl || file.tempFilePath || file.path, |
| | | type: file.type, |
| | | size: file.size, |
| | | })); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | onShow(async () => { |
| | | // 先获取备件选项,再初始化表单 |
| | | const pageId = getPageId(); |
| | | if (pageId) { |
| | | await fetchSparePartOptions(pageId); |
| | | } |
| | | // 页面显示时初始化表单 |
| | | initForm(); |
| | | }); |
| | | const sparePartOptions = ref([]); |
| | | const fetchSparePartOptions = deviceLedgerId => { |
| | | return new Promise((resolve, reject) => { |
| | | getSparePartsOptions({ deviceLedgerId: deviceLedgerId }) |
| | | .then(res => { |
| | | if (res.code == 200) { |
| | | sparePartOptions.value = res.data || []; |
| | | const idArray = |
| | | typeof sparePartsIds.value === "string" |
| | | ? sparePartsIds.value.split(",") |
| | | : sparePartsIds.value; |
| | | |
| | | if (idArray.length > 0) { |
| | | selectedSpareParts.value = sparePartOptions.value |
| | | .filter( |
| | | option => |
| | | idArray.includes(option.id.toString()) || |
| | | idArray.includes(option.value?.toString()) |
| | | ) |
| | | .map(option => ({ |
| | | id: option.id || option.value, |
| | | name: option.name, |
| | | code: option.code, |
| | | value: option.id || option.value, |
| | | })); |
| | | // 设置备件IDs |
| | | form.value.sparePartsIds = idArray.join(","); |
| | | } |
| | | resolve(res.data); |
| | | } else { |
| | | resolve([]); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.error("获取备件选项失败:", err); |
| | | resolve([]); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 格式化文件URL |
| | | const formatFileUrl = url => { |
| | | if (!url) return ""; |
| | | |
| | | // 如果已经是完整的URL(http或https开头),直接返回 |
| | | if (url.startsWith("http://") || url.startsWith("https://")) { |
| | | return url; |
| | | } |
| | | |
| | | // 如果是本地路径(如 D:\\ruoyi\\prod\\uploads...),需要转换为网络URL |
| | | // 从路径中提取uploads后面的部分 |
| | | const uploadsIndex = url.indexOf("uploads"); |
| | | if (uploadsIndex !== -1) { |
| | | const relativePath = url.substring(uploadsIndex); |
| | | // 使用baseUrl + /profile/ + 相对路径 |
| | | return `http://192.168.1.35:8888/profile/${relativePath}`; |
| | | } |
| | | |
| | | // 其他情况,尝试直接拼接 |
| | | return `http://192.168.1.35:8888/profile/${url}`; |
| | | }; |
| | | |
| | | // 格式化文件大小 |
| | | const formatFileSize = size => { |
| | | if (!size) return ""; |
| | | if (size < 1024) return size + "B"; |
| | | if (size < 1024 * 1024) return (size / 1024).toFixed(1) + "KB"; |
| | | return (size / (1024 * 1024)).toFixed(1) + "MB"; |
| | | }; |
| | | |
| | | // 拍照/拍视频 |
| | | const chooseMedia = type => { |
| | | if (uploadFiles.value.length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `最多只能选择${uploadConfig.limit}个文件`, |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const remaining = uploadConfig.limit - uploadFiles.value.length; |
| | | |
| | | // 优先:chooseMedia(支持 image/video) |
| | | 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(), |
| | | uid: Date.now() + Math.random() + idx, |
| | | }; |
| | | |
| | | console.log("chooseMedia 成功获取文件:", file); |
| | | handleBeforeUpload(file); |
| | | }); |
| | | } catch (e) { |
| | | console.error("处理拍摄结果失败:", e); |
| | | uni.showToast({ title: "处理文件失败", icon: "error" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("拍摄失败:", err); |
| | | uni.showToast({ title: "拍摄失败", icon: "error" }); |
| | | }, |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 降级:chooseImage / chooseVideo |
| | | if (type === "video") { |
| | | uni.chooseVideo({ |
| | | sourceType: ["camera", "album"], |
| | | maxDuration: uploadConfig.maxVideoDuration, |
| | | camera: "back", |
| | | success: res => { |
| | | try { |
| | | if (!res.tempFilePath) { |
| | | throw new Error("未获取到视频文件"); |
| | | } |
| | | |
| | | const file = { |
| | | tempFilePath: res.tempFilePath, |
| | | path: res.tempFilePath, |
| | | type: "video", |
| | | name: `video_${Date.now()}.mp4`, |
| | | size: res.size || 0, |
| | | duration: res.duration || 0, |
| | | createTime: new Date().getTime(), |
| | | uid: Date.now() + Math.random(), |
| | | }; |
| | | |
| | | handleBeforeUpload(file); |
| | | } catch (error) { |
| | | console.error("处理拍视频结果失败:", error); |
| | | uni.showToast({ title: "处理视频失败", icon: "error" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("拍视频失败:", err); |
| | | uni.showToast({ title: "拍视频失败", icon: "error" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera", "album"], |
| | | success: res => { |
| | | const tempFilePath = res?.tempFilePaths?.[0]; |
| | | const tempFile = res?.tempFiles?.[0] || {}; |
| | | if (!tempFilePath) return; |
| | | handleBeforeUpload({ |
| | | tempFilePath, |
| | | path: tempFilePath, |
| | | type: "image", |
| | | name: `photo_${Date.now()}.jpg`, |
| | | size: tempFile.size || 0, |
| | | createTime: Date.now(), |
| | | uid: Date.now() + Math.random(), |
| | | }); |
| | | }, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 删除文件 |
| | | const removeFile = index => { |
| | | uni.showModal({ |
| | | title: "确认删除", |
| | | content: "确定要删除这个文件吗?", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | uploadFiles.value.splice(index, 1); |
| | | uni.showToast({ |
| | | title: "删除成功", |
| | | icon: "success", |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // 上传前校验 |
| | | const handleBeforeUpload = async file => { |
| | | // 校验文件类型 |
| | | if ( |
| | | uploadConfig.fileType && |
| | | Array.isArray(uploadConfig.fileType) && |
| | | uploadConfig.fileType.length > 0 |
| | | ) { |
| | | const fileName = file.name || ""; |
| | | const fileExtension = fileName |
| | | ? fileName.split(".").pop().toLowerCase() |
| | | : ""; |
| | | |
| | | // 根据文件类型确定期望的扩展名 |
| | | let expectedTypes = []; |
| | | if (file.type === "image") { |
| | | expectedTypes = ["jpg", "jpeg", "png", "gif", "webp"]; |
| | | } else if (file.type === "video") { |
| | | expectedTypes = ["mp4", "mov", "avi", "wmv"]; |
| | | } |
| | | |
| | | // 检查文件扩展名是否在允许的类型中 |
| | | if (fileExtension && expectedTypes.length > 0) { |
| | | const isAllowed = expectedTypes.some( |
| | | type => uploadConfig.fileType.includes(type) && type === fileExtension |
| | | ); |
| | | |
| | | if (!isAllowed) { |
| | | uni.showToast({ |
| | | title: `文件格式不支持,请拍摄 ${expectedTypes.join("/")} 格式的文件`, |
| | | icon: "none", |
| | | }); |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 校验通过,开始上传 |
| | | uploadFile(file); |
| | | return true; |
| | | }; |
| | | |
| | | // 文件上传处理 |
| | | const uploadFile = async file => { |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | number.value++; |
| | | |
| | | // 确保token存在 |
| | | const token = userStore.token; |
| | | if (!token) { |
| | | handleUploadError("用户未登录"); |
| | | return; |
| | | } |
| | | |
| | | uploadWithUniUploadFile(file, file.tempFilePath || file.path || "", token); |
| | | }; |
| | | |
| | | // 使用uni.uploadFile上传 |
| | | const uploadWithUniUploadFile = (file, filePath, token) => { |
| | | if (!filePath) { |
| | | handleUploadError("文件路径不存在"); |
| | | return; |
| | | } |
| | | |
| | | const uploadTask = uni.uploadFile({ |
| | | url: uploadFileUrl.value, |
| | | filePath: filePath, |
| | | name: "file", |
| | | formData: { |
| | | type: 10, // 保养附件类型 |
| | | }, |
| | | header: { |
| | | Authorization: `Bearer ${token}`, |
| | | }, |
| | | success: res => { |
| | | try { |
| | | if (res.statusCode === 200) { |
| | | const response = JSON.parse(res.data); |
| | | if (response.code === 200) { |
| | | handleUploadSuccess(response, file); |
| | | uni.showToast({ |
| | | title: "上传成功", |
| | | icon: "success", |
| | | }); |
| | | } else { |
| | | handleUploadError(response.msg || "服务器返回错误"); |
| | | } |
| | | } else { |
| | | handleUploadError(`服务器错误,状态码: ${res.statusCode}`); |
| | | } |
| | | } catch (e) { |
| | | console.error("解析响应失败:", e); |
| | | console.error("原始响应数据:", res.data); |
| | | handleUploadError("响应数据解析失败: " + e.message); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("上传失败:", err.errMsg || err); |
| | | number.value--; |
| | | |
| | | let errorMessage = "上传失败"; |
| | | if (err.errMsg) { |
| | | if (err.errMsg.includes("statusCode: null")) { |
| | | errorMessage = "网络连接失败,请检查网络设置"; |
| | | } else if (err.errMsg.includes("timeout")) { |
| | | errorMessage = "上传超时,请重试"; |
| | | } else if (err.errMsg.includes("fail")) { |
| | | errorMessage = "上传失败,请检查网络连接"; |
| | | } else { |
| | | errorMessage = err.errMsg; |
| | | } |
| | | } |
| | | |
| | | handleUploadError(errorMessage); |
| | | }, |
| | | complete: () => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | }, |
| | | }); |
| | | |
| | | // 监听上传进度 |
| | | if (uploadTask && uploadTask.onProgressUpdate) { |
| | | uploadTask.onProgressUpdate(res => { |
| | | uploadProgress.value = res.progress; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 上传成功处理 |
| | | const handleUploadSuccess = (res, file) => { |
| | | console.log("上传成功响应:", res); |
| | | |
| | | // 处理不同的数据结构:可能是数组,也可能是单个对象 |
| | | let uploadedFile = null; |
| | | uploadedFile = res.data; |
| | | |
| | | if (!uploadedFile) { |
| | | console.error("无法解析上传响应数据:", res); |
| | | number.value--; |
| | | handleUploadError("上传响应数据格式错误", false); |
| | | return; |
| | | } |
| | | console.log("上传成功文件数据:", uploadedFile, file); |
| | | |
| | | // 确保上传的文件数据完整,包含id和type |
| | | const fileData = { |
| | | name: uploadedFile.originalName, |
| | | type: file.type, |
| | | url: uploadedFile.tempPath, |
| | | }; |
| | | |
| | | uploadFiles.value.push(fileData); |
| | | number.value = 0; |
| | | }; |
| | | |
| | | // 上传失败处理 |
| | | const handleUploadError = (message = "上传文件失败", showRetry = false) => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | |
| | | if (showRetry) { |
| | | uni.showModal({ |
| | | title: "上传失败", |
| | | content: message + ",是否重试?", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | // 用户选择重试,这里可以重新触发上传 |
| | | } |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 页面加载时初始化表单 |
| | | initForm(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upkeep-maintain { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | @import "@/static/scss/form-common.scss"; |
| | | .upkeep-maintain { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); |
| | | z-index: 1000; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 6.375rem; |
| | | background: #C7C9CC; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 6.375rem; |
| | | background: #c7c9cc; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 14rem; |
| | | background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 14rem; |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // 响应式调整 |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | // 响应式调整 |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | |
| | | /* 设备备件多选样式 */ |
| | | .spare-parts-container { |
| | | flex: 1; |
| | | min-height: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .spare-parts-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .spare-part-tag { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | background: #2c7be5; |
| | | color: #fff; |
| | | padding: 4px 8px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .placeholder { |
| | | color: #c0c4cc; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | /* 备件选择弹窗样式 */ |
| | | .spare-part-popup { |
| | | padding: 16px; |
| | | max-height: 60vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #e8e8e8; |
| | | } |
| | | |
| | | .popup-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .spare-part-options { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .spare-part-option { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | font-size: 14px; |
| | | color: #333; |
| | | } |
| | | |
| | | .spare-part-option.selected { |
| | | color: #2c7be5; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* 备件选择弹窗样式 */ |
| | | .spare-part-popup { |
| | | width: 100%; |
| | | max-height: 80vh; |
| | | background: #fff; |
| | | border-radius: 16px 16px 0 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .popup-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .spare-part-options { |
| | | padding: 10px 0; |
| | | max-height: 60vh; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .spare-part-option { |
| | | position: relative; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 14px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | transition: all 0.2s ease; |
| | | } |
| | | |
| | | .spare-part-option:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .spare-part-option:hover { |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .spare-part-option.selected { |
| | | background: #e6f7ff; |
| | | color: #1890ff; |
| | | } |
| | | |
| | | .spare-part-option.selected::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 0; |
| | | bottom: 0; |
| | | width: 4px; |
| | | background: #1890ff; |
| | | } |
| | | |
| | | /* 文件上传样式 */ |
| | | .simple-upload-area { |
| | | width: 100%; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | display: flex; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .upload-progress { |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .file-list { |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .file-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .file-preview-container { |
| | | position: relative; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .file-preview { |
| | | width: 80px; |
| | | height: 80px; |
| | | border-radius: 4px; |
| | | } |
| | | |
| | | .video-preview { |
| | | width: 80px; |
| | | height: 80px; |
| | | background: #333; |
| | | border-radius: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | color: #fff; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: -8px; |
| | | right: -8px; |
| | | width: 20px; |
| | | height: 20px; |
| | | background: #f56c6c; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .file-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .file-name { |
| | | display: block; |
| | | font-size: 14px; |
| | | color: #333; |
| | | margin-bottom: 4px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .file-size { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .empty-state { |
| | | padding: 20px 0; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | </style> |