| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** |
| | | * @desc è·åå·¡æ£æ¸
åå表 |
| | | * @param {Object} params - æ¥è¯¢åæ° |
| | | * @param {string} params.date - å·¡æ£æ¥æ |
| | | * @param {string} params.inspector - å·¡æ£å |
| | | * @param {number} params.status - å·¡æ£ç¶æ 0:å¾
å·¡æ£ 1:å·¡æ£ä¸ 2:已宿 |
| | | * @returns {Promise} |
| | | */ |
| | | export const getInspectionList = (params) => { |
| | | return request({ |
| | | url: "/device/inspection/list", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc è·åå·¡æ£è¯¦æ
|
| | | * @param {string|number} id - å·¡æ£ID |
| | | * @returns {Promise} |
| | | */ |
| | | export const getInspectionDetail = (id) => { |
| | | return request({ |
| | | url: `/device/inspection/${id}`, |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc å¼å§å·¡æ£ |
| | | * @param {Object} data - å·¡æ£æ°æ® |
| | | * @param {string|number} data.inspectionId - å·¡æ£ID |
| | | * @param {string} data.startTime - å¼å§æ¶é´ |
| | | * @returns {Promise} |
| | | */ |
| | | export const startInspection = (data) => { |
| | | return request({ |
| | | url: "/device/inspection/start", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc æäº¤å·¡æ£è®°å½ |
| | | * @param {Object} data - å·¡æ£è®°å½æ°æ® |
| | | * @param {string|number} data.deviceId - 设å¤ID |
| | | * @param {string} data.deviceCode - 设å¤ç¼ç |
| | | * @param {string} data.inspectionDate - å·¡æ£æ¥æ |
| | | * @param {string} data.inspector - å·¡æ£å |
| | | * @param {string} data.scanTime - æ«ç æ¶é´ |
| | | * @param {Array} data.items - å·¡æ£é¡¹ç®å表 |
| | | * @param {string} data.completedAt - 宿æ¶é´ |
| | | * @returns {Promise} |
| | | */ |
| | | export const submitInspectionRecord = (data) => { |
| | | return request({ |
| | | url: "/device/inspection/submit", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc æ´æ°å·¡æ£é¡¹ç® |
| | | * @param {Object} data - å·¡æ£é¡¹ç®æ°æ® |
| | | * @param {string|number} data.inspectionId - å·¡æ£ID |
| | | * @param {string|number} data.itemId - 项ç®ID |
| | | * @param {string} data.result - å·¡æ£ç»æ normal:æ£å¸¸ abnormal:å¼å¸¸ |
| | | * @param {string} data.abnormalDesc - å¼å¸¸æè¿° |
| | | * @param {Array} data.images - å¾çå表 |
| | | * @param {Array} data.videos - è§é¢å表 |
| | | * @param {string} data.remark - 夿³¨ |
| | | * @returns {Promise} |
| | | */ |
| | | export const updateInspectionItem = (data) => { |
| | | return request({ |
| | | url: "/device/inspection/item/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc æ«ç æå¡ |
| | | * @param {Object} data - æå¡æ°æ® |
| | | * @param {string|number} data.inspectionId - å·¡æ£ID |
| | | * @param {string} data.deviceCode - 设å¤ç¼ç |
| | | * @param {string} data.qrCode - äºç»´ç å
容 |
| | | * @param {string} data.checkInTime - æå¡æ¶é´ |
| | | * @param {string} data.location - æå¡ä½ç½® |
| | | * @returns {Promise} |
| | | */ |
| | | export const checkInByQRCode = (data) => { |
| | | return request({ |
| | | url: "/device/inspection/checkin", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc ä¸ä¼ å·¡æ£æä»¶ï¼å¾ç/è§é¢ï¼ |
| | | * @param {FormData} formData - æä»¶æ°æ® |
| | | * @returns {Promise} |
| | | */ |
| | | export const uploadInspectionFile = (formData) => { |
| | | return request({ |
| | | url: "/device/inspection/upload", |
| | | method: "post", |
| | | data: formData, |
| | | headers: { |
| | | 'Content-Type': 'multipart/form-data' |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc è·åå·¡æ£ç»è®¡æ°æ® |
| | | * @param {Object} params - æ¥è¯¢åæ° |
| | | * @param {string} params.startDate - å¼å§æ¥æ |
| | | * @param {string} params.endDate - ç»ææ¥æ |
| | | * @param {string} params.inspector - å·¡æ£å |
| | | * @returns {Promise} |
| | | */ |
| | | export const getInspectionStats = (params) => { |
| | | return request({ |
| | | url: "/device/inspection/stats", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc è·åå·¡æ£åå²è®°å½ |
| | | * @param {Object} params - æ¥è¯¢åæ° |
| | | * @param {string|number} params.deviceId - 设å¤ID |
| | | * @param {number} params.current - å½å页 |
| | | * @param {number} params.size - 页é¢å¤§å° |
| | | * @returns {Promise} |
| | | */ |
| | | export const getInspectionHistory = (params) => { |
| | | return request({ |
| | | url: "/device/inspection/history", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc 导åºå·¡æ£è®°å½ |
| | | * @param {Object} params - 导åºåæ° |
| | | * @param {string} params.startDate - å¼å§æ¥æ |
| | | * @param {string} params.endDate - ç»ææ¥æ |
| | | * @param {string} params.inspector - å·¡æ£å |
| | | * @param {Array} params.deviceIds - 设å¤IDå表 |
| | | * @returns {Promise} |
| | | */ |
| | | export const exportInspectionRecords = (params) => { |
| | | return request({ |
| | | url: "/device/inspection/export", |
| | | method: "get", |
| | | params, |
| | | responseType: 'blob' |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc å é¤å·¡æ£è®°å½ |
| | | * @param {string|number} id - å·¡æ£è®°å½ID |
| | | * @returns {Promise} |
| | | */ |
| | | export const deleteInspectionRecord = (id) => { |
| | | return request({ |
| | | url: `/device/inspection/${id}`, |
| | | method: "delete", |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc æ¹éå é¤å·¡æ£è®°å½ |
| | | * @param {Array} ids - å·¡æ£è®°å½IDå表 |
| | | * @returns {Promise} |
| | | */ |
| | | export const batchDeleteInspectionRecords = (ids) => { |
| | | return request({ |
| | | url: "/device/inspection/batch/delete", |
| | | method: "delete", |
| | | data: { ids }, |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc è·å设å¤äºç»´ç |
| | | * @param {string|number} deviceId - 设å¤ID |
| | | * @returns {Promise} |
| | | */ |
| | | export const getDeviceQRCode = (deviceId) => { |
| | | return request({ |
| | | url: `/device/qrcode/${deviceId}`, |
| | | method: "get", |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * @desc éªè¯è®¾å¤äºç»´ç |
| | | * @param {Object} data - éªè¯æ°æ® |
| | | * @param {string} data.qrCode - äºç»´ç å
容 |
| | | * @param {string|number} data.deviceId - 设å¤ID |
| | | * @returns {Promise} |
| | | */ |
| | | export const verifyDeviceQRCode = (data) => { |
| | | return request({ |
| | | url: "/device/qrcode/verify", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | }; |
| | |
| | | // åºç¨çæ¬ |
| | | version: "1.1.0", |
| | | // åºç¨logo |
| | | logo: "/static/logo.png", |
| | | logo: "/static/app-logo.png", |
| | | // 宿¹ç½ç« |
| | | site_url: "http://ruoyi.vip", |
| | | // æ¿çåè®® |
| | |
| | | "navigationBarTitleText": "ç»´ä¿®ä¿å
»", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/equipmentManagement/inspection/index", |
| | | "style": { |
| | | "navigationBarTitleText": "设å¤å·¡æ£", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/equipmentManagement/inspection/detail", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¡æ£è¯¦æ
", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/equipmentManagement/smartDispatch/index", |
| | | "style": { |
| | | "navigationBarTitleText": "æºè½æ´¾å", |
| | | "navigationStyle": "custom" |
| | | } |
| | | } |
| | | ], |
| | | "subPackages": [ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="inspection-detail"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="设å¤å·¡æ£è¯¦æ
" @back="goBack" /> |
| | | |
| | | <!-- 设å¤ä¿¡æ¯å¡ç --> |
| | | <view class="device-card"> |
| | | <view class="device-header"> |
| | | <view class="device-icon"> |
| | | <up-icon name="settings" size="24" color="#1890ff"></up-icon> |
| | | </view> |
| | | <view class="device-info"> |
| | | <text class="device-name">{{ deviceInfo.deviceName }}</text> |
| | | <text class="device-code">{{ deviceInfo.deviceCode }}</text> |
| | | </view> |
| | | <view class="qr-scan" @click="scanDeviceQR"> |
| | | <up-icon name="scan" size="20" color="#1890ff"></up-icon> |
| | | <text class="scan-text">æ«ç </text> |
| | | </view> |
| | | </view> |
| | | <view class="device-details"> |
| | | <view class="detail-item"> |
| | | <text class="label">ä½ç½®ï¼</text> |
| | | <text class="value">{{ deviceInfo.location }}</text> |
| | | </view> |
| | | <view class="detail-item"> |
| | | <text class="label">å·¡æ£æ¶é´ï¼</text> |
| | | <text class="value">{{ deviceInfo.inspectionTime }}</text> |
| | | </view> |
| | | <view class="detail-item"> |
| | | <text class="label">è´è´£äººï¼</text> |
| | | <text class="value">{{ deviceInfo.inspector }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å·¡æ£é¡¹ç®æ¸
å --> |
| | | <view class="inspection-items"> |
| | | <view class="section-title"> |
| | | <up-icon name="list" size="18" color="#333"></up-icon> |
| | | <text class="title-text">å·¡æ£é¡¹ç®æ¸
å</text> |
| | | <text class="progress-text">({{ completedItems }}/{{ totalItems }})</text> |
| | | </view> |
| | | |
| | | <view class="items-list"> |
| | | <view |
| | | v-for="(item, index) in inspectionItems" |
| | | :key="index" |
| | | class="inspection-item" |
| | | :class="{ 'completed': item.completed, 'abnormal': item.isAbnormal }" |
| | | > |
| | | <view class="item-header" @click="toggleItem(index)"> |
| | | <view class="item-left"> |
| | | <view class="checkbox" :class="{ 'checked': item.completed }"> |
| | | <up-icon v-if="item.completed" name="checkmark" size="14" color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-name">{{ item.name }}</text> |
| | | </view> |
| | | <view class="item-status"> |
| | | <u-tag v-if="item.isAbnormal" type="error" size="mini">å¼å¸¸</u-tag> |
| | | <u-tag v-else-if="item.completed" type="success" size="mini">æ£å¸¸</u-tag> |
| | | <u-tag v-else type="info" size="mini">å¾
æ£</u-tag> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å±å¼ç详æ
å
容 --> |
| | | <view v-if="item.expanded" class="item-content"> |
| | | <view class="item-description"> |
| | | <text class="desc-text">{{ item.description }}</text> |
| | | </view> |
| | | |
| | | <!-- å·¡æ£ç»æéæ© --> |
| | | <view class="result-section"> |
| | | <text class="section-label">å·¡æ£ç»æï¼</text> |
| | | <view class="result-options"> |
| | | <u-radio-group v-model="item.result" @change="onResultChange(index, $event)"> |
| | | <u-radio |
| | | v-for="option in resultOptions" |
| | | :key="option.value" |
| | | :label="option.value" |
| | | :name="option.label" |
| | | size="small" |
| | | > |
| | | {{ option.label }} |
| | | </u-radio> |
| | | </u-radio-group> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å¼å¸¸æ
åµæè¿° --> |
| | | <view v-if="item.result === 'abnormal'" class="abnormal-section"> |
| | | <text class="section-label">å¼å¸¸æè¿°ï¼</text> |
| | | <up-textarea |
| | | v-model="item.abnormalDesc" |
| | | placeholder="è¯·è¯¦ç»æè¿°å¼å¸¸æ
åµ" |
| | | :maxlength="200" |
| | | count |
| | | height="80" |
| | | ></up-textarea> |
| | | </view> |
| | | |
| | | <!-- å¾çä¸ä¼ --> |
| | | <view class="upload-section"> |
| | | <text class="section-label">ç°åºç
§çï¼</text> |
| | | <up-upload |
| | | :fileList="item.images" |
| | | @afterRead="(event) => afterRead(event, index, 'images')" |
| | | @delete="(event) => deleteFile(event, index, 'images')" |
| | | name="images" |
| | | multiple |
| | | :maxCount="5" |
| | | :previewImage="true" |
| | | > |
| | | <view class="upload-btn"> |
| | | <up-icon name="camera" size="20" color="#999"></up-icon> |
| | | <text class="upload-text">æ·»å ç
§ç</text> |
| | | </view> |
| | | </up-upload> |
| | | </view> |
| | | |
| | | <!-- è§é¢ä¸ä¼ --> |
| | | <view class="upload-section"> |
| | | <text class="section-label">ç°åºè§é¢ï¼</text> |
| | | <up-upload |
| | | :fileList="item.videos" |
| | | @afterRead="(event) => afterRead(event, index, 'videos')" |
| | | @delete="(event) => deleteFile(event, index, 'videos')" |
| | | name="videos" |
| | | :maxCount="2" |
| | | accept="video" |
| | | > |
| | | <view class="upload-btn"> |
| | | <up-icon name="play-circle" size="20" color="#999"></up-icon> |
| | | <text class="upload-text">æ·»å è§é¢</text> |
| | | </view> |
| | | </up-upload> |
| | | </view> |
| | | |
| | | <!-- 夿³¨ --> |
| | | <view class="remark-section"> |
| | | <text class="section-label">夿³¨ï¼</text> |
| | | <up-textarea |
| | | v-model="item.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯ï¼å¯éï¼" |
| | | :maxlength="100" |
| | | count |
| | | height="60" |
| | | ></up-textarea> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- åºé¨æä½æé® --> |
| | | <view class="bottom-actions"> |
| | | <u-button |
| | | type="primary" |
| | | size="large" |
| | | :disabled="!canSubmit" |
| | | @click="submitInspection" |
| | | :loading="submitting" |
| | | > |
| | | {{ allCompleted ? 'æäº¤å·¡æ£è®°å½' : `ç»§ç»å·¡æ£ (${completedItems}/${totalItems})` }} |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { submitInspectionRecord } from '@/api/equipmentManagement/inspection' |
| | | import dayjs from 'dayjs' |
| | | |
| | | // 设å¤ä¿¡æ¯ |
| | | const deviceInfo = ref({}) |
| | | |
| | | // å·¡æ£é¡¹ç®å表 |
| | | const inspectionItems = ref([]) |
| | | |
| | | // æäº¤ç¶æ |
| | | const submitting = ref(false) |
| | | |
| | | // å·¡æ£ç»æé项 |
| | | const resultOptions = [ |
| | | { label: 'æ£å¸¸', value: 'normal' }, |
| | | { label: 'å¼å¸¸', value: 'abnormal' } |
| | | ] |
| | | |
| | | // æ¾ç¤ºæç¤ºä¿¡æ¯ |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | |
| | | // 计ç®å±æ§ |
| | | const totalItems = computed(() => inspectionItems.value.length) |
| | | const completedItems = computed(() => inspectionItems.value.filter(item => item.completed).length) |
| | | const allCompleted = computed(() => completedItems.value === totalItems.value && totalItems.value > 0) |
| | | const canSubmit = computed(() => completedItems.value > 0) |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | if (completedItems.value > 0) { |
| | | uni.showModal({ |
| | | title: 'æç¤º', |
| | | content: 'å½åææªä¿åçå·¡æ£è®°å½ï¼ç¡®å®è¦ç¦»å¼åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | uni.navigateBack() |
| | | } |
| | | } |
| | | }) |
| | | } else { |
| | | uni.navigateBack() |
| | | } |
| | | } |
| | | |
| | | // æ«æè®¾å¤äºç»´ç |
| | | const scanDeviceQR = () => { |
| | | uni.scanCode({ |
| | | success: (res) => { |
| | | console.log('æ«ç ç»æï¼', res) |
| | | if (res.result.includes(deviceInfo.value.deviceCode)) { |
| | | showToast('设å¤ç¡®è®¤æå') |
| | | // è®°å½æ«ç æ¶é´ |
| | | deviceInfo.value.scanTime = new Date().toISOString() |
| | | } else { |
| | | showToast('设å¤äºç»´ç ä¸å¹é
') |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | console.log('æ«ç 失败ï¼', err) |
| | | showToast('æ«ç 失败') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 忢巡æ£é¡¹ç® |
| | | const toggleItem = (index) => { |
| | | inspectionItems.value[index].expanded = !inspectionItems.value[index].expanded |
| | | } |
| | | |
| | | // å·¡æ£ç»ææ¹å |
| | | const onResultChange = (index, value) => { |
| | | const item = inspectionItems.value[index] |
| | | item.result = value |
| | | item.completed = true |
| | | item.isAbnormal = value === 'abnormal' |
| | | |
| | | // 妿鿩æ£å¸¸ï¼æ¸
空å¼å¸¸æè¿° |
| | | if (value === 'normal') { |
| | | item.abnormalDesc = '' |
| | | } |
| | | } |
| | | |
| | | // æä»¶ä¸ä¼ åå¤ç |
| | | const afterRead = async (event, index, type) => { |
| | | const { file } = event |
| | | const item = inspectionItems.value[index] |
| | | |
| | | // 模æä¸ä¼ è¿ç¨ |
| | | uni.showLoading({ title: 'ä¸ä¼ ä¸...' }) |
| | | |
| | | try { |
| | | // è¿éåºè¯¥è°ç¨å®é
çä¸ä¼ API |
| | | await new Promise(resolve => setTimeout(resolve, 1000)) |
| | | |
| | | // æ·»å å°å¯¹åºçæä»¶å表 |
| | | if (type === 'images') { |
| | | item.images = item.images || [] |
| | | item.images.push({ |
| | | url: file.url, |
| | | name: file.name, |
| | | size: file.size |
| | | }) |
| | | } else if (type === 'videos') { |
| | | item.videos = item.videos || [] |
| | | item.videos.push({ |
| | | url: file.url, |
| | | name: file.name, |
| | | size: file.size |
| | | }) |
| | | } |
| | | |
| | | uni.hideLoading() |
| | | showToast('ä¸ä¼ æå') |
| | | } catch (error) { |
| | | uni.hideLoading() |
| | | showToast('ä¸ä¼ 失败') |
| | | } |
| | | } |
| | | |
| | | // å 餿件 |
| | | const deleteFile = (event, index, type) => { |
| | | const item = inspectionItems.value[index] |
| | | if (type === 'images') { |
| | | item.images.splice(event.index, 1) |
| | | } else if (type === 'videos') { |
| | | item.videos.splice(event.index, 1) |
| | | } |
| | | } |
| | | |
| | | // æäº¤å·¡æ£è®°å½ |
| | | const submitInspection = async () => { |
| | | if (!canSubmit.value) { |
| | | showToast('请è³å°å®æä¸é¡¹å·¡æ£') |
| | | return |
| | | } |
| | | |
| | | // æ£æ¥å¼å¸¸é¡¹ç®æ¯å¦å¡«åäºæè¿° |
| | | const abnormalItems = inspectionItems.value.filter(item => item.isAbnormal) |
| | | for (const item of abnormalItems) { |
| | | if (!item.abnormalDesc || item.abnormalDesc.trim() === '') { |
| | | showToast(`请填å"${item.name}"çå¼å¸¸æè¿°`) |
| | | return |
| | | } |
| | | } |
| | | |
| | | submitting.value = true |
| | | |
| | | try { |
| | | const recordData = { |
| | | deviceId: deviceInfo.value.id, |
| | | deviceCode: deviceInfo.value.deviceCode, |
| | | inspectionDate: dayjs().format('YYYY-MM-DD'), |
| | | inspector: deviceInfo.value.inspector, |
| | | scanTime: deviceInfo.value.scanTime, |
| | | items: inspectionItems.value.map(item => ({ |
| | | name: item.name, |
| | | result: item.result, |
| | | completed: item.completed, |
| | | isAbnormal: item.isAbnormal, |
| | | abnormalDesc: item.abnormalDesc, |
| | | images: item.images || [], |
| | | videos: item.videos || [], |
| | | remark: item.remark |
| | | })), |
| | | completedAt: new Date().toISOString() |
| | | } |
| | | |
| | | // 模æAPIè°ç¨ |
| | | await new Promise(resolve => setTimeout(resolve, 2000)) |
| | | |
| | | // å®é
APIè°ç¨ |
| | | // await submitInspectionRecord(recordData) |
| | | |
| | | showToast('å·¡æ£è®°å½æäº¤æå') |
| | | |
| | | // è¿ååè¡¨é¡µé¢ |
| | | setTimeout(() => { |
| | | uni.navigateBack() |
| | | }, 1500) |
| | | |
| | | } catch (error) { |
| | | showToast('æäº¤å¤±è´¥ï¼è¯·éè¯') |
| | | } finally { |
| | | submitting.value = false |
| | | } |
| | | } |
| | | |
| | | // åå§åæ°æ® |
| | | const initData = () => { |
| | | // ä»åå¨ä¸è·åå½åå·¡æ£ä¿¡æ¯ |
| | | const currentInspection = uni.getStorageSync('currentInspection') |
| | | if (currentInspection) { |
| | | deviceInfo.value = currentInspection |
| | | } |
| | | |
| | | // 模æå·¡æ£é¡¹ç®æ°æ® |
| | | inspectionItems.value = [ |
| | | { |
| | | name: '设å¤å¤è§æ£æ¥', |
| | | description: 'æ£æ¥è®¾å¤å¤è§æ¯å¦ææåãéèãåå½¢çå¼å¸¸æ
åµ', |
| | | completed: false, |
| | | expanded: false, |
| | | result: '', |
| | | isAbnormal: false, |
| | | abnormalDesc: '', |
| | | images: [], |
| | | videos: [], |
| | | remark: '' |
| | | }, |
| | | { |
| | | name: 'è¿è¡ç¶ææ£æ¥', |
| | | description: 'æ£æ¥è®¾å¤è¿è¡æ¯å¦æ£å¸¸ï¼ææ å¼å¸¸å£°é³ãæ¯å¨ç', |
| | | completed: false, |
| | | expanded: false, |
| | | result: '', |
| | | isAbnormal: false, |
| | | abnormalDesc: '', |
| | | images: [], |
| | | videos: [], |
| | | remark: '' |
| | | }, |
| | | { |
| | | name: 'å®å
¨è£
ç½®æ£æ¥', |
| | | description: 'æ£æ¥åç±»å®å
¨è£
ç½®æ¯å¦å®å¥½ï¼å®å
¨æ è¯æ¯å¦æ¸
æ°', |
| | | completed: false, |
| | | expanded: false, |
| | | result: '', |
| | | isAbnormal: false, |
| | | abnormalDesc: '', |
| | | images: [], |
| | | videos: [], |
| | | remark: '' |
| | | }, |
| | | { |
| | | name: 'ç¯å¢æ¡ä»¶æ£æ¥', |
| | | description: 'æ£æ¥è®¾å¤å¨å´ç¯å¢æ¯å¦ç¬¦åè¦æ±ï¼éé£ãç
§æçæ¯å¦æ£å¸¸', |
| | | completed: false, |
| | | expanded: false, |
| | | result: '', |
| | | isAbnormal: false, |
| | | abnormalDesc: '', |
| | | images: [], |
| | | videos: [], |
| | | remark: '' |
| | | }, |
| | | { |
| | | name: '仪表读æ°è®°å½', |
| | | description: 'è®°å½ç¸å
³ä»ªè¡¨ç读æ°ï¼æ£æ¥æ¯å¦å¨æ£å¸¸èå´å
', |
| | | completed: false, |
| | | expanded: false, |
| | | result: '', |
| | | isAbnormal: false, |
| | | abnormalDesc: '', |
| | | images: [], |
| | | videos: [], |
| | | remark: '' |
| | | } |
| | | ] |
| | | } |
| | | |
| | | onMounted(() => { |
| | | initData() |
| | | }) |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶å·æ°æ°æ® |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .inspection-detail { |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | min-height: 100vh; |
| | | padding-bottom: 80px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | height: 200px; |
| | | background: linear-gradient(135deg, rgba(102, 126, 234, 0.8) 0%, rgba(118, 75, 162, 0.8) 100%); |
| | | z-index: 0; |
| | | } |
| | | } |
| | | |
| | | .device-card { |
| | | background: rgba(255, 255, 255, 0.95); |
| | | backdrop-filter: blur(15px); |
| | | margin: 10px 20px; |
| | | border-radius: 20px; |
| | | padding: 24px; |
| | | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
| | | border: 1px solid rgba(255, 255, 255, 0.2); |
| | | position: relative; |
| | | z-index: 1; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); |
| | | } |
| | | } |
| | | |
| | | .device-header { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 16px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .device-icon { |
| | | width: 56px; |
| | | height: 56px; |
| | | background: linear-gradient(135deg, #667eea, #764ba2); |
| | | border-radius: 16px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3); |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: scale(1.05); |
| | | } |
| | | } |
| | | |
| | | .device-info { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .device-name { |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | .device-code { |
| | | font-size: 13px; |
| | | color: #8c8c8c; |
| | | font-weight: 500; |
| | | padding: 4px 12px; |
| | | background: rgba(140, 140, 140, 0.1); |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | width: fit-content; |
| | | } |
| | | |
| | | .qr-scan { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 6px; |
| | | padding: 12px 16px; |
| | | background: linear-gradient(135deg, #52c41a, #389e0d); |
| | | border-radius: 12px; |
| | | box-shadow: 0 4px 15px rgba(82, 196, 26, 0.3); |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: scale(1.05); |
| | | box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4); |
| | | } |
| | | |
| | | &:active { |
| | | transform: scale(0.98); |
| | | } |
| | | } |
| | | |
| | | .scan-text { |
| | | font-size: 13px; |
| | | color: #ffffff; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .device-details { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | background: rgba(248, 250, 252, 0.8); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | backdrop-filter: blur(10px); |
| | | } |
| | | |
| | | .detail-item { |
| | | display: flex; |
| | | align-items: center; |
| | | font-size: 14px; |
| | | padding: 8px 0; |
| | | transition: all 0.2s ease; |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.5); |
| | | margin: 0 -8px; |
| | | padding-left: 8px; |
| | | padding-right: 8px; |
| | | border-radius: 8px; |
| | | } |
| | | } |
| | | |
| | | .label { |
| | | color: #595959; |
| | | min-width: 80px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .value { |
| | | color: #262626; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .inspection-items { |
| | | margin: 10px 20px; |
| | | position: relative; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .section-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 20px 0; |
| | | border-bottom: 1px solid rgba(255, 255, 255, 0.2); |
| | | background: rgba(255, 255, 255, 0.95); |
| | | backdrop-filter: blur(15px); |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | margin-bottom: 16px; |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .title-text { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | flex: 1; |
| | | } |
| | | |
| | | .progress-text { |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | background: linear-gradient(135deg, #667eea, #764ba2); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .items-list { |
| | | background: rgba(255, 255, 255, 0.95); |
| | | backdrop-filter: blur(15px); |
| | | border-radius: 20px; |
| | | overflow: hidden; |
| | | margin-top: 0; |
| | | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
| | | border: 1px solid rgba(255, 255, 255, 0.2); |
| | | } |
| | | |
| | | .inspection-item { |
| | | border-bottom: 1px solid rgba(0, 0, 0, 0.06); |
| | | transition: all 0.3s ease; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | &.completed { |
| | | background: rgba(82, 196, 26, 0.05); |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | |
| | | &.abnormal { |
| | | background: rgba(255, 77, 79, 0.05); |
| | | border-left: 4px solid #ff4d4f; |
| | | } |
| | | |
| | | &:hover { |
| | | background: rgba(102, 126, 234, 0.05); |
| | | } |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 15px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | flex: 1; |
| | | } |
| | | |
| | | .checkbox { |
| | | width: 20px; |
| | | height: 20px; |
| | | border: 2px solid #d9d9d9; |
| | | border-radius: 4px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | |
| | | &.checked { |
| | | background: #52c41a; |
| | | border-color: #52c41a; |
| | | } |
| | | } |
| | | |
| | | .item-name { |
| | | font-size: 15px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .item-status { |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .item-content { |
| | | padding: 0 15px 20px; |
| | | border-top: 1px solid #f5f5f5; |
| | | } |
| | | |
| | | .item-description { |
| | | padding: 15px 0; |
| | | } |
| | | |
| | | .desc-text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .result-section, |
| | | .abnormal-section, |
| | | .upload-section, |
| | | .remark-section { |
| | | margin-top: 15px; |
| | | } |
| | | |
| | | .section-label { |
| | | display: block; |
| | | font-size: 14px; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .result-options { |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .upload-btn { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 88px; |
| | | height: 88px; |
| | | border: 2px dashed rgba(102, 126, 234, 0.3); |
| | | border-radius: 16px; |
| | | background: rgba(102, 126, 234, 0.05); |
| | | gap: 8px; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | border-color: rgba(102, 126, 234, 0.5); |
| | | background: rgba(102, 126, 234, 0.1); |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); |
| | | } |
| | | |
| | | &:active { |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | .upload-text { |
| | | font-size: 13px; |
| | | color: #667eea; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .bottom-actions { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | background: rgba(255, 255, 255, 0.95); |
| | | backdrop-filter: blur(20px); |
| | | padding: 20px; |
| | | border-top: 1px solid rgba(255, 255, 255, 0.2); |
| | | box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.1); |
| | | z-index: 10; |
| | | |
| | | button { |
| | | height: 48px; |
| | | border-radius: 16px; |
| | | font-weight: 600; |
| | | font-size: 16px; |
| | | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | &:active { |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="inspection-page"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="设å¤å·¡æ£" @back="goBack" /> |
| | | |
| | | <!-- ç»è®¡ä¿¡æ¯å¡ç --> |
| | | <view class="stats-cards"> |
| | | <view class="stat-card"> |
| | | <text class="stat-number">{{ totalCount }}</text> |
| | | <text class="stat-label">æ»ä»»å¡</text> |
| | | </view> |
| | | <view class="stat-card"> |
| | | <text class="stat-number">{{ completedCount }}</text> |
| | | <text class="stat-label">已宿</text> |
| | | </view> |
| | | <view class="stat-card"> |
| | | <text class="stat-number">{{ pendingCount }}</text> |
| | | <text class="stat-label">å¾
å·¡æ£</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å·¡æ£æ¸
å --> |
| | | <view class="inspection-list" v-if="inspectionList.length > 0"> |
| | | <view v-for="(item, index) in inspectionList" :key="index"> |
| | | <view class="inspection-item" @click="startInspection(item)"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="device-icon" :class="getStatusClass(item.status)"> |
| | | <up-icon :name="getStatusIcon(item.status)" size="16" color="#ffffff"></up-icon> |
| | | </view> |
| | | <view class="device-info"> |
| | | <text class="device-name">{{ item.deviceName }}</text> |
| | | <text class="device-location">{{ item.location }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="status-tag"> |
| | | <u-tag :type="getTagType(item.status)" size="mini"> |
| | | {{ getStatusText(item.status) }} |
| | | </u-tag> |
| | | </view> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">设å¤ç¼å·</text> |
| | | <text class="detail-value">{{ item.deviceCode || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¡æ£æ¶é´</text> |
| | | <text class="detail-value">{{ item.inspectionTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è´è´£äºº</text> |
| | | <text class="detail-value">{{ item.inspector || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row" v-if="item.status === 2"> |
| | | <text class="detail-label">宿æ¶é´</text> |
| | | <text class="detail-value">{{ formatDateTime(item.completedTime) || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æä½æé® --> |
| | | <view class="action-buttons"> |
| | | <u-button |
| | | type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.status === 2" |
| | | @click.stop="startInspection(item)" |
| | | > |
| | | {{ item.status === 0 ? 'å¼å§å·¡æ£' : item.status === 1 ? 'ç»§ç»å·¡æ£' : 'æ¥ç详æ
' }} |
| | | </u-button> |
| | | <u-button |
| | | type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.status !== 1" |
| | | @click.stop="scanQRCode(item)" |
| | | > |
| | | æ«ç æå¡ |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <up-empty mode="data" text="ææ å·¡æ£ä»»å¡"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { getInspectionList } from '@/api/equipmentManagement/inspection' |
| | | import dayjs from 'dayjs' |
| | | |
| | | // éä¸çæ¥æ |
| | | const selectedDate = ref(Date.now()) |
| | | |
| | | // å·¡æ£æ¸
åæ°æ® |
| | | const inspectionList = ref([]) |
| | | |
| | | // æ¾ç¤ºæç¤ºä¿¡æ¯ |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | |
| | | // 计ç®ç»è®¡æ°æ® |
| | | const totalCount = computed(() => inspectionList.value.length) |
| | | const completedCount = computed(() => inspectionList.value.filter(item => item.status === 2).length) |
| | | const pendingCount = computed(() => inspectionList.value.filter(item => item.status === 0).length) |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // æ¥ææ ¼å¼åå¨ |
| | | const dateFormatter = (type, value) => { |
| | | if (type === 'year') { |
| | | return `${value}å¹´` |
| | | } |
| | | if (type === 'month') { |
| | | return `${value}æ` |
| | | } |
| | | if (type === 'day') { |
| | | return `${value}æ¥` |
| | | } |
| | | return value |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (timestamp) => { |
| | | return dayjs(timestamp).format('YYYYå¹´MMæDDæ¥') |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateStr) => { |
| | | if (!dateStr) return '' |
| | | return dayjs(dateStr).format('MM-DD HH:mm') |
| | | } |
| | | |
| | | // æ¥ææ¹åäºä»¶ |
| | | const onDateChange = (value) => { |
| | | selectedDate.value = value.value |
| | | getList() |
| | | } |
| | | |
| | | // è·åç¶ææ ·å¼ç±» |
| | | const getStatusClass = (status) => { |
| | | switch (status) { |
| | | case 0: return 'status-pending' |
| | | case 1: return 'status-progress' |
| | | case 2: return 'status-completed' |
| | | default: return 'status-pending' |
| | | } |
| | | } |
| | | |
| | | // è·åç¶æå¾æ |
| | | const getStatusIcon = (status) => { |
| | | switch (status) { |
| | | case 0: return 'clock' |
| | | case 1: return 'play-circle' |
| | | case 2: return 'checkmark-circle' |
| | | default: return 'clock' |
| | | } |
| | | } |
| | | |
| | | // è·åæ ç¾ç±»å |
| | | const getTagType = (status) => { |
| | | switch (status) { |
| | | case 0: return 'warning' |
| | | case 1: return 'primary' |
| | | case 2: return 'success' |
| | | default: return 'info' |
| | | } |
| | | } |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | switch (status) { |
| | | case 0: return 'å¾
å·¡æ£' |
| | | case 1: return 'å·¡æ£ä¸' |
| | | case 2: return '已宿' |
| | | default: return 'æªç¥' |
| | | } |
| | | } |
| | | |
| | | // å¼å§å·¡æ£ |
| | | const startInspection = (item) => { |
| | | // åå¨å½åå·¡æ£é¡¹ç®ä¿¡æ¯ |
| | | uni.setStorageSync('currentInspection', item) |
| | | uni.navigateTo({ |
| | | url: '/pages/equipmentManagement/inspection/detail' |
| | | }) |
| | | } |
| | | |
| | | // æ«ç æå¡ |
| | | const scanQRCode = (item) => { |
| | | uni.scanCode({ |
| | | success: (res) => { |
| | | console.log('æ«ç ç»æï¼', res) |
| | | // éªè¯äºç»´ç å
容 |
| | | if (res.result.includes(item.deviceCode)) { |
| | | showToast('æå¡æå') |
| | | // æ´æ°æå¡ç¶æ |
| | | updateCheckInStatus(item.id) |
| | | } else { |
| | | showToast('äºç»´ç ä¸å¹é
ï¼è¯·æ«ææ£ç¡®ç设å¤äºç»´ç ') |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | console.log('æ«ç 失败ï¼', err) |
| | | showToast('æ«ç 失败') |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æ´æ°æå¡ç¶æ |
| | | const updateCheckInStatus = (id) => { |
| | | // è¿éåºè¯¥è°ç¨APIæ´æ°æå¡ç¶æ |
| | | // ææ¶æ¨¡ææ´æ°æ¬å°æ°æ® |
| | | const item = inspectionList.value.find(item => item.id === id) |
| | | if (item) { |
| | | item.checkInTime = new Date().toISOString() |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢å·¡æ£æ¸
å |
| | | const getList = () => { |
| | | uni.showLoading({ |
| | | title: 'å è½½ä¸...', |
| | | mask: true |
| | | }) |
| | | |
| | | const params = { |
| | | date: dayjs(selectedDate.value).format('YYYY-MM-DD') |
| | | } |
| | | |
| | | // æ¨¡ææ°æ®ï¼å®é
åºè¯¥è°ç¨API |
| | | setTimeout(() => { |
| | | inspectionList.value = [ |
| | | { |
| | | id: 1, |
| | | deviceName: 'ç©ºåæºA01', |
| | | deviceCode: 'KYJ-A01', |
| | | location: 'ç产车é´Aåº', |
| | | inspectionTime: '08:00-09:00', |
| | | inspector: 'å¼ ä¸', |
| | | status: 0, // 0:å¾
å·¡æ£ 1:å·¡æ£ä¸ 2:已宿 |
| | | completedTime: null |
| | | }, |
| | | { |
| | | id: 2, |
| | | deviceName: 'å·å´å¡B02', |
| | | deviceCode: 'LQT-B02', |
| | | location: 'ç产车é´Båº', |
| | | inspectionTime: '09:00-10:00', |
| | | inspector: 'æå', |
| | | status: 1, |
| | | completedTime: null |
| | | }, |
| | | { |
| | | id: 3, |
| | | deviceName: 'ååå¨C03', |
| | | deviceCode: 'BYQ-C03', |
| | | location: 'é
çµæ¿', |
| | | inspectionTime: '10:00-11:00', |
| | | inspector: 'çäº', |
| | | status: 2, |
| | | completedTime: '2024-01-15T10:30:00' |
| | | } |
| | | ] |
| | | uni.hideLoading() |
| | | }, 1000) |
| | | |
| | | // å®é
APIè°ç¨ |
| | | // getInspectionList(params) |
| | | // .then((res) => { |
| | | // inspectionList.value = res.records || res.data?.records || [] |
| | | // uni.hideLoading() |
| | | // }) |
| | | // .catch(() => { |
| | | // uni.hideLoading() |
| | | // showToast('è·åæ°æ®å¤±è´¥') |
| | | // }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | |
| | | onShow(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/styles/sales-common.scss'; |
| | | |
| | | .inspection-page { |
| | | background: #ffffff; |
| | | min-height: 100vh; |
| | | padding-bottom: 20px; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 10px; |
| | | padding: 0 15px; |
| | | margin: 15px 0; |
| | | } |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | padding: 15px 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 6px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | border: 1px solid #f0f0f0; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | .stat-number { |
| | | font-size: 24px; |
| | | font-weight: 700; |
| | | color: #1890ff; |
| | | } |
| | | |
| | | .stat-label { |
| | | font-size: 13px; |
| | | color: #666; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .inspection-list { |
| | | padding: 0 15px; |
| | | } |
| | | |
| | | .inspection-item { |
| | | background: #ffffff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
| | | border: 1px solid #f0f0f0; |
| | | transition: all 0.3s ease; |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); |
| | | } |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | gap: 12px; |
| | | flex: 1; |
| | | } |
| | | |
| | | .device-icon { |
| | | width: 48px; |
| | | height: 48px; |
| | | border-radius: 16px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); |
| | | transition: all 0.3s ease; |
| | | |
| | | &.status-pending { |
| | | background: linear-gradient(135deg, #faad14, #fa8c16); |
| | | box-shadow: 0 4px 16px rgba(250, 173, 20, 0.3); |
| | | } |
| | | |
| | | &.status-progress { |
| | | background: linear-gradient(135deg, #1890ff, #096dd9); |
| | | box-shadow: 0 4px 16px rgba(24, 144, 255, 0.3); |
| | | } |
| | | |
| | | &.status-completed { |
| | | background: linear-gradient(135deg, #52c41a, #389e0d); |
| | | box-shadow: 0 4px 16px rgba(82, 196, 26, 0.3); |
| | | } |
| | | |
| | | &:hover { |
| | | transform: scale(1.05); |
| | | } |
| | | } |
| | | |
| | | .device-info { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | } |
| | | |
| | | .device-name { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #1a1a1a; |
| | | line-height: 1.3; |
| | | } |
| | | |
| | | .device-location { |
| | | font-size: 13px; |
| | | color: #8c8c8c; |
| | | font-weight: 500; |
| | | padding: 2px 8px; |
| | | background: rgba(140, 140, 140, 0.1); |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | width: fit-content; |
| | | } |
| | | |
| | | .status-tag { |
| | | flex-shrink: 0; |
| | | transform: scale(1.1); |
| | | } |
| | | |
| | | .item-details { |
| | | margin: 15px 0; |
| | | background: rgba(248, 250, 252, 0.8); |
| | | border-radius: 12px; |
| | | padding: 12px; |
| | | backdrop-filter: blur(10px); |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 10px 0; |
| | | border-bottom: 1px solid rgba(0, 0, 0, 0.06); |
| | | transition: all 0.2s ease; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | padding-bottom: 0; |
| | | } |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.5); |
| | | margin: 0 -8px; |
| | | padding-left: 8px; |
| | | padding-right: 8px; |
| | | border-radius: 8px; |
| | | } |
| | | } |
| | | |
| | | .detail-label { |
| | | font-size: 14px; |
| | | color: #595959; |
| | | min-width: 80px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 14px; |
| | | color: #262626; |
| | | text-align: right; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-top: 15px; |
| | | padding-top: 15px; |
| | | border-top: 1px solid rgba(0, 0, 0, 0.06); |
| | | } |
| | | |
| | | .action-btn { |
| | | flex: 1; |
| | | height: 44px; |
| | | border-radius: 12px; |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| | | |
| | | &:hover { |
| | | transform: translateY(-2px); |
| | | box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); |
| | | } |
| | | |
| | | &:active { |
| | | transform: translateY(0); |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding: 80px 20px; |
| | | text-align: center; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="æºè½æ´¾å" @back="goBack" /> |
| | | |
| | | <!-- çéåºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥è®¾å¤åç§°æç´¢" |
| | | v-model="searchKeyword" |
| | | @change="filterTasks" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="filterTasks"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ç»è®¡ä¿¡æ¯åºå --> |
| | | <view class="summary-info"> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">å¾
æ´¾åä»»å¡</text> |
| | | <text class="summary-value highlight">{{ pendingTasks.length }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">ç»´ä¿®ç»æ°é</text> |
| | | <text class="summary-value">{{ repairTeams.length }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="summary-label">仿¥å·²æ´¾å</text> |
| | | <text class="summary-value">{{ todayDispatchedCount }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å¾
æ´¾åä»»å¡å表 --> |
| | | <view class="ledger-list" v-if="filteredTasks.length > 0"> |
| | | <view v-for="(task, index) in filteredTasks" :key="task.id"> |
| | | <view class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" size="16" color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ task.deviceName }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <view class="item-tag" :class="getDeviceTypeClass(task.deviceType)"> |
| | | <text class="tag-text">{{ task.deviceType }}</text> |
| | | </view> |
| | | <view class="priority-tag" :class="getPriorityClass(task.priority)"> |
| | | <text class="tag-text">{{ getPriorityText(task.priority) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">设å¤ç¼å·</text> |
| | | <text class="detail-value">{{ task.deviceCode }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ
éæè¿°</text> |
| | | <text class="detail-value">{{ task.faultDescription }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥ä¿®äºº</text> |
| | | <text class="detail-value">{{ task.reporterName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥ä¿®æ¶é´</text> |
| | | <text class="detail-value">{{ formatDate(task.reportTime) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">é¢ä¼°å·¥æ¶</text> |
| | | <text class="detail-value highlight">{{ task.estimatedHours }}å°æ¶</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æéæè½</text> |
| | | <text class="detail-value">{{ task.requiredSkills.join('ã') }}</text> |
| | | </view> |
| | | |
| | | <!-- æ¨èç»´ä¿®ç» --> |
| | | <view class="recommended-team" v-if="task.recommendedTeam"> |
| | | <view class="team-header"> |
| | | <text class="team-title">æ¨èç»´ä¿®ç»</text> |
| | | <view class="match-score"> |
| | | <text class="score-text">å¹é
度: {{ task.recommendedTeam.matchScore }}%</text> |
| | | </view> |
| | | </view> |
| | | <view class="team-info"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç»å</text> |
| | | <text class="detail-value">{{ task.recommendedTeam.teamName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è´è´£äºº</text> |
| | | <text class="detail-value">{{ task.recommendedTeam.leaderName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½åè´è½½</text> |
| | | <text class="detail-value" :class="getWorkloadClass(task.recommendedTeam.currentWorkload)"> |
| | | {{ task.recommendedTeam.currentWorkload }}% |
| | | </text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æè½å¹é
</text> |
| | | <text class="detail-value">{{ task.recommendedTeam.matchedSkills.join('ã') }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æä½æé® --> |
| | | <view class="action-buttons"> |
| | | <u-button |
| | | type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | @click="confirmDispatch(task)" |
| | | :disabled="!task.recommendedTeam" |
| | | > |
| | | 确认派å |
| | | </u-button> |
| | | <u-button |
| | | type="primary" |
| | | size="small" |
| | | plain |
| | | class="action-btn" |
| | | @click="manualDispatch(task)" |
| | | > |
| | | æå¨æ´¾å |
| | | </u-button> |
| | | <u-button |
| | | type="warning" |
| | | size="small" |
| | | plain |
| | | class="action-btn" |
| | | @click="reAnalyze(task)" |
| | | > |
| | | éæ°åæ |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <text>ææ å¾
æ´¾åä»»å¡</text> |
| | | </view> |
| | | |
| | | |
| | | |
| | | <!-- æå¨æ´¾åå¼¹çª --> |
| | | <up-popup v-model:show="showManualDispatchPopup" mode="center" border-radius="20"> |
| | | <view class="manual-dispatch-popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">æå¨æ´¾å</text> |
| | | </view> |
| | | |
| | | <view class="team-list"> |
| | | <view |
| | | v-for="team in availableTeams" |
| | | :key="team.id" |
| | | class="team-option" |
| | | :class="{ active: selectedTeamId === team.id }" |
| | | @click="selectedTeamId = team.id" |
| | | > |
| | | <view class="team-info"> |
| | | <text class="team-name">{{ team.teamName }}</text> |
| | | <text class="team-leader">è´è´£äºº: {{ team.leaderName }}</text> |
| | | <text class="team-workload" :class="getWorkloadClass(team.currentWorkload)"> |
| | | è´è½½: {{ team.currentWorkload }}% |
| | | </text> |
| | | </view> |
| | | <view class="team-skills"> |
| | | <text class="skills-label">æè½:</text> |
| | | <text class="skills-text">{{ team.skills.join('ã') }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="popup-actions"> |
| | | <u-button type="info" plain @click="showManualDispatchPopup = false">åæ¶</u-button> |
| | | <u-button type="primary" @click="confirmManualDispatch" :disabled="!selectedTeamId">确认派å</u-button> |
| | | </view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- æµ®å¨æä½æé® --> |
| | | <view class="fab-button" @click="autoDispatchAll"> |
| | | <up-icon name="plus" size="24" color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from 'vue' |
| | | import { onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | |
| | | // æç´¢å
³é®è¯ |
| | | const searchKeyword = ref('') |
| | | |
| | | |
| | | |
| | | // æå¨æ´¾åç¸å
³ |
| | | const showManualDispatchPopup = ref(false) |
| | | const selectedTask = ref(null) |
| | | const selectedTeamId = ref(null) |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // 仿¥å·²æ´¾åæ°é |
| | | const todayDispatchedCount = ref(12) |
| | | |
| | | // å¾
æ´¾å任塿°æ® |
| | | const pendingTasks = ref([ |
| | | { |
| | | id: 1, |
| | | deviceName: 'æ°æ§è½¦åºCK6140', |
| | | deviceCode: 'CNC-001', |
| | | deviceType: 'æºæ¢°è®¾å¤', |
| | | faultDescription: '主轴å¼åï¼åå精度ä¸é', |
| | | reporterName: 'å¼ å¸å
', |
| | | reportTime: '2024-01-15 09:30:00', |
| | | priority: 1, |
| | | estimatedHours: 4, |
| | | requiredSkills: ['æºæ¢°ç»´ä¿®', 'æ°æ§ææ¯'], |
| | | recommendedTeam: { |
| | | id: 1, |
| | | teamName: 'æºæ¢°ç»´ä¿®ä¸ç»', |
| | | leaderName: 'æå·¥ç¨å¸', |
| | | currentWorkload: 65, |
| | | matchScore: 95, |
| | | matchedSkills: ['æºæ¢°ç»´ä¿®', 'æ°æ§ææ¯'] |
| | | } |
| | | }, |
| | | { |
| | | id: 2, |
| | | deviceName: 'åé¢å¨ABB-ACS800', |
| | | deviceCode: 'INV-002', |
| | | deviceType: 'çµæ°è®¾å¤', |
| | | faultDescription: 'é¢ç¹æ¥è¦ï¼è¾åºçµåä¸ç¨³å®', |
| | | reporterName: 'çææ¯å', |
| | | reportTime: '2024-01-15 10:15:00', |
| | | priority: 2, |
| | | estimatedHours: 3, |
| | | requiredSkills: ['çµæ°ç»´ä¿®', 'åé¢å¨ææ¯'], |
| | | recommendedTeam: { |
| | | id: 2, |
| | | teamName: 'çµæ°ç»´ä¿®ç»', |
| | | leaderName: '赵工ç¨å¸', |
| | | currentWorkload: 45, |
| | | matchScore: 88, |
| | | matchedSkills: ['çµæ°ç»´ä¿®', 'åé¢å¨ææ¯'] |
| | | } |
| | | }, |
| | | { |
| | | id: 3, |
| | | deviceName: 'ååä¼ æå¨PT100', |
| | | deviceCode: 'SEN-003', |
| | | deviceType: '仪表设å¤', |
| | | faultDescription: '读æ°å¼å¸¸ï¼é¶ç¹æ¼ç§»ä¸¥é', |
| | | reporterName: 'éæä½å', |
| | | reportTime: '2024-01-15 11:00:00', |
| | | priority: 3, |
| | | estimatedHours: 2, |
| | | requiredSkills: ['仪表维修', 'ä¼ æå¨ææ¯'], |
| | | recommendedTeam: { |
| | | id: 3, |
| | | teamName: '仪表维修ç»', |
| | | leaderName: 'åå·¥ç¨å¸', |
| | | currentWorkload: 30, |
| | | matchScore: 92, |
| | | matchedSkills: ['仪表维修', 'ä¼ æå¨ææ¯'] |
| | | } |
| | | }, |
| | | { |
| | | id: 4, |
| | | deviceName: 'å·¥æ§æºIPC-610', |
| | | deviceCode: 'PC-004', |
| | | deviceType: 'è®¡ç®æºè®¾å¤', |
| | | faultDescription: 'ç³»ç»é¢ç¹æ»æºï¼ç¡¬çæ
é', |
| | | reporterName: 'åç¨åºå', |
| | | reportTime: '2024-01-15 13:45:00', |
| | | priority: 1, |
| | | estimatedHours: 5, |
| | | requiredSkills: ['è®¡ç®æºç»´ä¿®', 'ç³»ç»ç»´æ¤'], |
| | | recommendedTeam: { |
| | | id: 4, |
| | | teamName: 'ITç»´ä¿®ç»', |
| | | leaderName: 'å¨å·¥ç¨å¸', |
| | | currentWorkload: 80, |
| | | matchScore: 90, |
| | | matchedSkills: ['è®¡ç®æºç»´ä¿®', 'ç³»ç»ç»´æ¤'] |
| | | } |
| | | }, |
| | | { |
| | | id: 5, |
| | | deviceName: 'å车TCM-FD30', |
| | | deviceCode: 'VEH-005', |
| | | deviceType: '车è¾è®¾å¤', |
| | | faultDescription: 'æ¶²åç³»ç»æ¼æ²¹ï¼åéæ å', |
| | | reporterName: '马叿º', |
| | | reportTime: '2024-01-15 14:20:00', |
| | | priority: 2, |
| | | estimatedHours: 6, |
| | | requiredSkills: ['车è¾ç»´ä¿®', 'æ¶²åææ¯'], |
| | | recommendedTeam: { |
| | | id: 5, |
| | | teamName: '车è¾ç»´ä¿®ç»', |
| | | leaderName: 'å´å¸å
', |
| | | currentWorkload: 55, |
| | | matchScore: 85, |
| | | matchedSkills: ['车è¾ç»´ä¿®', 'æ¶²åææ¯'] |
| | | } |
| | | } |
| | | ]) |
| | | |
| | | // ç»´ä¿®ç»æ°æ® |
| | | const repairTeams = ref([ |
| | | { |
| | | id: 1, |
| | | teamName: 'æºæ¢°ç»´ä¿®ä¸ç»', |
| | | leaderName: 'æå·¥ç¨å¸', |
| | | currentWorkload: 65, |
| | | skills: ['æºæ¢°ç»´ä¿®', 'æ°æ§ææ¯', 'çæ¥ææ¯'] |
| | | }, |
| | | { |
| | | id: 2, |
| | | teamName: 'çµæ°ç»´ä¿®ç»', |
| | | leaderName: '赵工ç¨å¸', |
| | | currentWorkload: 45, |
| | | skills: ['çµæ°ç»´ä¿®', 'åé¢å¨ææ¯', 'PLCç¼ç¨'] |
| | | }, |
| | | { |
| | | id: 3, |
| | | teamName: '仪表维修ç»', |
| | | leaderName: 'åå·¥ç¨å¸', |
| | | currentWorkload: 30, |
| | | skills: ['仪表维修', 'ä¼ æå¨ææ¯', 'èªå¨åæ§å¶'] |
| | | }, |
| | | { |
| | | id: 4, |
| | | teamName: 'ITç»´ä¿®ç»', |
| | | leaderName: 'å¨å·¥ç¨å¸', |
| | | currentWorkload: 80, |
| | | skills: ['è®¡ç®æºç»´ä¿®', 'ç³»ç»ç»´æ¤', 'ç½ç»ææ¯'] |
| | | }, |
| | | { |
| | | id: 5, |
| | | teamName: '车è¾ç»´ä¿®ç»', |
| | | leaderName: 'å´å¸å
', |
| | | currentWorkload: 55, |
| | | skills: ['车è¾ç»´ä¿®', 'æ¶²åææ¯', 'å卿ºç»´ä¿®'] |
| | | }, |
| | | { |
| | | id: 6, |
| | | teamName: 'æºæ¢°ç»´ä¿®äºç»', |
| | | leaderName: 'é±å·¥ç¨å¸', |
| | | currentWorkload: 40, |
| | | skills: ['æºæ¢°ç»´ä¿®', 'ç²¾å¯å å·¥', '设å¤è°è¯'] |
| | | } |
| | | ]) |
| | | |
| | | // 计ç®å¯ç¨ç»´ä¿®ç»ï¼æé¤å½åæ¨èçç»ï¼ |
| | | const availableTeams = computed(() => { |
| | | if (!selectedTask.value) return repairTeams.value |
| | | return repairTeams.value.filter(team => |
| | | !selectedTask.value.recommendedTeam || team.id !== selectedTask.value.recommendedTeam.id |
| | | ) |
| | | }) |
| | | |
| | | // çéåçä»»å¡å表 |
| | | const filteredTasks = computed(() => { |
| | | let tasks = pendingTasks.value |
| | | |
| | | // æç´¢å
³é®è¯çé |
| | | if (searchKeyword.value) { |
| | | tasks = tasks.filter(task => |
| | | task.deviceName.includes(searchKeyword.value) || |
| | | task.deviceCode.includes(searchKeyword.value) || |
| | | task.faultDescription.includes(searchKeyword.value) |
| | | ) |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | return tasks |
| | | }) |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (dateStr) => { |
| | | if (!dateStr) return '' |
| | | const date = new Date(dateStr) |
| | | const month = String(date.getMonth() + 1).padStart(2, '0') |
| | | const day = String(date.getDate()).padStart(2, '0') |
| | | const hours = String(date.getHours()).padStart(2, '0') |
| | | const minutes = String(date.getMinutes()).padStart(2, '0') |
| | | return `${month}-${day} ${hours}:${minutes}` |
| | | } |
| | | |
| | | // è·å设å¤ç±»åæ ·å¼ç±» |
| | | const getDeviceTypeClass = (deviceType) => { |
| | | const typeMap = { |
| | | 'æºæ¢°è®¾å¤': 'tag-mechanical', |
| | | 'çµæ°è®¾å¤': 'tag-electric', |
| | | '仪表设å¤': 'tag-instrument', |
| | | 'è®¡ç®æºè®¾å¤': 'tag-computer', |
| | | '车è¾è®¾å¤': 'tag-vehicle', |
| | | 'å
¶ä»è®¾å¤': 'tag-other' |
| | | } |
| | | return typeMap[deviceType] || 'tag-unknown' |
| | | } |
| | | |
| | | // è·åä¼å
çº§æ ·å¼ç±» |
| | | const getPriorityClass = (priority) => { |
| | | const priorityMap = { |
| | | 1: 'priority-urgent', |
| | | 2: 'priority-high', |
| | | 3: 'priority-medium', |
| | | 4: 'priority-low' |
| | | } |
| | | return priorityMap[priority] || 'priority-medium' |
| | | } |
| | | |
| | | // è·åä¼å
çº§ææ¬ |
| | | const getPriorityText = (priority) => { |
| | | const priorityMap = { |
| | | 1: 'ç´§æ¥', |
| | | 2: 'é«', |
| | | 3: 'ä¸', |
| | | 4: 'ä½' |
| | | } |
| | | return priorityMap[priority] || 'ä¸' |
| | | } |
| | | |
| | | // è·åå·¥ä½è´è½½æ ·å¼ç±» |
| | | const getWorkloadClass = (workload) => { |
| | | if (workload >= 80) return 'danger' |
| | | if (workload >= 60) return 'highlight' |
| | | return '' |
| | | } |
| | | |
| | | // çéä»»å¡ |
| | | const filterTasks = () => { |
| | | // æç´¢é»è¾å·²å¨è®¡ç®å±æ§filteredTasksä¸å¤ç |
| | | // å½searchKeywordååæ¶ï¼è®¡ç®å±æ§ä¼èªå¨éæ°è®¡ç® |
| | | // è¿éå¯ä»¥æ·»å é¢å¤ççéé»è¾ææç¤º |
| | | if (searchKeyword.value && filteredTasks.value.length === 0) { |
| | | // 妿ææç´¢å
³é®è¯ä½æ²¡æç»æï¼å¯ä»¥ç»ç¨æ·æç¤º |
| | | console.log('æªæ¾å°å¹é
ç设å¤ä»»å¡') |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | // 确认派å |
| | | const confirmDispatch = (task) => { |
| | | uni.showModal({ |
| | | title: '确认派å', |
| | | content: `确认å°ä»»å¡"${task.deviceName}"æ´¾ç»"${task.recommendedTeam.teamName}"ï¼`, |
| | | confirmText: '确认', |
| | | cancelText: 'åæ¶', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | // æ¨¡ææ´¾åæå |
| | | const index = pendingTasks.value.findIndex(t => t.id === task.id) |
| | | if (index > -1) { |
| | | pendingTasks.value.splice(index, 1) |
| | | todayDispatchedCount.value++ |
| | | showToast('æ´¾åæå') |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // æå¨æ´¾å |
| | | const manualDispatch = (task) => { |
| | | selectedTask.value = task |
| | | selectedTeamId.value = null |
| | | showManualDispatchPopup.value = true |
| | | } |
| | | |
| | | // 确认æå¨æ´¾å |
| | | const confirmManualDispatch = () => { |
| | | if (!selectedTeamId.value) { |
| | | showToast('è¯·éæ©ç»´ä¿®ç»') |
| | | return |
| | | } |
| | | |
| | | const selectedTeam = repairTeams.value.find(team => team.id === selectedTeamId.value) |
| | | if (!selectedTeam) { |
| | | showToast('ç»´ä¿®ç»ä¸åå¨') |
| | | return |
| | | } |
| | | |
| | | // å
å
³éæå¨æ´¾åå¼¹æ¡ |
| | | showManualDispatchPopup.value = false |
| | | |
| | | uni.showModal({ |
| | | title: '确认派å', |
| | | content: `确认å°ä»»å¡"${selectedTask.value.deviceName}"æ´¾ç»"${selectedTeam.teamName}"ï¼`, |
| | | confirmText: '确认', |
| | | cancelText: 'åæ¶', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | // æ¨¡ææ´¾åæå |
| | | const index = pendingTasks.value.findIndex(t => t.id === selectedTask.value.id) |
| | | if (index > -1) { |
| | | pendingTasks.value.splice(index, 1) |
| | | todayDispatchedCount.value++ |
| | | showToast('æ´¾åæå') |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // éæ°åæ |
| | | const reAnalyze = (task) => { |
| | | showToast('æ£å¨éæ°åæ...') |
| | | |
| | | // 模æéæ°åæè¿ç¨ |
| | | setTimeout(() => { |
| | | // éæºè°æ´å¹é
åº¦åæ¨èç» |
| | | const teams = repairTeams.value.filter(team => |
| | | team.skills.some(skill => task.requiredSkills.includes(skill)) |
| | | ) |
| | | |
| | | if (teams.length > 0) { |
| | | const randomTeam = teams[Math.floor(Math.random() * teams.length)] |
| | | const matchedSkills = randomTeam.skills.filter(skill => task.requiredSkills.includes(skill)) |
| | | const matchScore = Math.floor(Math.random() * 20) + 80 // 80-99çéæºå¹é
度 |
| | | |
| | | task.recommendedTeam = { |
| | | id: randomTeam.id, |
| | | teamName: randomTeam.teamName, |
| | | leaderName: randomTeam.leaderName, |
| | | currentWorkload: randomTeam.currentWorkload, |
| | | matchScore: matchScore, |
| | | matchedSkills: matchedSkills |
| | | } |
| | | |
| | | showToast('éæ°åæå®æ') |
| | | } else { |
| | | showToast('æªæ¾å°åéçç»´ä¿®ç»') |
| | | } |
| | | }, 1500) |
| | | } |
| | | |
| | | // ä¸é®èªå¨æ´¾å |
| | | const autoDispatchAll = () => { |
| | | const tasksWithRecommendation = filteredTasks.value.filter(task => task.recommendedTeam) |
| | | |
| | | if (tasksWithRecommendation.length === 0) { |
| | | showToast('没æå¯èªå¨æ´¾åçä»»å¡') |
| | | return |
| | | } |
| | | |
| | | uni.showModal({ |
| | | title: 'ä¸é®æ´¾å', |
| | | content: `确认èªå¨æ´¾å${tasksWithRecommendation.length}个任å¡ï¼`, |
| | | confirmText: '确认', |
| | | cancelText: 'åæ¶', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | // æ¨¡ææ¹éæ´¾å |
| | | tasksWithRecommendation.forEach(task => { |
| | | const index = pendingTasks.value.findIndex(t => t.id === task.id) |
| | | if (index > -1) { |
| | | pendingTasks.value.splice(index, 1) |
| | | todayDispatchedCount.value++ |
| | | } |
| | | }) |
| | | showToast(`æåæ´¾å${tasksWithRecommendation.length}个任å¡`) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶çåå§åé»è¾ |
| | | }) |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶çé»è¾ |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/styles/sales-common.scss'; |
| | | |
| | | // æºè½æ´¾åç¹ææ ·å¼ |
| | | .sales-account { |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | |
| | | |
| | | // 设å¤ç±»åæ ç¾æ ·å¼ |
| | | .tag-mechanical { |
| | | background: #4caf50; |
| | | } |
| | | |
| | | .tag-electric { |
| | | background: #2196f3; |
| | | } |
| | | |
| | | .tag-instrument { |
| | | background: #ff9800; |
| | | } |
| | | |
| | | .tag-computer { |
| | | background: #9c27b0; |
| | | } |
| | | |
| | | .tag-vehicle { |
| | | background: #795548; |
| | | } |
| | | |
| | | .tag-other { |
| | | background: #607d8b; |
| | | } |
| | | |
| | | // ä¼å
级æ ç¾æ ·å¼ |
| | | .priority-urgent { |
| | | background: #f44336; |
| | | } |
| | | |
| | | .priority-high { |
| | | background: #ff9800; |
| | | } |
| | | |
| | | .priority-medium { |
| | | background: #2196f3; |
| | | } |
| | | |
| | | .priority-low { |
| | | background: #4caf50; |
| | | } |
| | | |
| | | // æ¨èç»´ä¿®ç»æ ·å¼ |
| | | .recommended-team { |
| | | margin-top: 16px; |
| | | padding: 12px; |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #2979ff; |
| | | } |
| | | |
| | | .team-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .team-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | |
| | | .match-score { |
| | | background: #2979ff; |
| | | padding: 2px 8px; |
| | | border-radius: 12px; |
| | | } |
| | | |
| | | .score-text { |
| | | font-size: 12px; |
| | | color: #ffffff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .team-info { |
| | | .detail-row { |
| | | margin-bottom: 4px; |
| | | } |
| | | } |
| | | |
| | | // å¼¹çªæ ·å¼ |
| | | .manual-dispatch-popup { |
| | | padding: 20px; |
| | | background: #ffffff; |
| | | border-radius: 20px; |
| | | max-height: 80vh; |
| | | width: 90vw; |
| | | max-width: 400px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .popup-title { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | |
| | | .popup-actions { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-top: 20px; |
| | | padding-top: 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .popup-actions .u-button { |
| | | flex: 1; |
| | | } |
| | | |
| | | // ç»´ä¿®ç»éé¡¹æ ·å¼ |
| | | .team-list { |
| | | max-height: 300px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .team-option { |
| | | padding: 12px; |
| | | margin-bottom: 8px; |
| | | border: 1px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | |
| | | &.active { |
| | | border-color: #2979ff; |
| | | background: #f3f7ff; |
| | | } |
| | | |
| | | &:hover { |
| | | border-color: #2979ff; |
| | | } |
| | | } |
| | | |
| | | .team-name { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .team-leader, |
| | | .team-workload { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .team-skills { |
| | | margin-top: 8px; |
| | | padding-top: 8px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .skills-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | .skills-text { |
| | | font-size: 12px; |
| | | color: #333; |
| | | } |
| | | </style> |
| | |
| | | { |
| | | icon: '/static/images/icon/shbeibaoyang@2x.png', |
| | | label: '设å¤ä¿å
»', |
| | | }, |
| | | { |
| | | icon: '/static/images/icon/shebeixunjian@2x.png', |
| | | label: '设å¤å·¡æ£', |
| | | }, |
| | | { |
| | | icon: 'flash', |
| | | label: 'æºè½æ´¾å', |
| | | bgColor: '#ff6b35' |
| | | } |
| | | ]); |
| | | |
| | |
| | | url: '/pages/equipmentManagement/upkeep/index' |
| | | }); |
| | | break; |
| | | case '设å¤å·¡æ£': |
| | | uni.navigateTo({ |
| | | url: '/pages/equipmentManagement/inspection/index' |
| | | }); |
| | | break; |
| | | case 'æºè½æ´¾å': |
| | | uni.navigateTo({ |
| | | url: '/pages/equipmentManagement/smartDispatch/index' |
| | | }); |
| | | break; |
| | | default: |
| | | uni.showToast({ |
| | | title: `ç¹å»äº${item.label}`, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"> |
| | | <!-- èæ¯åå½¢ --> |
| | | <circle cx="24" cy="24" r="20" fill="#1890ff" opacity="0.1"/> |
| | | |
| | | <!-- 设å¤ä¸»ä½ --> |
| | | <rect x="14" y="12" width="20" height="16" rx="2" fill="#1890ff" stroke="#1890ff" stroke-width="1.5"/> |
| | | |
| | | <!-- 设å¤å±å¹ --> |
| | | <rect x="16" y="14" width="16" height="8" rx="1" fill="#ffffff"/> |
| | | |
| | | <!-- å±å¹å
å®¹çº¿æ¡ --> |
| | | <line x1="18" y1="16" x2="30" y2="16" stroke="#1890ff" stroke-width="1"/> |
| | | <line x1="18" y1="18" x2="26" y2="18" stroke="#1890ff" stroke-width="1"/> |
| | | <line x1="18" y1="20" x2="28" y2="20" stroke="#1890ff" stroke-width="1"/> |
| | | |
| | | <!-- è®¾å¤æé® --> |
| | | <circle cx="18" cy="25" r="1.5" fill="#ffffff"/> |
| | | <circle cx="22" cy="25" r="1.5" fill="#ffffff"/> |
| | | <circle cx="26" cy="25" r="1.5" fill="#ffffff"/> |
| | | <circle cx="30" cy="25" r="1.5" fill="#ffffff"/> |
| | | |
| | | <!-- å·¡æ£è·¯å¾ --> |
| | | <path d="M10 32 Q16 28 24 32 Q32 36 38 32" stroke="#52c41a" stroke-width="2" fill="none" stroke-dasharray="2,2"/> |
| | | |
| | | <!-- å·¡æ£ç¹ --> |
| | | <circle cx="10" cy="32" r="2" fill="#52c41a"/> |
| | | <circle cx="24" cy="32" r="2" fill="#52c41a"/> |
| | | <circle cx="38" cy="32" r="2" fill="#52c41a"/> |
| | | |
| | | <!-- æ£æ¥æ è®° --> |
| | | <path d="M20 36 L22 38 L26 34" stroke="#52c41a" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/> |
| | | |
| | | <!-- æ«æçº¿ --> |
| | | <line x1="8" y1="8" x2="12" y2="8" stroke="#faad14" stroke-width="2" stroke-linecap="round"/> |
| | | <line x1="8" y1="8" x2="8" y2="12" stroke="#faad14" stroke-width="2" stroke-linecap="round"/> |
| | | |
| | | <line x1="36" y1="8" x2="40" y2="8" stroke="#faad14" stroke-width="2" stroke-linecap="round"/> |
| | | <line x1="40" y1="8" x2="40" y2="12" stroke="#faad14" stroke-width="2" stroke-linecap="round"/> |
| | | </svg> |