src/api/equipment/management/index.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,32 @@ // 设å¤ç®¡ç import request from '@/utils/request' // /equipmentManagement/list // æ¥è¯¢è®¾å¤ç®¡çå表 export function getManagementList(query) { return request({ url: '/equipmentManagement/list', method: 'get', params: query }) } // /equipmentManagement/addOrEditEquipment // æ·»å æç¼è¾è®¾å¤ export function addOrEditEquipment(data) { return request({ url: '/equipmentManagement/addOrEditEquipment', method: 'post', data }) } // /equipmentManagement/delEquipment // å é¤è®¾å¤ export function delEquipment(data) { return request({ url: '/equipmentManagement/delEquipment', method: 'delete', data }) } src/api/equipment/requisition/index.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,32 @@ // 设å¤ç®¡ç import request from '@/utils/request' // /equipmentUsageRecord/list // æ¥è¯¢è®¾å¤ä½¿ç¨è®°å½ export function getUsageRecordList(query) { return request({ url: '/equipmentUsageRecord/list', method: 'get', params: query }) } // /equipmentUsageRecord/addOrEditUsageRecord // æ·»å æç¼è¾è®¾å¤ä½¿ç¨è®°å½ export function addOrEditUsageRecord(data) { return request({ url: '/equipmentUsageRecord/addOrEditUsageRecord', method: 'post', data }) } // /equipmentUsageDetail/list?usageId=3 // 设å¤é¢ç¨å½è¿è®°å½æ¥è¯¢ export function getUsageDetailList(usageId) { return request({ url: `/equipmentUsageDetail/list?usageId=${usageId}`, method: 'get' }) } src/api/equipmentManagement/inspection.js
@@ -374,4 +374,12 @@ url: '/equipment/inspection/template/' + id, method: 'get' }) } // å·¡æ£ä¸ä¼ export function uploadInspectionTask(query) { return request({ url: '/inspectionTask/addOrEditInspectionTask', method: 'post', data: query }) } src/api/login.js
@@ -1,14 +1,28 @@ import request from '@/utils/request' // ç»å½æ¹æ³ export function loginCheckFactory(username, password, factoryId) { export function login(username, password, code, uuid) { const data = { username, password, factoryId code, uuid } return request({ url: '/loginCheckFactory', url: '/login', headers: { isToken: false, repeatSubmit: false }, method: 'post', data: data }) } // æ³¨åæ¹æ³ export function register(data) { return request({ url: '/register', headers: { isToken: false }, @@ -33,11 +47,14 @@ }) } // è·åå ¬å¸å表 export function userLoginFacotryList(params) { // è·åéªè¯ç export function getCodeImg() { return request({ url: '/userLoginFacotryList', url: '/captchaImage', headers: { isToken: false }, method: 'get', params: params timeout: 20000 }) } src/api/publicApi/index.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,42 @@ // ææ¡£ç®¡ç import request from '@/utils/request' // /system/user/listAll // æ¥è¯¢ææç¨æ·å表 export function userListAll() { return request({ url: '/system/user/listAll', method: 'get' }) } // /equipmentManagement/equipmentList // æ¥è¯¢è®¾å¤å表 export function getEquipmentList(query) { return request({ url: '/equipmentManagement/equipmentList', method: 'get', params: query }) } // /coalInfo/coalInfoList // æ¥è¯¢ç ¤ç§å表 export function getCoalInfoList(query) { return request({ url: '/coalInfo/coalInfoList', method: 'get', params: query }) } // /coalField/coalFieldList // æ¥è¯¢ç ¤è´¨å段å表 export function getCoalFieldList(query) { return request({ url: '/coalField/coalFieldList', method: 'get', params: query }) } src/components/imageUpload/index.vue
@@ -99,6 +99,7 @@ <script setup> import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'; import { getToken } from "@/utils/auth"; import config from '@/config.js'; // Props å®ä¹ const props = defineProps({ @@ -130,19 +131,10 @@ // 计ç®å±æ§ const uploadFileUrl = computed(() => { // è·ååºç¡APIå°åï¼éé uniappç¯å¢ let baseUrl = ''; // å°è¯å¤ç§æ¹å¼è·åbaseUrl if (process.env.VUE_APP_BASE_API) { baseUrl = process.env.VUE_APP_BASE_API; } else if (process.env.NODE_ENV === 'development') { baseUrl = 'http://192.168.1.147:9036'; } else { baseUrl = 'http://192.168.1.147:9036'; } // è·ååºç¡APIå°åï¼ä¼å ä½¿ç¨ config.js ä¸ç baseUrl const baseUrl = config.baseUrl || process.env.VUE_APP_BASE_API || 'http://192.168.1.147:7016'; const fullUrl = baseUrl + props.action; console.log('ä¸ä¼ URL:', fullUrl); return fullUrl; }); const headers = computed(() => { src/components/imageUpload/viewQrCodeFiles.vue
@@ -2,22 +2,20 @@ <view> <!-- å¼¹çª --> <u-popup v-model="dialogVisitable" :show="dialogVisitable" mode="center" width="90%" height="80%" border-radius="20" @close="cancel" :closeable="true" :customStyle="{ width: '92vw', maxWidth: '720rpx', height: '82vh' }" > <view class="popup-content"> <view class="popup-header"> <text class="popup-title">æ¥çéä»¶</text> <u-icon name="close" size="24" color="#999" @click="cancel" ></u-icon> </view> <view class="upload-container"> @@ -173,6 +171,7 @@ <style scoped lang="scss"> .popup-content { height: 100%; width: 100%; display: flex; flex-direction: column; } src/config.js
@@ -1,10 +1,6 @@ // åºç¨å ¨å±é ç½® const config = { // baseUrl: 'https://vue.ruoyi.vip/prod-api', // baseUrl: 'http://localhost/prod-api', baseUrl: 'http://114.132.189.42:9066', // å®å¤æ¶¦æ³° // baseUrl: 'http://114.132.189.42:9068', // æ°çæµ·å·å¼å¿ // baseUrl: 'http://192.168.1.147:9036', baseUrl: 'http://nj477vg8876.vicp.fun', //cloudåå°ç½å ³å°å // baseUrl: 'http://192.168.10.3:8080', // åºç¨ä¿¡æ¯ src/hooks/useDelete.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,167 @@ /** * éç¨å é¤åè½ç»åå¼å½æ°ï¼APPçæ¬ï¼ * æä¾ç»ä¸çå é¤ç¡®è®¤ãAPIè°ç¨ãæ°æ®æ´æ°é»è¾ï¼éé uni-appåuviewplus */ import { useToast } from "@/utils/uviewplus"; /** * å建å é¤åè½ * @param {Object} options é ç½®é项 * @param {Function|Function} options.deleteApi å é¤API彿°æè¿åAPI彿°ç彿° * @param {Function|Function} options.getList éæ°è·ååè¡¨æ°æ®ç彿°æè¿å彿°ç彿° * @param {Ref} options.selectedRows éä¸è¡çååºå¼å¼ç¨ * @param {Ref} options.tableData è¡¨æ ¼æ°æ®çååºå¼å¼ç¨ * @param {Ref} options.total æ»æ°çååºå¼å¼ç¨ * @param {String} options.confirmText 确认å é¤çæç¤ºææ¬ * @param {String} options.successText å 餿åçæç¤ºææ¬ * @param {Boolean} options.useLocalUpdate æ¯å¦ä½¿ç¨æ¬å°æ´æ°ï¼ä¸éæ°è¯·æ±æ¥å£ï¼ * @returns {Object} è¿åå é¤ç¸å ³çæ¹æ³ */ export function useDelete(options = {}) { const { deleteApi, getList, selectedRows, tableData, total, confirmText = "ç¡®å®å é¤éä¸çæ°æ®åï¼", successText = "å 餿å", useLocalUpdate = false } = options; /** * è·åå®é çå é¤API彿° * æ¯æç´æ¥ä¼ å ¥å½æ°æè¿å彿°ç彿° */ const getDeleteApi = () => { if (typeof deleteApi === 'function') { // å°è¯è°ç¨çæ¯å¦è¿å彿° try { const result = deleteApi(); return typeof result === 'function' ? result : deleteApi; } catch (error) { // 妿è°ç¨åºéï¼è¯´æè¿æ¬èº«å°±æ¯API彿° return deleteApi; } } return deleteApi; }; /** * è·åå®é çè·ååè¡¨å½æ° * æ¯æç´æ¥ä¼ å ¥å½æ°æè¿å彿°ç彿° */ const getListFunction = () => { if (typeof getList === 'function') { try { const result = getList(); return typeof result === 'function' ? result : getList; } catch (error) { // 妿è°ç¨åºéï¼è¯´æè¿æ¬èº«å°±æ¯åè¡¨å½æ° return getList; } } return getList; }; /** * æ¹éå 餿¹æ³ * @param {Array} customIds èªå®ä¹è¦å é¤çIDæ°ç»ï¼å¦æä¸ä¼ å使ç¨selectedRows */ const handleDelete = async (customIds = null) => { // ç¡®å®è¦å é¤çè¡ const rowsToDelete = customIds ? tableData.value.filter(item => customIds.includes(item.id)) : selectedRows.value; // æ£æ¥æ¯å¦æé䏿°æ® if (rowsToDelete.length === 0) { useToast().warning("è¯·éæ©è¦å é¤çæ°æ®"); return false; } try { // 确认å é¤ - 使ç¨uni-appçshowModalæ¿ä»£Element PlusçMessageBox const result = await uni.showModal({ title: 'æç¤º', content: confirmText, confirmText: 'ç¡®å®', cancelText: 'åæ¶', confirmColor: '#2979ff' }); // ç¨æ·ç¹å»åæ¶ if (!result.confirm) { useToast().info("已忶å é¤"); return false; } // æåID const ids = rowsToDelete.map(item => item.id); // è·åå½åçå é¤API彿° const currentDeleteApi = getDeleteApi(); if (!currentDeleteApi) { useToast().error("å é¤APIæªé ç½®"); return false; } // è°ç¨å é¤API const res = await currentDeleteApi(ids); if (res.code === 200) { // æ ¹æ®é ç½®éæ©æ´æ°æ¹å¼ if (useLocalUpdate) { // æ¬å°æ´æ°ï¼ä»è¡¨æ ¼æ°æ®ä¸ç§»é¤å·²å é¤ç项 tableData.value = tableData.value.filter(item => !ids.includes(item.id)); if (total && total.value !== undefined) { total.value = tableData.value.length; } } else { // éæ°è·åæ°æ® const currentGetList = getListFunction(); if (currentGetList) { await currentGetList(); } } // æ¸ ç©ºéä¸ç¶æ if (selectedRows && selectedRows.value) { selectedRows.value = []; } useToast().success(successText); return true; } else { return false; } } catch (error) { if (error !== "cancel") { // å ¶ä»é误å¤ç console.error("å 餿ä½å¤±è´¥:", error); } return false; } }; /** * å é¤åä¸ªé¡¹ç® * @param {Object} row è¦å é¤çè¡æ°æ® */ const handleDeleteSingle = async (row) => { return await handleDelete([row.id]); }; /** * å é¤å¤ä¸ªé¡¹ç®ï¼æ¹éå é¤ï¼ */ const handleDeleteBatch = async () => { return await handleDelete(); }; return { handleDelete, handleDeleteSingle, handleDeleteBatch }; } src/manifest.json
@@ -1,12 +1,16 @@ { "name" : "润泰çç©", "appid" : "__UNI__115F1B1", "name" : "é西æå¾·", "appid" : "__UNI__106D58A", "description" : "", "versionName" : "1.0.0", "versionCode" : "100", "transformPx" : false, /* 5+Appç¹æç¸å ³ */ "app-plus" : { "compatible" : { "usingComponents" : true, "ignoreVersion" : true }, "usingComponents" : true, "nvueStyleCompiler" : "uni-app", "compilerVersion" : 3, src/pages.json
@@ -296,7 +296,7 @@ } }, { "path": "pages/equipmentManagement/ledger/index", "path": "pages/management/index", "style": { "navigationBarTitleText": "设å¤å°è´¦", "navigationStyle": "custom" src/pages/index.vue
@@ -1,156 +1,16 @@ <template> <view class="content"> <view class="header-section"> <view class="currentFactory"> <up-text type="primary" :text="userStore.currentFactoryName" @click="show = true" size="18" class="factoryName" suffixIcon="arrow-right" :iconStyle="iconStyle"></up-text> </view> <up-picker :show="show" :columns="factoryList" @confirm="changeFactory" @cancel="show = false"></up-picker> </view> <view class="hero-section"> <view class="bg-img"> <view class="hero-content"> <text class="hero-title">润泰çç©ç§æ</text> <text class="hero-title">é西æå¾·ç¯ä¿</text> </view> <view class="hero-wave"></view> </view> </view> <!-- <view class="notice-section">--> <!-- <view class="notice">--> <!-- <view class="notice-content">--> <!-- <view class="notice-left">--> <!-- <text class="notice-status">éç¥</text>--> <!-- </view>--> <!-- <view class="notice-separator"></view>--> <!-- <view class="notice-right">--> <!-- <text class="notice-label">{{currentStatus}}</text>--> <!-- <text class="notice-text">彿¥éå®è®¾å¤æ°:<text class="notice-number">{{number}}<text class="notice-unit">个</text></text></text>--> <!-- </view>--> <!-- </view>--> <!-- </view>--> <!-- </view>--> <!-- è¥éç®¡çæ¨¡å --> <view class="common-module marketing-module"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">è¥é管ç</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4" > <up-grid-item v-for="(item, index) in marketingItems" :key="index" @click="handleCommonItemClick(item)" > <view class="icon-container" :style="{ background: item.bgColor }"> <up-icon :name="item.icon" :size="58" color="#ffffff" ></up-icon> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- éè´ç®¡ç模å --> <view class="common-module purchase-module"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">éè´ç®¡ç</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4" > <up-grid-item v-for="(item, index) in purchaseItems" :key="index" @click="handleCommonItemClick(item)" > <view class="icon-container" :style="{ background: item.bgColor }"> <up-icon :name="item.icon" :size="58" color="#ffffff" ></up-icon> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- åååå ¬æ¨¡å --> <view class="common-module collaboration-module"> <view class="module-header"> <view class="module-title-container"> <text class="module-title">åååå ¬</text> </view> </view> <view class="module-content"> <up-grid :border="false" col="4" > <up-grid-item v-for="(item, index) in collaborationItems" :key="index" @click="handleCommonItemClick(item)" > <view class="icon-container" :style="{ background: item.bgColor }"> <up-icon :name="item.icon" :size="58" color="#ffffff" ></up-icon> </view> <text class="item-label">{{item.label}}</text> </up-grid-item> </up-grid> </view> </view> <!-- çäº§ç®¡æ§æ¨¡å --> <!-- <view class="common-module production-module">--> <!-- <view class="module-header">--> <!-- <view class="module-title-container">--> <!-- <text class="module-title">ç产管æ§</text>--> <!-- </view>--> <!-- </view>--> <!-- <view class="module-content">--> <!-- <up-grid--> <!-- :border="false"--> <!-- col="4"--> <!-- >--> <!-- <up-grid-item--> <!-- v-for="(item, index) in productionItems"--> <!-- :key="index"--> <!-- @click="handleCommonItemClick(item)"--> <!-- >--> <!-- <view class="icon-container" :style="{ background: item.bgColor }">--> <!-- <up-icon--> <!-- :name="item.icon"--> <!-- :size="58"--> <!-- color="#ffffff"--> <!-- ></up-icon>--> <!-- </view>--> <!-- <text class="item-label">{{item.label}}</text>--> <!-- </up-grid-item>--> <!-- </up-grid>--> <!-- </view>--> <!-- </view>--> <!-- 设å¤ç®¡ç模å --> <view class="common-module equipment-module"> @@ -186,7 +46,6 @@ <script setup> import {ref, onMounted, nextTick, reactive} from 'vue'; import {userLoginFacotryList} from "@/api/login"; import modal from "@/plugins/modal"; import useUserStore from "@/store/modules/user"; @@ -214,245 +73,25 @@ }, 3000) } // è¥é管çåè½æ°æ® const marketingItems = reactive([ { icon: '/static/images/icon/xiaoshoutaizhang@2x.png', label: 'éå®å°è´¦', }, { icon: '/static/images/icon/kaipiaodengji@2x.png', label: 'å¼ç¥¨ç»è®°', }, { icon: '/static/images/icon/kaipiaotaizhang@2x.png', label: 'å¼ç¥¨å°è´¦', }, { icon: '/static/images/icon/huikuandengji@2x.png', label: '忬¾ç»è®°', }, { icon: '/static/images/icon/huikuanliushui@2x.png', label: '忬¾æµæ°´', }, { icon: '/static/images/icon/kehuwanglai@2x.png', label: '客æ·å¾æ¥', } ]); // éè´ç®¡çåè½æ°æ® const purchaseItems = reactive([ { icon: '/static/images/icon/caigoutaizhang@2x.png', label: 'éè´å°è´¦', }, { icon: '/static/images/icon/laipiaodengji@2x.png', label: 'æ¥ç¥¨ç»è®°', }, { icon: '/static/images/icon/laipiaotaizhang@2x.png', label: 'æ¥ç¥¨å°è´¦', }, { icon: '/static/images/icon/fukuanjingji@2x.png', label: '仿¬¾ç»è®°', }, { icon: '/static/images/icon/fukuanliushui@2x.png', label: '仿¬¾æµæ°´', }, { icon: '/static/images/icon/gongyingshangwanglai@2x.png', label: 'ä¾åºå徿¥', }, ]); // åååå ¬åè½æ°æ® const collaborationItems = reactive([ { icon: '/static/images/icon/xietongshenpi@2x.png', label: 'åå审æ¹', }, { icon: '/static/images/icon/kehubaifang@2x.png', label: 'å®¢æ·æè®¿', } ]); // ç产管æ§åè½æ°æ® const productionItems = reactive([ { icon: '/static/images/icon/shengchandingdan@2x.png', label: 'ç产订å', bgColor: '#FF9800' }, { icon: '/static/images/icon/shengchanpaigong@2x.png', label: 'ç产派工', bgColor: '#FF6B35' }, { icon: '/static/images/icon/shengchanpaichan@2x.png', label: 'å·¥åºæäº§', bgColor: '#E91E63' }, { icon: '/static/images/icon/shengchanbaogong@2x.png', label: 'ç产æ¥å·¥', bgColor: '#673AB7' }, { icon: '/static/images/icon/shengchanhesuan@2x.png', label: 'çäº§æ ¸ç®', bgColor: '#3F51B5' } ]); // 设å¤ç®¡çåè½æ°æ® const equipmentItems = reactive([ // { // icon: '/static/images/icon/shebeitaizhang@2x.png', // label: '设å¤å°è´¦', // }, { icon: '/static/images/icon/shbeibaoxiu@2x.png', label: 'è®¾å¤æ¥ä¿®', }, { icon: '/static/images/icon/shbeibaoyang@2x.png', label: '设å¤ä¿å »', icon: '/static/images/icon/shebeitaizhang@2x.png', label: '设å¤å°è´¦', }, { icon: '/static/images/icon/xunjianshangchuan@2x.png', label: 'å·¡æ£ä¸ä¼ ', }, { icon: '/static/images/icon/guzhangfenxi@2x.png', label: 'åæè¿½æº¯', bgColor: '#ff9800' }, { icon: '/static/images/icon/zhinengpaidan@2x.png', label: 'æºè½æ´¾å', bgColor: '#ff6b35' }, { icon: '/static/images/icon/zuoyezhidao@2x.png', label: 'ä½ä¸æå¯¼', bgColor: '#4caf50' }, { icon: '/static/images/icon/jieguoyanzheng@2x.png', label: 'ç»æéªè¯', bgColor: '#9c27b0' } ]); // å¤ç常ç¨åè½ç¹å» const handleCommonItemClick = (item) => { // æ ¹æ®ä¸åçåè½é¡¹è¿è¡è·³è½¬ switch (item.label) { case 'éå®å°è´¦': uni.navigateTo({ url: '/pages/sales/salesAccount/index' }); break; case 'å¼ç¥¨ç»è®°': uni.navigateTo({ url: '/pages/sales/invoicingRegistration/index' }); break; case 'å¼ç¥¨å°è´¦': uni.navigateTo({ url: '/pages/sales/invoiceLedger/index' }); break; case '忬¾ç»è®°': uni.navigateTo({ url: '/pages/sales/receiptPayment/index' }); break; case '忬¾æµæ°´': uni.navigateTo({ url: '/pages/sales/receiptPaymentHistory/index' }); break; case '客æ·å¾æ¥': uni.navigateTo({ url: '/pages/sales/receiptPaymentLedger/index' }); break; case 'éè´å°è´¦': uni.navigateTo({ url: '/pages/procurementManagement/procurementLedger/index' }); break; case 'æ¥ç¥¨ç»è®°': uni.navigateTo({ url: '/pages/procurementManagement/invoiceEntry/index' }); break; case 'æ¥ç¥¨å°è´¦': uni.navigateTo({ url: '/pages/procurementManagement/procurementInvoiceLedger/index' }); break; case '仿¬¾ç»è®°': uni.navigateTo({ url: '/pages/procurementManagement/paymentEntry/index' }); break; case '仿¬¾æµæ°´': uni.navigateTo({ url: '/pages/procurementManagement/receiptPaymentHistory/index' }); break; case 'ä¾åºå徿¥': uni.navigateTo({ url: '/pages/procurementManagement/paymentLedger/index' }); break; case 'åå审æ¹': uni.navigateTo({ url: '/pages/cooperativeOffice/collaborativeApproval/index' }); break; case 'å®¢æ·æè®¿': uni.navigateTo({ url: '/pages/cooperativeOffice/clientVisit/index' }); break; case 'ç产订å': uni.navigateTo({ url: '/pages/productionManagement/productionOrder/index' }); break; case 'ç产派工': uni.navigateTo({ url: '/pages/productionManagement/productionDispatching/index' }); break; case 'å·¥åºæäº§': uni.navigateTo({ url: '/pages/productionManagement/processScheduling/index' }); break; case 'ç产æ¥å·¥': uni.navigateTo({ url: '/pages/productionManagement/productionReport/index' }); break; case 'çäº§æ ¸ç®': uni.navigateTo({ url: '/pages/productionManagement/productionAccounting/index' }); break; case '设å¤å°è´¦': uni.navigateTo({ url: '/pages/equipmentManagement/ledger/index' url: '/pages/management/index' }); break; case 'è®¾å¤æ¥ä¿®': @@ -470,26 +109,6 @@ url: '/pages/inspectionUpload/index' }); break; case 'åæè¿½æº¯': uni.navigateTo({ url: '/pages/equipmentManagement/faultAnalysis/index' }); break; case 'æºè½æ´¾å': uni.navigateTo({ url: '/pages/equipmentManagement/smartDispatch/index' }); break; case 'ä½ä¸æå¯¼': uni.navigateTo({ url: '/pages/equipmentManagement/sop/index' }); break; case 'ç»æéªè¯': uni.navigateTo({ url: '/pages/equipmentManagement/verification/index' }); break; default: uni.showToast({ title: `ç¹å»äº${item.label}`, @@ -501,43 +120,6 @@ // å建对åç»ä»¶çå¼ç¨ const uToastRef = ref(null); function getUserLoginFacotryList() { userLoginFacotryList({userName: userStore.nickName}).then(res => { // æ£æ¥res.dataæ¯å¦ä¸ºæ°ç» factoryList.value[0] = [] if (res.data && Array.isArray(res.data)) { factoryListTem.value = res.data res.data.forEach(item => { factoryList.value[0].push(item.deptName) }) factoryId.value = userStore.currentDeptId } else { // 妿res.data䏿¯æ°ç»ï¼è®¾ç½®ä¸ºç©ºæ°ç» factoryList.value = [] } }).catch(error => { modal.msgError('è·åå ¬å¸å表失败:', error) factoryList.value = [] }) } const changeFactory = async (arr) => { show.value = false; const factoryId = factoryListTem.value[arr.indexs[0]].deptId const loginForm = { username: userStore.name, password: uni.getStorageSync('remembered_password'), factoryId: factoryId, } modal.loading("å·æ°ä¸ï¼è¯·èå¿çå¾ ...") userStore.loginCheckFactory(loginForm).then(() => { modal.closeLoading() nextTick(() => { loginSuccess() }); }).catch(() => { modal.closeLoading() }) } function loginSuccess(result) { uni.reLaunch({ url: '/pages/index' @@ -554,7 +136,6 @@ onMounted(() => { // è®¾ç½®ç¨æ·ä¿¡æ¯ userStore.getInfo() getUserLoginFacotryList() // å¯å¨éç¥ç¶æå®æ¶å¨ startStatusTimer() }); src/pages/inspectionUpload/components/formDia.vue
@@ -1,6 +1,6 @@ <template> <u-popup v-model="dialogVisitable" :show="dialogVisitable" mode="center" :round="10" :closeable="true" @@ -14,47 +14,68 @@ <view class="upload-container"> <view class="form-container"> <view class="title">ç产å</view> <u-upload :fileList="beforeModelValue" @afterRead="afterRead" @delete="deleteFile" name="before" multiple :maxCount="10" :maxSize="1024 * 1024" accept="video/*" :previewFullImage="true" ></u-upload> <view class="upload-buttons"> <u-button type="primary" size="small" @click="chooseVideo('before')" :disabled="beforeModelValue.length >= 10"> <u-icon name="video" size="16" color="#fff" style="margin-right: 5px;"></u-icon> éæ©è§é¢ </u-button> <text class="file-count">{{ beforeModelValue.length }}/10</text> </view> <view class="file-list" v-if="beforeModelValue.length > 0"> <view v-for="(file, index) in beforeModelValue" :key="index" class="file-item"> <text class="file-name">è§é¢{{ index + 1 }}</text> <u-button type="error" size="mini" :customStyle="{ minWidth: '120rpx', width: 'auto', padding: '0 20rpx', height: '60rpx' }" @click="deleteFile(index, 'before')" >å é¤</u-button> </view> </view> </view> <view class="form-container"> <view class="title">ç产å</view> <u-upload :fileList="afterModelValue" @afterRead="afterRead" @delete="deleteFile" name="after" multiple :maxCount="10" :maxSize="1024 * 1024" accept="video/*" :previewFullImage="true" ></u-upload> <view class="upload-buttons"> <u-button type="primary" size="small" @click="chooseVideo('after')" :disabled="afterModelValue.length >= 10"> <u-icon name="video" size="16" color="#fff" style="margin-right: 5px;"></u-icon> éæ©è§é¢ </u-button> <text class="file-count">{{ afterModelValue.length }}/10</text> </view> <view class="file-list" v-if="afterModelValue.length > 0"> <view v-for="(file, index) in afterModelValue" :key="index" class="file-item"> <text class="file-name">è§é¢{{ index + 1 }}</text> <u-button type="error" size="mini" :customStyle="{ minWidth: '120rpx', width: 'auto', padding: '0 20rpx', height: '60rpx' }" @click="deleteFile(index, 'after')" >å é¤</u-button> </view> </view> </view> <view class="form-container"> <view class="title">ç产é®é¢</view> <u-upload :fileList="issueModelValue" @afterRead="afterRead" @delete="deleteFile" name="issue" multiple :maxCount="10" :maxSize="1024 * 1024" accept="video/*" :previewFullImage="true" ></u-upload> <view class="upload-buttons"> <u-button type="primary" size="small" @click="chooseVideo('issue')" :disabled="issueModelValue.length >= 10"> <u-icon name="video" size="16" color="#fff" style="margin-right: 5px;"></u-icon> éæ©è§é¢ </u-button> <text class="file-count">{{ issueModelValue.length }}/10</text> </view> <view class="file-list" v-if="issueModelValue.length > 0"> <view v-for="(file, index) in issueModelValue" :key="index" class="file-item"> <text class="file-name">è§é¢{{ index + 1 }}</text> <u-button type="error" size="mini" :customStyle="{ minWidth: '120rpx', width: 'auto', padding: '0 20rpx', height: '60rpx' }" @click="deleteFile(index, 'issue')" >å é¤</u-button> </view> </view> </view> </view> @@ -67,10 +88,23 @@ </template> <script setup> import { ref } from 'vue' import { submitInspectionRecord } from '@/api/equipmentManagement/inspection.js' import { ref, computed } from 'vue' import {submitInspectionRecord, uploadInspectionTask} from '@/api/equipmentManagement/inspection.js' import { getToken } from '@/utils/auth' import config from '@/config.js' const emit = defineEmits(['closeDia']) // ä¸ä¼ æ¥å£URL const uploadFileUrl = computed(() => { return config.baseUrl + '/common/minioUploads' }) // ä¸ä¼ 请æ±å¤´ const uploadHeaders = computed(() => { const token = getToken() return token ? { Authorization: 'Bearer ' + token } : {} }) const dialogVisitable = ref(false) const beforeModelValue = ref([]) @@ -78,63 +112,171 @@ const issueModelValue = ref([]) const infoData = ref(null) // æä»¶ä¸ä¼ å¤ç const afterRead = (event) => { const { name, file } = event // éæ©è§é¢ const chooseVideo = (type) => { const currentList = type === 'before' ? beforeModelValue : type === 'after' ? afterModelValue : issueModelValue; // ä¸ä¼ æä»¶å°æå¡å¨ uni.uploadFile({ url: '/api/upload', // æ¿æ¢ä¸ºå®é çä¸ä¼ æ¥å£ filePath: file.url, name: 'file', if (currentList.value.length >= 10) { uni.showToast({ title: 'æå¤åªè½éæ©10个è§é¢', icon: 'none' }); return; } uni.chooseVideo({ sourceType: ['camera', 'album'], maxDuration: 60, camera: 'back', success: (res) => { const data = JSON.parse(res.data) if (data.code === 200) { try { if (!res.tempFilePath) { throw new Error('æªè·åå°è§é¢æä»¶'); } // ä»ä¸´æ¶è·¯å¾ä¸æåæä»¶åï¼å为åå§å±ç¤º const tempName = res.tempFilePath.split('/').pop() || `video_${Date.now()}.mp4` const fileItem = { url: data.data.url, name: file.name, status: 'success' } url: res.tempFilePath, name: tempName, originalFilename: tempName, size: res.size || 0, duration: res.duration || 0, status: 'pending' // å¾ ä¸ä¼ ç¶æ }; // æ ¹æ®nameæ·»å å°å¯¹åºçæ°ç» if (name === 'before') { beforeModelValue.value.push(fileItem) } else if (name === 'after') { afterModelValue.value.push(fileItem) } else if (name === 'issue') { issueModelValue.value.push(fileItem) } // æ·»å å°å¯¹åºçæ°ç» currentList.value.push(fileItem); // èªå¨ä¸ä¼ uploadFile(fileItem, type, currentList.value.length - 1); } catch (error) { console.error('å¤çè§é¢å¤±è´¥:', error); uni.showToast({ title: 'ä¸ä¼ æå', icon: 'success' }) } else { uni.showToast({ title: 'ä¸ä¼ 失败', title: 'å¤çè§é¢å¤±è´¥', icon: 'error' }) }); } }, fail: () => { fail: (err) => { console.error('éæ©è§é¢å¤±è´¥:', err); uni.showToast({ title: 'ä¸ä¼ 失败', title: 'éæ©è§é¢å¤±è´¥: ' + (err.errMsg || 'æªç¥é误'), icon: 'error' }) }); } }) }); } // ä¸ä¼ æä»¶å°æå¡å¨ const uploadFile = (fileItem, type, index) => { fileItem.status = 'uploading'; // ç¡®ä¿tokenåå¨ const token = getToken(); if (!token) { fileItem.status = 'failed'; uni.showToast({ title: 'ç¨æ·æªç»å½', icon: 'error' }); return; } uni.uploadFile({ url: uploadFileUrl.value, filePath: fileItem.url, name: 'files', // 注æï¼æ¥å£ä½¿ç¨çæ¯ 'files' è䏿¯ 'file' header: { 'Authorization': `Bearer ${token}` }, success: (res) => { try { if (res.statusCode === 200) { const response = typeof res.data === 'string' ? JSON.parse(res.data) : res.data; if (response.code === 200) { // ååºæ°æ®æ¯æ°ç»æ ¼å¼ï¼å第ä¸ä¸ªå ç´ const uploadedFile = Array.isArray(response.data) ? response.data[0] : response.data; console.log('ä¸ä¼ æåï¼è¿åçæä»¶ä¿¡æ¯:', uploadedFile); console.log('åå§æä»¶å:', uploadedFile.originalFilename); // æ´æ°æä»¶ä¿¡æ¯ - ä½¿ç¨ Object.assign ç¡®ä¿ååºå¼æ´æ° const displayName = uploadedFile.originalFilename || uploadedFile.bucketFilename || fileItem.name || 'æªå½åæä»¶'; Object.assign(fileItem, { url: uploadedFile.url || uploadedFile.downloadUrl, status: 'success', id: uploadedFile.id, name: displayName, originalFilename: uploadedFile.originalFilename || displayName, // ä¿ååå§æä»¶å bucketFilename: uploadedFile.bucketFilename, downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, size: uploadedFile.byteSize || fileItem.size, createTime: uploadedFile.createTime || new Date().getTime() }); console.log('æ´æ°åçæä»¶é¡¹:', fileItem); uni.showToast({ title: 'ä¸ä¼ æå', icon: 'success' }); } else { fileItem.status = 'failed'; uni.showToast({ title: response.msg || 'ä¸ä¼ 失败', icon: 'error' }); } } else { fileItem.status = 'failed'; uni.showToast({ title: `æå¡å¨é误ï¼ç¶æç : ${res.statusCode}`, icon: 'error' }); } } catch (e) { fileItem.status = 'failed'; console.error('è§£æååºå¤±è´¥:', e); console.error('åå§ååºæ°æ®:', res.data); uni.showToast({ title: 'è§£æååºå¤±è´¥', icon: 'error' }); } }, fail: (err) => { fileItem.status = 'failed'; console.error('ä¸ä¼ 失败:', err); let errorMessage = 'ä¸ä¼ 失败'; if (err.errMsg) { if (err.errMsg.includes('statusCode: null')) { errorMessage = 'ç½ç»è¿æ¥å¤±è´¥ï¼è¯·æ£æ¥ç½ç»è®¾ç½®'; } else if (err.errMsg.includes('timeout')) { errorMessage = 'ä¸ä¼ è¶ æ¶ï¼è¯·éè¯'; } else { errorMessage = err.errMsg; } } uni.showToast({ title: errorMessage, icon: 'error' }); } }); } // å 餿件 const deleteFile = (event) => { const { name, index } = event if (name === 'before') { beforeModelValue.value.splice(index, 1) } else if (name === 'after') { afterModelValue.value.splice(index, 1) } else if (name === 'issue') { issueModelValue.value.splice(index, 1) const deleteFile = (index, type) => { if (type === 'before') { beforeModelValue.value.splice(index, 1); } else if (type === 'after') { afterModelValue.value.splice(index, 1); } else if (type === 'issue') { issueModelValue.value.splice(index, 1); } } @@ -154,7 +296,7 @@ // æäº¤æ°æ® infoData.value.storageBlobDTO = arr await submitInspectionRecord({ ...infoData.value }) await uploadInspectionTask({ ...infoData.value }) uni.showToast({ title: 'æäº¤æå', @@ -253,4 +395,42 @@ border-top: 1px solid #f0f0f0; background-color: #fafafa; } .upload-buttons { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } .file-count { font-size: 12px; color: #999; } .file-list { margin-top: 10px; } .file-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f0f0; } .file-item:last-child { border-bottom: none; } .file-name { font-size: 14px; color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-right: 10px; } </style> src/pages/inspectionUpload/index.vue
@@ -1,2080 +1,430 @@ <template> <view class="inspection-upload-page"> <!-- 页é¢å¤´é¨ --> <PageHeader title="å·¡æ£ä¸ä¼ " @back="goBack"/> <!-- æ°æ®å表 --> <view class="table-section"> <!-- ç产巡æ£å表 --> <view class="task-list"> <view v-for="(item, index) in taskTableData" :key="index" class="task-item" > <view class="task-header"> <view class="task-info"> <text class="task-name">{{ item.taskName }}</text> <text class="task-location">{{ item.inspectionLocation }}</text> </view> <view class="task-actions"> <u-button type="primary" size="small" @click.stop="startScanForTask(item)" :customStyle="{ borderRadius: '15px', height: '30px', fontSize: '12px', marginRight: '8px' }" > æ«ç ä¸ä¼ </u-button> <u-button type="success" size="small" @click.stop="viewAttachments(item)" :customStyle="{ borderRadius: '15px', height: '30px', fontSize: '12px' }" > æ¥çéä»¶ </u-button> </view> </view> <view class="task-details"> <view class="detail-item"> <text class="detail-label">ä»»å¡ID</text> <text class="detail-value">{{ item.taskId || item.id }}</text> </view> <view class="detail-item"> <text class="detail-label">夿³¨</text> <text class="detail-value">{{ item.remarks || 'æ ' }}</text> </view> <view class="detail-item"> <text class="detail-label">æ§è¡äºº</text> <text class="detail-value">{{ item.inspector }}</text> </view> </view> </view> </view> <!-- ç©ºç¶æ --> <view v-if="taskTableData.length === 0" class="no-data"> <text>ææ æ°æ®</text> </view> </view> <!-- æ«ç åºå - å ¨å±å¼¹çª --> <view v-if="isScanning" class="qr-scan-overlay"> <view class="qr-scan-container"> <view class="scan-header"> <text class="scan-title">æ«æäºç»´ç </text> <u-button type="error" size="small" @click.stop="stopScan" :customStyle="{ borderRadius: '15px', height: '30px', fontSize: '12px' }" > å ³é </u-button> </view> <camera class="qr-camera" device-position="back" flash="off" @scancode="handleScanCode" @error="handleCameraError" ></camera> <view class="scan-frame-wrapper"> <view class="scan-frame"></view> <view class="scan-tip">请å°äºç»´ç æ¾å ¥æ¡å </view> </view> <u-alert v-if="cameraError" :title="cameraError" type="error" :showIcon="true" :closable="true" @close="cameraError = ''" :customStyle="{ margin: '10px 0' }" ></u-alert> </view> </view> <!-- å¾çä¸ä¼ å¼¹çª - åçå®ç° --> <view v-if="showUploadDialog" class="custom-modal-overlay" @click="closeUploadDialog"> <view class="custom-modal-container" @click.stop> <view class="upload-popup-content"> <view class="upload-popup-header"> <text class="upload-popup-title">ä¸ä¼ å·¡æ£è®°å½</text> </view> <view class="upload-popup-body"> <!-- åç±»æ ç¾é¡µ --> <view class="upload-tabs"> <view class="tab-item" :class="{ active: currentUploadType === 'before' }" @click="switchUploadType('before')" > ç产å </view> <view class="tab-item" :class="{ active: currentUploadType === 'after' }" @click="switchUploadType('after')" > çäº§ä¸ </view> <view class="tab-item" :class="{ active: currentUploadType === 'issue' }" @click="switchUploadType('issue')" > ç产å </view> </view> <!-- å¼å¸¸ç¶æéæ© --> <view class="exception-section"> <text class="section-title">æ¯å¦åå¨å¼å¸¸ï¼</text> <view class="exception-options"> <view class="exception-option" :class="{ active: hasException === false }" @click="setExceptionStatus(false)" > <u-icon name="checkmark-circle" size="20" color="#52c41a"></u-icon> <text>æ£å¸¸</text> </view> <view class="exception-option" :class="{ active: hasException === true }" @click="setExceptionStatus(true)" > <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> <text>åå¨å¼å¸¸</text> </view> </view> </view> <!-- å½ååç±»çä¸ä¼ åºå --> <view class="simple-upload-area"> <view class="upload-buttons"> <u-button type="primary" @click="chooseImage" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ marginRight: '10px', flex: 1 }" > <u-icon name="camera" size="18" color="#fff" style="margin-right: 5px;"></u-icon> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç §' }} </u-button> <u-button type="success" @click="chooseVideo" :loading="uploading" :disabled="getCurrentFiles().length >= uploadConfig.limit" :customStyle="{ flex: 1 }" > <u-icon name="video" size="18" color="#fff" style="margin-right: 5px;"></u-icon> {{ uploading ? 'ä¸ä¼ ä¸...' : 'æè§é¢' }} </u-button> </view> <!-- ä¸ä¼ è¿åº¦ --> <view v-if="uploading" class="upload-progress"> <u-line-progress :percentage="uploadProgress" :showText="true" activeColor="#409eff" ></u-line-progress> </view> <!-- å½ååç±»çæä»¶å表 --> <view v-if="getCurrentFiles().length > 0" class="file-list"> <view v-for="(file, index) in getCurrentFiles()" :key="index" class="file-item" > <view class="file-preview-container"> <image v-if="file.type === 'image'" :src="file.url || file.tempFilePath || file.path" class="file-preview" mode="aspectFill" /> <view v-else class="video-preview"> <u-icon name="video" size="24" color="#409eff"></u-icon> <text class="video-text">è§é¢</text> </view> <!-- å é¤æé® --> <view class="delete-btn" @click="removeFile(index)"> <u-icon name="close" size="12" color="#fff"></u-icon> </view> </view> <view class="file-info"> <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? 'å¾ç' : 'è§é¢') }}</text> <text class="file-size">{{ formatFileSize(file.size) }}</text> </view> </view> </view> <view v-if="getCurrentFiles().length === 0" class="empty-state"> <text>è¯·éæ©è¦ä¸ä¼ ç{{ getUploadTypeText() }}å¾çæè§é¢</text> </view> <!-- ç»è®¡ä¿¡æ¯ --> <view class="upload-summary"> <text class="summary-text"> ç产å: {{ beforeModelValue.length }}个æä»¶ | ç产ä¸: {{ afterModelValue.length }}个æä»¶ | ç产å: {{ issueModelValue.length }}个æä»¶ </text> </view> </view> </view> <view class="upload-popup-footer"> <u-button @click="closeUploadDialog" :customStyle="{ marginRight: '10px' }">åæ¶</u-button> <u-button v-if="hasException === true" type="warning" @click="goToRepair" :customStyle="{ marginRight: '10px' }" > æ°å¢æ¥ä¿® </u-button> <u-button type="primary" @click="submitUpload">æäº¤</u-button> </view> </view> </view> </view> <!-- æ¥çéä»¶å¼¹çª --> <view v-if="showAttachmentDialog" class="custom-modal-overlay" @click="closeAttachmentDialog"> <view class="custom-modal-container" @click.stop> <view class="attachment-popup-content"> <view class="attachment-popup-header"> <text class="attachment-popup-title">æ¥çéä»¶ - {{ currentViewTask?.taskName }}</text> <view class="close-btn-attachment" @click="closeAttachmentDialog"> <u-icon name="close" size="16" color="#666"></u-icon> </view> </view> <view class="attachment-popup-body"> <!-- åç±»æ ç¾é¡µ --> <view class="attachment-tabs"> <view class="tab-item" :class="{ active: currentViewType === 'before' }" @click="switchViewType('before')" > ç产å ({{ getAttachmentsByType(0).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'after' }" @click="switchViewType('after')" > çäº§ä¸ ({{ getAttachmentsByType(1).length }}) </view> <view class="tab-item" :class="{ active: currentViewType === 'issue' }" @click="switchViewType('issue')" > ç产å ({{ getAttachmentsByType(2).length }}) </view> </view> <!-- å½ååç±»çéä»¶å表 --> <view class="attachment-content"> <view v-if="getCurrentViewAttachments().length > 0" class="attachment-list"> <view v-for="(file, index) in getCurrentViewAttachments()" :key="index" class="attachment-item" @click="previewAttachment(file)" > <view class="attachment-preview-container"> <image v-if="file.type === 'image' || isImageFile(file)" :src="file.url || file.downloadUrl" class="attachment-preview" mode="aspectFill" /> <view v-else class="attachment-video-preview"> <u-icon name="video" size="24" color="#409eff"></u-icon> <text class="video-text">è§é¢</text> </view> </view> <view class="attachment-info"> <text class="attachment-name">{{ file.originalFilename || file.bucketFilename || file.name || 'éä»¶' }}</text> <text class="attachment-size">{{ formatFileSize(file.byteSize || file.size) }}</text> </view> </view> </view> <view v-else class="attachment-empty"> <text>该åç±»ææ éä»¶</text> </view> </view> </view> </view> </view> </view> <!-- è§é¢é¢è§å¼¹çª --> <view v-if="showVideoDialog" class="video-modal-overlay" @click="closeVideoPreview"> <view class="video-modal-container" @click.stop> <view class="video-modal-header"> <text class="video-modal-title">{{ currentVideoFile?.originalFilename || 'è§é¢é¢è§' }}</text> <view class="close-btn-video" @click="closeVideoPreview"> <u-icon name="close" size="16" color="#fff"></u-icon> </view> </view> <view class="video-modal-body"> <video v-if="currentVideoFile" :src="currentVideoFile.url || currentVideoFile.downloadUrl" class="video-player" controls autoplay @error="handleVideoError" ></video> </view> </view> </view> </view> <view class="app-container"> <!-- 页é¢å¤´é¨ --> <PageHeader title="å·¡æ£ä¸ä¼ " @back="goBack"/> <view class="card-container"> <!-- æ ç¾é¡µ --> <u-tabs :list="tabs" :current="activeTabIndex" @change="handleTabChange" :scrollable="false" lineWidth="30" lineColor="#2979ff" :activeStyle="{ color: '#2979ff', fontWeight: 'bold' }" /> <view> <!-- æ«ç 模å --> <view v-if="activeTab === 'qrCode'" class="scan-section"> <view class="scan-controls"> <u-button type="primary" :loading="scanLoading" @click="toggleScan" > {{ scanButtonText }} </u-button> </view> <!-- ç¶ææç¤º --> <view class="status-info"> <u-alert v-if="cameraError" :title="cameraError" type="error" :show-icon="true" :closable="true" @close="cameraError = null" /> <view v-if="isScanning" class="scanning-text"> <u-loading-icon mode="spinner" :color="statusColor" /> <text>æ£å¨æ«æäºç»´ç ...</text> </view> </view> </view> <view> <!-- å è½½ç¶æ --> <view v-if="tableLoading" class="loading-container"> <u-loading-icon text="å è½½ä¸..." /> </view> <!-- ç产巡æ£å表 --> <view v-else-if="activeTab !== 'qrCode' && tableData.length > 0" class="list-container"> <view v-for="(item, index) in tableData" :key="index" class="list-item"> <view class="item-content"> <view class="item-row"> <text class="item-label">å·¡æ£ä»»å¡åç§°ï¼</text> <text class="item-value">{{ item.taskName || '-' }}</text> </view> <view class="item-row"> <text class="item-label">å°ç¹ï¼</text> <text class="item-value">{{ item.inspectionLocation || '-' }}</text> </view> <view class="item-row"> <text class="item-label">夿³¨ï¼</text> <text class="item-value">{{ item.remarks || '-' }}</text> </view> <view class="item-row"> <text class="item-label">æ§è¡å·¡æ£äººï¼</text> <text class="item-value">{{ item.inspector || '-' }}</text> </view> </view> <view class="item-actions"> <u-button type="primary" size="small" @click="handleAdd(item)">ä¸ä¼ </u-button> </view> </view> </view> <!-- ç°åºå·¡æ£å表 --> <view v-else-if="activeTab === 'qrCode' && tableData.length > 0" class="list-container"> <view v-for="(item, index) in tableData" :key="index" class="list-item"> <view class="item-content"> <view class="item-row"> <text class="item-label">设å¤åç§°ï¼</text> <text class="item-value">{{ item.deviceName || '-' }}</text> </view> <view class="item-row"> <text class="item-label">æå¨ä½ç½®æè¿°ï¼</text> <text class="item-value">{{ item.location || '-' }}</text> </view> <view class="item-row"> <text class="item-label">å·¡æ£äººï¼</text> <text class="item-value">{{ item.scanner || '-' }}</text> </view> <view class="item-row"> <text class="item-label">å·¡æ£æ¶é´ï¼</text> <text class="item-value">{{ item.scanTime || '-' }}</text> </view> </view> <view class="item-actions"> <u-button type="primary" size="small" @click="viewFile(item)">æ¥çéä»¶</u-button> </view> </view> </view> <!-- ç©ºæ°æ® --> <view v-else-if="!tableLoading && tableData.length === 0" class="no-data"> <text>ææ æ°æ®</text> </view> </view> </view> </view> <form-dia ref="formDia" @closeDia="handleQuery"></form-dia> <qr-code-form-dia ref="qrCodeFormDia" @closeDia="handleQuery"></qr-code-form-dia> <view-qr-code-files ref="viewQrCodeFiles"></view-qr-code-files> </view> </template> <script setup> import { onMounted, onUnmounted, ref, nextTick, computed } from 'vue' import { onShow } from '@dcloudio/uni-app' import PageHeader from '@/components/PageHeader.vue' import { getLedgerById } from '@/api/equipmentManagement/ledger.js' import {inspectionTaskList, uploadInspectionTask} from "@/api/inspectionManagement"; import { getToken } from "@/utils/auth"; import { onMounted, ref, reactive, computed, nextTick } from "vue"; import FormDia from "@/pages/inspectionUpload/components/formDia.vue"; import { useToast } from '@/utils/uviewplus'; import QrCodeFormDia from "@/pages/inspectionUpload/components/qrCodeFormDia.vue"; import { qrCodeList, qrCodeScanRecordList } from "@/api/inspectionUpload/index.js"; import { inspectionTaskList } from "@/api/inspectionManagement/index.js"; import ViewQrCodeFiles from "@/components/imageUpload/viewQrCodeFiles.vue"; // ç»ä»¶å¼ç¨å·²ç§»é¤ const { showToast } = useToast(); // å è½½æç¤ºæ¹æ³ const showLoadingToast = (message) => { uni.showLoading({ title: message, mask: true }) } const closeToast = () => { uni.hideLoading() } const formDia = ref(); const qrCodeFormDia = ref(); const viewQrCodeFiles = ref(); // å½åæ ç¾ const activeTab = ref("task"); const activeTabIndex = ref(0); const tabName = ref("task"); // æ ç¾é¡µæ°æ® const tabs = reactive([ { name: "ç产巡æ£", value: "task" }, { name: "ç°åºå·¡æ£", value: "qrCode" }, ]); // è¡¨æ ¼æ°æ® const taskTableData = ref([]) // çäº§å·¡æ£æ°æ® // å½åæ«æçä»»å¡ const currentScanningTask = ref(null) const infoData = ref(null); // ä¸ä¼ ç¸å ³ç¶æ const showUploadDialog = ref(false) const uploadFiles = ref([]) // ä¿çç¨äºå ¼å®¹æ§ const uploadStatusType = ref(0) const uploading = ref(false) const uploadProgress = ref(0) const number = ref(0) const uploadList = ref([]) // ä¸ä¸ªåç±»çä¸ä¼ ç¶æ const beforeModelValue = ref([]) // ç产å const afterModelValue = ref([]) // çäº§ä¸ const issueModelValue = ref([]) // ç产å // å½åæ¿æ´»çä¸ä¼ ç±»å const currentUploadType = ref('before') // 'before', 'after', 'issue' // æ¥çéä»¶ç¸å ³ç¶æ const showAttachmentDialog = ref(false) const currentViewTask = ref(null) const currentViewType = ref('before') // 'before', 'after', 'issue' const attachmentList = ref([]) // å½åæ¥çä»»å¡çéä»¶å表 // è§é¢é¢è§ç¸å ³ç¶æ const showVideoDialog = ref(false) const currentVideoFile = ref(null) // å¼å¸¸ç¶æ const hasException = ref(null) // null: æªéæ©, true: åå¨å¼å¸¸, false: æ£å¸¸ // ä¸ä¼ é ç½® const uploadConfig = { action: "/common/minioUploads", limit: 10, fileSize: 50, // MB fileType: ['jpg', 'jpeg', 'png', 'mp4', 'mov'], maxVideoDuration: 60 // ç§ } // 计ç®ä¸ä¼ URL const uploadFileUrl = computed(() => { let baseUrl = ''; if (process.env.VUE_APP_BASE_API) { baseUrl = process.env.VUE_APP_BASE_API; } else if (process.env.NODE_ENV === 'development') { baseUrl = 'http://192.168.1.147:9036'; } else { baseUrl = 'http://192.168.1.147:9036'; } return baseUrl + uploadConfig.action; }) // 计ç®è¯·æ±å¤´ const headers = computed(() => { const token = getToken(); return token ? { Authorization: "Bearer " + token } : {}; }) // 请æ±åæ¶æ å¿ï¼ç¨äºåæ¶æ£å¨è¿è¡çè¯·æ± let isRequestCancelled = false const tableData = ref([]); const tableLoading = ref(false); // æ«ç ç¸å ³ç¶æ const isScanning = ref(false) const cameraError = ref('') const isScanning = ref(false); const scanLoading = ref(false); const cameraError = ref(null); // çå½å¨æ onMounted(() => { // å»¶è¿åå§åï¼ç¡®ä¿DOM已渲æ nextTick(() => { getList() }) }) const statusColor = computed(() => { return isScanning.value ? '#67C23A' : '#F56C6C'; }); onShow(() => { // 页颿¾ç¤ºæ¶å·æ°æ°æ® getList() }) // çå½å¨æç®¡ç onMounted(async () => { handleTabChange({ index: 0 }); }); // ç»ä»¶éæ¯æ¶çæ¸ ç onUnmounted(() => { // è®¾ç½®åæ¶æ å¿ï¼é»æ¢åç»ç弿¥æä½ isRequestCancelled = true // 忢æ«ç if (isScanning.value) { isScanning.value = false } // å ³éä¸ä¼ å¼¹çª if (showUploadDialog.value) { showUploadDialog.value = false } }) // æ ç¾é¡µåæ¢ const handleTabChange = (e) => { const index = typeof e === 'object' && e.index !== undefined ? e.index : e; const selectedTab = tabs[index]; if (selectedTab) { activeTab.value = selectedTab.value; activeTabIndex.value = index; tabName.value = selectedTab.value; tableData.value = []; getList(); } }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack() } // æ¥è¯¢æ°æ® // ç¹å»æ¥è¯¢ const handleQuery = () => { getList() } getList(); }; // è·ååè¡¨æ°æ® const getList = () => { // æ¾ç¤ºå è½½æç¤º showLoadingToast('å è½½ä¸...') // è®¾ç½®åæ¶æ å¿ isRequestCancelled = false inspectionTaskList({}).then(res => { // æ£æ¥ç»ä»¶æ¯å¦è¿åå¨ä¸è¯·æ±æªè¢«åæ¶ if (!isRequestCancelled) { // å¤çä¸åçæ°æ®ç»æ let records = []; if (res && res.data) { // å°è¯å¤ç§å¯è½çæ°æ®ç»æ if (Array.isArray(res.data.records)) { records = res.data.records; } else if (Array.isArray(res.data.rows)) { records = res.data.rows; } else if (Array.isArray(res.data)) { records = res.data; } else if (Array.isArray(res.data.list)) { records = res.data.list; } } if (records.length > 0) { taskTableData.value = records; } else { taskTableData.value = []; uni.showToast({ title: 'ææ å·¡æ£ä»»å¡æ°æ®', icon: 'none' }); } } // å ³éå è½½æç¤º closeToast() }).catch(err => { // æ£æ¥ç»ä»¶æ¯å¦è¿åå¨ä¸è¯·æ±æªè¢«åæ¶ if (!isRequestCancelled) { taskTableData.value = []; // æ·»å é误æç¤º uni.showToast({ title: 'è·åæ°æ®å¤±è´¥', icon: 'error' }) } // å ³éå è½½æç¤º closeToast() }) } tableLoading.value = true; if (tabName.value === "task") { inspectionTaskList({ size: -1, current: -1 }).then(res => { tableLoading.value = false; tableData.value = res.data.records || []; }).catch(err => { tableLoading.value = false; showToast('è·åæ°æ®å¤±è´¥', 'error'); }); } else { qrCodeScanRecordList({ size: -1, current: -1 }).then(res => { tableLoading.value = false; // å¤çæ°æ®ï¼æ ¼å¼ååæ®µ tableData.value = (res.data.records || []).map(item => ({ ...item, deviceName: item.qrCode?.deviceName || '-', location: item.qrCode?.location || '-' })); }).catch(err => { tableLoading.value = false; showToast('è·åæ°æ®å¤±è´¥', 'error'); }); } }; // 为æå®ä»»å¡å¼å§æ«ç const startScanForTask = async (task) => { try { // è®°å½å½åæ«æçä»»å¡ currentScanningTask.value = task // æ¾ç¤ºæ«æçé¢ isScanning.value = true // 使ç¨uniappçæ«ç API uni.scanCode({ success: (res) => { handleScanSuccess(res) }, fail: (err) => { uni.showToast({ title: 'æ«ç 失败', icon: 'error' }) // å ³éæ«æçé¢ isScanning.value = false } }) } catch (e) { uni.showToast({ title: 'å¯å¨æ«ç 失败', icon: 'error' }) isScanning.value = false } } // ä¸ä¼ const handleAdd = (row) => { console.log('handleAdd 被è°ç¨', row); console.log('formDia.value:', formDia.value); if (!formDia.value) { showToast('ç»ä»¶æªåå§å', 'error'); return; } nextTick(() => { if (formDia.value && formDia.value.openDialog) { formDia.value.openDialog(row); } else { console.error('formDia.value.openDialog ä¸åå¨'); showToast('æå¼å¼¹çªå¤±è´¥', 'error'); } }); }; // æ¥çéä»¶ const viewFile = (row) => { nextTick(() => { viewQrCodeFiles.value?.openDialog(row); }); }; // æ«ç æé®ææ¬ const scanButtonText = computed(() => { if (scanLoading.value) return 'æ£å¨åå§å...'; return isScanning.value ? '忢æ«ç ' : 'å¼å§æ«ç '; }); // 忢æ«ç ç¶æ const toggleScan = async () => { if (isScanning.value) { stopScan(); } else { startScan(); } }; // å¼å§æ«ç const startScan = () => { if (isScanning.value) { showToast('æ£å¨æ«æä¸ï¼è¯·ç¨å...', 'warning'); return; } scanLoading.value = true; // è°ç¨uni-appçæ«ç API uni.scanCode({ scanType: ['qrCode', 'barCode'], success: (res) => { scanLoading.value = false; handleScanSuccess(res.result); }, fail: (err) => { scanLoading.value = false; console.error('æ«ç 失败:', err); cameraError.value = 'æ«ç 失败ï¼è¯·éè¯'; setTimeout(() => { cameraError.value = null; }, 3000); } }); }; // æ«ææåå¤ç const handleScanSuccess = async (result) => { try { if (!result) { showToast('æ«ç ç»æä¸ºç©º', 'warning'); return; } showToast('è¯å«æå', 'success'); // è§£æäºç»´ç æ°æ® let qrData; try { qrData = JSON.parse(result); } catch (e) { // 妿䏿¯JSONæ ¼å¼ï¼ç´æ¥ä½¿ç¨åå§æ°æ® qrData = { data: result }; } callBackendAPI(qrData); } catch (error) { showToast(error.message || 'å¤çæ«ç ç»æå¤±è´¥', 'error'); } }; // è°ç¨å端API const callBackendAPI = (result) => { nextTick(() => { qrCodeFormDia.value?.openDialog(result); }); }; // 忢æ«ç const stopScan = () => { isScanning.value = false currentScanningTask.value = null } // æ«ç æåå¤ç const handleScanSuccess = async (result) => { try { // è§£æäºç»´ç æ°æ®ï¼æådeviceId let deviceId = '' // æ£æ¥æ¯å¦æ¯URLæ ¼å¼ if (result.result.includes('deviceId=')) { // ä»URL䏿ådeviceId const url = result.result const match = url.match(/deviceId=(\d+)/) if (match && match[1]) { deviceId = match[1] } } else { // å°è¯è§£æJSONæ ¼å¼ try { const qrData = JSON.parse(result.result) deviceId = qrData.deviceId || qrData.qrCodeId || '' } catch (e) { // 妿䏿¯JSONæ ¼å¼ï¼ç´æ¥ä½¿ç¨ç»æ deviceId = result.result } } if (!deviceId) { uni.showToast({ title: 'æªè¯å«å°è®¾å¤ID', icon: 'error' }) isScanning.value = false return } // è·åå½åä»»å¡çtaskId const currentTaskId = currentScanningTask.value?.taskId || currentScanningTask.value?.id // 对æ¯deviceIdåtaskId if (deviceId === currentTaskId.toString()) { uni.showToast({ title: 'è¯å«æå', icon: 'success' }) // å å ³éæ«æçé¢ isScanning.value = false // å»¶è¿æå¼ä¸ä¼ å¼¹çªï¼ç¡®ä¿æ«æçé¢å®å ¨å ³é setTimeout(() => { openUploadDialog(currentScanningTask.value) }, 300) } else { uni.showToast({ title: 'è¯·æ«ææ£ç¡®ç设å¤', icon: 'error' }) // å ³éæ«æçé¢ isScanning.value = false } } catch (error) { uni.showToast({ title: error.message || 'æ°æ®è§£æå¤±è´¥', icon: 'error' }) // å ³éæ«æçé¢ isScanning.value = false } } // æå¼ä¸ä¼ å¼¹çª const openUploadDialog = (task) => { // 设置任å¡ä¿¡æ¯å°infoData if (task) { infoData.value = { ...task, taskId: task.taskId || task.id, storageBlobDTO: [] // åå§åæä»¶å表 }; } // 设置ä¸ä¼ ç¶æç±»åï¼å¯ä»¥æ ¹æ®ä»»å¡ç±»å设置ä¸åçç¶æï¼ uploadStatusType.value = 0 // é»è®¤ç¶æ // æ¸ ç©ºä¹åçæä»¶ uploadFiles.value = [] // æ¾ç¤ºä¸ä¼ å¼¹çª showUploadDialog.value = true } // å ³éä¸ä¼ å¼¹çª const closeUploadDialog = () => { showUploadDialog.value = false uploadFiles.value = [] // æ¸ çä¸ä¸ªåç±»çæ°æ® beforeModelValue.value = [] afterModelValue.value = [] issueModelValue.value = [] currentUploadType.value = 'before' hasException.value = null // éç½®å¼å¸¸ç¶æ infoData.value = null // æ¸ ç任塿°æ® } // 忢ä¸ä¼ ç±»å const switchUploadType = (type) => { currentUploadType.value = type } // è·åå½ååç±»çæä»¶å表 const getCurrentFiles = () => { switch (currentUploadType.value) { case 'before': return beforeModelValue.value case 'after': return afterModelValue.value case 'issue': return issueModelValue.value default: return [] } } // è·åä¸ä¼ ç±»åææ¬ const getUploadTypeText = () => { switch (currentUploadType.value) { case 'before': return 'ç产å' case 'after': return 'ç产ä¸' case 'issue': return 'ç产å' default: return '' } } // å¤çä¸ä¼ æä»¶æ´æ° const handleUploadUpdate = (files) => { uploadFiles.value = files } // 设置å¼å¸¸ç¶æ const setExceptionStatus = (status) => { hasException.value = status } // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ const goToRepair = () => { try { // åå¨å½åä»»å¡ä¿¡æ¯å°æ¬å°åå¨ï¼ä¾æ¥ä¿®é¡µé¢ä½¿ç¨ const taskInfo = { taskId: infoData.value?.taskId || infoData.value?.id, taskName: infoData.value?.taskName, inspectionLocation: infoData.value?.inspectionLocation, inspector: infoData.value?.inspector, // ä¼ éå½åä¸ä¼ çæä»¶ä¿¡æ¯ uploadedFiles: { before: beforeModelValue.value, after: afterModelValue.value, issue: issueModelValue.value } } uni.setStorageSync('repairTaskInfo', JSON.stringify(taskInfo)) // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ uni.navigateTo({ url: '/pages/equipmentManagement/repair/add' }) // å ³éä¸ä¼ å¼¹çª closeUploadDialog() } catch (error) { console.error('跳转æ¥ä¿®é¡µé¢å¤±è´¥:', error) uni.showToast({ title: '跳转失败ï¼è¯·éè¯', icon: 'error' }) } } // æäº¤ä¸ä¼ const submitUpload = async () => { try { // æ£æ¥æ¯å¦éæ©äºå¼å¸¸ç¶æ if (hasException.value === null) { uni.showToast({ title: 'è¯·éæ©æ¯å¦åå¨å¼å¸¸', icon: 'none' }) return } // æ£æ¥æ¯å¦æä»»ä½æä»¶ä¸ä¼ const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length; if (totalFiles === 0) { uni.showToast({ title: '请å ä¸ä¼ æä»¶', icon: 'none' }) return } // æ¾ç¤ºæäº¤ä¸çå è½½æç¤º showLoadingToast('æäº¤ä¸...') // æç §æ¨çé»è¾åå¹¶ææåç±»çæä»¶ let arr = []; if (beforeModelValue.value.length > 0) { arr.push(...beforeModelValue.value); } if (afterModelValue.value.length > 0) { arr.push(...afterModelValue.value); } if (issueModelValue.value.length > 0) { arr.push(...issueModelValue.value); } // æäº¤æ°æ® infoData.value.storageBlobDTO = arr; // æ·»å å¼å¸¸ç¶æä¿¡æ¯ infoData.value.hasException = hasException.value; const result = await uploadInspectionTask({...infoData.value}); // æ£æ¥æäº¤ç»æ if (result && (result.code === 200 || result.success)) { // æäº¤æå closeToast(); // å ³éå è½½æç¤º uni.showToast({ title: 'æäº¤æå', icon: 'success' }) // å ³éå¼¹çª closeUploadDialog() // å·æ°å表 setTimeout(() => { getList() }, 500) } else { // æäº¤å¤±è´¥ closeToast(); uni.showToast({ title: result?.msg || result?.message || 'æäº¤å¤±è´¥', icon: 'error' }) } } catch (error) { console.error('æäº¤ä¸ä¼ 失败:', error) closeToast(); // å ³éå è½½æç¤º let errorMessage = 'æäº¤å¤±è´¥'; if (error.message) { errorMessage = error.message; } else if (error.msg) { errorMessage = error.msg; } else if (typeof error === 'string') { errorMessage = error; } uni.showToast({ title: errorMessage, icon: 'error' }) } } // æå头é误å¤ç const handleCameraError = (error) => { cameraError.value = 'æå头访é®å¤±è´¥ï¼è¯·æ£æ¥æé设置' } // æ«ç äºä»¶å¤ç const handleScanCode = (result) => { handleScanSuccess(result) } // æ¥çéä»¶ const viewAttachments = async (task) => { try { currentViewTask.value = task currentViewType.value = 'before' // è§£ææ°çæ°æ®ç»æ attachmentList.value = [] // ç产åéä»¶ (type=0) if (task.beforeProduction && Array.isArray(task.beforeProduction)) { const beforeFiles = task.beforeProduction.map(file => ({ ...file, type: 0 // ç¡®ä¿type为0 })) attachmentList.value.push(...beforeFiles) } // ç产ä¸éä»¶ (type=1) if (task.afterProduction && Array.isArray(task.afterProduction)) { const afterFiles = task.afterProduction.map(file => ({ ...file, type: 1 // ç¡®ä¿type为1 })) attachmentList.value.push(...afterFiles) } // ç产åéä»¶ (type=2) if (task.productionIssues && Array.isArray(task.productionIssues)) { const issueFiles = task.productionIssues.map(file => ({ ...file, type: 2 // ç¡®ä¿type为2 })) attachmentList.value.push(...issueFiles) } showAttachmentDialog.value = true } catch (error) { uni.showToast({ title: 'è·åé件失败', icon: 'error' }) } } // å ³ééä»¶æ¥çå¼¹çª const closeAttachmentDialog = () => { showAttachmentDialog.value = false currentViewTask.value = null attachmentList.value = [] currentViewType.value = 'before' } // 忢æ¥çç±»å const switchViewType = (type) => { currentViewType.value = type } // æ ¹æ®typeè·å对åºåç±»çéä»¶ const getAttachmentsByType = (typeValue) => { return attachmentList.value.filter(file => file.type === typeValue) || [] } // è·åå½åæ¥çç±»åçéä»¶ const getCurrentViewAttachments = () => { switch (currentViewType.value) { case 'before': return getAttachmentsByType(0) case 'after': return getAttachmentsByType(1) case 'issue': return getAttachmentsByType(2) default: return [] } } // 夿æ¯å¦ä¸ºå¾çæä»¶ const isImageFile = (file) => { // æ£æ¥contentTypeåæ®µ if (file.contentType && file.contentType.startsWith('image/')) { return true } // æ£æ¥åæçtypeåæ®µ if (file.type === 'image') return true // æ£æ¥æä»¶æ©å±å const name = file.bucketFilename || file.originalFilename || file.name || '' const ext = name.split('.').pop()?.toLowerCase() return ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext) } // é¢è§éä»¶ const previewAttachment = (file) => { if (isImageFile(file)) { // é¢è§å¾ç const imageUrls = getCurrentViewAttachments() .filter(f => isImageFile(f)) .map(f => f.url || f.downloadUrl) uni.previewImage({ urls: imageUrls, current: file.url || file.downloadUrl }) } else { // é¢è§è§é¢ - æ¾ç¤ºè§é¢ææ¾å¼¹çª showVideoPreview(file) } } // æ¾ç¤ºè§é¢é¢è§ const showVideoPreview = (file) => { currentVideoFile.value = file showVideoDialog.value = true } // å ³éè§é¢é¢è§ const closeVideoPreview = () => { showVideoDialog.value = false currentVideoFile.value = null } // è§é¢ææ¾é误å¤ç const handleVideoError = (error) => { uni.showToast({ title: 'è§é¢ææ¾å¤±è´¥', icon: 'error' }) } // æç § const chooseImage = () => { if (uploadFiles.value.length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½ææ${uploadConfig.limit}个æä»¶`, icon: 'none' }); return; } uni.chooseImage({ count: 1, sizeType: ['compressed', 'original'], sourceType: ['camera'], success: (res) => { try { if (!res.tempFilePaths || res.tempFilePaths.length === 0) { throw new Error('æªè·åå°å¾çæä»¶'); } const tempFilePath = res.tempFilePaths[0]; const tempFile = res.tempFiles && res.tempFiles[0] ? res.tempFiles[0] : {}; const file = { tempFilePath: tempFilePath, path: tempFilePath, // ä¿æå ¼å®¹æ§ type: 'image', name: `photo_${Date.now()}.jpg`, size: tempFile.size || 0, createTime: new Date().getTime(), uid: Date.now() + Math.random() }; handleBeforeUpload(file); } catch (error) { console.error('å¤çæç §ç»æå¤±è´¥:', error); uni.showToast({ title: 'å¤çå¾ç失败', icon: 'error' }); } }, fail: (err) => { console.error('æç §å¤±è´¥:', err); uni.showToast({ title: 'æç §å¤±è´¥: ' + (err.errMsg || 'æªç¥é误'), icon: 'error' }); } }); } // æè§é¢ const chooseVideo = () => { if (uploadFiles.value.length >= uploadConfig.limit) { uni.showToast({ title: `æå¤åªè½ææ${uploadConfig.limit}个æä»¶`, icon: 'none' }); return; } uni.chooseVideo({ sourceType: ['camera'], maxDuration: uploadConfig.maxVideoDuration, camera: 'back', success: (res) => { try { if (!res.tempFilePath) { throw new Error('æªè·åå°è§é¢æä»¶'); } const file = { tempFilePath: res.tempFilePath, path: res.tempFilePath, // ä¿æå ¼å®¹æ§ type: 'video', name: `video_${Date.now()}.mp4`, size: res.size || 0, duration: res.duration || 0, createTime: new Date().getTime(), uid: Date.now() + Math.random() }; handleBeforeUpload(file); } catch (error) { console.error('å¤çæè§é¢ç»æå¤±è´¥:', error); uni.showToast({ title: 'å¤çè§é¢å¤±è´¥', icon: 'error' }); } }, fail: (err) => { console.error('æè§é¢å¤±è´¥:', err); uni.showToast({ title: 'æè§é¢å¤±è´¥: ' + (err.errMsg || 'æªç¥é误'), icon: 'error' }); } }); } // å 餿件 const removeFile = (index) => { uni.showModal({ title: '确认å é¤', content: 'ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼', success: (res) => { if (res.confirm) { // æ ¹æ®å½åä¸ä¼ ç±»åå é¤å¯¹åºåç±»çæä»¶ switch (currentUploadType.value) { case 'before': beforeModelValue.value.splice(index, 1); break; case 'after': afterModelValue.value.splice(index, 1); break; case 'issue': issueModelValue.value.splice(index, 1); break; } uni.showToast({ title: 'å 餿å', icon: 'success' }); } } }); } // æ£æ¥ç½ç»è¿æ¥ const checkNetworkConnection = () => { return new Promise((resolve) => { uni.getNetworkType({ success: (res) => { if (res.networkType === 'none') { resolve(false); } else { resolve(true); } }, fail: () => { resolve(false); } }); }); } // ä¸ä¼ åæ ¡éª const handleBeforeUpload = async (file) => { // æ£æ¥ç½ç»è¿æ¥ const hasNetwork = await checkNetworkConnection(); if (!hasNetwork) { uni.showToast({ title: 'ç½ç»è¿æ¥ä¸å¯ç¨ï¼è¯·æ£æ¥ç½ç»è®¾ç½®', icon: 'none' }); return false; } // æ ¡éªæä»¶å¤§å° if (uploadConfig.fileSize && file.size) { const isLt = file.size / 1024 / 1024 < uploadConfig.fileSize; if (!isLt) { uni.showToast({ title: `æä»¶å¤§å°ä¸è½è¶ è¿ ${uploadConfig.fileSize} MB!`, icon: 'none' }); return false; } } // æ ¡éªè§é¢æ¶é¿ if (file.type === 'video' && file.duration && file.duration > uploadConfig.maxVideoDuration) { uni.showToast({ title: `è§é¢æ¶é¿ä¸è½è¶ è¿ ${uploadConfig.maxVideoDuration} ç§!`, icon: 'none' }); return false; } // æ ¡éªæä»¶ç±»å if (uploadConfig.fileType && Array.isArray(uploadConfig.fileType) && uploadConfig.fileType.length > 0) { const fileName = file.name || ''; const fileExtension = fileName ? fileName.split('.').pop().toLowerCase() : ''; // æ ¹æ®æä»¶ç±»åç¡®å®ææçæ©å±å let expectedTypes = []; if (file.type === 'image') { expectedTypes = ['jpg', 'jpeg', 'png', 'gif', 'webp']; } else if (file.type === 'video') { expectedTypes = ['mp4', 'mov', 'avi', 'wmv']; } // æ£æ¥æä»¶æ©å±åæ¯å¦å¨å 许çç±»åä¸ if (fileExtension && expectedTypes.length > 0) { const isAllowed = expectedTypes.some(type => uploadConfig.fileType.includes(type) && type === fileExtension ); if (!isAllowed) { uni.showToast({ title: `æä»¶æ ¼å¼ä¸æ¯æï¼è¯·ææ ${expectedTypes.join('/')} æ ¼å¼çæä»¶`, icon: 'none' }); return false; } } } // æ ¡éªéè¿ï¼å¼å§ä¸ä¼ uploadFile(file); return true; } // æä»¶ä¸ä¼ å¤ç const uploadFile = (file) => { uploading.value = true; uploadProgress.value = 0; number.value++; // å¢å ä¸ä¼ è®¡æ° // ç¡®ä¿æä»¶è·¯å¾æ£ç¡® const filePath = file.tempFilePath || file.path; if (!filePath) { handleUploadError('æä»¶è·¯å¾ä¸åå¨'); return; } // ç¡®ä¿tokenåå¨ const token = getToken(); if (!token) { handleUploadError('ç¨æ·æªç»å½'); return; } // åå¤ä¸ä¼ åæ° const uploadParams = { url: uploadFileUrl.value, filePath: filePath, name: 'files', formData: { type: uploadStatusType.value || 0 }, header: { 'Authorization': `Bearer ${token}` } }; const uploadTask = uni.uploadFile({ ...uploadParams, success: (res) => { try { if (res.statusCode === 200) { const response = JSON.parse(res.data); if (response.code === 200) { handleUploadSuccess(response, file); uni.showToast({ title: 'ä¸ä¼ æå', icon: 'success' }); } else { handleUploadError(response.msg || 'æå¡å¨è¿åé误'); } } else { handleUploadError(`æå¡å¨é误ï¼ç¶æç : ${res.statusCode}`); } } catch (e) { console.error('è§£æååºå¤±è´¥:', e); console.error('åå§ååºæ°æ®:', res.data); handleUploadError('ååºæ°æ®è§£æå¤±è´¥: ' + e.message); } }, fail: (err) => { console.error('ä¸ä¼ 失败:', err.errMsg || err); number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° let errorMessage = 'ä¸ä¼ 失败'; if (err.errMsg) { if (err.errMsg.includes('statusCode: null')) { errorMessage = 'ç½ç»è¿æ¥å¤±è´¥ï¼è¯·æ£æ¥ç½ç»è®¾ç½®'; } else if (err.errMsg.includes('timeout')) { errorMessage = 'ä¸ä¼ è¶ æ¶ï¼è¯·éè¯'; } else if (err.errMsg.includes('fail')) { errorMessage = 'ä¸ä¼ 失败ï¼è¯·æ£æ¥ç½ç»è¿æ¥'; } else { errorMessage = err.errMsg; } } handleUploadError(errorMessage); }, complete: () => { uploading.value = false; uploadProgress.value = 0; } }); // çå¬ä¸ä¼ è¿åº¦ if (uploadTask && uploadTask.onProgressUpdate) { uploadTask.onProgressUpdate((res) => { uploadProgress.value = res.progress; }); } } // ä¸ä¼ 失败å¤ç const handleUploadError = (message = 'ä¸ä¼ æä»¶å¤±è´¥', showRetry = true) => { if (showRetry) { uni.showModal({ title: 'ä¸ä¼ 失败', content: message + 'ï¼æ¯å¦éè¯ï¼', success: (res) => { if (res.confirm) { // ç¨æ·éæ©éè¯ï¼è¿éå¯ä»¥éæ°è§¦åä¸ä¼ } } }); } else { uni.showToast({ title: message, icon: 'error' }); } } // ä¸ä¼ æååè° const handleUploadSuccess = (res, file) => { if (res.code === 200 && res.data && Array.isArray(res.data) && res.data.length > 0) { const uploadedFile = res.data[0]; // æ ¹æ®å½åä¸ä¼ ç±»å设置typeåæ®µ let typeValue = 0; // é»è®¤ä¸ºç产å switch (currentUploadType.value) { case 'before': typeValue = 0; break; case 'after': typeValue = 1; break; case 'issue': typeValue = 2; break; } // ç¡®ä¿ä¸ä¼ çæä»¶æ°æ®å®æ´ï¼å å«idåtype const fileData = { ...file, id: uploadedFile.id, // æ·»å æå¡å¨è¿åçid url: uploadedFile.url || uploadedFile.downloadUrl, bucketFilename: uploadedFile.bucketFilename || file.name, downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, size: uploadedFile.size || file.size, createTime: uploadedFile.createTime || new Date().getTime(), type: typeValue // æ·»å ç±»ååæ®µï¼0=ç产å, 1=ç产ä¸, 2=ç产å }; uploadList.value.push(fileData); uploadedSuccessfully(); } else { number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° handleUploadError(res.msg || 'ä¸ä¼ 失败'); } } // ä¸ä¼ ç»æå¤ç const uploadedSuccessfully = () => { if (number.value > 0 && uploadList.value.length === number.value) { // æ ¹æ®å½åä¸ä¼ ç±»åï¼å°æä»¶æ·»å å°å¯¹åºçåç±» switch (currentUploadType.value) { case 'before': beforeModelValue.value = [...beforeModelValue.value, ...uploadList.value]; break; case 'after': afterModelValue.value = [...afterModelValue.value, ...uploadList.value]; break; case 'issue': issueModelValue.value = [...issueModelValue.value, ...uploadList.value]; break; } // éç½®ç¶æ uploadList.value = []; number.value = 0; } } // æ ¼å¼åæä»¶å¤§å° const formatFileSize = (size) => { if (!size) return ''; if (size < 1024) return size + 'B'; if (size < 1024 * 1024) return (size / 1024).toFixed(1) + 'KB'; return (size / (1024 * 1024)).toFixed(1) + 'MB'; } isScanning.value = false; scanLoading.value = false; }; // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; </script> <style scoped> .inspection-upload-page { min-height: 100vh; background-color: #f5f5f5; .app-container { padding: 20rpx; background-color: #f5f5f5; min-height: 100vh; } .table-section { padding: 15px; .card-container { background-color: #fff; border-radius: 16rpx; padding: 20rpx; } .task-list { display: flex; flex-direction: column; gap: 12px; .scan-section { margin: 20rpx 0; } .task-item { background: #fff; border-radius: 12px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; .scan-controls { display: flex; justify-content: center; margin-bottom: 20rpx; } .task-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; .status-info { margin-top: 32rpx; text-align: center; } .task-info { flex: 1; display: flex; flex-direction: column; gap: 4px; .scanning-text { display: flex; align-items: center; justify-content: center; color: #2979ff; margin-top: 16rpx; font-size: 28rpx; } .task-name { font-size: 16px; font-weight: 600; color: #333; .scanning-text text { margin-left: 10rpx; } .task-location { font-size: 14px; color: #666; .loading-container { display: flex; justify-content: center; align-items: center; padding: 40rpx 0; } .task-actions { display: flex; gap: 8px; .list-container { margin-top: 20rpx; } .task-details { display: flex; flex-direction: column; gap: 6px; .list-item { background-color: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1px solid #f0f0f0; } .detail-item { display: flex; justify-content: space-between; align-items: center; .item-content { margin-bottom: 20rpx; } .detail-label { font-size: 12px; color: #999; .item-row { display: flex; margin-bottom: 16rpx; font-size: 28rpx; line-height: 40rpx; } .detail-value { font-size: 12px; color: #666; font-weight: 500; .item-row:last-child { margin-bottom: 0; } .item-label { color: #666; width: 200rpx; flex-shrink: 0; } .item-value { color: #333; flex: 1; word-break: break-all; } .item-actions { display: flex; justify-content: flex-end; padding-top: 20rpx; border-top: 1px solid #f0f0f0; } .no-data { text-align: center; padding: 40px 20px; color: #999; font-size: 14px; text-align: center; padding: 80rpx 0; color: #999; font-size: 28rpx; } /* æ«ç å¼¹çªæ ·å¼ */ .qr-scan-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 9999; display: flex; align-items: center; justify-content: center; /* ç§»å¨ç«¯ä¼å */ @media (max-width: 768px) { .app-container { padding: 10rpx; } } .qr-scan-container { width: 90vw; max-width: 400px; background: #fff; border-radius: 12px; padding: 20px; position: relative; } .scan-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .scan-title { font-size: 18px; font-weight: 600; color: #333; } .qr-camera { width: 100%; height: 300px; border-radius: 8px; overflow: hidden; margin-bottom: 15px; } .scan-frame-wrapper { position: relative; display: flex; flex-direction: column; align-items: center; } .scan-frame { width: 200px; height: 200px; border: 2px solid #409eff; border-radius: 8px; position: relative; } .scan-frame::before, .scan-frame::after { content: ''; position: absolute; width: 20px; height: 20px; border: 2px solid #409eff; } .scan-frame::before { top: -2px; left: -2px; border-right: none; border-bottom: none; } .scan-frame::after { bottom: -2px; right: -2px; border-left: none; border-top: none; } .scan-tip { margin-top: 10px; font-size: 14px; color: #666; text-align: center; } /* èªå®ä¹æ¨¡ææ¡æ ·å¼ */ .custom-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px; } .custom-modal-container { width: 100%; max-width: 500px; max-height: 80vh; display: flex; align-items: center; justify-content: center; } /* ä¸ä¼ å¼¹çªæ ·å¼ */ .upload-popup-content { background: #fff; border-radius: 12px; width: 100%; min-height: 300px; max-height: 70vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .upload-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid #eee; } .upload-popup-title { font-size: 16px; font-weight: 600; color: #333; } .upload-popup-body { flex: 1; padding: 20px; overflow-y: auto; } .upload-popup-footer { display: flex; justify-content: flex-end; padding: 15px 20px; border-top: 1px solid #eee; gap: 10px; } /* ç®åä¸ä¼ ç»ä»¶æ ·å¼ */ .simple-upload-area { padding: 15px; } .upload-buttons { display: flex; gap: 10px; margin-bottom: 15px; } .file-list { margin-top: 15px; display: flex; flex-wrap: wrap; gap: 12px; } .file-item { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; padding: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; width: calc(50% - 6px); min-width: 120px; } .file-item:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } .file-preview-container { position: relative; margin-bottom: 8px; } .file-preview { width: 80px; height: 80px; border-radius: 8px; object-fit: cover; border: 2px solid #f0f0f0; } .video-preview { width: 80px; height: 80px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2px solid #f0f0f0; } .video-text { font-size: 12px; color: #666; margin-top: 4px; } .delete-btn { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; background: #ff4757; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 2px 4px rgba(255, 71, 87, 0.3); transition: all 0.3s ease; } .delete-btn:hover { background: #ff3742; transform: scale(1.1); } .file-info { text-align: center; width: 100%; } .file-name { font-size: 12px; color: #333; font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px; } .file-size { font-size: 10px; color: #999; margin-top: 2px; display: block; } .empty-state { text-align: center; padding: 40px 20px; color: #999; font-size: 14px; background: #f8f9fa; border-radius: 8px; border: 2px dashed #ddd; } .upload-progress { margin: 15px 0; padding: 0 10px; } /* ä¸ä¼ æ ç¾é¡µæ ·å¼ */ .upload-tabs { display: flex; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px; padding: 4px; } .tab-item { flex: 1; text-align: center; padding: 8px 12px; font-size: 14px; color: #666; border-radius: 6px; transition: all 0.3s ease; cursor: pointer; } .tab-item.active { background: #409eff; color: #fff; font-weight: 500; } .tab-item:hover:not(.active) { background: #e9ecef; color: #333; } /* å¼å¸¸ç¶æéæ©æ ·å¼ */ .exception-section { margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef; } .section-title { display: block; font-size: 14px; font-weight: 600; color: #333; margin-bottom: 12px; } .exception-options { display: flex; gap: 12px; } .exception-option { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px 16px; background: #fff; border: 2px solid #e9ecef; border-radius: 8px; cursor: pointer; transition: all 0.3s ease; font-size: 14px; color: #666; } .exception-option.active { border-color: #409eff; background: #f0f8ff; color: #409eff; font-weight: 500; } .exception-option:hover:not(.active) { border-color: #d9d9d9; background: #fafafa; } /* ç»è®¡ä¿¡æ¯æ ·å¼ */ .upload-summary { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 6px; border-left: 3px solid #409eff; } .summary-text { font-size: 12px; color: #666; line-height: 1.4; } /* æ¥çéä»¶å¼¹çªæ ·å¼ */ .attachment-popup-content { background: #fff; border-radius: 12px; width: 100%; min-height: 400px; max-height: 70vh; overflow: hidden; display: flex; flex-direction: column; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); } .attachment-popup-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid #eee; background: #f8f9fa; } .attachment-popup-title { font-size: 16px; font-weight: 600; color: #333; } .close-btn-attachment { width: 28px; height: 28px; border-radius: 50%; background: #f5f5f5; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; } .close-btn-attachment:hover { background: #e9ecef; transform: scale(1.1); } .attachment-popup-body { flex: 1; padding: 15px 20px; overflow-y: auto; } .attachment-tabs { display: flex; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px; padding: 4px; } .attachment-content { min-height: 200px; } .attachment-list { display: flex; flex-wrap: wrap; gap: 12px; } .attachment-item { display: flex; flex-direction: column; align-items: center; background: #fff; border-radius: 12px; padding: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); transition: all 0.3s ease; width: calc(33.33% - 8px); min-width: 100px; cursor: pointer; } .attachment-item:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); } .attachment-preview-container { margin-bottom: 8px; } .attachment-preview { width: 80px; height: 80px; border-radius: 8px; object-fit: cover; border: 2px solid #f0f0f0; } .attachment-video-preview { width: 80px; height: 80px; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); border-radius: 8px; display: flex; flex-direction: column; align-items: center; justify-content: center; border: 2px solid #f0f0f0; } .attachment-info { text-align: center; width: 100%; } .attachment-name { font-size: 12px; color: #333; font-weight: 500; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px; } .attachment-size { font-size: 10px; color: #999; margin-top: 2px; display: block; } .attachment-empty { text-align: center; padding: 60px 20px; color: #999; font-size: 14px; background: #f8f9fa; border-radius: 8px; border: 2px dashed #ddd; } /* è§é¢é¢è§å¼¹çªæ ·å¼ */ .video-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); z-index: 10001; display: flex; align-items: center; justify-content: center; padding: 20px; } .video-modal-container { width: 90%; max-width: 800px; max-height: 80vh; background: #000; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5); } .video-modal-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: rgba(0, 0, 0, 0.7); color: #fff; } .video-modal-title { font-size: 16px; font-weight: 500; color: #fff; } .close-btn-video { width: 28px; height: 28px; border-radius: 50%; background: rgba(255, 255, 255, 0.2); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; } .close-btn-video:hover { background: rgba(255, 255, 255, 0.3); transform: scale(1.1); } .video-modal-body { position: relative; background: #000; } .video-player { width: 100%; height: auto; max-height: 60vh; display: block; } </style> </style> src/pages/login.vue
@@ -6,22 +6,10 @@ <view class="login-form-content"> <view class="input-item flex align-center"> <up-input prefixIcon="account" placeholder="请è¾å ¥è´¦å·" border="bottom" @blur="getUserLoginFacotryList" maxlength="30" v-model="loginForm.userName" clearable></up-input> </view> <view class="input-item flex align-center"> <up-input prefixIcon="lock" placeholder="请è¾å ¥å¯ç " border="bottom" maxlength="20" v-model="loginForm.password" clearable type="password"></up-input> </view> <view class="input-item flex align-center select-container"> <up-icon name="tags" size="18"></up-icon> <up-picker-data v-model="loginForm.factoryId" title="è¯·éæ©å ¬å¸" :options="factoryList" valueKey="id" style="width: 100%;" labelKey="name"> </up-picker-data> </view> <view> <button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">ç»å½</button> @@ -50,7 +38,6 @@ icon: 'none' }) } import { userLoginFacotryList} from '@/api/login' import { ref, onMounted } from "vue"; import useUserStore from '@/store/modules/user' import { getWxCode } from '@/utils/geek'; @@ -63,7 +50,6 @@ const loginForm = ref({ userName: "", password: "", factoryId: "", currentFatoryName: "", }); const factoryList = ref([]) // å ¬å¸å表 @@ -109,37 +95,11 @@ }) } function getUserLoginFacotryList() { if(loginForm.value.userName){ userLoginFacotryList({userName:loginForm.value.userName}).then(res => { console.log('res',res) // æ£æ¥res.dataæ¯å¦ä¸ºæ°ç» if (res.data && Array.isArray(res.data)) { // éæ°ç»è£ æ°æ®æ ¼å¼ï¼deptIdåæidï¼deptNameåæname factoryList.value = res.data.map(item => ({ id: item.deptId, name: item.deptName })) } else { // 妿res.data䏿¯æ°ç»ï¼è®¾ç½®ä¸ºç©ºæ°ç» factoryList.value = [] } }).catch(error => { showToast('è·åå ¬å¸å表失败:', error) factoryList.value = [] }) }else { factoryList.value = [] } } async function handleLogin() { if (loginForm.value.userName === "") { showToast("请è¾å ¥æ¨çè´¦å·") } else if (loginForm.value.password === "") { showToast("请è¾å ¥æ¨çå¯ç ") } else if (loginForm.value.factoryId === "") { showToast("è¯·éæ©å ¬å¸") } else { showToast("ç»å½ä¸ï¼è¯·èå¿çå¾ ...") pwdLogin() @@ -147,7 +107,7 @@ }; // å¯ç ç»å½ async function pwdLogin() { userStore.loginCheckFactory(loginForm.value).then(() => { userStore.login(loginForm.value).then(() => { modal.closeLoading() // ç»å½æååä¿åå¯ç savePassword(); @@ -168,7 +128,6 @@ // 页é¢å è½½æ¶æ£æ¥æ¯å¦æä¿åçå¯ç onMounted(() => { loadPassword(); getUserLoginFacotryList() }); </script> src/pages/management/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,680 @@ <template> <view class="equipment-management"> <!-- 页é¢å¤´é¨ --> <PageHeader title="设å¤ç®¡ç" @back="goBack"/> <!-- æç´¢åºå --> <view class="search-section" v-if="shouldShowSearch"> <view class="search-bar"> <view class="search-input"> <up-input class="search-text" :placeholder="searchPlaceholder" v-model="queryParams.searchAll" clearable /> </view> <view class="filter-button" @click="search"> <up-icon name="search" size="24" color="#999"></up-icon> </view> </view> </view> <!-- æ ç¾é¡µ --> <!-- <view class="tabs-section"> <u-tabs :list="tabs" :current="activeTabIndex" @change="handleTabChange" :scrollable="false" lineWidth="30" lineColor="#2979ff" :activeStyle="{ color: '#2979ff', fontWeight: 'bold' }" /> </view> --> <!-- å表åºå --> <scroll-view class="list-container" scroll-y refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh" > <!-- å è½½ç¶æ --> <view v-if="loading" class="loading-container"> <u-loading-icon text="å è½½ä¸..." /> </view> <!-- 设å¤å表 --> <view v-else-if="!loading && tableData.length > 0" class="ledger-list"> <view v-for="(item, index) in tableData" :key="item.id || index"> <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">{{ item.equipmentNo || 'æªç¼å·' }}</text> </view> </view> <up-divider></up-divider> <view class="item-details"> <!-- 设å¤åè¡¨é¡µé¢ --> <!-- <template v-if="activeTab === 'management'"> --> <view class="detail-row"> <text class="detail-label">设å¤åç§°</text> <text class="detail-value">{{ item.equipmentName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">èæ</text> <text class="detail-value">{{ (item.consumables !== undefined ? item.consumables : item.isConsumables) ? 'æ¯' : 'å¦' }}</text> </view> <view class="detail-row"> <text class="detail-label">æ»æ°é</text> <text class="detail-value highlight">{{ item.quantity || 0 }}</text> </view> <view class="detail-row"> <text class="detail-label">å·²ä½¿ç¨æ°é</text> <text class="detail-value">{{ item.usedNo || 0 }}</text> </view> <view class="detail-row"> <text class="detail-label">è§æ ¼åå·</text> <text class="detail-value">{{ item.specification || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">éè´æ¥æ</text> <text class="detail-value">{{ item.purchaseDate || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">éè´ä»·æ ¼</text> <text class="detail-value">{{ item.purchasePrice ? `Â¥${item.purchasePrice}` : '-' }}</text> </view> <!-- </template> --> <!-- 设å¤é¢ç¨é¡µé¢ --> <!-- <template v-else-if="activeTab === 'equipmentRequisition'"> <view class="detail-row"> <text class="detail-label">é¢ç¨äºº</text> <text class="detail-value">{{ item.userName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">设å¤åç§°</text> <text class="detail-value">{{ item.equipmentName || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">é¢ç¨æ°é</text> <text class="detail-value highlight">{{ item.usageQuantity || 0 }}</text> </view> <view class="detail-row"> <text class="detail-label">å½è¿æ°é</text> <text class="detail-value">{{ item.returnQuantity || 0 }}</text> </view> <view class="detail-row"> <text class="detail-label">使ç¨ç¶æ</text> <text class="detail-value" :style="{ color: getStatusColor(item.equipmentStatus) }"> {{ getStatusText(item.equipmentStatus) }} </text> </view> <view class="detail-row"> <text class="detail-label">使ç¨å¼å§æ¶é´</text> <text class="detail-value">{{ item.usageStartTime || '-' }}</text> </view> <view class="detail-row"> <text class="detail-label">使ç¨ç»ææ¶é´</text> <text class="detail-value">{{ item.usageEndTime || '-' }}</text> </view> <view class="detail-row" v-if="item.remarks"> <text class="detail-label">夿³¨</text> <text class="detail-value">{{ item.remarks }}</text> </view> </template> --> </view> <!-- æä½æé® - åªå¨è®¾å¤é¢ç¨é¡µé¢æ¾ç¤º --> <!-- <template v-if="activeTab === 'equipmentRequisition'"> <up-divider></up-divider> <view class="action-buttons"> <u-button type="info" size="small" class="action-btn" @click.stop="handleView(item)" > æ¥ç </u-button> <u-button v-if="shouldShowReturnButton(item)" type="success" size="small" class="action-btn" :disabled="item.equipmentStatus === EQUIPMENT_STATUS.RETURNED" @click.stop="handleReturn(item)" > å½è¿ </u-button> </view> </template> --> </view> </view> </view> <!-- ç©ºç¶æ --> <view v-else-if="!loading && tableData.length === 0" class="no-data"> <text>ææ {{ currentTabConfig?.label || '' }}æ°æ®</text> </view> </scroll-view> <!-- æµ®å¨æä½æé® - 设å¤é¢ç¨é¡µé¢æ¾ç¤ºé¢ç¨æé® --> <!-- <view v-if="activeTab === 'equipmentRequisition'" class="fab-button" @tap="handleAdd" > <up-icon name="plus" size="24" color="#ffffff"></up-icon> </view> --> <!-- å¼¹çªç»ä»¶ --> <!-- <managementDialog v-if="activeTab == 'management'" v-model:copyForm="copyForm" v-model:managementFormDialog="manaDialog" :addOrEdit="addOrEdit" :form="form" @submit="getList" /> --> <!-- <EquipmentRequisition v-model="equipmentRequisitionDialog" :formData="form" :maxQuantity="getMaxQuantity()" :addOrEdit="addOrEdit" :equipmentStatus="form.equipmentStatus" @submit="onEquipmentRequisitionSubmit" /> --> <!-- æ¥ç详æ å¼¹çª --> <!-- <u-popup v-model="dialogTableVisible" :title="dialogTableTitle" mode="center" :closeable="true" > <view class="dialog-content"> <u-table :data="dialogTableData" :columns="dialogTableColumns" height="400px" /> </view> </u-popup> --> </view> </template> <script setup> import { computed, onMounted, reactive, ref, nextTick, toRefs } from "vue"; // uviewplusç»ä»¶å¯¼å ¥ import { useToast } from '@/utils/uviewplus' const { showToast } = useToast() // ç»ä»¶å¯¼å ¥ import PageHeader from "@/components/PageHeader.vue"; // import managementDialog from "./mould/managementDialog.vue"; // import EquipmentRequisition from "./mould/equipmentRequisitionDialog.vue"; // API æå¡å¯¼å ¥ import { getManagementList, } from "@/api/equipment/management/index.js"; // import { getUsageRecordList, getUsageDetailList } from "@/api/equipment/requisition/index.js"; // 设å¤ç¶ææä¸¾ const EQUIPMENT_STATUS = { USING: 1, // 使ç¨ä¸ PARTIAL_RETURN: 2, // é¨åå½è¿ RETURNED: 3 // å·²å½è¿ }; // è·åç¶æææ¬ const getStatusText = (status) => { switch(status) { case EQUIPMENT_STATUS.USING: return "使ç¨ä¸"; case EQUIPMENT_STATUS.PARTIAL_RETURN: return "é¨åå½è¿"; case EQUIPMENT_STATUS.RETURNED: return "å·²å½è¿"; default: return "æªç¥ç¶æ"; } }; // è·åç¶æé¢è² const getStatusColor = (status) => { switch(status) { case EQUIPMENT_STATUS.USING: return '#409eff'; // èè² case EQUIPMENT_STATUS.PARTIAL_RETURN: return '#e6a23c'; // æ©è² case EQUIPMENT_STATUS.RETURNED: return '#67c23a'; // ç»¿è² default: return '#909399'; // ç°è² } }; // ååºå¼ç¶æç®¡ç - 使ç¨è§£æåé»è®¤å¼ const initFormState = () => ({ consumables: false }); const state = reactive({ form: initFormState(), title: "", copyForm: {}, addOrEdit: "add", loading: false, activeTab: "management", tableData: [], // åé¡µç¶æï¼ä¿çç¨äºAPIè°ç¨ï¼ä½ä¸æ¾ç¤ºå页ç»ä»¶ï¼ pageNum: 1, pageSize: 20, total: 0, // 䏿巿°ç¶æ refreshing: false, // æ¥è¯¢åæ° queryParams: { searchAll: "", }, }); // 使ç¨è§£æç®åè®¿é® const { form, title, copyForm, addOrEdit, loading, activeTab, tableData, pageNum, pageSize, total, queryParams, refreshing, } = toRefs(state); // æ·»å 缺失çååºå¼åé const manaDialog = ref(false); const equipmentRequisitionDialog = ref(false); const usageRecordDialog = ref(false); const dialogTableVisible = ref(false); const dialogTableTitle = ref(''); const dialogTableData = ref([]); const dialogTableColumns = ref([]); // æ ç¾é¡µé ç½® - 便äºåç»æ©å± const tabsConfig = { management: { label: "设å¤å表", searchPlaceholder: "设å¤ç¼å·/设å¤åç§°/è§æ ¼åå·", showSearch: true, api: getManagementList, columns: [ { prop: "equipmentNo", label: "设å¤ç¼å·", minWidth: 100 }, { prop: "equipmentName", label: "设å¤åç§°", minWidth: 100 }, { prop: "consumables", label: "èæ", formatter: (row) => { const value = row.consumables !== undefined ? row.consumables : row.isConsumables; return value ? "æ¯" : "å¦"; }, minWidth: 100 }, { prop: "quantity", label: "æ»æ°é", minWidth: 100 }, { prop: "usedNo", label: "å·²ä½¿ç¨æ°é", minWidth: 100 }, { prop: "specification", label: "è§æ ¼åå·", minWidth: 100 }, { prop: "purchaseDate", label: "éè´æ¥æ", minWidth: 100 }, { prop: "purchasePrice", label: "éè´ä»·æ ¼", minWidth: 100 }, ], }, // equipmentRequisition: { // label: "设å¤é¢ç¨", // searchPlaceholder: "设å¤ç¼å·/设å¤åç§°/è§æ ¼åå·", // showSearch: true, // api: getUsageRecordList, // columns: [ // { prop: "userName", label: "é¢ç¨äºº", minWidth: 100 }, // { prop: "equipmentNo", label: "设å¤ç¼å·", minWidth: 100 }, // { prop: "equipmentName", label: "设å¤åç§°", minWidth: 100 }, // { prop: "usageQuantity", label: "é¢ç¨æ°é", minWidth: 100 }, // {prop: "returnQuantity" , label: "å½è¿æ°é", minWidth: 100, // formatter: (row) => row.returnQuantity || 0 // }, // { // prop: "equipmentStatus", // label: "使ç¨ç¶æ", // minWidth: 100, // formatter: (row) => getStatusText(row.equipmentStatus), // cellStyle: (row) => ({ color: getStatusColor(row.equipmentStatus) }), // }, // { prop: "usageStartTime", label: "使ç¨å¼å§æ¶é´", minWidth: 100 }, // { prop: "usageEndTime", label: "使ç¨ç»ææ¶é´", minWidth: 100 }, // { prop: "remarks", label: "夿³¨", minWidth: 100 }, // ], // }, }; // æ ç¾é¡µæ°æ® - u-tabséè¦nameä½ä¸ºæ¾ç¤ºææ¬ // const tabs = computed(() => // Object.entries(tabsConfig).map(([name, config]) => ({ // name: config.label, // 使ç¨labelä½ä¸ºæ¾ç¤ºææ¬ // value: name, // ä¿ååå§nameå¼ // })) // ); const tabs = computed(() => [ { name: tabsConfig.management.label, value: 'management' } ]); // å½åæ ç¾é¡µç´¢å¼ - 使ç¨refè䏿¯computedï¼ç¡®ä¿ååºå¼æ´æ° const activeTabIndex = ref(0); // å½åæ ç¾é¡µé ç½® const currentTabConfig = computed(() => tabsConfig[activeTab.value]); // 计ç®å±æ§ const searchPlaceholder = computed( () => currentTabConfig.value?.searchPlaceholder || "请è¾å ¥æç´¢ä¿¡æ¯" ); const shouldShowSearch = computed( () => currentTabConfig.value?.showSearch || false ); // 夿æ¯å¦æ¾ç¤ºå½è¿æé® // const shouldShowReturnButton = (item) => { // if (activeTab.value !== 'equipmentRequisition') { // return false; // } // // å¦æè®¾å¤ç¶ææ¯å·²å½è¿ï¼ä¸æ¾ç¤ºå½è¿æé® // if (item.equipmentStatus === EQUIPMENT_STATUS.RETURNED) { // return false; // } // // å ¶ä»ç¶æé½æ¾ç¤ºå½è¿æé®ï¼ä½¿ç¨ä¸ãé¨åå½è¿ãæè 没æç¶æï¼ // return true; // }; // 卿è·åèªå®ä¹æé®é ç½®ï¼ä¿çç¨äºå ¶ä»ç¨éï¼ // const getCustomButtons = () => { // const buttons = []; // // // å¨è®¾å¤é¢ç¨é¡µé¢æ·»å å½è¿æé® // if (activeTab.value === 'equipmentRequisition') { // buttons.push({ // name: 'return', // label: 'å½è¿', // type: 'success', // size: 'small', // link: true, // show: (row) => shouldShowReturnButton(row), // disabled: (row) => row.equipmentStatus === EQUIPMENT_STATUS.RETURNED, // }); // } // // return buttons; // }; // å¤çèªå®ä¹æé®ç¹å»äºä»¶ // const handleCustomButtonClick = ({ buttonName, row }) => { // switch (buttonName) { // case 'return': // handleReturn(row); // break; // // å¯ä»¥å¨è¿éæ·»å æ´å¤èªå®ä¹æé®çå¤çé»è¾ // default: // console.warn(`æªå¤ççèªå®ä¹æé®: ${buttonName}`); // } // }; // const handleReturn = (row) => { // // æ£æ¥è®¾å¤ç¶æ // if (row.equipmentStatus === EQUIPMENT_STATUS.RETURNED) { // showToast('该设å¤å·²å½è¿å®æï¼æ é忬¡å½è¿', 'warning'); // return; // } // // if (row.equipmentStatus !== EQUIPMENT_STATUS.USING && row.equipmentStatus !== EQUIPMENT_STATUS.PARTIAL_RETURN) { // showToast('该设å¤å½åç¶æä¸æ¯æå½è¿æä½', 'warning'); // return; // } // // form.value = { ...row }; // addOrEdit.value = "return"; // 设置为å½è¿æ¨¡å¼ // title.value = `å½è¿è®¾å¤ - ${row.equipmentName || ''}`; // copyForm.value = { ...row }; // equipmentRequisitionDialog.value = true; // console.log("å½è¿è®¾å¤ï¼", row); // }; // æ ç¾é¡µåæ¢å¤ç - u-tabsç@changeäºä»¶ä¼ éçæ¯å¯¹è±¡ { index: number } const handleTabChange = (e) => { const index = typeof e === 'object' && e.index !== undefined ? e.index : e; console.log('handleTabChange:', e, 'index:', index, 'tabs:', tabs.value); const selectedTab = tabs.value[index]; if (selectedTab && selectedTab.value !== activeTab.value) { activeTab.value = selectedTab.value; activeTabIndex.value = index; resetState(); getList(); } }; const resetState = () => { form.value = { consumables: false, }; addOrEdit.value = "add"; loading.value = true; tableData.value = []; pageNum.value = 1; pageSize.value = 20; total.value = 0; queryParams.value.searchAll = ""; refreshing.value = false; }; const resetQuery = () => { queryParams.value.searchAll = ""; pageNum.value = 1; getList(); }; const search = () => { pageNum.value = 1; getList(); }; // å¤çæ°å¢é¢ç¨ // const handleAdd = () => { // console.log('handleAdd 被è°ç¨', 'activeTab:', activeTab.value, 'activeTabIndex:', activeTabIndex.value); // addOrEdit.value = "add"; // form.value = {}; // title.value = `æ°å¢${currentTabConfig.value?.label || '设å¤é¢ç¨'}`; // copyForm.value = {}; // console.log('å夿å¼å¼¹çªï¼equipmentRequisitionDialogå½åå¼:', equipmentRequisitionDialog.value); // equipmentRequisitionDialog.value = true; // console.log('å¼¹çªå·²æå¼ï¼equipmentRequisitionDialogæ°å¼:', equipmentRequisitionDialog.value); // }; // const handleView = async (row) => { // // åªå¤ç设å¤é¢ç¨é¡µé¢çæ¥çåè½ // if (activeTab.value === 'equipmentRequisition') { // try { // dialogTableTitle.value = `${row.equipmentName || '设å¤'} - æä½è®°å½è¯¦æ `; // // // æ¾ç¤ºå è½½ç¶æ // dialogTableVisible.value = true; // dialogTableData.value = []; // // // è°ç¨è¯¦æ æ¥å£è·åæ°æ® // const { data, code } = await getUsageDetailList(row.id); // if (code === 200 && data) { // // å¤çæ°ç»æ°æ®ï¼ç´æ¥æ¾ç¤ºæä½è®°å½å表 // if (Array.isArray(data)) { // dialogTableData.value = data; // dialogTableColumns.value = [ // { prop: 'equipmentNo', label: '设å¤ç¼å·', minWidth: 100 }, // { prop: 'equipmentName', label: '设å¤åç§°', minWidth: 120 }, // { prop: 'specification', label: 'è§æ ¼åå·', minWidth: 100 }, // { // prop: 'operationType', // label: 'æä½ç±»å', // minWidth: 80, // formatter: (row) => row.operationType === 1 ? 'é¢ç¨' : 'å½è¿' // }, // { prop: 'quantity', label: 'æä½æ°é', minWidth: 80 }, // { prop: 'operator', label: 'æä½äºº', minWidth: 80 }, // { prop: 'remark', label: '夿³¨', minWidth: 150, showOverflowTooltip: true }, // { prop: 'createTime', label: 'æä½æ¶é´', minWidth: 150 } // ]; // } else { // showToast('ææ æä½è®°å½', 'warning'); // } // } else { // showToast('è·åè¯¦æ æ°æ®å¤±è´¥', 'error'); // dialogTableVisible.value = false; // } // // } catch (error) { // console.error('è·å详æ 失败:', error); // showToast('è·åè¯¦æ æ°æ®å¤±è´¥', 'error'); // dialogTableVisible.value = false; // } // } // }; // 䏿巿° const onRefresh = async () => { refreshing.value = true; pageNum.value = 1; await getList(); refreshing.value = false; }; // è·åè®¾å¤æå¤§å¯é¢ç¨æ°é // const getMaxQuantity = () => { // if (form.value.equipmentId) { // const equipment = tableData.value.find( // (item) => item.equipmentId === form.value.equipmentId // ); // return equipment ? equipment.quantity : null; // } // return null; // }; // 设å¤é¢ç¨å¼¹çªæäº¤å¤ç // const onEquipmentRequisitionSubmit = (formData) => { // if (formData.equipmentStatus === EQUIPMENT_STATUS.RETURNED) { // showToast("设å¤å·²å®å ¨å½è¿", 'success'); // } else if (formData.equipmentStatus === EQUIPMENT_STATUS.PARTIAL_RETURN) { // } else if (formData.equipmentStatus === EQUIPMENT_STATUS.USING) { // showToast("设å¤é¢ç¨æå", 'success'); // } else { // showToast("æä½æå", 'success'); // } // equipmentRequisitionDialog.value = false; // nextTick(() => { // form.value = {}; // getList(); // }); // }; // æ°æ®è·å const getList = async () => { try { if (!refreshing.value) { loading.value = true; } const apiParams = { current: pageNum.value, pageSize: pageSize.value, searchAll: queryParams.value.searchAll, }; const response = await currentTabConfig.value.api(apiParams); console.log('APIååº:', response); // requestå·¥å ·è¿åçæ¯ { code, data, msg } ç»æ const { data, code } = response; if (code !== 200) { showToast("è·åæ°æ®å¤±è´¥ï¼" + (response?.msg || "æªç¥é误"), 'error'); return; } // data æ¯ { records: [...], total: 3, ... } ç»æ const records = data?.records || data?.rows || []; console.log('è§£æåçæ°æ®:', records); // æ°æ®å段æ å°ï¼å° isConsumables æ å°ä¸º consumables const mappedRecords = records.map(item => ({ ...item, consumables: item.isConsumables !== undefined ? item.isConsumables : item.consumables })); // 妿æ¯ä¸æå·æ°ï¼éç½®æ°æ®ï¼å¦åè¿½å æ°æ®ï¼ç¨äºä¸æå è½½æ´å¤ï¼ä½å½åä¸å®ç°ï¼ if (pageNum.value === 1) { tableData.value = mappedRecords; } else { tableData.value = [...tableData.value, ...mappedRecords]; } total.value = data?.total || 0; console.log('æç»è¡¨æ ¼æ°æ®:', tableData.value); } catch (error) { console.error('è·åæ°æ®å¤±è´¥:', error); showToast("è·åæ°æ®å¤±è´¥ï¼è¯·ç¨ååè¯", 'error'); } finally { loading.value = false; } }; // ç»ä»¶æè½½ onMounted(() => { activeTab.value = "management"; activeTabIndex.value = 0; // ä¸éç½®æç´¢æ¡ä»¶ï¼ç´æ¥å è½½æ°æ® loading.value = true; getList(); }); // è¿åä¸ä¸é¡µ const goBack = () => { uni.navigateBack(); }; </script> <style scoped lang="scss"> @import '@/styles/sales-common.scss'; .equipment-management { min-height: 100vh; background: #f8f9fa; position: relative; } .tabs-section { background: #ffffff; padding: 0 20px; } .list-container { flex: 1; height: calc(100vh - 200px); } .loading-container { display: flex; justify-content: center; align-items: center; padding: 40px; color: #999; } .no-data { padding: 40px 0; text-align: center; color: #999; font-size: 14px; } </style> src/pages/management/mould/equipmentRequisitionDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,853 @@ <template> <u-popup :show="visible" mode="center" :closeable="true" @close="handleClose" :round="10" width="90%" :safe-area-inset-bottom="true" > <view class="popup-content"> <view class="popup-header"> <text class="popup-title">{{ isReturnMode ? '设å¤å½è¿' : isEdit ? 'ç¼è¾è®¾å¤é¢ç¨' : 'æ°å¢è®¾å¤é¢ç¨' }}</text> </view> <scroll-view class="form-scroll" scroll-y> <u-form :model="form" :rules="rules" ref="formRef" label-width="110" input-align="right"> <u-form-item label="é¢ç¨äºº" prop="userId" border-bottom required> <u-input v-model="displayUserName" placeholder="è¯·éæ©é¢ç¨äºº" readonly @click="showUserPicker = true" :disabled="isViewMode || isReturnMode" /> <template #right> <u-icon name="arrow-right" @click="showUserPicker = true"></u-icon> </template> </u-form-item> <u-form-item label="设å¤åç§°" prop="equipmentId" border-bottom required> <u-input v-model="displayEquipmentName" placeholder="è¯·éæ©è®¾å¤åç§°" readonly @click="showEquipmentPicker = true" :disabled="isViewMode || isReturnMode" /> <template #right> <u-icon name="arrow-right" @click="showEquipmentPicker = true"></u-icon> </template> </u-form-item> <u-form-item label="æ¯å¦ä¸ºæ¶èå" prop="consumables" border-bottom required> <u-input v-model="displayConsumables" placeholder="è¯·éæ©æ¯å¦ä¸ºæ¶èå" readonly @click="showConsumablesPicker = true" :disabled="isViewMode || isReturnMode" /> <template #right> <u-icon name="arrow-right" @click="showConsumablesPicker = true"></u-icon> </template> </u-form-item> <u-form-item v-if="!isReturnMode && formData.status !== 2" label="é¢ç¨æ°é" prop="usageQuantity" border-bottom required> <view class="number-box-wrapper"> <u-number-box v-model="form.usageQuantity" :min="1" :max="maxQuantity || 999" :disabled="isViewMode" integer @change="onUsageQuantityChange" /> <text v-if="maxQuantity !== null" class="info-text">(æå¤{{ maxQuantity }}å°)</text> </view> </u-form-item> <u-form-item v-if="isReturnMode" label="æ¬æ¬¡å½è¿æ°é" prop="returnQuantity" border-bottom required> <view class="number-box-wrapper"> <u-number-box v-model="form.returnQuantity" :min="1" :max="Math.max(1, remainingReturnQuantity)" :disabled="remainingReturnQuantity <= 0" integer /> <text class="info-text" v-if="remainingReturnQuantity > 0">(æå¤{{ remainingReturnQuantity }}å°)</text> <text class="info-text" v-else>(å·²å ¨é¨å½è¿)</text> </view> </u-form-item> <u-form-item v-if="!isReturnMode" label="使ç¨å¼å§æ¶é´" prop="usageStartTime" border-bottom required> <u-input v-model="form.usageStartTime" placeholder="è¯·éæ©å¼å§æ¶é´" readonly @click="showDatePicker('usageStartTime')" /> <template #right> <u-icon name="arrow-right" @click="showDatePicker('usageStartTime')"></u-icon> </template> </u-form-item> <u-form-item v-if="isReturnMode" label="å½è¿æ¶é´" prop="returnTime" border-bottom required> <u-input v-model="form.returnTime" placeholder="è¯·éæ©å½è¿æ¶é´" readonly @click="showDatePicker('returnTime')" /> <template #right> <u-icon name="arrow-right" @click="showDatePicker('returnTime')"></u-icon> </template> </u-form-item> <u-form-item label="夿³¨" border-bottom> <u-textarea v-model="form.remarks" placeholder="请è¾å ¥å¤æ³¨" :disabled="isViewMode" :autoHeight="true" count /> </u-form-item> </u-form> </scroll-view> <view class="popup-footer"> <u-button @click="handleClose" :customStyle="{ marginRight: '10px', flex: 1 }" :disabled="isSubmitting">åæ¶</u-button> <u-button type="primary" @click="debouncedSubmit" v-if="!isViewMode" :customStyle="{ flex: 1 }" :loading="isSubmitting">ç¡®å®</u-button> </view> </view> <!-- æ¥æéæ©å¨ --> <u-datetime-picker :show="showDatePickerVisible" v-model="datePickerValue" mode="date" @confirm="onDateConfirm" @cancel="showDatePickerVisible = false" /> <!-- ç¨æ·éæ©å¨ --> <up-action-sheet :show="showUserPicker" :actions="userActionList" title="éæ©é¢ç¨äºº" @select="onUserSelect" @close="showUserPicker = false" /> <!-- 设å¤éæ©å¨ --> <up-action-sheet :show="showEquipmentPicker" :actions="equipmentActionList" title="éæ©è®¾å¤åç§°" @select="onEquipmentSelect" @close="showEquipmentPicker = false" /> <!-- æ¶èåéæ©å¨ --> <up-action-sheet :show="showConsumablesPicker" :actions="consumablesActionList" title="éæ©æ¯å¦ä¸ºæ¶èå" @select="onConsumablesSelect" @close="showConsumablesPicker = false" /> </u-popup> </template> <script setup> import { ref, watch, computed, onMounted, nextTick } from "vue"; import { useToast, useModal } from "@/utils/uviewplus"; import { getEquipmentList, userListAll } from "@/api/publicApi/index.js"; import { addOrEditUsageRecord } from "@/api/equipment/requisition/index.js"; import useUserStore from "@/store/modules/user"; const { showToast } = useToast(); const { showModal } = useModal(); const userStore = useUserStore(); let userList = ref([]); // è·åç¨æ·å表 const fetchUserList = async () => { try { const res = await userListAll(); if (res.code === 200) { userList.value = res.data || res.rows || []; console.log('ç¨æ·å表è·åæå:', userList.value); } else { showToast('è·åç¨æ·å表失败', 'error'); } } catch (error) { console.error('è·åç¨æ·å表失败:', error); showToast('è·åç¨æ·å表失败', 'error'); } }; // 鿩卿¾ç¤ºç¶æ const showUserPicker = ref(false); const showEquipmentPicker = ref(false); const showConsumablesPicker = ref(false); // ç¨æ·éæ©å表ï¼ç¨äº action-sheetï¼ const userActionList = computed(() => { return userList.value.map(item => ({ name: item.nickName || item.nickname || item.userName || item.name || 'æªç¥', value: item.userId || item.id })); }); // 设å¤éæ©å表ï¼ç¨äº action-sheetï¼ const equipmentActionList = computed(() => { return equipmentList.value.map(item => ({ name: item.equipmentName || item.name || 'æªç¥è®¾å¤', value: item.id || item.equipmentId })); }); // æ¾ç¤ºç¨çææ¬ï¼ä½¿ç¨ ref ç¡®ä¿ååºå¼ï¼ const displayUserName = ref(''); const displayEquipmentName = ref(''); const displayConsumables = ref(''); // æ¶èåéæ©å表 const consumablesActionList = computed(() => [ { name: 'æ¯', value: true }, { name: 'å¦', value: false } ]); // çå¬ form ååï¼æ´æ°æ¾ç¤ºææ¬ watch( () => form.value.userId, (userId) => { if (userId) { const user = userList.value.find(u => { const uId = u.userId || u.id; return uId == userId || String(uId) === String(userId); }); displayUserName.value = user ? (user.nickName || user.nickname || user.userName || user.name) : ''; } else { displayUserName.value = ''; } }, { immediate: true } ); watch( () => form.value.equipmentId, (equipmentId) => { if (equipmentId) { const equipment = equipmentList.value.find(e => { const eId = e.id || e.equipmentId; return eId == equipmentId || String(eId) === String(equipmentId); }); displayEquipmentName.value = equipment ? (equipment.equipmentName || equipment.name) : ''; } else { displayEquipmentName.value = ''; } }, { immediate: true } ); watch( () => form.value.consumables, (consumables) => { if (consumables === true || consumables === 'true' || consumables === 1) { displayConsumables.value = 'æ¯'; } else if (consumables === false || consumables === 'false' || consumables === 0) { displayConsumables.value = 'å¦'; } else { displayConsumables.value = ''; } }, { immediate: true } ); // ç¨æ·éæ© const onUserSelect = (item) => { const userId = item.value || item.userId || item.id; form.value.userId = userId; // ç«å³æ´æ°æ¾ç¤ºææ¬ const user = userList.value.find(u => { const uId = u.userId || u.id; return uId == userId || String(uId) === String(userId); }); displayUserName.value = user ? (user.nickName || user.nickname || user.userName || user.name) : ''; showUserPicker.value = false; // éæ©ç¨æ·åè·å设å¤å表 getEquipment(); nextTick(() => { if (formRef.value) { formRef.value.validateField('userId'); } }); }; // 设å¤éæ© const onEquipmentSelect = (item) => { const equipmentId = item.value || item.id || item.equipmentId; form.value.equipmentId = equipmentId; // ç«å³æ´æ°æ¾ç¤ºææ¬ const equipment = equipmentList.value.find(e => { const eId = e.id || e.equipmentId; return eId == equipmentId || String(eId) === String(equipmentId); }); displayEquipmentName.value = equipment ? (equipment.equipmentName || equipment.name) : ''; showEquipmentPicker.value = false; nextTick(() => { if (formRef.value) { formRef.value.validateField('equipmentId'); } }); }; // æ¶èåéæ© const onConsumablesSelect = (item) => { const consumables = item.value !== undefined ? item.value : (item.name === 'æ¯' ? true : false); form.value.consumables = consumables; // ç«å³æ´æ°æ¾ç¤ºææ¬ displayConsumables.value = consumables ? 'æ¯' : 'å¦'; showConsumablesPicker.value = false; nextTick(() => { if (formRef.value) { formRef.value.validateField('consumables'); } }); }; // é¢ç¨æ°éåå const onUsageQuantityChange = (value) => { // ç¡®ä¿å¼æ¯æ°åç±»å const numValue = Number(value) || 1; form.value.usageQuantity = numValue; // åªæå¨å¼æææ¶æè§¦åéªè¯ï¼é¿å é»è®¤å¼1触åé误æç¤º if (numValue >= 1) { nextTick(() => { if (formRef.value) { // æ¸ é¤ä¹åçé误ï¼ç¶åéæ°éªè¯ formRef.value.clearValidate('usageQuantity'); formRef.value.validateField('usageQuantity'); } }); } }; // è·å设å¤å表 const equipmentList = ref([]); // è·åææ°æ°æ® const getEquipment = async () => { try { const res = await getEquipmentList(); if (res.code === 200) { equipmentList.value = res.data || res.rows || []; console.log('设å¤å表è·åæå:', equipmentList.value); } else { showToast("è·å设å¤å表失败", 'error'); } } catch (error) { console.error("è·å设å¤å表失败:", error); showToast("è·å设å¤å表失败", 'error'); } }; const props = defineProps({ modelValue: Boolean, formData: { type: Object, default: () => ({}), }, addOrEdit: { type: String, default: "add", }, }); // é»è®¤è¡¨ååå§å¼ const defaultForm = { userId: "", equipmentId: "", consumables: false, usageQuantity: 1, usageStartTime: "", returnQuantity: 1, returnTime: "", remarks: "", }; const form = ref({ ...defaultForm }); const maxQuantity = computed(() => { if (!form.value.equipmentId) return 0; const eq = equipmentList.value.find( (item) => item.id == form.value.equipmentId ); // 鲿¢ä¸º0æè´æ°ï¼æå°ä¸º1 return eq && eq.quantity > 0 ? eq.quantity : 999; }); const emit = defineEmits(["update:modelValue", "submit"]); const visible = computed({ get: () => { console.log('å¼¹çª visible get:', props.modelValue); return props.modelValue; }, set: (v) => { console.log('å¼¹çª visible set:', v); emit("update:modelValue", v); }, }); // çå¬ modelValue åå watch( () => props.modelValue, (newVal) => { console.log('å¼¹çª modelValue åå:', newVal); }, { immediate: true } ); // çå¬è®¾å¤éæ©åå watch( () => form.value.equipmentId, (newId) => { if (newId) { const eq = equipmentList.value.find(item => item.id == newId); } } ); const isViewMode = computed( () => props.addOrEdit === "view" || props.addOrEdit === "viewRow" ); // 夿æ¯å¦ä¸ºå½è¿æ¨¡å¼ const isReturnMode = computed(() => props.addOrEdit === "return"); // 计ç®å©ä½å¯å½è¿æ°é const remainingReturnQuantity = computed(() => { if (!isReturnMode.value || !props.formData.usageQuantity) return 0; const totalUsageQuantity = props.formData.usageQuantity || 0; // æ»ä½¿ç¨æ°é const alreadyReturnedQuantity = props.formData.totalReturnNo || 0; // å·²å½è¿æ°é const remaining = totalUsageQuantity - alreadyReturnedQuantity; // å©ä½å¯å½è¿æ°é return Math.max(0, remaining); // ç¡®ä¿ä¸ä¸ºè´æ° }); const isEdit = computed(() => !!props.formData?.id); const formRef = ref(); const isSubmitting = ref(false); // æ¥æéæ©å¨ const showDatePickerVisible = ref(false); const datePickerValue = ref(Date.now()); const currentDateField = ref(''); // æ¾ç¤ºæ¥æéæ©å¨ const showDatePicker = (field) => { if (isViewMode.value) return; currentDateField.value = field; if (form.value[field]) { datePickerValue.value = new Date(form.value[field]).getTime(); } else { datePickerValue.value = Date.now(); } showDatePickerVisible.value = true; }; // æ¥æç¡®è®¤ const onDateConfirm = (e) => { const date = new Date(e.value); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); form.value[currentDateField.value] = `${year}-${month}-${day}`; showDatePickerVisible.value = false; }; // è·åå½åæ¥æï¼YYYY-MM-DDæ ¼å¼ï¼ const getCurrentDate = () => { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; // ç¡®ä¿åå§åæ¶è·åæ°æ® onMounted(() => { fetchUserList(); getEquipment(); }); // æ è®°æ¯å¦å·²ç»åå§åï¼é¿å watch è¦çç¨æ·éæ© const isInitialized = ref(false); watch( () => props.formData, (val) => { // åªå¨åå§åæ¶æè formData æå®é ååæ¶ææ´æ° if (!isInitialized.value) { if (val && Object.keys(val).length > 0) { // åå¹¶ç°æå¼ï¼é¿å è¦çç¨æ·å·²éæ©çå¼ form.value = { ...defaultForm, ...val }; // å½è¿æ¨¡å¼åå§å if (isReturnMode.value) { form.value.returnTime = getCurrentDate(); const maxReturnQuantity = remainingReturnQuantity.value; form.value.returnQuantity = maxReturnQuantity > 0 ? Math.min(1, maxReturnQuantity) : 0; } } else { form.value = { ...defaultForm }; } isInitialized.value = true; console.log('formData watch åå§åï¼form.value:', form.value); } }, { immediate: true } ); // çå¬å¼¹çªæå¼ï¼éç½®åå§åæ è®°å¹¶è·åæ°æ® watch( () => props.modelValue, (newVal) => { if (newVal) { // å¼¹çªæå¼æ¶éç½®åå§åæ è®°ï¼å 许 formData watch éæ°åå§å isInitialized.value = false; // å¼¹çªæå¼æ¶éæ°è·åæ°æ® if (userList.value.length === 0) { fetchUserList(); } if (equipmentList.value.length === 0) { getEquipment(); } } else { // å¼¹çªå ³éæ¶éç½® isInitialized.value = false; } } ); const rules = computed(() => { const baseRules = { userId: [ { required: true, message: "è¯·éæ©é¢ç¨äºº", trigger: "change", validator: (rule, value, callback) => { if (!form.value.userId) { callback(new Error("è¯·éæ©é¢ç¨äºº")); } else { callback(); } } } ], equipmentId: [ { required: true, message: "è¯·éæ©è®¾å¤åç§°", trigger: "change", validator: (rule, value, callback) => { if (!form.value.equipmentId) { callback(new Error("è¯·éæ©è®¾å¤åç§°")); } else { callback(); } } } ], consumables: [ { required: true, message: "è¯·éæ©æ¯å¦ä¸ºæ¶èå", trigger: "change", validator: (rule, value, callback) => { if (form.value.consumables === undefined || form.value.consumables === null || form.value.consumables === '') { callback(new Error("è¯·éæ©æ¯å¦ä¸ºæ¶èå")); } else { callback(); } } } ], }; if (isReturnMode.value) { // å½è¿æ¨¡å¼çéªè¯è§å return { ...baseRules, returnQuantity: [ { required: true, message: "请è¾å ¥å½è¿æ°é", trigger: "blur" }, { type: "number", min: 1, message: "è³å°å½è¿1å°", trigger: "blur" }, { validator: (rule, value, callback) => { const remaining = remainingReturnQuantity.value; if (remaining <= 0) { callback(new Error("å·²å ¨é¨å½è¿ï¼æ æ³ç»§ç»å½è¿")); } else if (value > remaining) { callback( new Error(`å½è¿æ°éä¸è½å¤§äºå©ä½å¯å½è¿æ°é(${remaining}å°)`) ); } else { callback(); } }, trigger: "blur", }, ], returnTime: [ { required: true, message: "è¯·éæ©å½è¿æ¶é´", trigger: "change" }, ], }; } else { // æ°å¢/ç¼è¾æ¨¡å¼çéªè¯è§å return { ...baseRules, usageQuantity: [ { required: true, message: "请è¾å ¥é¢ç¨æ°é", trigger: "blur", validator: (rule, value, callback) => { const numValue = Number(value) || 0; // 妿å¼ä¸ºç©ºææªå®ä¹ï¼ææ¥é if (value === null || value === undefined || value === '') { callback(new Error("请è¾å ¥é¢ç¨æ°é")); } else if (numValue < 1) { callback(new Error("è³å°é¢ç¨1å°")); } else if (maxQuantity.value !== null && maxQuantity.value > 0 && numValue > maxQuantity.value) { callback(new Error("é¢ç¨æ°éä¸è½å¤§äºè®¾å¤æ°é")); } else { callback(); } } }, ], usageStartTime: [ { required: true, message: "è¯·éæ©å¼å§æ¶é´", trigger: "change" }, ], }; } }); function handleClose() { emit("update:modelValue", false); isSubmitting.value = false; } // éç¨é²æå½æ° function debounce(fn, delay = 800) { let timer = null; return function (...args) { if (timer) clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); timer = null; }, delay); }; } // 鲿åçæäº¤æ¹æ³ const debouncedSubmit = debounce(handleSubmit, 800); async function handleSubmit() { console.log('handleSubmit 被è°ç¨'); console.log('formRef.value:', formRef.value); console.log('å½åè¡¨åæ°æ®:', JSON.stringify(form.value)); if (isSubmitting.value) { console.log('æ£å¨æäº¤ä¸ï¼å¿½ç¥éå¤ç¹å»'); return; } if (!formRef.value) { console.error('formRef ä¸åå¨'); showToast('表åå¼ç¨ä¸åå¨ï¼è¯·å·æ°é¡µé¢éè¯', 'error'); return; } try { console.log('å¼å§éªè¯è¡¨å...'); const valid = await formRef.value.validate().catch((errors) => { console.log('表åéªè¯å¤±è´¥ï¼é误信æ¯:', errors); // 妿éªè¯å¤±è´¥ï¼errors å¯è½æ¯é误æ°ç»æ false if (errors && Array.isArray(errors) && errors.length > 0) { const firstError = errors[0]; showToast(firstError.message || '请å®å表åä¿¡æ¯', 'error'); } else { showToast('请å®å表åä¿¡æ¯', 'error'); } return false; }); console.log('表åéªè¯ç»æ:', valid); if (!valid) { console.log('表åéªè¯å¤±è´¥ï¼åæ¢æäº¤'); return; } console.log('表åéªè¯éè¿ï¼å¼å§æäº¤'); isSubmitting.value = true; let submitData = { ...form.value }; // å½è¿æ¨¡å¼å¤ç if (isReturnMode.value) { const currentReturnQuantity = form.value.returnQuantity; const totalUsageQuantity = props.formData.usageQuantity; const alreadyReturnedQuantity = props.formData.totalReturnNo || 0; const newTotalReturnedQuantity = alreadyReturnedQuantity + currentReturnQuantity; // 夿æ¯å¦å ¨é¨å½è¿å®æ let equipmentStatus = 2; // é»è®¤ä¸ºé¨åå½è¿ let isFullyReturned = newTotalReturnedQuantity >= totalUsageQuantity; if (isFullyReturned) { equipmentStatus = 3; // å ¨é¨å½è¿å®æ // å ¨é¨å½è¿æ¶ç确认æç¤º try { const result = await showModal({ title: 'ç¡®è®¤å ¨é¨å½è¿', content: `确认å°è®¾å¤"${props.formData.equipmentName || 'æªç¥è®¾å¤'}"å ¨é¨å½è¿åï¼å½è¿å设å¤ç¶æå°å为"å·²å½è¿"ã`, confirmText: '确认å½è¿', cancelText: 'åæ¶', type: 'success' }); if (!result) { showToast('已忶å½è¿æä½', 'info'); isSubmitting.value = false; return; } } catch (error) { showToast('已忶å½è¿æä½', 'info'); isSubmitting.value = false; return; } console.log("设å¤å½è¿å®æ:", { 设å¤åç§°: props.formData.equipmentName, æ»ä½¿ç¨æ°é: totalUsageQuantity, æ°çå½è¿æ»æ°: newTotalReturnedQuantity, ç¶æ: "å·²å ¨é¨å½è¿", }); } else { console.log("设å¤é¨åå½è¿:", { 设å¤åç§°: props.formData.equipmentName, æ»ä½¿ç¨æ°é: totalUsageQuantity, å·²å½è¿æ°é: newTotalReturnedQuantity, å©ä½æªå½è¿: totalUsageQuantity - newTotalReturnedQuantity, ç¶æ: "é¨åå½è¿", }); } submitData = { ...props.formData, totalReturnNo: newTotalReturnedQuantity, returnQuantity: currentReturnQuantity, returnTime: form.value.returnTime, equipmentStatus: equipmentStatus, remarks: form.value.remarks, usageQuantity: totalUsageQuantity, }; } let { code, data } = await addOrEditUsageRecord(submitData); if (code !== 200) { showToast(data.msg || "æä½å¤±è´¥", 'error'); isSubmitting.value = false; return; } if (code == 200 && data == 1) { showToast('æä½æå', 'success'); emit("submit", submitData); handleClose(); } else { showToast(data.msg || "æä½å¤±è´¥", 'error'); isSubmitting.value = false; } } catch (error) { console.error("æäº¤å¤±è´¥:", error); showToast("æä½å¤±è´¥ï¼è¯·ç¨ååè¯", 'error'); isSubmitting.value = false; } } </script> <style scoped lang="scss"> @import '@/static/scss/form-common.scss'; .popup-content { width: 90vw; max-width: 500px; background-color: #fff; border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; max-height: 80vh; } .popup-header { padding: 20px; text-align: center; border-bottom: 1px solid #f0f0f0; flex-shrink: 0; } .popup-title { font-size: 18px; font-weight: 600; color: #333; } .form-scroll { flex: 1; max-height: calc(80vh - 140px); overflow-y: auto; } .popup-footer { display: flex; justify-content: center; padding: 15px 20px; border-top: 1px solid #f0f0f0; background-color: #fafafa; flex-shrink: 0; } .number-box-wrapper { display: flex; align-items: center; gap: 12px; width: 100%; justify-content: flex-end; } .info-text { font-size: 12px; color: #999; margin-left: 8px; } .tip-text { font-size: 12px; color: #2979ff; margin-top: 8px; padding: 8px 12px; background: #f0f7ff; border-radius: 4px; } </style> src/pages/management/mould/managementDialog.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,260 @@ <template> <div> <u-popup v-model="dialogVisible" :title="addOrEdit === 'edit' ? 'ç¼è¾' : 'æ°å¢' + '设å¤'" mode="center" :close-on-click-overlay="false" :before-close="handleClose" :border-radius="20" width="90%" > <u-form ref="formRef" :model="formData" :rules="rules" label-width="auto" > <view class="form-row"> <view class="form-col"> <u-form-item label="设å¤ç¼å·" prop="equipmentNo"> <u-input v-model="formData.equipmentNo" placeholder="请è¾å ¥è®¾å¤ç¼å·" :disabled="isViewMode" border="surround" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="设å¤åç§°" prop="equipmentName"> <u-input v-model="formData.equipmentName" placeholder="请è¾å ¥è®¾å¤åç§°" :disabled="isViewMode" border="surround" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="æ¯å¦ä¸ºèæ" prop="consumables"> <u-select v-model="formData.consumables" :list="consumablesOptions" placeholder="è¯·éæ©æ¯å¦ä¸ºèæ" :disabled="isViewMode" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="è§æ ¼åå·" prop="specification"> <u-input v-model="formData.specification" placeholder="请è¾å ¥è§æ ¼åå·" :disabled="isViewMode" border="surround" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="åæ¾ä½ç½®" prop="storageLocation"> <u-input v-model="formData.storageLocation" placeholder="请è¾å ¥åæ¾ä½ç½®" :disabled="isViewMode" border="surround" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="éè´ä»·æ ¼" prop="purchasePrice"> <u-input v-model="formData.purchasePrice" placeholder="请è¾å ¥éè´ä»·æ ¼" :disabled="isViewMode" border="surround" type="number" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="éè´æ¥æ" prop="purchaseDate"> <u-datetime-picker v-model="formData.purchaseDate" mode="date" placeholder="è¯·éæ©éè´æ¥æ" :disabled="isViewMode" /> </u-form-item> </view> </view> <view class="dialog-footer"> <u-button @click="cancelForm" type="default" size="normal">åæ¶</u-button> <u-button v-if="!isViewMode" @click="submitForm" type="primary" size="normal">ç¡®å®</u-button> </view> </u-form> </u-popup> </div> </template> <script setup> import { ref, watch, computed, reactive } from "vue"; import { addOrEditEquipment } from "@/api/equipment/management/index.js"; import { useToast } from "@/utils/uviewplus"; const { showToast } = useToast(); const props = defineProps({ beforeClose: { type: Function, default: () => {}, }, form: { type: Object, default: () => ({}), }, addOrEdit: { type: String, default: "add", }, title: { type: String, default: "", }, }); const emit = defineEmits(["submit", "handleBeforeClose"]); // 计ç®å±æ§ï¼ç»ä¸æ§å¶æ¯å¦ç¦ç¨ const isViewMode = computed(() => props.addOrEdit === "viewRow"); // 表åå¼ç¨åæ°æ® const formRef = ref(null); const formData = ref({}); const copyForm = defineModel("copyForm", { required: true, type: Object, default: () => ({}), }); // å¼¹çªå¯è§æ§ const dialogVisible = defineModel("managementFormDialog", { required: true, type: Boolean, }); // èæé项 const consumablesOptions = reactive([ { label: "æ¯", value: "1" }, { label: "å¦", value: "0" } ]); // çå¬å¤é¨ä¼ å ¥çè¡¨åæ°æ®ååï¼åå¹¶çå¬é»è¾ watch( [() => props.form, () => props.addOrEdit], ([newForm, newAddOrEdit]) => { formData.value = { ...newForm }; }, { deep: true, immediate: true } ); // æäº¤è¡¨å const submitForm = async () => { if (!formRef.value) return; try { const valid = await formRef.value.validate(); if (!valid) return; const result = await addOrEditEquipment({ ...formData.value }); const title = props.title.includes("æ°å¢") ? "æ°å¢" : "ç¼è¾"; if (result.code === 200 && result.msg) { showToast({ title: `${title}æåï¼${result.msg}`, type: 'success' }); emit("submit", { title, ...formData.value, result }); closeDialog(); } else { showToast({ title: `${title}失败ï¼${result.msg}`, type: 'error' }); } } catch (error) { console.error("æäº¤å¤±è´¥:", error); showToast({ title: "æäº¤å¤±è´¥ï¼è¯·éè¯", type: 'error' }); } }; // å ³éå¼¹çªçç»ä¸æ¹æ³ const closeDialog = () => { emit("update:managementFormDialog", false); formData.value = {}; }; // åæ¶è¡¨å const cancelForm = () => { closeDialog(); }; // é置表å const resetForm = () => { if (!formRef.value) return; formData.value = JSON.parse(JSON.stringify(copyForm.value)); }; // å ³éå¼¹çª const handleClose = () => { emit("handleBeforeClose"); closeDialog(); }; const rules = reactive({ equipmentNo: [ { required: true, message: "请è¾å ¥è®¾å¤ç¼å·", trigger: "blur" }, ], equipmentName: [ { required: true, message: "请è¾å ¥è®¾å¤åç§°", trigger: "blur" }, ], consumables: [ { required: true, message: "è¯·éæ©æ¯å¦ä¸ºèæ", trigger: "change" }, ], }); </script> <style lang="scss" scoped> .form-row { display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 20rpx; } .form-col { flex: 1; margin-right: 20rpx; } .form-col:last-child { margin-right: 0; } .dialog-footer { display: flex; justify-content: flex-end; margin-top: 40rpx; flex-direction: row; gap: 20rpx; } // å¼¹çªæ ·å¼éé :deep(.u-popup__content) { padding: 30rpx; } :deep(.u-form-item) { margin-bottom: 20rpx; } </style> src/pages/management/mould/usageRecord.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,265 @@ <template> <div> <u-popup v-model="dialogVisible" :title="addOrEdit === 'edit' ? 'ç¼è¾' : 'æ°å¢' + '设å¤'" mode="center" :close-on-click-overlay="false" :before-close="handleClose" :border-radius="20" width="90%" > <u-form ref="formRef" :model="formData" :rules="rules" label-width="auto" > <view class="form-row"> <view class="form-col"> <u-form-item label="设å¤ç¼å·" prop="equipmentId"> <u-input v-model="formData.equipmentId" placeholder="请è¾å ¥è®¾å¤ç¼å·" :disabled="isViewMode" border="surround" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="设å¤åç§°" prop="equipmentName"> <u-input v-model="formData.equipmentName" placeholder="请è¾å ¥è®¾å¤åç§°" :disabled="isViewMode" border="surround" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="æ°é" prop="quantity"> <u-input v-model="formData.quantity" placeholder="请è¾å ¥æ°é" :disabled="isViewMode" border="surround" type="number" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="è§æ ¼åå·" prop="specification"> <u-input v-model="formData.specification" placeholder="请è¾å ¥è§æ ¼åå·" :disabled="isViewMode" border="surround" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="使ç¨ç¶æ" prop="usageStatus"> <u-select v-model="formData.usageStatus" :list="usageStatusOptions" placeholder="è¯·éæ©ä½¿ç¨ç¶æ" :disabled="isViewMode" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="åæ¾ä½ç½®" prop="storageLocation"> <u-input v-model="formData.storageLocation" placeholder="请è¾å ¥åæ¾ä½ç½®" :disabled="isViewMode" border="surround" /> </u-form-item> </view> </view> <view class="form-row"> <view class="form-col"> <u-form-item label="éè´ä»·æ ¼" prop="purchasePrice"> <u-input v-model="formData.purchasePrice" placeholder="请è¾å ¥éè´ä»·æ ¼" :disabled="isViewMode" border="surround" type="number" /> </u-form-item> </view> <view class="form-col"> <u-form-item label="éè´æ¥æ" prop="purchaseDate"> <u-datetime-picker v-model="formData.purchaseDate" mode="date" placeholder="è¯·éæ©éè´æ¥æ" :disabled="isViewMode" /> </u-form-item> </view> </view> <view class="dialog-footer"> <u-button v-if="!isViewMode" @click="cancelForm" type="default" size="normal">åæ¶</u-button> <u-button v-if="!isViewMode" @click="submitForm" type="primary" size="normal">ç¡®å®</u-button> </view> </u-form> </u-popup> </div> </template> <script setup> import { ref, watch, computed, reactive } from "vue"; import { addOrEditEquipment } from "@/api/equipment/management/index.js"; import { useToast } from "@/utils/uviewplus"; const { showToast } = useToast(); const props = defineProps({ beforeClose: { type: Function, default: () => {}, }, form: { type: Object, default: () => ({}), }, addOrEdit: { type: String, default: "add", }, title: { type: String, default: "", }, }); const emit = defineEmits(["submit", "handleBeforeClose"]); // 计ç®å±æ§ï¼ç»ä¸æ§å¶æ¯å¦ç¦ç¨ const isViewMode = computed(() => props.addOrEdit === "viewRow"); // 表åå¼ç¨åæ°æ® const formRef = ref(null); const formData = ref({}); const copyForm = defineModel("copyForm", { required: true, type: Object, default: () => ({}), }); // å¼¹çªå¯è§æ§ const dialogVisible = defineModel("managementFormDialog", { required: true, type: Boolean, }); // 使ç¨ç¶æé项 const usageStatusOptions = reactive([ { label: "å¯ç¨", value: "1" }, { label: "ç¦ç¨", value: "0" } ]); // çå¬å¤é¨ä¼ å ¥çè¡¨åæ°æ®ååï¼åå¹¶çå¬é»è¾ watch( [() => props.form, () => props.addOrEdit], ([newForm, newAddOrEdit]) => { formData.value = { ...newForm }; }, { deep: true, immediate: true } ); // æäº¤è¡¨å const submitForm = async () => { if (!formRef.value) return; try { const valid = await formRef.value.validate(); if (!valid) return; const result = await addOrEditEquipment({ ...formData.value }); const title = props.title.includes("æ°å¢") ? "æ°å¢" : "ç¼è¾"; if (result.code === 200 && result.msg) { showToast({ title: `${title}æåï¼${result.msg}`, type: 'success' }); emit("submit", { title, ...formData.value, result }); closeDialog(); } else { showToast({ title: `${title}失败ï¼${result.msg}`, type: 'error' }); } } catch (error) { console.error("æäº¤å¤±è´¥:", error); showToast({ title: "æäº¤å¤±è´¥ï¼è¯·éè¯", type: 'error' }); } }; // å ³éå¼¹çªçç»ä¸æ¹æ³ const closeDialog = () => { emit("update:managementFormDialog", false); formData.value = {}; }; // åæ¶è¡¨å const cancelForm = () => { closeDialog(); }; // é置表å const resetForm = () => { if (!formRef.value) return; formData.value = JSON.parse(JSON.stringify(copyForm.value)); }; // å ³éå¼¹çª const handleClose = () => { emit("handleBeforeClose"); closeDialog(); }; const rules = reactive({ equipmentId: [ { required: true, message: "请è¾å ¥è®¾å¤ç¼å·", trigger: "blur" }, ], }); </script> <style lang="scss" scoped> .form-row { display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 20rpx; } .form-col { flex: 1; margin-right: 20rpx; } .form-col:last-child { margin-right: 0; } .dialog-footer { display: flex; justify-content: flex-end; margin-top: 40rpx; flex-direction: row; gap: 20rpx; } // å¼¹çªæ ·å¼éé :deep(.u-popup__content) { padding: 30rpx; } :deep(.u-form-item) { margin-bottom: 20rpx; } </style> src/store/modules/user.ts
@@ -1,4 +1,4 @@ import {logout, getInfo, loginCheckFactory} from "@/api/login"; import {logout, getInfo, login} from "@/api/login"; import { getToken, setToken, removeToken } from "@/utils/auth"; import defAva from "@/static/images/profile.jpg"; import { defineStore } from "pinia"; @@ -26,12 +26,12 @@ }), actions: { // é¨é¨ç»å½ loginCheckFactory(userInfo: any) { login(userInfo: any) { const userName = userInfo.userName const password = userInfo.password const factoryId = userInfo.factoryId return new Promise((resolve, reject) => { loginCheckFactory(userName, password, factoryId).then((res: any) => { login(userName, password, factoryId).then((res: any) => { setToken(res.token) this.token = res.token resolve(null) src/utils/uviewplus.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,82 @@ /** * uview-plus å·¥å ·å½æ°å°è£ * æä¾ç»ä¸çæç¤ºåæ¨¡ææ¡å·¥å · */ /** * ä½¿ç¨ Toast æç¤ºå·¥å · * @returns {{showToast: function}} */ export const useToast = () => { /** * æ¾ç¤ºæç¤ºä¿¡æ¯ * @param {string} message æç¤ºæ¶æ¯ * @param {string} [type='info'] æç¤ºç±»åï¼'success' | 'error' | 'warning' | 'info' * @param {number} [duration=2000] æ¾ç¤ºæ¶é¿ï¼æ¯«ç§ï¼ */ const showToast = (message, type = 'info', duration = 2000) => { // æ å° uview-plus ç徿 ç±»å const iconMap = { success: 'success', error: 'error', warning: 'warning', info: 'info' }; uni.showToast({ title: message, icon: iconMap[type] || 'none', duration: duration }); }; return { showToast }; }; /** * ä½¿ç¨ Modal æ¨¡ææ¡å·¥å · * @returns {{showModal: function}} */ export const useModal = () => { /** * æ¾ç¤ºç¡®è®¤æ¨¡ææ¡ * @param {Object} options é ç½®é项 * @param {string} options.title æ é¢ * @param {string} options.content å 容 * @param {boolean} [options.showCancel=true] æ¯å¦æ¾ç¤ºåæ¶æé® * @param {string} [options.confirmText='确认'] 确认æé®ææ¬ * @param {string} [options.cancelText='åæ¶'] åæ¶æé®ææ¬ * @returns {Promise<boolean>} ç¨æ·ç¡®è®¤ç»æ */ const showModal = (options) => { return new Promise((resolve) => { uni.showModal({ title: options.title || 'æç¤º', content: options.content || '', showCancel: options.showCancel !== false, confirmText: options.confirmText || '确认', cancelText: options.cancelText || 'åæ¶', success: (res) => { resolve(res.confirm); }, fail: () => { resolve(false); } }); }); }; return { showModal }; }; /** * é»è®¤å¯¼åº */ export default { useToast, useModal };