| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/inspectionManagement/index", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¡æ£ä»»å¡ç®¡ç", |
| | | "navigationStyle": "custom", |
| | | "enablePullDownRefresh": true, |
| | | "backgroundColor": "#f8f8f8" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/equipmentManagement/faultAnalysis/index", |
| | | "style": { |
| | | "navigationBarTitleText": "æ
éåæè¿½æº¯", |
| | |
| | | "navigationBarTitleText": "RuoYi", |
| | | "navigationBarBackgroundColor": "#FFFFFF" |
| | | } |
| | | } |
| | | } |
| | |
| | | icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | label: "å±é©ä½ä¸å®¡æ¹", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | label: "éæ£ææ¥ä¸æ¥", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | // label: "éæ£ææ¥ä¸æ¥", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | label: "å±é©ç©æç®¡æ§", |
| | |
| | | icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | label: "åºæ¥é¢æ¡æ¥é
", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | label: "äºæ
䏿¥è®°å½", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | label: "å®å
¨å¹è®èæ ¸", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | // label: "äºæ
䏿¥è®°å½", |
| | | // }, |
| | | // { |
| | | // icon: "/static/images/icon/guzhangfenxi@2x.png", |
| | | // label: "å®å
¨å¹è®èæ ¸", |
| | | // }, |
| | | ]); |
| | | // åååå
¬åè½æ°æ® |
| | | const collaborationItems = reactive([ |
| | |
| | | icon: "/static/images/icon/baoxiaoguanli.png", |
| | | label: "åå审æ¹", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/huiyiliebiao@2x.png", |
| | | label: "ä¼è®®ç®¡ç", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/huiyiliebiao@2x.png", |
| | | // label: "ä¼è®®ç®¡ç", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/tongzhigonggao@2x.png", |
| | | label: "éç¥å
Œ", |
| | |
| | | icon: "/static/images/icon/zhishiku@2x.png", |
| | | label: "ç¥è¯åº", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/yongyinguanli@2x.png", |
| | | label: "ç¨å°ç®¡ç", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/yongyinguanli@2x.png", |
| | | // label: "ç¨å°ç®¡ç", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/guizhangzhidu@2x.png", |
| | | label: "è§ç« å¶åº¦", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/kehubaifang@2x.png", |
| | | label: "å®¢æ·æè®¿", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/kehubaifang@2x.png", |
| | | // label: "å®¢æ·æè®¿", |
| | | // }, |
| | | ]); |
| | | |
| | | // ç产管æ§åè½æ°æ® |
| | |
| | | icon: "/static/images/icon/xunjianshangchuan@2x.png", |
| | | label: "å·¡æ£ç®¡ç", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/xunjianshangchuan@2x.png", |
| | | label: "å·¡æ£ä»»å¡ç®¡ç", |
| | | } |
| | | ]); |
| | | |
| | | // å¤ç常ç¨åè½ç¹å» |
| | |
| | | case "å·¡æ£ç®¡ç": |
| | | uni.navigateTo({ |
| | | url: "/pages/inspectionUpload/index", |
| | | }); |
| | | break; |
| | | case "å·¡æ£ä»»å¡ç®¡ç": |
| | | uni.navigateTo({ |
| | | url: "/pages/inspectionManagement/index", |
| | | }); |
| | | break; |
| | | case "åæè¿½æº¯": |
| | |
| | | { icon: "/static/images/icon/shbeibaoxiu@2x.png", label: "è®¾å¤æ¥ä¿®" }, |
| | | { icon: "/static/images/icon/shbeibaoyang@2x.png", label: "设å¤ä¿å
»" }, |
| | | { icon: "/static/images/icon/xunjianshangchuan@2x.png", label: "å·¡æ£ç®¡ç" }, |
| | | { icon: "/static/images/icon/xunjianshangchuan@2x.png", label: "å·¡æ£ä»»å¡ç®¡ç" }, |
| | | ]; |
| | | const filteredEquipment = originalEquipment.filter(item => { |
| | | return allowedMenuTitles.has(item.label); |
| | | }); |
| | | if (filteredEquipment.some(i => i.label === "å·¡æ£ç®¡ç")) { |
| | | const task = originalEquipment.find(i => i.label === "å·¡æ£ä»»å¡ç®¡ç"); |
| | | if (task && !filteredEquipment.some(i => i.label === "å·¡æ£ä»»å¡ç®¡ç")) { |
| | | filteredEquipment.push(task); |
| | | } |
| | | } |
| | | equipmentItems.splice(0, equipmentItems.length, ...filteredEquipment); |
| | | }; |
| | | |
| | |
| | | box-shadow: 0 0.375rem 1.25rem rgba(0, 0, 0, 0.4); |
| | | } |
| | | } |
| | | </style> |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <u-popup :show="dialogVisitable" |
| | | mode="center" |
| | | :round="12" |
| | | :zIndex="900" |
| | | @close="cancel"> |
| | | <view class="popup-content"> |
| | | <view class="popup-title">{{ operationType === "add" ? "æ°å¢å·¡æ£ä»»å¡" : "ç¼è¾å·¡æ£ä»»å¡" }}</view> |
| | | <view class="form-body"> |
| | | <view class="form-item"> |
| | | <text class="label">设å¤åç§°</text> |
| | | <picker mode="selector" |
| | | :range="deviceOptions" |
| | | range-key="deviceName" |
| | | :value="deviceIndex" |
| | | @change="onDeviceChange"> |
| | | <view class="picker-value">{{ form.taskName || "è¯·éæ©è®¾å¤" }}</view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item"> |
| | | <text class="label">å·¡æ£äºº</text> |
| | | <view class="picker-value inspector-picker" |
| | | @click="openInspectorPopup"> |
| | | <text>{{ inspectorText || "è¯·éæ©å·¡æ£äºº" }}</text> |
| | | <u-icon name="arrow-right" |
| | | size="14" |
| | | color="#999" /> |
| | | </view> |
| | | <view class="inspector-tags" |
| | | v-if="form.inspector?.length"> |
| | | <view v-for="userId in form.inspector" |
| | | :key="userId" |
| | | class="inspector-tag" |
| | | @click="toggleInspector(userId)"> |
| | | <text>{{ getUserName(userId) }}</text> |
| | | <u-icon name="close" |
| | | size="11" |
| | | color="#1677ff" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="form-item"> |
| | | <text class="label">夿³¨</text> |
| | | <u-textarea v-model="form.remarks" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | :height="80" |
| | | count /> |
| | | </view> |
| | | <view class="form-item"> |
| | | <text class="label">ä»»å¡é¢ç</text> |
| | | <picker mode="selector" |
| | | :range="frequencyOptions" |
| | | range-key="label" |
| | | :value="frequencyIndex" |
| | | @change="onFrequencyChange"> |
| | | <view class="picker-value">{{ currentFrequencyLabel || "è¯·éæ©ä»»å¡é¢ç" }}</view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item" |
| | | v-if="form.frequencyType === 'DAILY'"> |
| | | <text class="label">æ¶é´</text> |
| | | <picker mode="time" |
| | | :value="form.frequencyDetail || '08:00'" |
| | | @change="onDailyTimeChange"> |
| | | <view class="picker-value">{{ form.frequencyDetail || "è¯·éæ©æ¶é´" }}</view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item" |
| | | v-if="form.frequencyType === 'WEEKLY'"> |
| | | <text class="label">æ¯å¨æ¥æ</text> |
| | | <picker mode="selector" |
| | | :range="weekOptions" |
| | | range-key="label" |
| | | :value="weekIndex" |
| | | @change="onWeekChange"> |
| | | <view class="picker-value">{{ currentWeekLabel || "è¯·éæ©ææ" }}</view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item" |
| | | v-if="form.frequencyType === 'WEEKLY'"> |
| | | <text class="label">æ¯å¨æ¶é´</text> |
| | | <picker mode="time" |
| | | :value="form.time || '08:00'" |
| | | @change="onWeekTimeChange"> |
| | | <view class="picker-value">{{ form.time || "è¯·éæ©æ¶é´" }}</view> |
| | | </picker> |
| | | </view> |
| | | <view class="form-item" |
| | | v-if="form.frequencyType === 'MONTHLY'"> |
| | | <text class="label">æ¯ææ¥æä¸æ¶é´</text> |
| | | <picker mode="date" |
| | | fields="day" |
| | | :value="monthlyDate" |
| | | @change="onMonthlyDateChange"> |
| | | <view class="picker-value">{{ monthlyDate || "è¯·éæ©æ¥æ" }}</view> |
| | | </picker> |
| | | <picker mode="time" |
| | | :value="monthlyTime" |
| | | @change="onMonthlyTimeChange"> |
| | | <view class="picker-value">{{ monthlyTime || "è¯·éæ©æ¶é´" }}</view> |
| | | </picker> |
| | | </view> |
| | | </view> |
| | | <view class="popup-footer"> |
| | | <u-button @click="cancel">åæ¶</u-button> |
| | | <u-button type="primary" |
| | | @click="submitForm">ä¿å</u-button> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | <u-popup :show="showInspectorPopup" |
| | | mode="bottom" |
| | | :round="16" |
| | | :zIndex="1100" |
| | | @close="closeInspectorPopup"> |
| | | <view class="inspector-popup"> |
| | | <view class="inspector-header"> |
| | | <text class="inspector-title">鿩巡æ£äºº</text> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="inspector-list"> |
| | | <view v-for="item in userList" |
| | | :key="item.userId" |
| | | class="inspector-row" |
| | | @click="toggleInspector(item.userId)"> |
| | | <text class="inspector-name">{{ item.nickName }}</text> |
| | | <u-icon v-if="form.inspector.includes(item.userId)" |
| | | name="checkmark-circle-fill" |
| | | color="#1677ff" |
| | | size="20" /> |
| | | <u-icon v-else |
| | | name="" |
| | | color="#d5d8de" |
| | | size="20" /> |
| | | </view> |
| | | </scroll-view> |
| | | <view class="inspector-footer"> |
| | | <u-button @click="closeInspectorPopup">åæ¶</u-button> |
| | | <u-button type="primary" |
| | | @click="confirmInspector">ç¡®å®</u-button> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, reactive, ref } from "vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { addOrEditTimingTask } from "@/api/inspectionManagement/index.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | |
| | | const emit = defineEmits(["closeDia"]); |
| | | const userStore = useUserStore(); |
| | | const dialogVisitable = ref(false); |
| | | const operationType = ref("add"); |
| | | const deviceOptions = ref([]); |
| | | const userList = ref([]); |
| | | const showInspectorPopup = ref(false); |
| | | |
| | | const defaultForm = () => ({ |
| | | id: undefined, |
| | | taskId: undefined, |
| | | taskName: "", |
| | | inspector: [], |
| | | inspectorIds: "", |
| | | remarks: "", |
| | | frequencyType: "", |
| | | frequencyDetail: "", |
| | | week: "", |
| | | time: "", |
| | | registrantId: undefined, |
| | | }); |
| | | |
| | | const form = reactive(defaultForm()); |
| | | |
| | | const frequencyOptions = [ |
| | | { label: "æ¯æ¥", value: "DAILY" }, |
| | | { label: "æ¯å¨", value: "WEEKLY" }, |
| | | { label: "æ¯æ", value: "MONTHLY" }, |
| | | ]; |
| | | |
| | | const weekOptions = [ |
| | | { label: "å¨ä¸", value: "MON" }, |
| | | { label: "å¨äº", value: "TUE" }, |
| | | { label: "å¨ä¸", value: "WED" }, |
| | | { label: "å¨å", value: "THU" }, |
| | | { label: "å¨äº", value: "FRI" }, |
| | | { label: "å¨å
", value: "SAT" }, |
| | | { label: "卿¥", value: "SUN" }, |
| | | ]; |
| | | |
| | | const monthlyDate = ref(""); |
| | | const monthlyTime = ref(""); |
| | | |
| | | const deviceIndex = computed(() => { |
| | | const index = deviceOptions.value.findIndex(item => item.id === form.taskId); |
| | | return index >= 0 ? index : 0; |
| | | }); |
| | | |
| | | const frequencyIndex = computed(() => { |
| | | const index = frequencyOptions.findIndex(item => item.value === form.frequencyType); |
| | | return index >= 0 ? index : 0; |
| | | }); |
| | | |
| | | const weekIndex = computed(() => { |
| | | const index = weekOptions.findIndex(item => item.value === form.week); |
| | | return index >= 0 ? index : 0; |
| | | }); |
| | | |
| | | const currentFrequencyLabel = computed(() => { |
| | | return frequencyOptions.find(item => item.value === form.frequencyType)?.label || ""; |
| | | }); |
| | | |
| | | const currentWeekLabel = computed(() => { |
| | | return weekOptions.find(item => item.value === form.week)?.label || ""; |
| | | }); |
| | | |
| | | const inspectorText = computed(() => { |
| | | if (!form.inspector?.length) return ""; |
| | | const nameMap = new Map(userList.value.map(item => [item.userId, item.nickName])); |
| | | return form.inspector.map(id => nameMap.get(id)).filter(Boolean).join("ã"); |
| | | }); |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, defaultForm()); |
| | | monthlyDate.value = ""; |
| | | monthlyTime.value = ""; |
| | | showInspectorPopup.value = false; |
| | | }; |
| | | |
| | | const loadBaseData = async () => { |
| | | const [userRes, deviceRes] = await Promise.all([ |
| | | userListNoPageByTenantId(), |
| | | getDeviceLedger(), |
| | | ]); |
| | | userList.value = userRes?.data || []; |
| | | deviceOptions.value = deviceRes?.data || []; |
| | | }; |
| | | |
| | | const parseWeeklyDetail = detail => { |
| | | if (!detail || typeof detail !== "string" || !detail.includes(",")) return; |
| | | const [week, time] = detail.split(","); |
| | | form.week = week || ""; |
| | | form.time = time || ""; |
| | | }; |
| | | |
| | | const parseMonthlyDetail = detail => { |
| | | if (!detail || typeof detail !== "string" || !detail.includes(",")) return; |
| | | const [day, time] = detail.split(","); |
| | | if (day) monthlyDate.value = `${new Date().getFullYear()}-${new Date().getMonth() + 1 < 10 ? `0${new Date().getMonth() + 1}` : new Date().getMonth() + 1}-${day.padStart(2, "0")}`; |
| | | if (time) monthlyTime.value = time; |
| | | }; |
| | | |
| | | const setDeviceModel = id => { |
| | | const matched = deviceOptions.value.find(item => item.id === id); |
| | | if (matched) { |
| | | form.taskId = matched.id; |
| | | form.taskName = matched.deviceName; |
| | | } |
| | | }; |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogVisitable.value = true; |
| | | resetForm(); |
| | | try { |
| | | await loadBaseData(); |
| | | if (type === "edit" && row) { |
| | | Object.assign(form, { |
| | | ...defaultForm(), |
| | | ...row, |
| | | inspector: String(row.inspectorIds || "") |
| | | .split(",") |
| | | .map(item => Number(item)) |
| | | .filter(Boolean), |
| | | }); |
| | | if (form.frequencyType === "WEEKLY") parseWeeklyDetail(form.frequencyDetail); |
| | | if (form.frequencyType === "MONTHLY") parseMonthlyDetail(form.frequencyDetail); |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: "åå§å失败", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const onDeviceChange = e => { |
| | | const index = Number(e?.detail?.value || 0); |
| | | const selected = deviceOptions.value[index]; |
| | | if (!selected) return; |
| | | setDeviceModel(selected.id); |
| | | }; |
| | | |
| | | const getUserName = userId => { |
| | | const user = userList.value.find(item => item.userId === userId); |
| | | return user?.nickName || ""; |
| | | }; |
| | | |
| | | const openInspectorPopup = () => { |
| | | showInspectorPopup.value = true; |
| | | }; |
| | | |
| | | const closeInspectorPopup = () => { |
| | | showInspectorPopup.value = false; |
| | | }; |
| | | |
| | | const toggleInspector = userId => { |
| | | if (form.inspector.includes(userId)) { |
| | | form.inspector = form.inspector.filter(id => id !== userId); |
| | | } else { |
| | | form.inspector = [...form.inspector, userId]; |
| | | } |
| | | }; |
| | | |
| | | const confirmInspector = () => { |
| | | showInspectorPopup.value = false; |
| | | }; |
| | | |
| | | const onFrequencyChange = e => { |
| | | const index = Number(e?.detail?.value || 0); |
| | | const selected = frequencyOptions[index]; |
| | | form.frequencyType = selected?.value || ""; |
| | | form.frequencyDetail = ""; |
| | | form.week = ""; |
| | | form.time = ""; |
| | | monthlyDate.value = ""; |
| | | monthlyTime.value = ""; |
| | | }; |
| | | |
| | | const onDailyTimeChange = e => { |
| | | form.frequencyDetail = e?.detail?.value || ""; |
| | | }; |
| | | |
| | | const onWeekChange = e => { |
| | | const index = Number(e?.detail?.value || 0); |
| | | const selected = weekOptions[index]; |
| | | form.week = selected?.value || ""; |
| | | }; |
| | | |
| | | const onWeekTimeChange = e => { |
| | | form.time = e?.detail?.value || ""; |
| | | }; |
| | | |
| | | const onMonthlyDateChange = e => { |
| | | monthlyDate.value = e?.detail?.value || ""; |
| | | }; |
| | | |
| | | const onMonthlyTimeChange = e => { |
| | | monthlyTime.value = e?.detail?.value || ""; |
| | | }; |
| | | |
| | | const validateForm = () => { |
| | | if (!form.taskId) { |
| | | uni.showToast({ title: "è¯·éæ©è®¾å¤", icon: "none" }); |
| | | return false; |
| | | } |
| | | if (!form.inspector.length) { |
| | | uni.showToast({ title: "è¯·éæ©å·¡æ£äºº", icon: "none" }); |
| | | return false; |
| | | } |
| | | if (!form.frequencyType) { |
| | | uni.showToast({ title: "è¯·éæ©ä»»å¡é¢ç", icon: "none" }); |
| | | return false; |
| | | } |
| | | if (form.frequencyType === "DAILY" && !form.frequencyDetail) { |
| | | uni.showToast({ title: "è¯·éæ©æ¶é´", icon: "none" }); |
| | | return false; |
| | | } |
| | | if (form.frequencyType === "WEEKLY" && (!form.week || !form.time)) { |
| | | uni.showToast({ title: "è¯·éæ©æ¯å¨æ¥æåæ¶é´", icon: "none" }); |
| | | return false; |
| | | } |
| | | if (form.frequencyType === "MONTHLY" && (!monthlyDate.value || !monthlyTime.value)) { |
| | | uni.showToast({ title: "è¯·éæ©æ¯ææ¥æåæ¶é´", icon: "none" }); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | const buildFrequencyDetail = () => { |
| | | if (form.frequencyType === "WEEKLY") return `${form.week},${form.time}`; |
| | | if (form.frequencyType === "MONTHLY") { |
| | | const day = monthlyDate.value.split("-")[2] || ""; |
| | | return `${day},${monthlyTime.value}`; |
| | | } |
| | | return form.frequencyDetail; |
| | | }; |
| | | |
| | | const submitForm = async () => { |
| | | if (!validateForm()) return; |
| | | try { |
| | | const userInfo = await userStore.getInfo(); |
| | | const payload = { |
| | | ...form, |
| | | inspectorIds: form.inspector.join(","), |
| | | frequencyDetail: buildFrequencyDetail(), |
| | | registrantId: userInfo?.user?.userId, |
| | | }; |
| | | delete payload.inspector; |
| | | delete payload.week; |
| | | delete payload.time; |
| | | await addOrEditTimingTask(payload); |
| | | uni.showToast({ |
| | | title: "æäº¤æå", |
| | | icon: "success", |
| | | }); |
| | | cancel(); |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: "æäº¤å¤±è´¥ï¼è¯·éè¯", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | resetForm(); |
| | | emit("closeDia"); |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | :deep(.uni-picker-container) { |
| | | z-index: 1200 !important; |
| | | } |
| | | |
| | | .popup-content { |
| | | width: 88vw; |
| | | max-height: 80vh; |
| | | background: #fff; |
| | | border-radius: 20rpx; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .popup-title { |
| | | text-align: center; |
| | | font-size: 32rpx; |
| | | color: #1f1f1f; |
| | | font-weight: 600; |
| | | padding: 24rpx 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | } |
| | | |
| | | .form-body { |
| | | padding: 24rpx; |
| | | max-height: 56vh; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .form-item { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .label { |
| | | display: block; |
| | | margin-bottom: 10rpx; |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .picker-value { |
| | | min-height: 72rpx; |
| | | background: #f7f8fa; |
| | | border-radius: 12rpx; |
| | | padding: 0 20rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | color: #333; |
| | | font-size: 26rpx; |
| | | } |
| | | |
| | | .inspector-picker { |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .inspector-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | margin-top: 10rpx; |
| | | gap: 10rpx; |
| | | } |
| | | |
| | | .inspector-tag { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8rpx; |
| | | background: #edf3ff; |
| | | color: #1677ff; |
| | | font-size: 22rpx; |
| | | border-radius: 999rpx; |
| | | padding: 6rpx 14rpx; |
| | | } |
| | | |
| | | .popup-footer { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | padding: 20rpx 24rpx 24rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | } |
| | | |
| | | .inspector-popup { |
| | | background: #fff; |
| | | border-radius: 24rpx 24rpx 0 0; |
| | | overflow: hidden; |
| | | max-height: 70vh; |
| | | padding-bottom: env(safe-area-inset-bottom); |
| | | } |
| | | |
| | | .inspector-header { |
| | | padding: 24rpx; |
| | | text-align: center; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | } |
| | | |
| | | .inspector-title { |
| | | color: #1f1f1f; |
| | | font-size: 30rpx; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .inspector-list { |
| | | max-height: 46vh; |
| | | padding: 0 24rpx; |
| | | } |
| | | |
| | | .inspector-row { |
| | | min-height: 82rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | border-bottom: 1rpx solid #f5f5f5; |
| | | } |
| | | |
| | | .inspector-name { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | } |
| | | |
| | | .inspector-footer { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | padding: 20rpx 24rpx 24rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog :title="operationType === 'add' ? 'æ°å¢äºç»´ç ' : 'ç¼è¾äºç»´ç '" |
| | | v-model="dialogVisitable" width="500px" @close="cancel"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="设å¤åç§°" prop="deviceName"> |
| | | <el-input v-model="form.deviceName" placeholder="请è¾å
¥è®¾å¤åç§°" maxlength="30" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æå¨ä½ç½®æè¿°" prop="location"> |
| | | <el-input v-model="form.location" placeholder="请è¾å
¥æå¨ä½ç½®æè¿°" maxlength="30"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div> |
| | | <el-button type="primary" @click="submitForm">çæå¹¶æå°äºç»´ç </el-button> |
| | | </div> |
| | | <div v-if="isShowQrCode" class="print-section" ref="qrCodeContainer" id="qrCodeContainer"> |
| | | <vue-qrcode :value="qrCodeValue" :width="qrCodeSize"></vue-qrcode> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import {reactive, ref} from "vue"; |
| | | import printJS from 'print-js'; |
| | | import {addOrEditQrCode} from "@/api/inspectionUpload/index.js"; |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits() |
| | | const userStore = useUserStore() |
| | | const dialogVisitable = ref(false); |
| | | const isShowQrCode = ref(false); |
| | | const operationType = ref('add'); |
| | | |
| | | const qrCodeValue = ref(''); |
| | | const qrCodeSize = ref(100); |
| | | const data = reactive({ |
| | | form: { |
| | | deviceName: '', |
| | | location: '', |
| | | qrCodeId: '', |
| | | id: '' |
| | | }, |
| | | rules: { |
| | | deviceName: [{ required: true, message: '请è¾å
¥è®¾å¤åç§°', trigger: 'blur' }], |
| | | location: [{ required: true, message: '请è¾å
¥å°ç¹', trigger: 'blur' }] |
| | | } |
| | | }) |
| | | const { form, rules } = toRefs(data) |
| | | |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = async (type, row) => { |
| | | dialogVisitable.value = true |
| | | qrCodeValue.value = '' |
| | | isShowQrCode.value = false; |
| | | if (type === 'edit') { |
| | | form.value.id = row.id |
| | | form.value.qrCodeId = row.id |
| | | form.value.deviceName = row.deviceName |
| | | form.value.location = row.location |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | } |
| | | } |
| | | // æäº¤å并表å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | addOrEditQrCode(form.value).then((res) => { |
| | | form.value.qrCodeId = res.data |
| | | }) |
| | | // å°è¡¨åæ°æ®è½¬ä¸º JSON å符串ä½ä¸ºäºç»´ç å
容 |
| | | qrCodeValue.value = JSON.stringify(form.value); |
| | | isShowQrCode.value = true; |
| | | showQrCode() |
| | | } |
| | | }) |
| | | } |
| | | const showQrCode = () => { |
| | | // å»¶è¿æ§è¡æå°ï¼é¿å
DOM æ´æ°åå°±è°ç¨æå° |
| | | setTimeout(() => { |
| | | printJS({ |
| | | printable: 'qrCodeContainer',//é¡µé¢ |
| | | type: "html",//ææ¡£ç±»å |
| | | maxWidth: 360, |
| | | style: `@page { |
| | | margin:0; |
| | | size: 400px 75px collapse; |
| | | margin-top:3px; |
| | | &:first-of-type{ |
| | | margin-top:0 !important; |
| | | } |
| | | } |
| | | html{ |
| | | zoom:100%; |
| | | } |
| | | @media print{ |
| | | width: 400px; |
| | | height: 75px; |
| | | margin:0; |
| | | }`, |
| | | targetStyles: ["*"], // 使ç¨domçæææ ·å¼ï¼å¾éè¦ |
| | | font_size: '0.20cm', |
| | | }); |
| | | }, 300); |
| | | } |
| | | // å
³éå并表å |
| | | const cancel = () => { |
| | | proxy.resetForm("formRef") |
| | | dialogVisitable.value = false |
| | | emit('closeDia') |
| | | } |
| | | defineExpose({ openDialog }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .print-section { |
| | | text-align: center; |
| | | margin-top: 30px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <u-popup :show="dialogVisitable" |
| | | mode="bottom" |
| | | :round="16" |
| | | @close="cancel"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">æ¥çéä»¶</text> |
| | | <view class="close-icon" |
| | | @click="cancel"> |
| | | <u-icon name="close" |
| | | size="14" |
| | | color="#666" /> |
| | | </view> |
| | | </view> |
| | | <view class="popup-body"> |
| | | <view class="tabs"> |
| | | <view v-for="tab in tabs" |
| | | :key="tab.key" |
| | | class="tab-item" |
| | | :class="{ active: currentType === tab.key }" |
| | | @click="currentType = tab.key"> |
| | | {{ tab.label }} ({{ getCurrentList(tab.key).length }}) |
| | | </view> |
| | | </view> |
| | | <view class="file-list" |
| | | v-if="getCurrentList(currentType).length"> |
| | | <view v-for="(file, index) in getCurrentList(currentType)" |
| | | :key="index" |
| | | class="file-item" |
| | | @click="previewFile(file)"> |
| | | <image v-if="isImageFile(file)" |
| | | :src="file.url" |
| | | class="thumb" |
| | | mode="aspectFill" /> |
| | | <view v-else |
| | | class="video-thumb"> |
| | | <u-icon name="video" |
| | | size="28" |
| | | color="#1677ff" /> |
| | | <text class="video-text">è§é¢</text> |
| | | </view> |
| | | <text class="name">{{ file.name || "éä»¶" }}</text> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="empty"> |
| | | <text>ææ éä»¶</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | <u-popup :show="showVideoPopup" |
| | | mode="center" |
| | | :round="10" |
| | | @close="closeVideoPopup"> |
| | | <view class="video-container"> |
| | | <video :src="videoUrl" |
| | | controls |
| | | autoplay |
| | | class="video-player" /> |
| | | </view> |
| | | </u-popup> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import config from "@/config"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const currentType = ref("before"); |
| | | const showVideoPopup = ref(false); |
| | | const videoUrl = ref(""); |
| | | const filesMap = ref({ |
| | | before: [], |
| | | after: [], |
| | | issue: [], |
| | | }); |
| | | |
| | | const tabs = [ |
| | | { key: "before", label: "ç产å" }, |
| | | { key: "after", label: "ç产ä¸" }, |
| | | { key: "issue", label: "ç产å" }, |
| | | ]; |
| | | |
| | | const normalizeUrl = raw => { |
| | | if (!raw) return ""; |
| | | const url = String(raw).trim(); |
| | | if (!url) return ""; |
| | | if (/^https?:\/\//i.test(url)) return url; |
| | | if (url.startsWith("/")) return `${config.fileUrl}${url}`; |
| | | if (/^[a-zA-Z]:\\/.test(url)) { |
| | | const normalized = url.replace(/\\/g, "/"); |
| | | const idx = normalized.indexOf("/prod/"); |
| | | if (idx >= 0) { |
| | | const relative = normalized.slice(idx + "/prod/".length); |
| | | return `${config.fileUrl}/${relative}`; |
| | | } |
| | | return normalized; |
| | | } |
| | | return `${config.fileUrl}/${url.replace(/^\//, "")}`; |
| | | }; |
| | | |
| | | const isImageFile = file => { |
| | | if (file?.contentType?.startsWith("image/")) return true; |
| | | const name = String(file?.name || file?.bucketFilename || "").toLowerCase(); |
| | | return /\.(jpg|jpeg|png|gif|bmp|webp)$/.test(name); |
| | | }; |
| | | |
| | | const normalizeFile = (file, type) => ({ |
| | | ...file, |
| | | type, |
| | | url: normalizeUrl(file?.url || file?.downloadUrl), |
| | | name: file?.originalFilename || file?.bucketFilename || file?.name, |
| | | }); |
| | | |
| | | const getCurrentList = type => filesMap.value[type] || []; |
| | | |
| | | const previewFile = file => { |
| | | if (isImageFile(file)) { |
| | | const urls = getCurrentList(currentType.value) |
| | | .filter(item => isImageFile(item)) |
| | | .map(item => item.url); |
| | | uni.previewImage({ |
| | | urls, |
| | | current: file.url, |
| | | }); |
| | | return; |
| | | } |
| | | videoUrl.value = file.url; |
| | | showVideoPopup.value = true; |
| | | }; |
| | | |
| | | const closeVideoPopup = () => { |
| | | showVideoPopup.value = false; |
| | | videoUrl.value = ""; |
| | | }; |
| | | |
| | | const openDialog = row => { |
| | | const allList = Array.isArray(row?.commonFileList) ? row.commonFileList : []; |
| | | const beforeList = Array.isArray(row?.commonFileListBefore) |
| | | ? row.commonFileListBefore |
| | | : allList.filter(item => item?.type === 10); |
| | | const afterList = Array.isArray(row?.commonFileListAfter) |
| | | ? row.commonFileListAfter |
| | | : allList.filter(item => item?.type === 11); |
| | | const issueList = Array.isArray(row?.commonFileListIssue) |
| | | ? row.commonFileListIssue |
| | | : allList.filter(item => item?.type === 12); |
| | | filesMap.value = { |
| | | before: beforeList.map(item => normalizeFile(item, "before")).filter(item => item.url), |
| | | after: afterList.map(item => normalizeFile(item, "after")).filter(item => item.url), |
| | | issue: issueList.map(item => normalizeFile(item, "issue")).filter(item => item.url), |
| | | }; |
| | | currentType.value = "before"; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | closeVideoPopup(); |
| | | filesMap.value = { |
| | | before: [], |
| | | after: [], |
| | | issue: [], |
| | | }; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .popup-content { |
| | | width: 100vw; |
| | | max-height: 82vh; |
| | | background: #fff; |
| | | border-radius: 24rpx 24rpx 0 0; |
| | | overflow: hidden; |
| | | padding-bottom: env(safe-area-inset-bottom); |
| | | } |
| | | |
| | | .popup-header { |
| | | padding: 24rpx 20rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | text-align: center; |
| | | position: relative; |
| | | } |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | color: #1f1f1f; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .popup-body { |
| | | padding: 20rpx 20rpx 26rpx; |
| | | max-height: 68vh; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .tabs { |
| | | display: flex; |
| | | background: #f4f5f8; |
| | | border-radius: 12rpx; |
| | | padding: 6rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 12rpx 0; |
| | | color: #666; |
| | | font-size: 24rpx; |
| | | border-radius: 10rpx; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background: #1677ff; |
| | | color: #fff; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .file-list { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 16rpx; |
| | | } |
| | | |
| | | .file-item { |
| | | background: #fafafa; |
| | | border-radius: 12rpx; |
| | | padding: 10rpx; |
| | | } |
| | | |
| | | .thumb { |
| | | width: 100%; |
| | | height: 180rpx; |
| | | border-radius: 8rpx; |
| | | } |
| | | |
| | | .video-thumb { |
| | | width: 100%; |
| | | height: 180rpx; |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #edf3ff; |
| | | } |
| | | |
| | | .video-text { |
| | | font-size: 22rpx; |
| | | color: #1677ff; |
| | | margin-top: 6rpx; |
| | | } |
| | | |
| | | .name { |
| | | margin-top: 8rpx; |
| | | font-size: 22rpx; |
| | | color: #333; |
| | | display: block; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .empty { |
| | | text-align: center; |
| | | color: #999; |
| | | padding: 40rpx 0; |
| | | } |
| | | |
| | | .video-container { |
| | | width: 94vw; |
| | | background: #000; |
| | | } |
| | | |
| | | .video-player { |
| | | width: 94vw; |
| | | height: 55vw; |
| | | } |
| | | |
| | | .close-icon { |
| | | position: absolute; |
| | | right: 24rpx; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 44rpx; |
| | | height: 44rpx; |
| | | border-radius: 50%; |
| | | background: #f5f5f5; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="æ¥çéä»¶" |
| | | v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <div class="upload-container"> |
| | | <div class="form-container"> |
| | | <div class="title">å·¡æ£éä»¶</div> |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | <!-- ç»ä¸åªä½æ¥çå¨ --> |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <!-- å¾ç --> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <Video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | // å¾çæ°ç» |
| | | const beforeProductionImgs = ref([]); |
| | | // è§é¢æ°ç» |
| | | const beforeProductionVideos = ref([]); |
| | | // åªä½æ¥çå¨ç¶æ |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); // åå¨å½åè¦æ¥ççåªä½å表ï¼å«å¾çåè§é¢å¯¹è±¡ï¼ |
| | | const mediaType = ref('image'); // image | video |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.storageBlobDTO); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | // æ¾ç¤ºåªä½ï¼å¾ç or è§é¢ï¼ |
| | | function showMedia(mediaArray, index, type) { |
| | | mediaList.value = mediaArray; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | } |
| | | // å
³éåªä½æ¥çå¨ |
| | | function closeMediaViewer() { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | } |
| | | // 表åå
³éæ¹æ³ |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | const images = []; |
| | | const videos = []; |
| | | items.forEach(item => { |
| | | if (item.contentType?.startsWith('image/')) { |
| | | images.push(item.url); |
| | | } else if (item.contentType?.startsWith('video/')) { |
| | | videos.push(item.url); |
| | | } |
| | | }); |
| | | return { images, videos }; |
| | | } |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | } |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="inspection-management-page"> |
| | | <PageHeader title="å·¡æ£ä»»å¡ç®¡ç" |
| | | @back="goBack" /> |
| | | <view class="toolbar"> |
| | | <view class="tab-wrap"> |
| | | <view v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="switchTab(tab.name)"> |
| | | {{ tab.label }} |
| | | </view> |
| | | </view> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥å·¡æ£ä»»å¡åç§°" |
| | | v-model="queryParams.taskName" |
| | | clearable /> |
| | | </view> |
| | | <view class="search-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="meta-bar"> |
| | | <text class="meta-text">å
± {{ total }} æ¡</text> |
| | | </view> |
| | | </view> |
| | | <view class="list-section"> |
| | | <uni-swipe-action> |
| | | <uni-swipe-action-item v-for="item in tableData" |
| | | :key="item.id" |
| | | :right-options="activeTab === 'taskManage' ? swipeOptions : []" |
| | | :disabled="activeTab !== 'taskManage'" |
| | | @click="onSwipeActionClick($event, item)"> |
| | | <view class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" |
| | | size="14" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.taskName || "--" }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <u-tag :type="getFrequencyTagType(item.frequencyType)" |
| | | :text="formatFrequency(item.frequencyType) || 'æªç¥é¢æ¬¡'" /> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä»»å¡ç¼å·</text> |
| | | <text class="detail-value">{{ item.id || "--" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ§è¡å·¡æ£äºº</text> |
| | | <view class="tag-wrap" |
| | | v-if="item.inspector?.length"> |
| | | <uni-tag v-for="(person, index) in item.inspector" |
| | | :key="index" |
| | | :text="person" |
| | | type="primary" |
| | | size="small" |
| | | inverted /> |
| | | </view> |
| | | <text class="detail-value" |
| | | v-else>--</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å¼å§æ¥æä¸æ¶é´</text> |
| | | <text class="detail-value highlight">{{ formatFrequencyDetail(item.frequencyDetail) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç»è®°äºº</text> |
| | | <text class="detail-value">{{ item.registrant || "--" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç»è®°æ¥æ</text> |
| | | <text class="detail-value">{{ item.createTime || "--" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remarks || "æ " }}</text> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="card-actions"> |
| | | <u-button v-if="activeTab === 'taskManage'" |
| | | type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="handleAdd(item)">ç¼è¾</u-button> |
| | | <u-button v-else |
| | | type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewFile(item)">æ¥çéä»¶</u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </uni-swipe-action-item> |
| | | </uni-swipe-action> |
| | | <uni-load-more :status="loadMoreStatus"></uni-load-more> |
| | | <view v-if="!loading && tableData.length === 0" |
| | | class="no-data"> |
| | | <text>ææ æ°æ®</text> |
| | | </view> |
| | | </view> |
| | | <view v-if="activeTab === 'taskManage'" |
| | | class="fab-button" |
| | | @click="handleAdd()"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <form-dia ref="formDia" |
| | | @closeDia="handleQuery" /> |
| | | <view-files ref="viewFiles" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, nextTick } from "vue"; |
| | | import { onShow, onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FormDia from "@/pages/inspectionManagement/components/formDia.vue"; |
| | | import ViewFiles from "@/pages/inspectionManagement/components/viewFiles.vue"; |
| | | import { |
| | | delTimingTask, |
| | | inspectionTaskList, |
| | | timingTaskList, |
| | | } from "@/api/inspectionManagement/index.js"; |
| | | |
| | | const formDia = ref(); |
| | | const viewFiles = ref(); |
| | | const activeTab = ref("taskManage"); |
| | | const tabs = [ |
| | | { name: "taskManage", label: "宿¶ä»»å¡ç®¡ç" }, |
| | | { name: "task", label: "宿¶ä»»å¡è®°å½" }, |
| | | ]; |
| | | const queryParams = reactive({ |
| | | taskName: "", |
| | | }); |
| | | const pageParams = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | const total = ref(0); |
| | | const loading = ref(false); |
| | | const tableData = ref([]); |
| | | const swipeOptions = [ |
| | | { |
| | | text: "å é¤", |
| | | style: { |
| | | backgroundColor: "#ee0a24", |
| | | }, |
| | | }, |
| | | ]; |
| | | |
| | | const noMore = computed(() => tableData.value.length >= total.value); |
| | | const loadMoreStatus = computed(() => { |
| | | if (loading.value) return "loading"; |
| | | if (noMore.value) return "noMore"; |
| | | return "more"; |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const formatFrequency = value => { |
| | | if (value === "DAILY") return "æ¯æ¥"; |
| | | if (value === "WEEKLY") return "æ¯å¨"; |
| | | if (value === "MONTHLY") return "æ¯æ"; |
| | | if (value === "QUARTERLY") return "å£åº¦"; |
| | | return ""; |
| | | }; |
| | | |
| | | const getFrequencyTagType = value => { |
| | | if (value === "DAILY") return "success"; |
| | | if (value === "WEEKLY") return "primary"; |
| | | if (value === "MONTHLY") return "warning"; |
| | | return "info"; |
| | | }; |
| | | |
| | | const formatFrequencyDetail = value => { |
| | | if (!value || typeof value !== "string") return "--"; |
| | | return value.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | item => |
| | | ({ |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | })[item] || item |
| | | ); |
| | | }; |
| | | |
| | | const normalizeInspector = val => { |
| | | if (!val) return []; |
| | | if (Array.isArray(val)) return val.filter(Boolean); |
| | | if (typeof val === "string") { |
| | | return val |
| | | .split(",") |
| | | .map(item => item.trim()) |
| | | .filter(Boolean); |
| | | } |
| | | return [String(val)]; |
| | | }; |
| | | |
| | | const switchTab = tabName => { |
| | | if (activeTab.value === tabName) return; |
| | | activeTab.value = tabName; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const getList = async () => { |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | try { |
| | | const params = { |
| | | ...queryParams, |
| | | current: pageParams.current, |
| | | size: pageParams.size, |
| | | }; |
| | | const request = activeTab.value === "task" ? inspectionTaskList : timingTaskList; |
| | | const res = await request(params); |
| | | const records = res?.data?.records || []; |
| | | const normalized = records.map(item => ({ |
| | | ...item, |
| | | inspector: normalizeInspector(item.inspector), |
| | | })); |
| | | if (pageParams.current === 1) { |
| | | tableData.value = normalized; |
| | | } else { |
| | | tableData.value = [...tableData.value, ...normalized]; |
| | | } |
| | | total.value = Number(res?.data?.total || 0); |
| | | } catch (error) { |
| | | if (pageParams.current === 1) { |
| | | tableData.value = []; |
| | | } |
| | | uni.showToast({ |
| | | title: "è·åæ°æ®å¤±è´¥", |
| | | icon: "none", |
| | | }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | pageParams.current = 1; |
| | | total.value = 0; |
| | | getList(); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loading.value || noMore.value) return; |
| | | pageParams.current += 1; |
| | | getList(); |
| | | }; |
| | | |
| | | const handleAdd = row => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(row ? "edit" : "add", row); |
| | | }); |
| | | }; |
| | | |
| | | const viewFile = row => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | const deleteOne = async row => { |
| | | if (!row?.id) return; |
| | | const canDelete = await new Promise(resolve => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "æ¯å¦ç¡®è®¤å é¤è¯¥å·¡æ£ä»»å¡ï¼", |
| | | success: modalRes => resolve(Boolean(modalRes.confirm)), |
| | | fail: () => resolve(false), |
| | | }); |
| | | }); |
| | | if (!canDelete) return; |
| | | try { |
| | | await delTimingTask([row.id]); |
| | | uni.showToast({ |
| | | title: "å 餿å", |
| | | icon: "success", |
| | | }); |
| | | handleQuery(); |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: "å é¤å¤±è´¥", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const onSwipeActionClick = (event, row) => { |
| | | if (activeTab.value !== "taskManage") return; |
| | | if (event?.position !== "right") return; |
| | | deleteOne(row); |
| | | }; |
| | | |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | |
| | | onReachBottom(() => { |
| | | loadMore(); |
| | | }); |
| | | |
| | | onPullDownRefresh(() => { |
| | | handleQuery(); |
| | | uni.stopPullDownRefresh(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inspection-management-page { |
| | | min-height: 100vh; |
| | | background: #f6f7fb; |
| | | } |
| | | |
| | | .toolbar { |
| | | padding: 20rpx 24rpx; |
| | | background: #fff; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .tab-wrap { |
| | | display: flex; |
| | | background: #f4f5f8; |
| | | border-radius: 16rpx; |
| | | padding: 6rpx; |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 14rpx 0; |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | border-radius: 12rpx; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background: #1677ff; |
| | | color: #fff; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .search-section { |
| | | margin-top: 20rpx; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #f7f8fa; |
| | | border-radius: 14rpx; |
| | | padding: 8rpx 12rpx 8rpx 16rpx; |
| | | border: 1rpx solid #eef1f5; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .search-text { |
| | | background: transparent !important; |
| | | } |
| | | |
| | | :deep(.search-text .u-input__content), |
| | | :deep(.search-text .up-input__content) { |
| | | background: transparent !important; |
| | | padding: 0 !important; |
| | | } |
| | | |
| | | .search-button { |
| | | width: 64rpx; |
| | | height: 64rpx; |
| | | border-radius: 12rpx; |
| | | background: #ffffff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .meta-bar { |
| | | margin-top: 16rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background: #f7f9fc; |
| | | border-radius: 12rpx; |
| | | padding: 10rpx 16rpx; |
| | | } |
| | | |
| | | .meta-text { |
| | | color: #5c6b8a; |
| | | font-size: 22rpx; |
| | | } |
| | | |
| | | .list-section { |
| | | padding: 20rpx 24rpx; |
| | | padding-bottom: calc(132rpx + env(safe-area-inset-bottom)); |
| | | } |
| | | |
| | | .ledger-item { |
| | | background: #ffffff; |
| | | border-radius: 20rpx; |
| | | margin-bottom: 16rpx; |
| | | overflow: hidden; |
| | | box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.05); |
| | | padding: 0 20rpx; |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 22rpx 0; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10rpx; |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .item-right { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .document-icon { |
| | | width: 38rpx; |
| | | height: 38rpx; |
| | | border-radius: 8rpx; |
| | | background: #2979ff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 28rpx; |
| | | color: #1f1f1f; |
| | | font-weight: 600; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .item-details { |
| | | padding: 18rpx 0 20rpx; |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | align-items: flex-end; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | } |
| | | |
| | | .detail-label { |
| | | min-width: 160rpx; |
| | | color: #777; |
| | | font-size: 24rpx; |
| | | } |
| | | |
| | | .detail-value { |
| | | flex: 1; |
| | | color: #333; |
| | | font-size: 24rpx; |
| | | text-align: right; |
| | | word-break: break-all; |
| | | margin-left: 12rpx; |
| | | } |
| | | |
| | | .detail-value.highlight { |
| | | color: #2979ff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .tag-wrap { |
| | | flex: 1; |
| | | display: flex; |
| | | gap: 10rpx; |
| | | flex-wrap: wrap; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .card-actions { |
| | | padding-top: 8rpx; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .action-btn { |
| | | min-width: 140rpx; |
| | | } |
| | | |
| | | .fab-button { |
| | | position: fixed; |
| | | bottom: calc(30px + env(safe-area-inset-bottom)); |
| | | right: 30px; |
| | | width: 56px; |
| | | height: 56px; |
| | | background: #2979ff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4px 16px rgba(41, 121, 255, 0.3); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .no-data { |
| | | padding: 40rpx 0; |
| | | text-align: center; |
| | | color: #999; |
| | | } |
| | | </style> |