Merge branch 'dev_NEW_pro' into dev_天津_宝东
# Conflicts:
# src/config.js
# src/manifest.json
# src/pages/sales/salesQuotation/detail.vue
# src/pages/sales/salesQuotation/edit.vue
# src/pages/sales/salesQuotation/index.vue
# src/pages/works.vue
已添加54个文件
已修改42个文件
已删除2个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // æ¥è¯¢åºç¡åæ°å表 |
| | | export function getBaseParamList(query) { |
| | | return request({ |
| | | url: "/technologyParam/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åºç¡åæ° |
| | | export function addBaseParam(data) { |
| | | return request({ |
| | | url: "/technologyParam/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾åºç¡åæ° |
| | | export function editBaseParam(data) { |
| | | return request({ |
| | | url: "/technologyParam/edit", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åºç¡åæ° |
| | | export function removeBaseParam(id) { |
| | | return request({ |
| | | url: "/technologyParam/remove/" + id, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // é件页颿¥å£ |
| | | import request from '@/utils/request' |
| | | |
| | | // éä»¶æ¥è¯¢ |
| | | export function attachmentList(query) { |
| | | return request({ |
| | | url: '/storageAttachment/list', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // éä»¶æ°å¢ |
| | | export function createAttachment(data) { |
| | | return request({ |
| | | url: '/storageAttachment/add', |
| | | method: 'post', |
| | | data |
| | | }) |
| | | } |
| | | |
| | | // éä»¶å é¤ |
| | | export function deleteAttachment(data) { |
| | | return request({ |
| | | url: '/storageAttachment/delete', |
| | | method: 'delete', |
| | | data |
| | | }) |
| | | } |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function approveProcessListPage(query) { |
| | | return request({ |
| | | url: '/approveProcess/list', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function getDept(query) { |
| | | return request({ |
| | | url: '/approveProcess/getDept', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/getDept", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function approveProcessGetInfo(query) { |
| | | return request({ |
| | | url: '/approveProcess/get', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/get", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ°å¢å®¡æ¹æµç¨ |
| | | export function approveProcessAdd(query) { |
| | | return request({ |
| | | url: '/approveProcess/add', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹å®¡æ¹æµç¨ |
| | | export function approveProcessUpdate(query) { |
| | | return request({ |
| | | url: '/approveProcess/update', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æäº¤å®¡æ¹ |
| | | export function updateApproveNode(query) { |
| | | return request({ |
| | | url: '/approveNode/updateApproveNode', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveNode/updateApproveNode", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤å®¡æ¹æµç¨ |
| | | export function approveProcessDelete(query) { |
| | | return request({ |
| | | url: '/approveProcess/deleteIds', |
| | | method: 'delete', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/deleteIds", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æ¥è¯¢å®¡æ¹æµç¨ |
| | | export function approveProcessDetails(query) { |
| | | return request({ |
| | | url: '/approveNode/details/' + query, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | return request({ |
| | | url: "/approveNode/details/" + query, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // 审æ¹è¯¦æ
|
| | | export function getDeliveryDetailByShippingNo(query) { |
| | | return request({ |
| | | url: "/shippingInfo/getDateilByShippingNo", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** |
| | | * 书æ¶ç®¡çç¸å
³APIæ¥å£ |
| | | * å
å«ä»åºç®¡çãè´§æ¶ç®¡çãå¾ä¹¦ç®¡ççåè½çæ¥å£ |
| | | */ |
| | | |
| | | /** |
| | | * è·åä»åºå表 |
| | | * @description è·åææä»åºçåºæ¬ä¿¡æ¯å表 |
| | | * @returns {Promise} è¿åä»åºåè¡¨æ°æ® |
| | | */ |
| | | export function getWarehouseList() { |
| | | return request({ |
| | | url: "/warehouse/tree", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ°å¢ä»åº |
| | | * @description å建æ°çä»åºè®°å½ |
| | | * @param {Object} data ä»åºä¿¡æ¯å¯¹è±¡ï¼å
å«ä»åºåç§°çåæ®µ |
| | | * @returns {Promise} è¿åæ°å¢ç»æ |
| | | */ |
| | | export function addWarehouse(data) { |
| | | return request({ |
| | | url: "/warehouse/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ´æ°ä»åºä¿¡æ¯ |
| | | * @description ä¿®æ¹ç°æä»åºçåºæ¬ä¿¡æ¯ |
| | | * @param {Object} data ä»åºä¿¡æ¯å¯¹è±¡ï¼å¿
é¡»å
å«ä»åºID |
| | | * @returns {Promise} è¿åæ´æ°ç»æ |
| | | */ |
| | | export function updateWarehouse(data) { |
| | | return request({ |
| | | url: "/warehouse/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * å é¤ä»åº |
| | | * @description æ ¹æ®ä»åºIDå 餿å®çä»åºè®°å½ |
| | | * @param {string|number} id ä»åºID |
| | | * @returns {Promise} è¿åå é¤ç»æ |
| | | */ |
| | | export function deleteWarehouse(data) { |
| | | return request({ |
| | | url: `/warehouse/delete/`, |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * è·åè´§æ¶å表 |
| | | * @description æ ¹æ®ä»åºIDè·å该ä»åºä¸çææè´§æ¶ä¿¡æ¯ |
| | | * @param {string|number} warehouseId ä»åºID |
| | | * @returns {Promise} è¿åè´§æ¶åè¡¨æ°æ® |
| | | */ |
| | | export function getShelfList(warehouseId) { |
| | | return request({ |
| | | url: `/shelf/list/${warehouseId}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ°å¢è´§æ¶ |
| | | * @description 卿å®ä»åºä¸å建æ°çè´§æ¶è®°å½ |
| | | * @param {Object} data è´§æ¶ä¿¡æ¯å¯¹è±¡ï¼å
å«è´§æ¶åç§°ã屿°ãåæ°çåæ®µ |
| | | * @returns {Promise} è¿åæ°å¢ç»æ |
| | | */ |
| | | export function addShelf(data) { |
| | | return request({ |
| | | url: "/warehouse/goodsShelves/add", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ´æ°è´§æ¶ä¿¡æ¯ |
| | | * @description ä¿®æ¹ç°æè´§æ¶çåºæ¬ä¿¡æ¯ |
| | | * @param {Object} data è´§æ¶ä¿¡æ¯å¯¹è±¡ï¼å¿
é¡»å
å«è´§æ¶ID |
| | | * @returns {Promise} è¿åæ´æ°ç»æ |
| | | */ |
| | | export function updateShelf(data) { |
| | | return request({ |
| | | url: "/warehouse/goodsShelves/update", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * å é¤è´§æ¶ |
| | | * @description æ ¹æ®è´§æ¶IDå 餿å®çè´§æ¶è®°å½ï¼åç«¯è¦æ±ä¼ å
¥ ID æ°ç»ï¼æ¯ææ¹éï¼ |
| | | * @param {Array<string|number>} data è´§æ¶IDæ°ç» |
| | | * @returns {Promise} è¿åå é¤ç»æ |
| | | */ |
| | | export function deleteShelf(data) { |
| | | return request({ |
| | | url: `/warehouse/goodsShelves/delete/`, |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * è·åä»åºç»æ |
| | | * @description è·åæå®ä»åºç宿´ç»æä¿¡æ¯ï¼å
æ¬è´§æ¶ã屿°ãåæ°ç |
| | | * @param {string|number} warehouseId ä»åºID |
| | | * @returns {Promise} è¿åä»åºç宿´ç»ææ°æ® |
| | | */ |
| | | export function getWarehouseStructure(data) { |
| | | return request({ |
| | | url: `/warehouse/goodsShelvesRowcol/list`, |
| | | method: "get", |
| | | params: data, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // ææ¡£åé
管çç¸å
³æ¥å£ |
| | | |
| | | // è·åææ¡£å表ï¼ç¨äºåé
书ç±éæ©ï¼ |
| | | export function getDocumentList() { |
| | | return request({ |
| | | url: "/documentation/list", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // åé
å页æ¥è¯¢ |
| | | export function getBorrowList(params) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/listPage", |
| | | method: "get", |
| | | params: params, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åé
|
| | | export function addBorrow(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ´æ°åé
|
| | | export function updateBorrow(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åé
|
| | | export function deleteBorrow(ids) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // è·ååç±»æ |
| | | export function getCategoryTree() { |
| | | return request({ |
| | | url: "/warehouse/documentClassification/getList", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢åç±» |
| | | export function addCategory(data) { |
| | | return request({ |
| | | url: "/warehouse/documentClassification/add", |
| | | method: "post", |
| | | data: { |
| | | category: data.category, |
| | | parentId: data.parentId, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹åç±» |
| | | export function updateCategory(data) { |
| | | return request({ |
| | | url: "/warehouse/documentClassification/update", |
| | | method: "put", |
| | | data: { |
| | | id: data.id, |
| | | category: data.category, |
| | | }, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åç±» |
| | | export function deleteCategory(ids) { |
| | | return request({ |
| | | url: "/warehouse/documentClassification/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // è·åææ¡£å表ï¼åé¡µï¼ |
| | | export function getDocumentList(query) { |
| | | return request({ |
| | | url: "/documentation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢ææ¡£ |
| | | export function addDocument(data) { |
| | | return request({ |
| | | url: "/documentation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹ææ¡£ |
| | | export function updateDocument(data) { |
| | | return request({ |
| | | url: "/documentation/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤ææ¡£ |
| | | export function deleteDocument(ids) { |
| | | return request({ |
| | | url: "/documentation/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // è·åææ¡£è¯¦æ
|
| | | export function getDocumentDetail(id) { |
| | | return request({ |
| | | url: "/document/" + id, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æç´¢ææ¡£ |
| | | export function searchDocument(query) { |
| | | return request({ |
| | | url: "/document/search", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // è·åä»åºç»æ |
| | | export function getWarehouseStructure() { |
| | | return request({ |
| | | url: "/document/warehouse/structure", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // é件管çç¸å
³æ¥å£ |
| | | // æ·»å éä»¶ |
| | | export function addDocumentationFile(data) { |
| | | return request({ |
| | | url: "/documentation/documentationFile/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // è·åéä»¶å表 |
| | | export function getDocumentationFileList(params) { |
| | | return request({ |
| | | url: "/documentation/documentationFile/listPage", |
| | | method: "get", |
| | | params: params, |
| | | }); |
| | | } |
| | | |
| | | // å é¤éä»¶ |
| | | export function deleteDocumentationFile(ids) { |
| | | return request({ |
| | | url: "/documentation/documentationFile/del", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // ææ¡£åé
管çç¸å
³æ¥å£ |
| | | export function getBorrowList(params) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/listPage", |
| | | method: "get", |
| | | params: params, |
| | | }); |
| | | } |
| | | |
| | | export function addBorrow(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function updateBorrow(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function deleteBorrow(ids) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/delete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // ç»è®¡ç¸å
³æ¥å£ |
| | | // è·åæ»ä½ç»è®¡æ°æ® |
| | | export function getDocumentationOverview() { |
| | | return request({ |
| | | url: "/documentation/overview", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·ååç±»ç»è®¡æ°æ® |
| | | export function getDocumentationCategoryStats() { |
| | | return request({ |
| | | url: "/documentation/category", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åç¶æç»è®¡æ°æ® |
| | | export function getDocumentationStatusStats() { |
| | | return request({ |
| | | url: "/documentation/status", |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢å½è¿è®°å½ |
| | | export function getReturnListPage(query) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/listPageReturn", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å½è¿æä½ |
| | | export function returnDocument(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/revent", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤å½è¿è®°å½ |
| | | export function deleteReturn(ids) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/reventDelete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | //æ ¹æ®ä¹¦ç±idæ¥è¯¢åé
è®°å½ |
| | | export function getBorrowListByDocumentationId(id) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/getByDocumentationId/"+id, |
| | | method: "get" |
| | | }); |
| | | } |
| | | |
| | | // æ´æ°åé
è®°å½ |
| | | export function updateBorrow(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å½è¿æ´æ° |
| | | export function reventUpdate(data) { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/reventUpdate", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // è·åææ¡£å表 |
| | | export function getDocumentList() { |
| | | return request({ |
| | | url: "/documentationBorrowManagement/list", |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // è·åæ¡£æ¡æ»ä½ç»è®¡ |
| | | export function getDocumentStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/overview", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡åç±»ç»è®¡ |
| | | export function getCategoryStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/category", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡ç¶æç»è®¡ |
| | | export function getStatusStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/status", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡åé
ç»è®¡ |
| | | export function getBorrowStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/borrow", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡å¹´åº¦ç»è®¡ |
| | | export function getYearStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/year", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡ä½ç½®ç»è®¡ |
| | | export function getLocationStatistics() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/location", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡è¶å¿ç»è®¡ |
| | | export function getTrendStatistics(params) { |
| | | return request({ |
| | | url: "/fileManagement/statistics/trend", |
| | | method: "get", |
| | | params: params, |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡åé
æè¡ |
| | | export function getBorrowRanking() { |
| | | return request({ |
| | | url: "/fileManagement/statistics/borrowRanking", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åæ¡£æ¡å类详æ
ç»è®¡ |
| | | export function getCategoryDetailStatistics(categoryId) { |
| | | return request({ |
| | | url: `/fileManagement/statistics/categoryDetail/${categoryId}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | |
| | | import request from "@/utils/request"; |
| | | // å页æ¥è¯¢åºåè®°å½å表 |
| | | export const getStockInventoryListPage = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/pagestockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryListPage = params => { |
| | | return request({ |
| | | url: "/stockInventory/pagestockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å页æ¥è¯¢èååºåè®°å½å表ï¼å
å«ååä¿¡æ¯ï¼ |
| | | export const getStockInventoryListPageCombined = params => { |
| | | return request({ |
| | | url: "/stockInventory/pageListCombinedStockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å建åºåè®°å½ |
| | | export const createStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/addstockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const createStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/addstockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // åå°åºåè®°å½ |
| | | export const subtractStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/subtractStockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const subtractStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/subtractStockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/stockInventoryPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryReportList = params => { |
| | | return request({ |
| | | url: "/stockInventory/stockInventoryPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryInAndOutReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/stockInAndOutRecord", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryInAndOutReportList = params => { |
| | | return request({ |
| | | url: "/stockInventory/stockInAndOutRecord", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å»ç»åºåè®°å½ |
| | | export const frozenStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const frozenStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // è§£å»åºåè®°å½ |
| | | export const thawStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const thawStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryByModelId = productModelId => { |
| | | return request({ |
| | | url: "/stockInventory/getByModelId", |
| | | method: "get", |
| | | params: { productModelId }, |
| | | }); |
| | | }; |
| | |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢éè´è¯¦æ
|
| | | export function getPurchaseByCode(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseByCode", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function approveProcessGetInfo(query) { |
| | | return request({ |
| | | url: '/approveProcess/get', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | // BOM å表å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/technologyBom/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ°å¢ BOM |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/technologyBom/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹ BOM |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/technologyBom/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤ BOM |
| | | export function batchDelete(ids) { |
| | | return request({ |
| | | url: "/technologyBom/batchDelete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | // å¤å¶ BOM |
| | | export function copy(data) { |
| | | return request({ |
| | | url: "/technologyBom/copy", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // è·å产åå表 (ç¨äºæ°å¢BOMæ¶éæ©äº§å) |
| | | export function getProductList(query) { |
| | | return request({ |
| | | url: "/product/ledger/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // --- BOM ç»æç¸å
³ --- |
| | | |
| | | // æ ¹æ® BOM ID è·åç»æå表 |
| | | export function queryStructureList(bomId) { |
| | | return request({ |
| | | url: "/technologyBomStructure/listByBomId/" + bomId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // ä¿å BOM ç»æ |
| | | export function addStructure(data) { |
| | | return request({ |
| | | url: "/technologyBomStructure/batchSave", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤ BOM ç»æé¡¹ |
| | | export function deleteStructure(id) { |
| | | return request({ |
| | | url: "/technologyBomStructure/batchDelete/" + id, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function getProcessList(query) { |
| | | return request({ |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function list() { |
| | | return request({ |
| | | url: "/technologyOperation/list", |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/technologyOperation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/technologyOperation/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function del(ids) { |
| | | return request({ |
| | | url: "/technologyOperation/batchDelete", |
| | | method: "delete", |
| | | data: ids, |
| | | }); |
| | | } |
| | | |
| | | export function getProcessParamList(params) { |
| | | return request({ |
| | | url: "/technologyOperationParam/list", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | export function addProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function editProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function deleteProcessParam(id) { |
| | | return request({ |
| | | url: `/technologyOperationParam/batchDelete/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | export function getDeviceLedger(query) { |
| | | return request({ |
| | | url: "/device/ledger/getDeviceLedger", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function getBaseParamList(query) { |
| | | return request({ |
| | | url: "/technologyParam/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // å·¥èºè·¯çº¿ç¸å
³æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢å·¥èºè·¯çº¿å表 |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/technologyRouting/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢å·¥èºè·¯çº¿é¡¹ç®å表 |
| | | export function findProcessRouteItemList(query) { |
| | | return request({ |
| | | url: "/technologyRoutingOperation/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºåæ°å表 |
| | | export function getProcessParamList(query) { |
| | | return request({ |
| | | url: "/technologyRoutingOperationParam/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢BOMç»æ (å·¥èºè·¯çº¿) |
| | | export function queryBomList(bomId) { |
| | | return request({ |
| | | url: "/technologyBomStructure/listByBomId/" + bomId, |
| | | method: "get", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ç产æ¥å·¥é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // è·åå·¥åºåæ°å表-ç产订å |
| | | export function findProcessParamListOrder(query) { |
| | | return request({ |
| | | url: `/productionOrderRoutingOperationParam/list`, |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·¦è¾¹è¡¨æ ¼çæ¥å£ (æ±æ») |
| | | export function salesLedgerProductionAccountingList(query) { |
| | | return request({ |
| | | url: "/productionAccount/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å³è¾¹è¡¨æ ¼çæ¥å£ (æç») |
| | | export function salesLedgerProductionAccountingListProductionDetails(query) { |
| | | return request({ |
| | | url: "/productionAccount/listProductionDetails", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | // ç产订å页颿¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function schedulingListPage(query) { |
| | | // å页æ¥è¯¢ç产订å |
| | | export function productOrderListPage(query) { |
| | | return request({ |
| | | url: "/salesLedger/scheduling/listPage", |
| | | url: "/productionOrder/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // ç产派工 |
| | | export function productionDispatch(query) { |
| | | |
| | | // çäº§è®¢åæº¯æºè¯¦æ
|
| | | export function getOrderDetail(npsNo) { |
| | | return request({ |
| | | url: "/salesLedger/scheduling/productionDispatch", |
| | | method: "post", |
| | | data: query, |
| | | url: "/productionOrder/ordeDetail", |
| | | method: "get", |
| | | params: { npsNo }, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // è·åçäº§è®¢åæ¥æºæ°æ® |
| | | export function getProductOrderSource(id) { |
| | | return request({ |
| | | url: `/productionOrder/source/${id}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // é¢æè¯¦æ
å表 |
| | | export function listMaterialPickingDetail(productionOrderId) { |
| | | return request({ |
| | | url: "/productionOrderPick/detail/" + productionOrderId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è¡¥æè®°å½å表 |
| | | export function listMaterialSupplementRecord(query) { |
| | | return request({ |
| | | url: "/productionOrderPickRecord/feeding", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // è·å颿BOMä¿¡æ¯ (å¯éï¼å¤ç¨) |
| | | export function listMaterialPickingBom(productionOrderId) { |
| | | return request({ |
| | | url: "/productionOrder/pick/" + productionOrderId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åç产订åå
³èçå·¥èºè·¯çº¿ä¸»ä¿¡æ¯ |
| | | export function getOrderProcessRouteMain(orderId) { |
| | | return request({ |
| | | url: "/productionOrderRouting/listMain", |
| | | method: "get", |
| | | params: { orderId }, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢BOMç»æ (ç产订å) |
| | | export function queryOrderBomList(bomId) { |
| | | return request({ |
| | | url: "/productionBomStructure/listByBomId/" + bomId, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºåæ°å表 (ç产订å) |
| | | export function findProcessParamListOrder(query) { |
| | | return request({ |
| | | url: "/productionOrderRoutingOperationParam/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // 主çäº§è®¡åæ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页å表 |
| | | export function productionPlanListPage(query) { |
| | | return request({ |
| | | url: "/productionPlan/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æåæ°æ® |
| | | export function loadProdData(query) { |
| | | return request({ |
| | | url: "/productionPlan/loadProdData", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ±æ»ç»è®¡ |
| | | export function summaryByProductType(query) { |
| | | return request({ |
| | | url: "/productionPlan/summaryByProductType", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ç产æ¥å·¥é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ç产æ¥å·¥ä¸»è¡¨ |
| | | export function productionProductMainListPage(query) { |
| | | return request({ |
| | | url: "/productionProductMain/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å 餿¥å·¥ |
| | | export function productionReportDelete(query) { |
| | | return request({ |
| | | url: "/productionProductMain/delete", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢æå
¥å表 |
| | | export function productionProductInputListPage(query) { |
| | | return request({ |
| | | url: "/productionProductInput/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | // æ ¹æ®IDè·åå·¥å详æ
|
| | | export function getProductWorkOrderById(query) { |
| | | return request({ |
| | | url: "/productWorkOrder/getProductWorkOrderById", |
| | | url: "/productionOperationTask/" + query.id, |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // ç产æ¥å·¥ |
| | |
| | | |
| | | export function productWorkOrderPage(query) { |
| | | return request({ |
| | | url: "/productWorkOrder/page", |
| | | url: "/productionOperationTask/page", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | |
| | | |
| | | export function updateProductWorkOrder(data) { |
| | | return request({ |
| | | url: "/productWorkOrder/updateProductWorkOrder", |
| | | url: "/productionOperationTask/updateProductWorkOrder", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | |
| | | }); |
| | | } |
| | | |
| | | export function assignProductWorkOrder(data) { |
| | | return request({ |
| | | url: "/productionOperationTask/assign", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¸è½½å·¥åæµè½¬å¡ï¼è¿åæä»¶æµï¼ |
| | | export function downProductWorkOrder(id) { |
| | | return request({ |
| | | url: "/productWorkOrder/down", |
| | | url: "/productionOperationTask/down", |
| | | method: "post", |
| | | data: { id }, |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºç»è®¡æ°æ® |
| | | export function getOperationStatistics(query) { |
| | | return request({ |
| | | url: "/productionOperationTask/getOperation", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="common-upload"> |
| | | <u-upload |
| | | :fileList="internalFileList" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | :name="name" |
| | | :multiple="multiple" |
| | | :maxCount="maxCount" |
| | | :accept="accept" |
| | | :disabled="disabled" |
| | | ></u-upload> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch, onMounted } from 'vue'; |
| | | import { getToken } from "@/utils/auth"; |
| | | import config from "@/config"; |
| | | |
| | | const props = defineProps({ |
| | | // ç¶ç»ä»¶ä¼ å
¥çæä»¶å表ï¼å¯¹åºå端åå¨ç对象åè¡¨ï¼ |
| | | modelValue: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | // æå¤§ä¸ä¼ æ°é |
| | | maxCount: { |
| | | type: Number, |
| | | default: 9 |
| | | }, |
| | | // æ¯å¦æ¯æå¤é |
| | | multiple: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // æ¥åçæä»¶ç±»å |
| | | accept: { |
| | | type: String, |
| | | default: 'image' |
| | | }, |
| | | // ä¸ä¼ æ¥å£å¯¹åºçåæ°å |
| | | name: { |
| | | type: String, |
| | | default: 'file' |
| | | }, |
| | | // æ¯å¦ç¦ç¨ |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:modelValue']); |
| | | |
| | | // ç¨äº u-upload æ¾ç¤ºçå
é¨å表 |
| | | const internalFileList = ref([]); |
| | | |
| | | // çå¬å¤é¨ modelValue ååï¼åæ¥å°å
鍿¾ç¤ºå表 |
| | | watch(() => props.modelValue, (newVal) => { |
| | | if (newVal) { |
| | | internalFileList.value = newVal.map(item => ({ |
| | | ...item, |
| | | url: item.url || item.previewURL, |
| | | status: 'success', |
| | | message: '' |
| | | })); |
| | | } |
| | | }, { immediate: true, deep: true }); |
| | | |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | // ä¸ä¼ é»è¾ |
| | | const uploadFilePromise = (url) => { |
| | | return new Promise((resolve, reject) => { |
| | | uni.uploadFile({ |
| | | url: config.baseUrl + "/common/upload", |
| | | filePath: url, |
| | | name: "files", // 注æï¼è¿éæ ¹æ®åä»£ç æ¯ "files" |
| | | header: { |
| | | Authorization: "Bearer " + getToken(), |
| | | }, |
| | | success: (res) => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | // 妿è¿åçæ¯æ°ç»ï¼å第ä¸ä¸ªå
ç´ |
| | | const resultData = Array.isArray(data.data) ? data.data[0] : data.data; |
| | | |
| | | // å¤ç url èµå¼ |
| | | if (!resultData.url && resultData.previewURL) { |
| | | resultData.url = resultData.previewURL; |
| | | } |
| | | // å
¼å®¹å代ç ä¸ç name èµå¼ |
| | | if (!resultData.name && resultData.originalFilename) { |
| | | resultData.name = resultData.originalFilename; |
| | | } |
| | | |
| | | resolve(resultData); |
| | | } else { |
| | | reject(data.msg || "ä¸ä¼ 失败"); |
| | | } |
| | | } catch (e) { |
| | | reject("è§£æååºå¤±è´¥"); |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | }, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // ä¸ä¼ åçå¤ç |
| | | const afterRead = async (event) => { |
| | | let lists = [].concat(event.file); |
| | | let currentModelValue = [...props.modelValue]; |
| | | |
| | | // å
å¨å
é¨åè¡¨ä¸æ·»å å ä½ï¼ä¸ä¼ ä¸ç¶æï¼ |
| | | lists.forEach(item => { |
| | | internalFileList.value.push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: 'ä¸ä¼ ä¸' |
| | | }); |
| | | }); |
| | | |
| | | for (let i = 0; i < lists.length; i++) { |
| | | try { |
| | | const result = await uploadFilePromise(lists[i].url); |
| | | |
| | | // æ´æ° modelValue |
| | | currentModelValue.push(result); |
| | | emit('update:modelValue', currentModelValue); |
| | | |
| | | } catch (e) { |
| | | // 妿ä¸ä¼ 失败ï¼ä»å
é¨å表ä¸ç§»é¤åææ·»å ç项 |
| | | const errorIndex = internalFileList.value.findIndex(item => item.status === 'uploading'); |
| | | if (errorIndex > -1) { |
| | | internalFileList.value.splice(errorIndex, 1); |
| | | } |
| | | showToast(typeof e === "string" ? e : "ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // å é¤å¤ç |
| | | const deleteFile = (event) => { |
| | | const newList = [...props.modelValue]; |
| | | newList.splice(event.index, 1); |
| | | emit('update:modelValue', newList); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .common-upload { |
| | | width: 100%; |
| | | } |
| | | </style> |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/basicParameters/index", |
| | | "style": { |
| | | "navigationBarTitleText": "åºç¡åæ°", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/basicParameters/edit", |
| | | "style": { |
| | | "navigationBarTitleText": "åæ°è¯¦æ
", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/processManagement/index", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¥åºç®¡ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/processManagement/edit", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¥åºè¯¦æ
", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/processManagement/params", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¥åºåæ°é
ç½®", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/bom/index", |
| | | "style": { |
| | | "navigationBarTitleText": "BOM管ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionDesign/bom/structure", |
| | | "style": { |
| | | "navigationBarTitleText": "BOMç»æ", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/cooperativeOffice/collaborativeApproval/index1", |
| | | "style": { |
| | | "navigationBarTitleText": "å
¬åºç®¡ç", |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/inspectionUpload/upload", |
| | | "style": { |
| | | "navigationBarTitleText": "ä¸ä¼ å·¡æ£è®°å½", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/inspectionUpload/attachment", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¥çéä»¶", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/equipmentManagement/faultAnalysis/index", |
| | | "style": { |
| | | "navigationBarTitleText": "æ
éåæè¿½æº¯", |
| | |
| | | "path": "pages/productionManagement/productionOrder/index", |
| | | "style": { |
| | | "navigationBarTitleText": "ç产订å", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionOrder/source", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¥æºæ°æ®", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionOrder/pickingDetail", |
| | | "style": { |
| | | "navigationBarTitleText": "é¢æè¯¦æ
", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/processRoute/index", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¥èºè·¯çº¿", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/processRoute/items", |
| | | "style": { |
| | | "navigationBarTitleText": "路线项ç®", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionReporting/ledger", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¥å·¥å°è´¦", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/workOrder/index", |
| | | "style": { |
| | | "navigationBarTitleText": "ç产工å", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | // { |
| | | // "path": "pages/productionManagement/productionCosting/index", |
| | | // "style": { |
| | | // "navigationBarTitleText": "çäº§æ ¸ç®", |
| | | // "navigationStyle": "custom" |
| | | // } |
| | | // }, |
| | | { |
| | | "path": "pages/productionManagement/mainProductionPlan/index", |
| | | "style": { |
| | | "navigationBarTitleText": "主ç产计å", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/mainProductionPlan/detail", |
| | | "style": { |
| | | "navigationBarTitleText": "ç产计å详æ
", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionScheduling/index", |
| | | "style": { |
| | | "navigationBarTitleText": "ç产æäº§", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionAccounting/index", |
| | | "style": { |
| | | "navigationBarTitleText": "çäº§æ ¸ç®", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/productionTraceability/index", |
| | | "style": { |
| | | "navigationBarTitleText": "ç产追溯", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/productionManagement/processStatistics/index", |
| | | "style": { |
| | | "navigationBarTitleText": "å·¥åºç产å®åµ", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/inventoryManagement/receiptManagement/index", |
| | | "style": { |
| | |
| | | "style": { |
| | | "navigationBarTitleText": "æ¶æ¯ä¸å¿" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/fileManagement/borrow/index", |
| | | "style": { |
| | | "navigationBarTitleText": "åé
管ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/fileManagement/borrow/edit", |
| | | "style": { |
| | | "navigationBarTitleText": "åé
ç»è®°", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/fileManagement/return/index", |
| | | "style": { |
| | | "navigationBarTitleText": "å½è¿ç®¡ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/fileManagement/return/edit", |
| | | "style": { |
| | | "navigationBarTitleText": "å½è¿ç»è®°", |
| | | "navigationStyle": "custom" |
| | | } |
| | | } |
| | | ], |
| | | "subPackages": [ |
| | |
| | | "navigationBarTitleText": "RuoYi", |
| | | "navigationBarBackgroundColor": "#FFFFFF" |
| | | } |
| | | } |
| | | } |
| | |
| | | <template> |
| | | <view class="approve-page"> |
| | | |
| | | <PageHeader title="å®¡æ ¸" @back="goBack" /> |
| | | |
| | | <PageHeader title="å®¡æ ¸" |
| | | @back="goBack" /> |
| | | <!-- ç³è¯·ä¿¡æ¯ --> |
| | | <view class="application-info"> |
| | | <view class="info-header"> |
| | |
| | | <text class="info-label">ç³è¯·æ¥æ</text> |
| | | <text class="info-value">{{ approvalData.approveTime }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approvalData.approveType === 2"> |
| | | <view class="info-row"> |
| | |
| | | <text class="info-value">{{ approvalData.endDate || '-' }}</text> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <view v-if="approvalData.approveType === 3" class="info-row"> |
| | | <view v-if="approvalData.approveType === 3" |
| | | class="info-row"> |
| | | <text class="info-label">åºå·®å°ç¹</text> |
| | | <text class="info-value">{{ approvalData.location || '-' }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <view v-if="approvalData.approveType === 4" class="info-row"> |
| | | <view v-if="approvalData.approveType === 4" |
| | | class="info-row"> |
| | | <text class="info-label">æ¥ééé¢</text> |
| | | <text class="info-value">{{ approvalData.price ? `Â¥${approvalData.price}` : '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å®¡æ¹æµç¨ --> |
| | | <view class="approval-process"> |
| | | <view class="process-header"> |
| | | <text class="process-title">å®¡æ¹æµç¨</text> |
| | | </view> |
| | | |
| | | <view class="process-steps"> |
| | | <view |
| | | v-for="(step, index) in approvalSteps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | <view v-for="(step, index) in approvalSteps" |
| | | :key="index" |
| | | class="process-step" |
| | | :class="{ |
| | | 'completed': step.status === 'completed', |
| | | 'current': step.status === 'current', |
| | | 'pending': step.status === 'pending', |
| | | 'rejected': step.status === 'rejected' |
| | | }" |
| | | > |
| | | }"> |
| | | <view class="step-indicator"> |
| | | <view class="step-dot"> |
| | | <text v-if="step.status === 'completed'" class="step-icon">â</text> |
| | | <text v-else-if="step.status === 'rejected'" class="step-icon">â</text> |
| | | <text v-else class="step-number">{{ index + 1 }}</text> |
| | | <text v-if="step.status === 'completed'" |
| | | class="step-icon">â</text> |
| | | <text v-else-if="step.status === 'rejected'" |
| | | class="step-icon">â</text> |
| | | <text v-else |
| | | class="step-number">{{ index + 1 }}</text> |
| | | </view> |
| | | <view v-if="index < approvalSteps.length - 1" class="step-line"></view> |
| | | <view v-if="index < approvalSteps.length - 1" |
| | | class="step-line"></view> |
| | | </view> |
| | | |
| | | <view class="step-content"> |
| | | <view class="step-info"> |
| | | <text class="step-title">{{ step.title }}</text> |
| | | <text class="step-approver">{{ step.approverName }}</text> |
| | | <text v-if="step.approveTime" class="step-time">{{ step.approveTime }}</text> |
| | | <text v-if="step.approveTime" |
| | | class="step-time">{{ step.approveTime }}</text> |
| | | </view> |
| | | |
| | | <view v-if="step.opinion" class="step-opinion"> |
| | | <view v-if="step.opinion" |
| | | class="step-opinion"> |
| | | <text class="opinion-label">å®¡æ¹æè§ï¼</text> |
| | | <text class="opinion-content">{{ step.opinion }}</text> |
| | | </view> |
| | | <!-- ç¾åå±ç¤º --> |
| | | <view v-if="step.urlTem" class="step-opinion" style="margin-top:8px;"> |
| | | <view v-if="step.urlTem" |
| | | class="step-opinion" |
| | | style="margin-top:8px;"> |
| | | <text class="opinion-label">ç¾åï¼</text> |
| | | <image :src="step.urlTem" mode="widthFix" style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | <image :src="step.urlTem" |
| | | mode="widthFix" |
| | | style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å®¡æ ¸æè§è¾å
¥ --> |
| | | <view v-if="canApprove" class="approval-input"> |
| | | <view v-if="canApprove" |
| | | class="approval-input"> |
| | | <view class="input-header"> |
| | | <text class="input-title">å®¡æ ¸æè§</text> |
| | | </view> |
| | | |
| | | <view class="input-content"> |
| | | <u-textarea |
| | | v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请è¾å
¥å®¡æ ¸æè§" |
| | | maxlength="200" |
| | | count |
| | | /> |
| | | <u-textarea v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请è¾å
¥å®¡æ ¸æè§" |
| | | maxlength="200" |
| | | count /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- åºé¨æä½æé® --> |
| | | <view v-if="canApprove" class="footer-actions"> |
| | | <u-button class="reject-btn" @click="handleReject">驳å</u-button> |
| | | <u-button class="approve-btn" @click="handleApprove">éè¿</u-button> |
| | | <view v-if="canApprove" |
| | | class="footer-actions"> |
| | | <u-button class="reject-btn" |
| | | @click="handleReject">驳å</u-button> |
| | | <u-button class="approve-btn" |
| | | @click="handleApprove">éè¿</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { approveProcessGetInfo, approveProcessDetails, updateApproveNode } from '@/api/collaborativeApproval/approvalProcess' |
| | | import useUserStore from '@/store/modules/user' |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { ref, onMounted, computed } from "vue"; |
| | | import { |
| | | approveProcessGetInfo, |
| | | approveProcessDetails, |
| | | updateApproveNode, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const userStore = useUserStore() |
| | | const approvalData = ref({}) |
| | | const approvalSteps = ref([]) |
| | | const approvalOpinion = ref('') |
| | | const approveId = ref('') |
| | | const userStore = useUserStore(); |
| | | const approvalData = ref({}); |
| | | const approvalSteps = ref([]); |
| | | const approvalOpinion = ref(""); |
| | | const approveId = ref(""); |
| | | |
| | | // ä»è¯¦æ
æ¥å£åæ®µå¯¹é½ canApproveï¼ä»
彿 isShen çèç¹æ¶å¯å®¡æ¹ |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true) |
| | | }) |
| | | // ä»è¯¦æ
æ¥å£åæ®µå¯¹é½ canApproveï¼ä»
彿 isShen çèç¹æ¶å¯å®¡æ¹ |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync('approveId') |
| | | if (approveId.value) { |
| | | loadApprovalData() |
| | | } |
| | | }) |
| | | |
| | | const loadApprovalData = () => { |
| | | // åºæ¬ç³è¯·ä¿¡æ¯ |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {} |
| | | }) |
| | | // 审æ¹èç¹è¯¦æ
|
| | | approveProcessDetails(approveId.value).then(res => { |
| | | const list = Array.isArray(res.data) ? res.data : [] |
| | | // ä¿ååå§èç¹æ°æ®ä¾æäº¤ä½¿ç¨ |
| | | activities.value = list |
| | | |
| | | approvalSteps.value = list.map((it, idx) => { |
| | | // èç¹ç¶ææ å°ï¼1=éè¿ï¼2=ä¸éè¿ï¼å¦åçæ¯å¦å½å(isShen)ï¼åé»è®¤ä¸ºå¾
å¤ç |
| | | let status = 'pending' |
| | | if (it.approveNodeStatus === 1) status = 'completed' |
| | | else if (it.approveNodeStatus === 2) status = 'rejected' |
| | | else if (it.isShen) status = 'current' |
| | | return { |
| | | title: `第${idx + 1}æ¥å®¡æ¹`, |
| | | approverName: it.approveNodeUser || 'æªç¥ç¨æ·', |
| | | status, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || '', |
| | | urlTem: it.urlTem || '', |
| | | isShen: !!it.isShen |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync('approveId'); |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | const submitForm = (status) => { |
| | | // å¯éï¼æ ¡éªå®¡æ ¸æè§ |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast('请è¾å
¥å®¡æ ¸æè§') |
| | | return |
| | | } |
| | | // æ¾å°å½åå¯å®¡æ¹èç¹ |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen) |
| | | if (!filteredActivities.length) { |
| | | showToast('å½åæ å¯å®¡æ¹èç¹') |
| | | return |
| | | } |
| | | // åå
¥ç¶æåæè§ |
| | | filteredActivities[0].approveNodeStatus = status |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || '' |
| | | // è®¡ç®æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length - 1 |
| | | // è°ç¨å端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? '审æ¹éè¿' : '审æ¹å·²é©³å' |
| | | showToast(msg) |
| | | // æç¤ºåè¿åä¸ä¸ä¸ªé¡µé¢ |
| | | setTimeout(() => { |
| | | goBack() // å
鍿¯ uni.navigateBack() |
| | | }, 800) |
| | | }) |
| | | } |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: '确认æä½', |
| | | content: 'ç¡®å®è¦éè¿æ¤å®¡æ¹åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(1) |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync("approveId"); |
| | | if (approveId.value) { |
| | | loadApprovalData(); |
| | | } |
| | | }) |
| | | } |
| | | }); |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: '确认æä½', |
| | | content: 'ç¡®å®è¦é©³åæ¤å®¡æ¹åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(2) |
| | | const loadApprovalData = () => { |
| | | // åºæ¬ç³è¯·ä¿¡æ¯ |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {}; |
| | | }); |
| | | // 审æ¹èç¹è¯¦æ
|
| | | approveProcessDetails(approveId.value).then(res => { |
| | | const list = Array.isArray(res.data) ? res.data : []; |
| | | // ä¿ååå§èç¹æ°æ®ä¾æäº¤ä½¿ç¨ |
| | | activities.value = list; |
| | | |
| | | approvalSteps.value = list.map((it, idx) => { |
| | | // èç¹ç¶ææ å°ï¼1=éè¿ï¼2=ä¸éè¿ï¼å¦åçæ¯å¦å½å(isShen)ï¼åé»è®¤ä¸ºå¾
å¤ç |
| | | let status = "pending"; |
| | | if (it.approveNodeStatus === 1) status = "completed"; |
| | | else if (it.approveNodeStatus === 2) status = "rejected"; |
| | | else if (it.isShen) status = "current"; |
| | | return { |
| | | title: `第${idx + 1}æ¥å®¡æ¹`, |
| | | approverName: it.approveNodeUser || "æªç¥ç¨æ·", |
| | | status, |
| | | approveTime: it.approveTime || null, |
| | | opinion: it.approveNodeReason || "", |
| | | urlTem: it.urlTem || "", |
| | | isShen: !!it.isShen, |
| | | }; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync("approveId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const submitForm = status => { |
| | | // å¯éï¼æ ¡éªå®¡æ ¸æè§ |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast("请è¾å
¥å®¡æ ¸æè§"); |
| | | return; |
| | | } |
| | | }) |
| | | } |
| | | // åå§èç¹æ°æ®ï¼ç¨äºæäº¤é»è¾ï¼ |
| | | const activities = ref([]) |
| | | // æ¾å°å½åå¯å®¡æ¹èç¹ |
| | | const filteredActivities = activities.value.filter( |
| | | activity => activity.isShen |
| | | ); |
| | | if (!filteredActivities.length) { |
| | | showToast("å½åæ å¯å®¡æ¹èç¹"); |
| | | return; |
| | | } |
| | | // åå
¥ç¶æåæè§ |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || ""; |
| | | // è®¡ç®æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = |
| | | activities.value.findIndex(a => a.isShen) === activities.value.length - 1; |
| | | // è°ç¨å端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? "审æ¹éè¿" : "审æ¹å·²é©³å"; |
| | | showToast(msg); |
| | | // æç¤ºåè¿åä¸ä¸ä¸ªé¡µé¢ |
| | | setTimeout(() => { |
| | | goBack(); // å
鍿¯ uni.navigateBack() |
| | | }, 800); |
| | | }); |
| | | }; |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: "确认æä½", |
| | | content: "ç¡®å®è¦éè¿æ¤å®¡æ¹åï¼", |
| | | success: res => { |
| | | if (res.confirm) submitForm(1); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: "确认æä½", |
| | | content: "ç¡®å®è¦é©³åæ¤å®¡æ¹åï¼", |
| | | success: res => { |
| | | if (res.confirm) submitForm(2); |
| | | }, |
| | | }); |
| | | }; |
| | | // åå§èç¹æ°æ®ï¼ç¨äºæäº¤é»è¾ï¼ |
| | | const activities = ref([]); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | } |
| | | |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-line { |
| | | display: none; |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | .step-line { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .step-dot { |
| | | width: 32px; |
| | | height: 32px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | position: relative; |
| | | z-index: 2; |
| | | } |
| | | |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .approval-input { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .reject-btn { |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .approval-input { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .reject-btn { |
| | | width: 120px; |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | |
| | | /* éé
u-buttonæ ·å¼ */ |
| | | :deep(.u-button) { |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | } |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | } |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <PageHeader title="å®¡æ¹æµç¨" |
| | | <PageHeader :title="operationType === 'detail' ? '详æ
' : 'å®¡æ¹æµç¨'" |
| | | @back="goBack" /> |
| | | <!-- 表ååºå --> |
| | | <u-form ref="formRef" |
| | |
| | | :rules="rules" |
| | | :model="form" |
| | | label-width="140rpx"> |
| | | <u-form-item prop="approveReason" |
| | | label="æµç¨ç¼å·"> |
| | | <u-input v-model="form.approveId" |
| | | disabled |
| | | placeholder="èªå¨ç¼å·" /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveReason" |
| | | :label="approveType === 5 ? 'éè´äºç±' : 'ç³è¯·äºç±'" |
| | | required> |
| | | <u-input v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :placeholder="approveType === 5 ? '请è¾å
¥éè´äºç±' : '请è¾å
¥ç³è¯·äºç±'" |
| | | show-word-limit /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" |
| | | label="ç³è¯·é¨é¨" |
| | | required> |
| | | <!-- <u-input v-model="form.approveDeptName" |
| | | <template v-if="operationType !== 'detail'"> |
| | | <u-form-item prop="approveReason" |
| | | label="æµç¨ç¼å·"> |
| | | <u-input v-model="form.approveId" |
| | | disabled |
| | | placeholder="èªå¨ç¼å·" /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveReason" |
| | | :label="approveType === 5 ? 'éè´äºç±' : 'ç³è¯·äºç±'" |
| | | required> |
| | | <u-input v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :placeholder="approveType === 5 ? '请è¾å
¥éè´äºç±' : '请è¾å
¥ç³è¯·äºç±'" |
| | | show-word-limit /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" |
| | | label="ç³è¯·é¨é¨" |
| | | required> |
| | | <!-- <u-input v-model="form.approveDeptName" |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" /> --> |
| | | <u-input v-model="form.approveDeptName" |
| | | readonly |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="approveUser" |
| | | <u-input v-model="form.approveDeptName" |
| | | readonly |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- <u-form-item prop="approveUser" |
| | | label="ç³è¯·äºº" |
| | | required> |
| | | <u-input v-model="form.approveUserName" |
| | |
| | | <up-icon name="arrow-right" |
| | | @click="showDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" |
| | | label="å¼å§æ¶é´" |
| | | </u-form-item> --> |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" |
| | | label="å¼å§æ¶é´" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | placeholder="请åå¼å§æ¶é´" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" |
| | | label="ç»ææ¶é´" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | placeholder="请åç»ææ¶é´" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="åºå·®å°ç¹" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | placeholder="请åå¼å§æ¶é´" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | <u-input v-model="form.location" |
| | | placeholder="请è¾å
¥åºå·®å°ç¹" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" |
| | | label="ç»ææ¶é´" |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 4" |
| | | prop="price" |
| | | label="æ¥ééé¢" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | placeholder="请åç»ææ¶é´" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | clearable /> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="åºå·®å°ç¹" |
| | | required> |
| | | <u-input v-model="form.location" |
| | | placeholder="请è¾å
¥åºå·®å°ç¹" |
| | | clearable /> |
| | | </u-form-item> |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 4" |
| | | prop="price" |
| | | label="æ¥ééé¢" |
| | | required> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | clearable /> |
| | | <!-- æ¥ä»·å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isQuotationApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="æ¥ä»·è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="quotationLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentQuotation || !currentQuotation.quotationNo" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºæ¥ä»·è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="æ¥ä»·åå·" |
| | | :value="currentQuotation.quotationNo"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentQuotation.customer"></u-cell> |
| | | <u-cell title="ä¸å¡å" |
| | | :value="currentQuotation.salesperson"></u-cell> |
| | | <u-cell title="æ¥ä»·æ¥æ" |
| | | :value="currentQuotation.quotationDate"></u-cell> |
| | | <u-cell title="æææè³" |
| | | :value="currentQuotation.validDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentQuotation.paymentMethod"></u-cell> |
| | | <u-cell title="æ¥ä»·æ»é¢"> |
| | | <template #value> |
| | | <text style="font-size: 32rpx; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in (currentQuotation.products || [])" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.product }}</text> |
| | | <text style="color: #e6a23c;">Â¥{{ Number(item.unitPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specification }} | åä½: {{ item.unit }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentQuotation.remark" |
| | | style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold;">夿³¨</view> |
| | | <view style="font-size: 26rpx; color: #666; margin-top: 10rpx;">{{ currentQuotation.remark }}</view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <!-- éè´å®¡æ¹è¯¦æ
--> |
| | | <view v-if="isPurchaseApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="éè´è¯¦æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="purchaseLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentPurchase || !currentPurchase.purchaseContractNumber" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºéè´è¯¦æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éè´ååå·" |
| | | :value="currentPurchase.purchaseContractNumber"></u-cell> |
| | | <u-cell title="ä¾åºååç§°" |
| | | :value="currentPurchase.supplierName"></u-cell> |
| | | <u-cell title="项ç®åç§°" |
| | | :value="currentPurchase.projectName"></u-cell> |
| | | <u-cell title="éå®ååå·" |
| | | :value="currentPurchase.salesContractNo"></u-cell> |
| | | <u-cell title="ç¾è®¢æ¥æ" |
| | | :value="currentPurchase.executionDate"></u-cell> |
| | | <u-cell title="å½å
¥æ¥æ" |
| | | :value="currentPurchase.entryDate"></u-cell> |
| | | <u-cell title="仿¬¾æ¹å¼" |
| | | :value="currentPurchase.paymentMethod"></u-cell> |
| | | <u-cell title="ååéé¢"> |
| | | <template #value> |
| | | <text style="font-size: 32rpx; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | </text> |
| | | </template> |
| | | </u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in (currentPurchase.productData || [])" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.productCategory }}</text> |
| | | <text style="color: #e6a23c;">Â¥{{ Number(item.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specificationModel }} | æ°é: {{ item.quantity }} {{ item.unit }} |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #999; margin-top: 4rpx;"> |
| | | å«ç¨åä»·: Â¥{{ Number(item.taxInclusiveUnitPrice ?? 0).toFixed(2) }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <!-- å货审æ¹è¯¦æ
--> |
| | | <view v-if="isDeliveryApproval" |
| | | style="margin: 20rpx 0;"> |
| | | <u-divider text="å货详æ
" |
| | | text-size="28rpx" |
| | | color="#2979ff"></u-divider> |
| | | <u-skeleton :loading="deliveryLoading" |
| | | rows="3" |
| | | animated> |
| | | <view v-if="!currentDelivery || !currentDelivery.shippingInfo" |
| | | style="padding: 40rpx; text-align: center; color: #999;"> |
| | | æªæ¥è¯¢å°å¯¹åºå货详æ
|
| | | </view> |
| | | <view v-else> |
| | | <u-cell-group :border="false"> |
| | | <u-cell title="éå®è®¢å" |
| | | :value="currentDelivery.shippingInfo.salesContractNo || '--'"></u-cell> |
| | | <u-cell title="å货订åå·" |
| | | :value="currentDelivery.shippingInfo.shippingNo || '--'"></u-cell> |
| | | <u-cell title="客æ·åç§°" |
| | | :value="currentDelivery.shippingInfo.customerName || '--'"></u-cell> |
| | | <u-cell title="åè´§ç±»å" |
| | | :value="currentDelivery.shippingInfo.type || '--'"></u-cell> |
| | | <u-cell title="åè´§æ¥æ" |
| | | :value="currentDelivery.shippingInfo.shippingDate || '--'"></u-cell> |
| | | <u-cell title="å®¡æ ¸ç¶æ" |
| | | :value="currentDelivery.shippingInfo.status || '--'"></u-cell> |
| | | <u-cell title="å货车çå·" |
| | | :value="currentDelivery.shippingInfo.shippingCarNumber || '--'"></u-cell> |
| | | <u-cell title="å¿«éå
¬å¸" |
| | | :value="currentDelivery.shippingInfo.expressCompany || '--'"></u-cell> |
| | | <u-cell title="å¿«éåå·" |
| | | :value="currentDelivery.shippingInfo.expressNumber || '--'"></u-cell> |
| | | </u-cell-group> |
| | | <view style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">产åæç»</view> |
| | | <view v-for="(item, index) in deliveryProductList" |
| | | :key="index" |
| | | style="background: #f8f8f8; border-radius: 8rpx; padding: 20rpx; margin-bottom: 10rpx;"> |
| | | <view style="display: flex; justify-content: space-between;"> |
| | | <text style="font-weight: bold;">{{ item.productName }}</text> |
| | | <text style="color: #2979ff;">æ°é: {{ item.deliveryQuantity }}</text> |
| | | </view> |
| | | <view style="font-size: 24rpx; color: #666; margin-top: 10rpx;"> |
| | | è§æ ¼: {{ item.specificationModel }} |
| | | </view> |
| | | <view v-if="item.batchNo" |
| | | style="font-size: 24rpx; color: #999; margin-top: 4rpx;"> |
| | | æ¹å·: {{ item.batchNo }} |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="currentDelivery.shippingInfo.storageBlobVOs && currentDelivery.shippingInfo.storageBlobVOs.length" |
| | | style="margin-top: 20rpx; padding: 0 30rpx;"> |
| | | <view style="font-size: 28rpx; font-weight: bold; margin-bottom: 10rpx;">åè´§å¾ç</view> |
| | | <CommonUpload :model-value="currentDelivery.shippingInfo.storageBlobVOs" |
| | | disabled /> |
| | | </view> |
| | | </view> |
| | | </u-skeleton> |
| | | </view> |
| | | <u-form-item v-if="operationType !== 'detail'" |
| | | label="å¾çéä»¶" |
| | | prop="storageBlobDTOS" |
| | | border-bottom> |
| | | <CommonUpload v-model="form.storageBlobDTOS" /> |
| | | </u-form-item> |
| | | </u-form> |
| | | <!-- éæ©å¨å¼¹çª --> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="éæ©é¨é¨" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åå¼å§æ¶é´éæ©å¨ --> |
| | | <up-popup :show="showStartDate" |
| | | mode="bottom" |
| | | @close="showStartDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åç»ææ¶é´éæ©å¨ --> |
| | | <up-popup :show="showEndDate" |
| | | mode="bottom" |
| | | @close="showEndDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- å®¡æ ¸æµç¨åºå --> |
| | | <view class="approval-process"> |
| | | <view class="approval-header"> |
| | | <text class="approval-title">å®¡æ ¸æµç¨</text> |
| | | <text class="approval-desc">æ¯ä¸ªæ¥éª¤åªè½éæ©ä¸ä¸ªå®¡æ¹äºº</text> |
| | | </view> |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审æ¹äºº</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | | </view> |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | </view> |
| | | <view class="delete-approver-btn" |
| | | @click="removeApprover(stepIndex)">Ã</view> |
| | | </view> |
| | | <view v-else |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">鿩审æ¹äºº</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1" |
| | | @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> |
| | | </view> |
| | | </view> |
| | | <view class="add-step-btn"> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">æ°å¢èç¹</u-button> |
| | | </view> |
| | | </view> |
| | | <template v-if="operationType !== 'detail'"> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="éæ©é¨é¨" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åå¼å§æ¶é´éæ©å¨ --> |
| | | <up-popup :show="showStartDate" |
| | | mode="bottom" |
| | | @close="showStartDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åç»ææ¶é´éæ©å¨ --> |
| | | <up-popup :show="showEndDate" |
| | | mode="bottom" |
| | | @close="showEndDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | </template> |
| | | <!-- åºé¨æé® --> |
| | | <view class="footer-btns"> |
| | | <view class="footer-btns" |
| | | v-if="operationType !== 'detail'"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | onUnmounted, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | watch, |
| | | } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { |
| | |
| | | approveProcessGetInfo, |
| | | approveProcessAdd, |
| | | approveProcessUpdate, |
| | | getDeliveryDetailByShippingNo, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | storageBlobDTOS: [], |
| | | startDate: "", |
| | | endDate: "", |
| | | location: "", |
| | |
| | | const productOptions = ref([]); |
| | | const operationType = ref(""); |
| | | const currentApproveStatus = ref(""); |
| | | const approverNodes = ref([]); |
| | | const userList = ref([]); |
| | | const formRef = ref(null); |
| | | const message = ref(""); |
| | | const showDate = ref(false); |
| | |
| | | const endDateValue = ref(Date.now()); |
| | | const userStore = useUserStore(); |
| | | const approveType = ref(0); |
| | | const isInitialLoading = ref(false); |
| | | |
| | | const quotationLoading = ref(false); |
| | | const currentQuotation = ref({}); |
| | | const purchaseLoading = ref(false); |
| | | const currentPurchase = ref({}); |
| | | const deliveryLoading = ref(false); |
| | | const currentDelivery = ref({}); |
| | | const deliveryProductList = ref([]); |
| | | |
| | | const isQuotationApproval = computed(() => Number(approveType.value) === 6); |
| | | const isPurchaseApproval = computed(() => Number(approveType.value) === 5); |
| | | const isDeliveryApproval = computed(() => Number(approveType.value) === 7); |
| | | |
| | | const getProductOptions = () => { |
| | | getDept().then(res => { |
| | |
| | | })); |
| | | }); |
| | | }; |
| | | const fileList = ref([]); |
| | | let nextApproverId = 2; |
| | | const getCurrentinfo = () => { |
| | | userStore.getInfo().then(res => { |
| | | form.value.approveDeptId = res.user.tenantId; |
| | | console.log(res.user.tenantId, "res.user.tenantId"); |
| | | }); |
| | | }; |
| | | |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.approveTime = formatDateToYMD(e.value); |
| | | currentDate.value = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // æ¾ç¤ºè¯·åå¼å§æ¶é´éæ©å¨ |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åå¼å§æ¶é´éæ© |
| | | const onStartDateConfirm = e => { |
| | | form.value.startDate = formatDateToYMD(e.value); |
| | | showStartDate.value = false; |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åç»ææ¶é´éæ© |
| | | const onEndDateConfirm = e => { |
| | | form.value.endDate = formatDateToYMD(e.value); |
| | | showEndDate.value = false; |
| | | }; |
| | | |
| | | const fetchDetailData = async row => { |
| | | // æ¥ä»·å®¡æ¹ |
| | | if (isQuotationApproval.value) { |
| | | const quotationNo = row?.approveReason; |
| | | if (quotationNo) { |
| | | quotationLoading.value = true; |
| | | getQuotationList({ quotationNo }) |
| | | .then(res => { |
| | | const records = res?.data?.records || []; |
| | | currentQuotation.value = records[0] || {}; |
| | | }) |
| | | .finally(() => { |
| | | quotationLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // éè´å®¡æ¹ |
| | | if (isPurchaseApproval.value) { |
| | | const purchaseContractNumber = row?.approveReason; |
| | | if (purchaseContractNumber) { |
| | | purchaseLoading.value = true; |
| | | getPurchaseByCode({ purchaseContractNumber }) |
| | | .then(res => { |
| | | currentPurchase.value = res; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢éè´è¯¦æ
失败:", err); |
| | | }) |
| | | .finally(() => { |
| | | purchaseLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // åè´§å®¡æ¹ |
| | | if (isDeliveryApproval.value) { |
| | | const deliveryNo = row?.approveReason; |
| | | if (deliveryNo) { |
| | | deliveryLoading.value = true; |
| | | currentDelivery.value = {}; |
| | | deliveryProductList.value = []; |
| | | getDeliveryDetailByShippingNo({ shippingNo: deliveryNo }) |
| | | .then(res => { |
| | | const detailData = res?.data || res || {}; |
| | | currentDelivery.value = detailData; |
| | | deliveryProductList.value = |
| | | detailData.shippingProductDetailDtoList || []; |
| | | }) |
| | | .catch(err => { |
| | | console.error("æ¥è¯¢å货详æ
失败:", err); |
| | | }) |
| | | .finally(() => { |
| | | deliveryLoading.value = false; |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // çå¬å®¡æ¹äºç±ååï¼å¦ææ¯ç¹å®å®¡æ¹ç±»ååå°è¯è·å详æ
|
| | | watch( |
| | | () => form.value.approveReason, |
| | | newVal => { |
| | | if (isInitialLoading.value) return; |
| | | if ( |
| | | newVal && |
| | | (isQuotationApproval.value || |
| | | isPurchaseApproval.value || |
| | | isDeliveryApproval.value) |
| | | ) { |
| | | // å»¶è¿ä¸ä¼å请æ±ï¼é¿å
è¾å
¥è¿ç¨ä¸é¢ç¹è§¦å |
| | | debounceFetchDetail(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | let timer = null; |
| | | const debounceFetchDetail = () => { |
| | | if (timer) clearTimeout(timer); |
| | | timer = setTimeout(() => { |
| | | fetchDetailData(form.value); |
| | | }, 800); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | getProductOptions(); |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveUserName = userStore.nickName; |
| | | form.value.approveTime = getCurrentDate(); |
| | |
| | | approveType.value = uni.getStorageSync("approveType") || 0; |
| | | |
| | | // 妿æ¯ç¼è¾æ¨¡å¼ï¼ä»æ¬å°åå¨è·åæ°æ® |
| | | if (operationType.value === "edit") { |
| | | if (operationType.value === "edit" || operationType.value === "detail") { |
| | | const storedData = uni.getStorageSync("invoiceLedgerEditRow"); |
| | | if (storedData) { |
| | | const row = JSON.parse(storedData); |
| | | fileList.value = row.commonFileList || []; |
| | | form.value.tempFileIds = fileList.value.map(file => file.id); |
| | | currentApproveStatus.value = row.approveStatus; |
| | | |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( |
| | | res => { |
| | | isInitialLoading.value = true; |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }) |
| | | .then(res => { |
| | | form.value = { ...res.data }; |
| | | // 忾审æ¹äºº |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(","); |
| | | approverNodes.value = userIds.map((userId, idx) => { |
| | | const userIdNum = parseInt(userId.trim()); |
| | | // ä»userList䏿¾å°å¯¹åºçç¨æ·ä¿¡æ¯ |
| | | const userInfo = userList.value.find( |
| | | user => user.userId === userIdNum |
| | | ); |
| | | return { |
| | | id: idx + 1, |
| | | userId: userIdNum, |
| | | nickName: userInfo ? userInfo.nickName : null, |
| | | }; |
| | | }); |
| | | nextApproverId = userIds.length + 1; |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼åå§åä¸ä¸ªç©ºç审æ¹èç¹ |
| | | approverNodes.value = [{ id: 1, userId: null, nickName: null }]; |
| | | nextApproverId = 2; |
| | | // 设置å¾çå表æ¾ç¤º |
| | | const fileData = |
| | | res.data.storageBlobVOS || res.data.commonFileList || []; |
| | | if (fileData.length > 0) { |
| | | form.value.storageBlobDTOS = fileData; |
| | | } |
| | | } |
| | | ); |
| | | // è·åé¢å¤è¯¦æ
|
| | | fetchDetailData(res.data); |
| | | }) |
| | | .finally(() => { |
| | | // å»¶è¿ä¸ä¼éç½®ï¼ç¡®ä¿ watch ä¸ä¼è¢«è§¦å |
| | | setTimeout(() => { |
| | | isInitialLoading.value = false; |
| | | }, 100); |
| | | }); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼åå§åä¸ä¸ªç©ºç审æ¹èç¹ |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | } |
| | | |
| | | // çå¬èç³»äººéæ©äºä»¶ |
| | | uni.$on("selectContact", handleSelectContact); |
| | | } catch (error) { |
| | | console.error("è·åé¨é¨æ°æ®å¤±è´¥:", error); |
| | | console.error("è·åæ°æ®å¤±è´¥:", error); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | // ç§»é¤äºä»¶çå¬ |
| | | uni.$off("selectContact", handleSelectContact); |
| | | }); |
| | | onUnmounted(() => {}); |
| | | |
| | | const onConfirm = item => { |
| | | // 设置éä¸çé¨é¨ |
| | |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // æ£æ¥æ¯ä¸ªå®¡æ¹æ¥éª¤æ¯å¦é½æå®¡æ¹äºº |
| | | const hasEmptyStep = approverNodes.value.some(step => !step.nickName); |
| | | if (hasEmptyStep) { |
| | | showToast("请为æ¯ä¸ªå®¡æ¹æ¥éª¤éæ©å®¡æ¹äºº"); |
| | | return; |
| | | } |
| | | |
| | | // æå¨æ£æ¥å¿
å¡«åæ®µï¼é²æ¢å æ°æ®ç±»åé®é¢å¯¼è´çæ ¡éªå¤±è´¥ |
| | | if (!form.value.approveReason || !form.value.approveReason.trim()) { |
| | | showToast("请è¾å
¥ç³è¯·äºç±"); |
| | |
| | | .then(valid => { |
| | | if (valid) { |
| | | // è¡¨åæ ¡éªéè¿ï¼å¯ä»¥æäº¤æ°æ® |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | console.log("approverNodes---", approverNodes.value); |
| | | form.value.approveUserIds = approverNodes.value |
| | | .map(node => node.userId) |
| | | .join(","); |
| | | form.value.approveType = approveType.value; |
| | | form.value.approveDeptId = Number(form.value.approveDeptId); |
| | | // const submitForm = { |
| | | // approveDeptId: form.value.approveDeptId, |
| | | // approveDeptName: form.value.approveDeptName, |
| | | // approveReason: form.value.approveReason, |
| | | // approveTime: form.value.approveTime, |
| | | // approveType: form.value.approveType, |
| | | // approveUser: form.value.approveUser, |
| | | // approveUserIds: form.value.approveUserIds, |
| | | // endDate: form.value.endDate, |
| | | // startDate: form.value.startDate, |
| | | // }; |
| | | // console.log("form.value---", form.value); |
| | | // console.log("submitForm", submitForm); |
| | | |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | | approveProcessAdd(form.value).then(res => { |
| | |
| | | }); |
| | | }; |
| | | |
| | | // å¤çèç³»äººéæ©ç»æ |
| | | const handleSelectContact = data => { |
| | | const { stepIndex, contact } = data; |
| | | // å°éä¸çè系人设置为对åºå®¡æ¹æ¥éª¤ç审æ¹äºº |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | }; |
| | | |
| | | const addApprover = stepIndex => { |
| | | // 跳转å°èç³»äººéæ©é¡µé¢ |
| | | uni.setStorageSync("stepIndex", stepIndex); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", |
| | | }); |
| | | }; |
| | | |
| | | const addApprovalStep = () => { |
| | | // æ·»å æ°çå®¡æ¹æ¥éª¤ |
| | | approverNodes.value.push({ userId: null, nickName: null }); |
| | | }; |
| | | |
| | | const removeApprover = stepIndex => { |
| | | // ç§»é¤å®¡æ¹äºº |
| | | approverNodes.value[stepIndex].userId = null; |
| | | approverNodes.value[stepIndex].nickName = null; |
| | | }; |
| | | |
| | | const removeApprovalStep = stepIndex => { |
| | | // ç¡®ä¿è³å°ä¿çä¸ä¸ªå®¡æ¹æ¥éª¤ |
| | | if (approverNodes.value.length > 1) { |
| | | approverNodes.value.splice(stepIndex, 1); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "è³å°éè¦ä¸ä¸ªå®¡æ¹æ¥éª¤", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.approveTime = formatDateToYMD(e.value); |
| | | currentDate.value = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // æ¾ç¤ºè¯·åå¼å§æ¶é´éæ©å¨ |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åå¼å§æ¶é´éæ© |
| | | const onStartDateConfirm = e => { |
| | | form.value.startDate = formatDateToYMD(e.value); |
| | | showStartDate.value = false; |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åç»ææ¶é´éæ© |
| | | const onEndDateConfirm = e => { |
| | | form.value.endDate = formatDateToYMD(e.value); |
| | | showEndDate.value = false; |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | .approval-header { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .approval-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .approval-desc { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* æ ·å¼å¢å¼ºä¸ºâç®æ´å°åå飿 ¼â */ |
| | | .approval-steps { |
| | | padding-left: 22px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 11px; |
| | | top: 40px; |
| | | bottom: 40px; |
| | | width: 2px; |
| | | background: linear-gradient( |
| | | to bottom, |
| | | #e6f7ff 0%, |
| | | #bae7ff 50%, |
| | | #91d5ff 100% |
| | | ); |
| | | border-radius: 1px; |
| | | } |
| | | } |
| | | |
| | | .approval-step { |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -18px; |
| | | top: 14px; // ä» 8px è°æ´ä¸º 14pxï¼ä¸æåä¸å¿å¯¹é½ |
| | | width: 12px; |
| | | height: 12px; |
| | | background: #fff; |
| | | border: 3px solid #006cfb; |
| | | border-radius: 50%; |
| | | z-index: 2; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | top: 12px; |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .step-title text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | background: #f0f0f0; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | position: relative; |
| | | line-height: 1.4; // ç¡®ä¿æåè¡é«ä¸è´ |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | gap: 12px; |
| | | position: relative; |
| | | border: 1px solid #e6f7ff; |
| | | box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | width: 48px; |
| | | height: 48px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | | } |
| | | |
| | | .avatar-text { |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .approver-info { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-name { |
| | | display: block; |
| | | font-size: 16px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-dept { |
| | | font-size: 12px; |
| | | color: #999; |
| | | background: rgba(0, 108, 251, 0.05); |
| | | padding: 2px 8px; |
| | | border-radius: 8px; |
| | | display: inline-block; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 2px; |
| | | height: 2px; |
| | | background: #006cfb; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .delete-approver-btn { |
| | | font-size: 16px; |
| | | color: #ff4d4f; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%); |
| | | border: 2px dashed #006cfb; |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | color: #006cfb; |
| | | font-size: 14px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | border: 2px solid #006cfb; |
| | | border-radius: 50%; |
| | | opacity: 0; |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .delete-step-btn { |
| | | color: #ff4d4f; |
| | | font-size: 12px; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | padding: 6px 12px; |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 6px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 4px; |
| | | background: #ff4d4f; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .step-line { |
| | | display: none; // éè忥ç线æ¡ï¼ä½¿ç¨ä¼ªå
ç´ ä»£æ¿ |
| | | } |
| | | |
| | | .add-step-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | .account-detail { |
| | | background-color: #fff; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // å¨ç»å®ä¹ |
| | | @keyframes pulse { |
| | | 0% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | 50% { |
| | | transform: scale(1.2); |
| | | opacity: 0.7; |
| | | } |
| | | 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | } |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes ripple { |
| | | 0% { |
| | | transform: translate(-50%, -50%) scale(0.8); |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translate(-50%, -50%) scale(1.6); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | /* 妿已æ .step-lineï¼è¿éæ´ç²¾åå®ä½å°å·¦ä¾§ä¸å°åç¹å¯¹é½ */ |
| | | .step-line { |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 48px; |
| | | width: 2px; |
| | | height: calc(100% - 48px); |
| | | background: #e5e7eb; |
| | | } |
| | | |
| | | .approver-container { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | gap: 12px; |
| | | padding: 10px 0; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 8px 10px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | position: relative; |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | background: #f3f4f6; |
| | | border: 2px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | animation: none; /* ç¦ç¨æè½¬çå¨ç»ï¼åå½ç®æ´ */ |
| | | } |
| | | |
| | | .avatar-text { |
| | | font-size: 14px; |
| | | color: #374151; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | padding: 0; |
| | | } |
| | | |
| | | .add-approver-btn .add-circle { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: 2px dashed #a0aec0; |
| | | border-radius: 50%; |
| | | color: #6b7280; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 22px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .add-approver-btn .add-label { |
| | | color: #3b82f6; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <view class="actions"> |
| | | <!-- <u-button type="primary" |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn edit" |
| | | :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8" |
| | | v-if="!(item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8 || item.approveType == 5 || item.approveType == 6 || item.approveType == 7)" |
| | | @click="handleItemClick(item)"> |
| | | ç¼è¾ |
| | | </u-button> --> |
| | | </u-button> |
| | | <u-button type="info" |
| | | v-if="item.approveType == 5 || item.approveType == 6 || item.approveType == 7" |
| | | size="small" |
| | | class="action-btn detail" |
| | | @click="handleDetailClick(item)"> |
| | | 详æ
|
| | | </u-button> |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn approve" |
| | |
| | | <text>ææ å®¡æ¹æ°æ®</text> |
| | | </view> |
| | | <!-- æµ®å¨æä½æé® --> |
| | | <!-- <view class="fab-button" |
| | | <view class="fab-button" |
| | | v-if="props.approveType != 5 && props.approveType != 6 && props.approveType != 7" |
| | | @click="handleAdd"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | | color="#ffffff"></up-icon> |
| | | </view> --> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¥ç详æ
|
| | | const handleDetailClick = item => { |
| | | uni.setStorageSync("invoiceLedgerEditRow", JSON.stringify(item)); |
| | | uni.setStorageSync("operationType", "detail"); |
| | | uni.setStorageSync("approveId", item.approveId); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/detail", |
| | | }); |
| | | }; |
| | | |
| | | // æ·»å æ°è®°å½ |
| | | const handleAdd = () => { |
| | | uni.setStorageSync("operationType", "add"); |
| | |
| | | placeholder="请è¾å
¥æ¥ä¿®äºº" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="维修人" |
| | | prop="maintenanceName" |
| | | border-bottom> |
| | | <u-input v-model="form.maintenanceName" |
| | | placeholder="请è¾å
¥ç»´ä¿®äºº" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="维修项ç®" |
| | | prop="machineryCategory" |
| | | border-bottom> |
| | | <u-input v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥ç»´ä¿®é¡¹ç®" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="æ
éç°è±¡" |
| | | prop="remark" |
| | | required |
| | |
| | | clearable |
| | | count |
| | | maxlength="200" /> |
| | | </u-form-item> |
| | | <u-form-item label="å¾çéä»¶" |
| | | prop="storageBlobDTOs" |
| | | border-bottom> |
| | | <CommonUpload v-model="form.storageBlobDTOs" /> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | <!-- æäº¤æé® --> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onUnmounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { onShow, onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { |
| | | addRepair, |
| | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref("add"); |
| | | const repairId = ref(""); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | |
| | | onLoad(options => { |
| | | if (options.id) { |
| | | repairId.value = options.id; |
| | | } |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | repairTime: dayjs().format("YYYY-MM-DD"), // æ¥ä¿®æ¥æ |
| | | repairName: undefined, // æ¥ä¿®äºº |
| | | maintenanceName: undefined, // 维修人 |
| | | machineryCategory: undefined, // ç»´ä¿®é¡¹ç® |
| | | remark: undefined, // æ
éç°è±¡ |
| | | storageBlobDTOs: [], // å¾çéä»¶ |
| | | }); |
| | | |
| | | // æ¥ä¿®ç¶æé项 |
| | |
| | | form.value.deviceModel = data.deviceModel; |
| | | form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD"); |
| | | form.value.repairName = data.repairName; |
| | | form.value.maintenanceName = data.maintenanceName; |
| | | form.value.machineryCategory = data.machineryCategory; |
| | | form.value.remark = data.remark; |
| | | form.value.storageBlobDTOs = data.storageBlobVOs || []; |
| | | repairStatusText.value = |
| | | repairStatusOptions.value.find(item => item.value == data.status) |
| | | ?.name || ""; |
| | |
| | | }; |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | // 页颿¾ç¤ºæ¶é»è¾ |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | // 页é¢å è½½æ¶è·å设å¤å表 |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value }; |
| | | |
| | | const { code } = id |
| | | ? await editRepair({ id: id, ...submitData }) |
| | | : await addRepair(submitData); |
| | |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.removeStorageSync("repairId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 使ç¨uni.getStorageSyncè·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | if (repairId.value) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | // å¯éï¼è·å忏
é¤åå¨çidï¼é¿å
å½±ååç»æä½ |
| | | uni.removeStorageSync("repairId"); |
| | | loadForm(repairId.value); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 使ç¨uni.getStorageSyncè·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | return id; |
| | | return repairId.value; |
| | | }; |
| | | </script> |
| | | |
| | |
| | | <text class="detail-value">{{ item.repairName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ
éç°è±¡</text> |
| | | <text class="detail-value">{{ item.remark || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">维修人</text> |
| | | <text class="detail-value">{{ item.maintenanceName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">维修项ç®</text> |
| | | <text class="detail-value">{{ item.machineryCategory || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ
éç°è±¡</text> |
| | | <text class="detail-value">{{ item.remark || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç»´ä¿®ç»æ</text> |
| | |
| | | const edit = id => { |
| | | if (!id) return; |
| | | // 使ç¨uni.setStorageSyncåå¨id |
| | | uni.setStorageSync("repairId", id); |
| | | // uni.setStorageSync("repairId", id); |
| | | uni.navigateTo({ |
| | | url: "/pages/equipmentManagement/repair/add", |
| | | url: "/pages/equipmentManagement/repair/add?id=" + id, |
| | | }); |
| | | }; |
| | | |
| | |
| | | <template> |
| | | <view class="upkeep-add"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader :title="operationType === 'edit' ? 'ç¼è¾ä¿å
»è®¡å' : 'æ°å¢ä¿å
»è®¡å'" @back="goBack" /> |
| | | |
| | | <!-- 表åå
容 --> |
| | | <u-form ref="formRef" :model="form" :rules="formRules" label-width="110px"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <u-form-item label="设å¤åç§°" prop="deviceNameText" required border-bottom> |
| | | <u-input |
| | | v-model="form.deviceNameText" |
| | | placeholder="è¯·éæ©è®¾å¤åç§°" |
| | | readonly |
| | | @click="showDevicePicker" |
| | | clearable |
| | | /> |
| | | <template #right> |
| | | <u-icon name="scan" @click="startScan" class="scan-icon" /> |
| | | </template> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="è§æ ¼åå·" prop="deviceModel" border-bottom> |
| | | <u-input |
| | | v-model="form.deviceModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | readonly |
| | | clearable |
| | | /> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="计åä¿å
»æ¥æ" prop="maintenancePlanTime" required border-bottom> |
| | | <u-input |
| | | v-model="form.maintenancePlanTime" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable |
| | | /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" @click="showDatePicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | |
| | | <!-- æäº¤æé® --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" @click="sendForm" :loading="loading">ä¿å</u-button> |
| | | </view> |
| | | </u-form> |
| | | |
| | | <!-- 设å¤éæ©å¨ --> |
| | | <up-action-sheet |
| | | :show="showDevice" |
| | | :actions="deviceActions" |
| | | title="éæ©è®¾å¤" |
| | | @select="onDeviceConfirm" |
| | | @close="showDevice = false" |
| | | /> |
| | | <up-datetime-picker |
| | | :show="showDate" |
| | | v-model="pickerDateValue" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" |
| | | /> |
| | | |
| | | </view> |
| | | <view class="upkeep-add"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader :title="operationType === 'edit' ? 'ç¼è¾ä¿å
»è®¡å' : 'æ°å¢ä¿å
»è®¡å'" |
| | | @back="goBack" /> |
| | | <!-- 表åå
容 --> |
| | | <u-form ref="formRef" |
| | | :model="form" |
| | | :rules="formRules" |
| | | label-width="110px"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <u-form-item label="设å¤åç§°" |
| | | prop="deviceNameText" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.deviceNameText" |
| | | placeholder="è¯·éæ©è®¾å¤åç§°" |
| | | readonly |
| | | @click="showDevicePicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="scan" |
| | | @click="startScan" |
| | | class="scan-icon" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="è§æ ¼åå·" |
| | | prop="deviceModel" |
| | | border-bottom> |
| | | <u-input v-model="form.deviceModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | readonly |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="计åä¿å
»æ¥æ" |
| | | prop="maintenancePlanTime" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.maintenancePlanTime" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" |
| | | @click="showDatePicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="ä¿å
»äºº" |
| | | prop="maintenancePerson" |
| | | border-bottom> |
| | | <u-input v-model="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äºº" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="ä¿å
»é¡¹ç®" |
| | | prop="machineryCategory" |
| | | border-bottom> |
| | | <u-input v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="éä»¶å¾ç" |
| | | prop="storageBlobDTOs" |
| | | border-bottom> |
| | | <CommonUpload v-model="form.storageBlobDTOs" /> |
| | | </u-form-item> |
| | | <!-- æäº¤æé® --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" |
| | | @click="sendForm" |
| | | :loading="loading">ä¿å</u-button> |
| | | </view> |
| | | </u-form> |
| | | <!-- 设å¤éæ©å¨ --> |
| | | <up-action-sheet :show="showDevice" |
| | | :actions="deviceActions" |
| | | title="éæ©è®¾å¤" |
| | | @select="onDeviceConfirm" |
| | | @close="showDevice = false" /> |
| | | <up-datetime-picker :show="showDate" |
| | | v-model="pickerDateValue" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onUnmounted } from 'vue'; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import { getDeviceLedger } from '@/api/equipmentManagement/ledger'; |
| | | import { addUpkeep, editUpkeep, getUpkeepById } from '@/api/equipmentManagement/upkeep'; |
| | | import dayjs from "dayjs"; |
| | | import { formatDateToYMD } from '@/utils/ruoyi'; |
| | | import { ref, computed, onMounted, onUnmounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { |
| | | addUpkeep, |
| | | editUpkeep, |
| | | getUpkeepById, |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | import dayjs from "dayjs"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»è®¡å表å", |
| | | }); |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»è®¡å表å", |
| | | }); |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref('add'); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]); |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref("add"); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | const currentDate = ref([ |
| | | new Date().getFullYear(), |
| | | new Date().getMonth() + 1, |
| | | new Date().getDate(), |
| | | ]); |
| | | |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | | const deviceNameText = ref(''); |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | const deviceActions = computed(() => { |
| | | return deviceOptions.value.map(item => ({ |
| | | text: item.deviceName, |
| | | value: item.id, |
| | | data: item |
| | | })); |
| | | }); |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | | const deviceNameText = ref(""); |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | const deviceActions = computed(() => { |
| | | return deviceOptions.value.map(item => ({ |
| | | text: item.deviceName, |
| | | value: item.id, |
| | | data: item, |
| | | })); |
| | | }); |
| | | |
| | | // æ«ç ç¸å
³ç¶æ |
| | | const isScanning = ref(false); |
| | | const scanTimer = ref(null); |
| | | // æ«ç ç¸å
³ç¶æ |
| | | const isScanning = ref(false); |
| | | const scanTimer = ref(null); |
| | | |
| | | // 表åéªè¯è§å |
| | | const formRules = { |
| | | deviceLedgerId: [{ required: true, trigger: "change", message: "è¯·éæ©è®¾å¤åç§°" }], |
| | | maintenancePlanTime: [{ required: true, trigger: "change", message: "è¯·éæ©è®¡åä¿å
»æ¥æ" }], |
| | | }; |
| | | // 表åéªè¯è§å |
| | | const formRules = { |
| | | deviceLedgerId: [ |
| | | { required: true, trigger: "change", message: "è¯·éæ©è®¾å¤åç§°" }, |
| | | ], |
| | | maintenancePlanTime: [ |
| | | { required: true, trigger: "change", message: "è¯·éæ©è®¡åä¿å
»æ¥æ" }, |
| | | ], |
| | | }; |
| | | |
| | | // ä½¿ç¨ ref 声æè¡¨åæ°æ® |
| | | const form = ref({ |
| | | deviceLedgerId: undefined, // 设å¤ID |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计åä¿å
»æ¥æ |
| | | }); |
| | | // ä½¿ç¨ ref 声æè¡¨åæ°æ® |
| | | const form = ref({ |
| | | deviceLedgerId: undefined, // 设å¤ID |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计åä¿å
»æ¥æ |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | machineryCategory: undefined, // ä¿å
»é¡¹ç® |
| | | storageBlobDTOs: [], // éä»¶å¾ç |
| | | }); |
| | | |
| | | // å 载设å¤å表 |
| | | const loadDeviceName = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (e) { |
| | | showToast('è·å设å¤å表失败'); |
| | | } |
| | | }; |
| | | // å 载设å¤å表 |
| | | const loadDeviceName = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (e) { |
| | | showToast("è·å设å¤å表失败"); |
| | | } |
| | | }; |
| | | |
| | | // å è½½è¡¨åæ°æ®ï¼ç¼è¾æ¨¡å¼ï¼ |
| | | const loadForm = async (id) => { |
| | | if (id) { |
| | | operationType.value = 'edit'; |
| | | try { |
| | | const { code, data } = await getUpkeepById(id); |
| | | if (code == 200) { |
| | | form.value.deviceLedgerId = data.deviceLedgerId; |
| | | form.value.deviceModel = data.deviceModel; |
| | | form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format("YYYY-MM-DD"); |
| | | // 设置设å¤åç§°æ¾ç¤º |
| | | const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId); |
| | | if (device) { |
| | | form.value.deviceNameText = device.deviceName; |
| | | } |
| | | } |
| | | } catch (e) { |
| | | showToast('è·å详æ
失败'); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | operationType.value = 'add'; |
| | | } |
| | | }; |
| | | // å è½½è¡¨åæ°æ®ï¼ç¼è¾æ¨¡å¼ï¼ |
| | | const loadForm = async id => { |
| | | if (id) { |
| | | operationType.value = "edit"; |
| | | try { |
| | | const { code, data } = await getUpkeepById(id); |
| | | if (code == 200) { |
| | | form.value.deviceLedgerId = data.deviceLedgerId; |
| | | form.value.deviceModel = data.deviceModel; |
| | | form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD" |
| | | ); |
| | | form.value.maintenancePerson = data.maintenancePerson; |
| | | form.value.machineryCategory = data.machineryCategory; |
| | | form.value.storageBlobDTOs = data.storageBlobVOs || []; |
| | | // 设置设å¤åç§°æ¾ç¤º |
| | | const device = deviceOptions.value.find( |
| | | item => item.id === data.deviceLedgerId |
| | | ); |
| | | if (device) { |
| | | form.value.deviceNameText = device.deviceName; |
| | | } |
| | | } |
| | | } catch (e) { |
| | | showToast("è·å详æ
失败"); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | operationType.value = "add"; |
| | | } |
| | | }; |
| | | |
| | | // æ«æäºç»´ç åè½ |
| | | const startScan = () => { |
| | | if (isScanning.value) { |
| | | showToast('æ£å¨æ«æä¸ï¼è¯·ç¨å...'); |
| | | return; |
| | | } |
| | | |
| | | // è°ç¨uni-appçæ«ç API |
| | | uni.scanCode({ |
| | | scanType: ['qrCode', 'barCode'], |
| | | success: (res) => { |
| | | handleScanResult(res.result); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('æ«ç 失败:', err); |
| | | showToast('æ«ç 失败ï¼è¯·éè¯'); |
| | | } |
| | | }); |
| | | }; |
| | | // æ«æäºç»´ç åè½ |
| | | const startScan = () => { |
| | | if (isScanning.value) { |
| | | showToast("æ£å¨æ«æä¸ï¼è¯·ç¨å..."); |
| | | return; |
| | | } |
| | | |
| | | // å¤çæ«ç ç»æ |
| | | const handleScanResult = (scanResult) => { |
| | | if (!scanResult) { |
| | | showToast('æ«ç ç»æä¸ºç©º'); |
| | | return; |
| | | } |
| | | |
| | | isScanning.value = true; |
| | | showToast('æ«ç æå'); |
| | | |
| | | // 3ç§åå¤çæ«ç ç»æ |
| | | scanTimer.value = setTimeout(() => { |
| | | processScanResult(scanResult); |
| | | isScanning.value = false; |
| | | }, 1000); |
| | | }; |
| | | function getDeviceIdByRegExp(url) { |
| | | // å¹é
deviceId=åé¢çæ°å |
| | | const reg = /deviceId=(\d+)/; |
| | | const match = url.match(reg); |
| | | // 妿å¹é
å°ç»æï¼è¿åæ°åç±»åï¼å¦åè¿ånull |
| | | return match ? Number(match[1]) : null; |
| | | } |
| | | // å¤çæ«ç ç»æå¹¶å¹é
è®¾å¤ |
| | | const processScanResult = (scanResult) => { |
| | | const deviceId = getDeviceIdByRegExp(scanResult); |
| | | const matchedDevice = deviceOptions.value.find(item => item.id == deviceId); |
| | | |
| | | if (matchedDevice) { |
| | | // æ¾å°å¹é
ç设å¤ï¼èªå¨å¡«å
|
| | | form.value.deviceLedgerId = matchedDevice.id; |
| | | form.value.deviceNameText = matchedDevice.deviceName; |
| | | form.value.deviceModel = matchedDevice.deviceModel; |
| | | showToast('设å¤ä¿¡æ¯å·²èªå¨å¡«å
'); |
| | | } else { |
| | | // æªæ¾å°å¹é
çè®¾å¤ |
| | | showToast('æªæ¾å°å¹é
ç设å¤ï¼è¯·æå¨éæ©'); |
| | | } |
| | | }; |
| | | // è°ç¨uni-appçæ«ç API |
| | | uni.scanCode({ |
| | | scanType: ["qrCode", "barCode"], |
| | | success: res => { |
| | | handleScanResult(res.result); |
| | | }, |
| | | fail: err => { |
| | | console.error("æ«ç 失败:", err); |
| | | showToast("æ«ç 失败ï¼è¯·éè¯"); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // æ¾ç¤ºè®¾å¤éæ©å¨ |
| | | const showDevicePicker = () => { |
| | | showDevice.value = true; |
| | | }; |
| | | // å¤çæ«ç ç»æ |
| | | const handleScanResult = scanResult => { |
| | | if (!scanResult) { |
| | | showToast("æ«ç ç»æä¸ºç©º"); |
| | | return; |
| | | } |
| | | |
| | | // 确认设å¤éæ© |
| | | const onDeviceConfirm = (selected) => { |
| | | // selected è¿åçæ¯éä¸é¡¹ |
| | | form.value.deviceLedgerId = selected.value; |
| | | form.value.deviceNameText = selected.name; |
| | | const selectedDevice = deviceOptions.value.find(item => item.id === selected.value); |
| | | if (selectedDevice) { |
| | | form.value.deviceModel = selectedDevice.deviceModel; |
| | | } |
| | | showDevice.value = false; |
| | | }; |
| | | isScanning.value = true; |
| | | showToast("æ«ç æå"); |
| | | |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | // 3ç§åå¤çæ«ç ç»æ |
| | | scanTimer.value = setTimeout(() => { |
| | | processScanResult(scanResult); |
| | | isScanning.value = false; |
| | | }, 1000); |
| | | }; |
| | | function getDeviceIdByRegExp(url) { |
| | | // å¹é
deviceId=åé¢çæ°å |
| | | const reg = /deviceId=(\d+)/; |
| | | const match = url.match(reg); |
| | | // 妿å¹é
å°ç»æï¼è¿åæ°åç±»åï¼å¦åè¿ånull |
| | | return match ? Number(match[1]) : null; |
| | | } |
| | | // å¤çæ«ç ç»æå¹¶å¹é
è®¾å¤ |
| | | const processScanResult = scanResult => { |
| | | const deviceId = getDeviceIdByRegExp(scanResult); |
| | | const matchedDevice = deviceOptions.value.find(item => item.id == deviceId); |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = (e) => { |
| | | form.value.maintenancePlanTime = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | if (matchedDevice) { |
| | | // æ¾å°å¹é
ç设å¤ï¼èªå¨å¡«å
|
| | | form.value.deviceLedgerId = matchedDevice.id; |
| | | form.value.deviceNameText = matchedDevice.deviceName; |
| | | form.value.deviceModel = matchedDevice.deviceModel; |
| | | showToast("设å¤ä¿¡æ¯å·²èªå¨å¡«å
"); |
| | | } else { |
| | | // æªæ¾å°å¹é
çè®¾å¤ |
| | | showToast("æªæ¾å°å¹é
ç设å¤ï¼è¯·æå¨éæ©"); |
| | | } |
| | | }; |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | }); |
| | | // æ¾ç¤ºè®¾å¤éæ©å¨ |
| | | const showDevicePicker = () => { |
| | | showDevice.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | // 确认设å¤éæ© |
| | | const onDeviceConfirm = selected => { |
| | | // selected è¿åçæ¯éä¸é¡¹ |
| | | form.value.deviceLedgerId = selected.value; |
| | | form.value.deviceNameText = selected.name; |
| | | const selectedDevice = deviceOptions.value.find( |
| | | item => item.id === selected.value |
| | | ); |
| | | if (selectedDevice) { |
| | | form.value.deviceModel = selectedDevice.deviceModel; |
| | | } |
| | | showDevice.value = false; |
| | | }; |
| | | |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | | onUnmounted(() => { |
| | | if (scanTimer.value) { |
| | | clearTimeout(scanTimer.value); |
| | | } |
| | | }); |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const sendForm = async () => { |
| | | try { |
| | | // æå¨éªè¯è¡¨å |
| | | const valid = await formRef.value.validate(); |
| | | if (!valid) return; |
| | | |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value }; |
| | | // ç¡®ä¿æ¥ææ ¼å¼æ£ç¡® |
| | | if (submitData.maintenancePlanTime && !submitData.maintenancePlanTime.includes(':')) { |
| | | submitData.maintenancePlanTime = submitData.maintenancePlanTime + ' 00:00:00'; |
| | | } |
| | | |
| | | const { code } = id |
| | | ? await editUpkeep({ id: id, ...submitData }) |
| | | : await addUpkeep(submitData); |
| | | |
| | | if (code == 200) { |
| | | showToast(`${id ? "ç¼è¾" : "æ°å¢"}计åæå`); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast('表åéªè¯å¤±è´¥'); |
| | | } |
| | | }; |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.maintenancePlanTime = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | // æ¸
é¤åå¨çid |
| | | uni.removeStorageSync('repairId'); |
| | | uni.navigateBack(); |
| | | }; |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | const id = uni.getStorageSync('repairId'); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | | } |
| | | }; |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | return uni.getStorageSync('repairId'); |
| | | }; |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | | onUnmounted(() => { |
| | | if (scanTimer.value) { |
| | | clearTimeout(scanTimer.value); |
| | | } |
| | | }); |
| | | |
| | | // æäº¤è¡¨å |
| | | const sendForm = async () => { |
| | | try { |
| | | // æå¨éªè¯è¡¨å |
| | | const valid = await formRef.value.validate(); |
| | | if (!valid) return; |
| | | |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value, status: 0 }; |
| | | |
| | | // ç¡®ä¿æ¥ææ ¼å¼æ£ç¡® |
| | | if ( |
| | | submitData.maintenancePlanTime && |
| | | !submitData.maintenancePlanTime.includes(":") |
| | | ) { |
| | | submitData.maintenancePlanTime = |
| | | submitData.maintenancePlanTime + " 00:00:00"; |
| | | } |
| | | |
| | | const { code } = id |
| | | ? await editUpkeep({ id: id, ...submitData }) |
| | | : await addUpkeep(submitData); |
| | | |
| | | if (code == 200) { |
| | | showToast(`${id ? "ç¼è¾" : "æ°å¢"}计åæå`); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast("表åéªè¯å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | // æ¸
é¤åå¨çid |
| | | uni.removeStorageSync("repairId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | | } |
| | | }; |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | return uni.getStorageSync("repairId"); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/static/scss/form-common.scss'; |
| | | .upkeep-add { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | @import "@/static/scss/form-common.scss"; |
| | | .upkeep-add { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); |
| | | z-index: 1000; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 6.375rem; |
| | | background: #C7C9CC; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 6.375rem; |
| | | background: #c7c9cc; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 14rem; |
| | | background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 14rem; |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // ååºå¼è°æ´ |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | // ååºå¼è°æ´ |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | |
| | | .scan-icon { |
| | | color: #1989fa; |
| | | font-size: 18px; |
| | | margin-left: 8px; |
| | | cursor: pointer; |
| | | } |
| | | .scan-icon { |
| | | color: #1989fa; |
| | | font-size: 18px; |
| | | margin-left: 8px; |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| | |
| | | <view v-if="fileList.length > 0" |
| | | class="file-list"> |
| | | <view v-for="(file, index) in fileList" |
| | | :key="file.id || index" |
| | | :key="file.storageAttachmentId || file.id || index" |
| | | class="file-item"> |
| | | <!-- æä»¶å¾æ --> |
| | | <!-- <view class="file-icon" |
| | |
| | | </view> --> |
| | | <!-- æä»¶ä¿¡æ¯ --> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ file.name }}</text> |
| | | <text class="file-name">{{ file.originalFilename || file.name }}</text> |
| | | <!-- <text class="file-meta">{{ formatFileSize(file.fileSize) }} · {{ file.uploadTime || file.createTime }}</text> --> |
| | | </view> |
| | | <!-- æä½æé® --> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import config from "@/config"; |
| | | import { getToken } from "@/utils/auth"; |
| | | // import { saveAs } from "file-saver"; |
| | | import { |
| | | listMaintenanceTaskFiles, |
| | | addMaintenanceTaskFile, |
| | | delMaintenanceTaskFile, |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | attachmentList, |
| | | createAttachment, |
| | | deleteAttachment, |
| | | } from "@/api/basicData/storageAttachment"; |
| | | import { blobValidate } from "@/utils/ruoyi"; |
| | | |
| | | // éä»¶å表 |
| | |
| | | // const fileType = fileName.split(".").pop(); |
| | | // 3. æé ä¿åæä»¶ä¿¡æ¯çåæ° |
| | | const saveData = { |
| | | name: fileName, |
| | | deviceMaintenanceId: upkeepId.value, |
| | | url: res.data.tempPath || "", |
| | | application: "file", |
| | | recordType: recordType.value, |
| | | recordId: upkeepId.value, |
| | | storageBlobDTOs: [ |
| | | { |
| | | name: fileName, |
| | | url: |
| | | res.data.url || |
| | | res.data.previewURL || |
| | | res.data.tempPath || |
| | | "", |
| | | ...res.data, |
| | | }, |
| | | ], |
| | | }; |
| | | console.log(saveData, "ä¿åæä»¶ä¿¡æ¯åæ°"); |
| | | // 4. è°ç¨ addRuleFile æ¥å£ä¿åæä»¶ä¿¡æ¯ |
| | | addMaintenanceTaskFile(saveData) |
| | | // 4. è°ç¨ createAttachment æ¥å£ä¿åæä»¶ä¿¡æ¯ |
| | | createAttachment(saveData) |
| | | .then(addRes => { |
| | | if (addRes.code === 200) { |
| | | // 5. æ·»å å°æä»¶å表 |
| | | const newFile = { |
| | | ...addRes.data, |
| | | uploadTime: new Date().toLocaleString(), |
| | | }; |
| | | // fileList.value.push(newFile); |
| | | // 5. å·æ°å表 |
| | | getFileList(); |
| | | showToast("ä¸ä¼ æå"); |
| | | } else { |
| | |
| | | }; |
| | | // ä¸è½½æä»¶ |
| | | const downloadFile = file => { |
| | | var url = |
| | | config.baseUrl + |
| | | "/common/download?fileName=" + |
| | | encodeURIComponent(file.url) + |
| | | "&delete=true"; |
| | | console.log(url, "url"); |
| | | let url = file.downloadURL || file.previewURL || file.url; |
| | | |
| | | if (!url) { |
| | | showToast("æä»¶å°åæ æ"); |
| | | return; |
| | | } |
| | | |
| | | // 妿䏿¯å®æ´çURLï¼åæ¼æ¥ |
| | | if (!url.startsWith("http")) { |
| | | url = |
| | | config.baseUrl + |
| | | "/common/download?fileName=" + |
| | | encodeURIComponent(url) + |
| | | "&delete=true"; |
| | | } |
| | | |
| | | console.log(url, "ä¸è½½å°å"); |
| | | |
| | | uni.showLoading({ title: "æ£å¨ä¸è½½...", mask: true }); |
| | | uni |
| | | .downloadFile({ |
| | | url: url, |
| | | responseType: "blob", |
| | | header: { Authorization: "Bearer " + getToken() }, |
| | | }) |
| | | .then(res => { |
| | | uni.hideLoading(); |
| | | let osType = uni.getStorageSync("deviceInfo").osName; |
| | | let filePath = res.tempFilePath; |
| | | if (osType === "ios") { |
| | |
| | | success: res => {}, |
| | | fail: err => { |
| | | console.log("uni.openDocument--fail"); |
| | | reject(err); |
| | | }, |
| | | }); |
| | | } else { |
| | |
| | | uni.showToast({ |
| | | icon: "none", |
| | | mask: true, |
| | | title: |
| | | "æä»¶å·²ä¿åï¼Android/data/uni.UNI720216F/apps/__UNI__720216F/" + |
| | | fileRes.savedFilePath, //ä¿åè·¯å¾ |
| | | duration: 3000, |
| | | title: "æä»¶å·²ä¸è½½å¹¶å°è¯æå¼", |
| | | duration: 2000, |
| | | }); |
| | | setTimeout(() => { |
| | | //æå¼ææ¡£æ¥ç |
| | |
| | | }, |
| | | fail: err => { |
| | | console.log("uni.save--fail"); |
| | | reject(err); |
| | | }, |
| | | }); |
| | | } |
| | | // const isBlob = blobValidate(res.data); |
| | | // if (isBlob) { |
| | | // const blob = new Blob([res.data], { type: "text/plain" }); |
| | | // const url = URL.createObjectURL(blob); |
| | | // const downloadLink = document.getElementById("downloadLink"); |
| | | // downloadLink.href = url; |
| | | // downloadLink.download = file.name; |
| | | // downloadLink.click(); |
| | | // showToast("ä¸è½½æå"); |
| | | // } else { |
| | | // showToast("ä¸è½½å¤±è´¥"); |
| | | // } |
| | | }) |
| | | .catch(err => { |
| | | uni.hideLoading(); |
| | | console.error("ä¸è½½å¤±è´¥:", err); |
| | | showToast("ä¸è½½å¤±è´¥"); |
| | | }); |
| | |
| | | content: `ç¡®å®è¦å é¤éä»¶ "${file.name}" åï¼`, |
| | | success: res => { |
| | | if (res.confirm) { |
| | | deleteFile(file.id, index); |
| | | deleteFile(file.storageAttachmentId || file.id, index); |
| | | } |
| | | }, |
| | | }); |
| | |
| | | mask: true, |
| | | }); |
| | | |
| | | delMaintenanceTaskFile([fileId]) |
| | | deleteAttachment([fileId]) |
| | | .then(res => { |
| | | uni.hideLoading(); |
| | | if (res.code === 200) { |
| | |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | const rulesRegulationsManagementId = ref(""); |
| | | const upkeepId = ref(""); |
| | | const recordType = ref(""); |
| | | |
| | | // 页é¢å è½½æ¶è·ååæ° |
| | | onLoad(options => { |
| | | if (options.recordId) { |
| | | upkeepId.value = options.recordId; |
| | | } else { |
| | | upkeepId.value = uni.getStorageSync("upkeepId"); |
| | | } |
| | | |
| | | if (options.recordType) { |
| | | recordType.value = options.recordType; |
| | | } else { |
| | | recordType.value = "device_maintenance"; // é»è®¤å
¼å®¹ |
| | | } |
| | | |
| | | getFileList(); |
| | | }); |
| | | |
| | | // 页é¢å è½½æ¶ |
| | | onMounted(() => { |
| | | // ä» API è·åéä»¶å表 |
| | | |
| | | // 仿¬å°åå¨è·å rulesRegulationsManagementId |
| | | rulesRegulationsManagementId.value = uni.getStorageSync( |
| | | "rulesRegulationsManagement" |
| | | ); |
| | | upkeepId.value = uni.getStorageSync("upkeepId"); |
| | | getFileList(); |
| | | // getFileList(); // onLoad ä¸å·²ç»è°ç¨äº |
| | | }); |
| | | |
| | | // è·åéä»¶å表 |
| | | const getFileList = () => { |
| | | if (!upkeepId.value) return; |
| | | |
| | | uni.showLoading({ |
| | | title: "å è½½ä¸...", |
| | | mask: true, |
| | | }); |
| | | |
| | | listMaintenanceTaskFiles({ |
| | | current: 1, |
| | | size: 100, |
| | | deviceMaintenanceId: upkeepId.value, |
| | | rulesRegulationsManagementId: upkeepId.value, |
| | | attachmentList({ |
| | | recordType: recordType.value, |
| | | recordId: upkeepId.value, |
| | | }) |
| | | .then(res => { |
| | | uni.hideLoading(); |
| | | if (res.code === 200) { |
| | | fileList.value = res.data.records || []; |
| | | fileList.value = res.data || []; |
| | | } else { |
| | | showToast("è·åéä»¶å表失败"); |
| | | } |
| | |
| | | <text class="detail-value">{{ formatDateTime(item.createTime) || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä¿å
»äºº</text> |
| | | <text class="detail-value">{{ item.maintenancePerson || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä¿å
»é¡¹ç®</text> |
| | | <text class="detail-value">{{ item.machineryCategory || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
ä¿å
»äºº</text> |
| | | <text class="detail-value">{{ item.maintenanceActuallyName || '-' }}</text> |
| | | </view> |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä¿å
ȍȾ</text> |
| | | <view class="detail-value"> |
| | | <text class="detail-value">{{ item.maintenanceResult || '-' }}</text> |
| | | <!-- <view class="detail-value"> |
| | | <u-tag v-if="item.maintenanceResult === 1" |
| | | type="success" |
| | | size="mini"> |
| | |
| | | ç»´ä¿® |
| | | </u-tag> |
| | | <text v-if="item.maintenanceResult === undefined || item.maintenanceResult === null">-</text> |
| | | </view> |
| | | </view> --> |
| | | </view> |
| | | </view> |
| | | <!-- æé®åºå --> |
| | |
| | | }; |
| | | // æ°å¢éä»¶ - 跳转å°éä»¶é¡µé¢ |
| | | const addFile = id => { |
| | | // ä½¿ç¨æ¬å°åå¨ä¼ éid |
| | | uni.setStorageSync("upkeepId", id); |
| | | uni.navigateTo({ |
| | | url: "/pages/equipmentManagement/upkeep/fileList", |
| | | url: `/pages/equipmentManagement/upkeep/fileList?recordId=${id}&recordType=device_maintenance`, |
| | | }); |
| | | }; |
| | | |
| | |
| | | <!-- ä¸ä¼ éä»¶ --> |
| | | <u-form-item v-if="form.status == '1'" |
| | | label="ä¿å
»éä»¶" |
| | | prop="storageBlobDTOs" |
| | | border-bottom> |
| | | <view class="simple-upload-area"> |
| | | <view class="upload-buttons"> |
| | | <u-button type="primary" |
| | | @click="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="uploadFiles.length >= uploadConfig.limit" |
| | | :customStyle="{ marginRight: '10px', flex: 1 }"> |
| | | <u-icon name="camera" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></u-icon> |
| | | {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç
§' }} |
| | | </u-button> |
| | | <!-- <u-button type="success" |
| | | @click="chooseMedia('video')" |
| | | :loading="uploading" |
| | | :disabled="uploadFiles.length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }"> |
| | | <uni-icons type="videocam" |
| | | name="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></uni-icons> |
| | | {{ uploading ? 'ä¸ä¼ ä¸...' : 'æè§é¢' }} |
| | | </u-button> --> |
| | | </view> |
| | | <!-- ä¸ä¼ è¿åº¦ --> |
| | | <view v-if="uploading" |
| | | class="upload-progress"> |
| | | <u-line-progress :percentage="uploadProgress" |
| | | :showText="true" |
| | | activeColor="#409eff"></u-line-progress> |
| | | </view> |
| | | <!-- ä¸ä¼ çæä»¶å表 --> |
| | | <view v-if="uploadFiles.length > 0" |
| | | class="file-list"> |
| | | <view v-for="(file, index) in uploadFiles" |
| | | :key="index" |
| | | class="file-item"> |
| | | <view class="file-preview-container"> |
| | | <!-- {{formatFileUrl(file.url)}} --> |
| | | <image v-if="file.type === 'image' || isImageFile(file)" |
| | | :src="formatFileUrl(file.url || file.tempFilePath || file.path || file.downloadUrl)" |
| | | class="file-preview" |
| | | mode="aspectFill" /> |
| | | <view v-else-if="file.type === 'video'" |
| | | class="video-preview"> |
| | | <uni-icons type="videocam" |
| | | name="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></uni-icons> |
| | | <text class="video-text">è§é¢</text> |
| | | </view> |
| | | <!-- å é¤æé® --> |
| | | <view class="delete-btn" |
| | | @click="removeFile(index)"> |
| | | <u-icon name="close" |
| | | size="12" |
| | | color="#fff"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? 'å¾ç' : 'è§é¢') |
| | | }}</text> |
| | | <text class="file-size">{{ formatFileSize(file.size) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="uploadFiles.length === 0" |
| | | class="empty-state"> |
| | | <text>è¯·éæ©è¦ä¸ä¼ çä¿å
»å¾ç</text> |
| | | </view> |
| | | </view> |
| | | <CommonUpload v-model="form.storageBlobDTOs" /> |
| | | </u-form-item> |
| | | <!-- æäº¤æé® --> |
| | | <view class="footer-btns"> |
| | |
| | | import { ref, onMounted, reactive } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import { addMaintenance } from "@/api/equipmentManagement/upkeep"; |
| | | import { getSparePartsList } from "@/api/equipmentManagement/repair"; |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | const sparePartsQtyRaw = ref(""); |
| | | |
| | | // æä»¶ä¸ä¼ ç¸å
³ |
| | | const uploadFiles = ref([]); |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | const number = ref(0); |
| | |
| | | maintenanceResult: undefined, // ä¿å
ȍȾ |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), // å®é
ä¿å
»æ¥æï¼åªæ¾ç¤ºæ¥æï¼ |
| | | sparePartsIds: undefined, // 设å¤å¤ä»¶ID |
| | | storageBlobDTOs: [], // ä¿å
»éä»¶ |
| | | }); |
| | | |
| | | // æ¸
é¤è¡¨åæ ¡éªç¶æ |
| | |
| | | maintenanceResult: undefined, |
| | | maintenanceActuallyTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | sparePartsIds: [], |
| | | storageBlobDTOs: [], |
| | | }; |
| | | maintenancestatusText.value = ""; |
| | | selectedSpareParts.value = []; |
| | |
| | | } else if (form.value.maintenanceResult === undefined) { |
| | | isValid = false; |
| | | errorMessage = "è¯·éæ©ä¿å
ȍȾ"; |
| | | } else if (uploadFiles.value.length === 0 && form.value.status == "1") { |
| | | } else if ( |
| | | (!form.value.storageBlobDTOs || |
| | | form.value.storageBlobDTOs.length === 0) && |
| | | form.value.status == "1" |
| | | ) { |
| | | isValid = false; |
| | | errorMessage = "请ä¸ä¼ ä¿å
ȍ
§ç"; |
| | | } |
| | |
| | | |
| | | const submitData = { |
| | | ...form.value, |
| | | imagesFile: form.value.status == "1" ? uploadFiles.value : [], |
| | | sparePartsIds: spareIds.length ? spareIds.join(",") : "", |
| | | sparePartsQty: spareIds.length |
| | | ? spareIds.map(pid => sparePartQtyMap?.[pid] ?? 1).join(",") |
| | |
| | | // éç½®éæ©çå¤ä»¶ |
| | | selectedSpareParts.value = []; |
| | | // éç½®ä¸ä¼ çæä»¶ |
| | | uploadFiles.value = []; |
| | | form.value.storageBlobDTOs = []; |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | maintenancestatusText.value = ""; |
| | |
| | | sparePartsIds.value = itemData.sparePartsIds; |
| | | |
| | | // å¡«å
éä»¶æ°æ® |
| | | if (itemData.files && itemData.files.length > 0) { |
| | | uploadFiles.value = itemData.files.map(file => ({ |
| | | if (itemData.storageBlobVOs && itemData.storageBlobVOs.length > 0) { |
| | | form.value.storageBlobDTOs = itemData.storageBlobVOs; |
| | | } else if (itemData.files && itemData.files.length > 0) { |
| | | form.value.storageBlobDTOs = itemData.files.map(file => ({ |
| | | id: file.id, |
| | | name: file.name || file.bucketFilename || file.originalFilename, |
| | | url: file.url || file.downloadUrl, |
| | |
| | | size: file.size || file.byteSize, |
| | | })); |
| | | } else if (itemData.uploadFiles && itemData.uploadFiles.length > 0) { |
| | | uploadFiles.value = itemData.uploadFiles.map(file => ({ |
| | | form.value.storageBlobDTOs = itemData.uploadFiles.map(file => ({ |
| | | id: file.id, |
| | | name: file.name || file.bucketFilename || file.originalFilename, |
| | | url: file.url || file.downloadUrl || file.tempFilePath || file.path, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="borrow-edit"> |
| | | <PageHeader :title="pageTitle" @back="goBack" /> |
| | | |
| | | <up-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110" |
| | | > |
| | | <up-form-item label="åé
人" prop="borrower" required> |
| | | <up-input |
| | | v-model="form.borrower" |
| | | placeholder="请è¾å
¥åé
人" |
| | | clearable |
| | | :disabled="isReturned" |
| | | /> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åé
书ç±" prop="documentationId" required> |
| | | <up-input |
| | | v-model="displayDocName" |
| | | placeholder="è¯·éæ©åé
书ç±" |
| | | readonly |
| | | :disabled="isEdit" |
| | | @click="!isEdit && (showDocPicker = true)" |
| | | /> |
| | | <template #right> |
| | | <up-icon v-if="!isEdit" name="arrow-right" @click="showDocPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åé
æ¥æ" prop="borrowDate" required> |
| | | <up-input |
| | | v-model="form.borrowDate" |
| | | placeholder="è¯·éæ©åé
æ¥æ" |
| | | readonly |
| | | @click="!isReturned && (showBorrowDatePicker = true)" |
| | | :disabled="isReturned" |
| | | /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="!isReturned && (showBorrowDatePicker = true)"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åºå½è¿æ¥æ" prop="dueReturnDate" required> |
| | | <up-input |
| | | v-model="form.dueReturnDate" |
| | | placeholder="è¯·éæ©åºå½è¿æ¥æ" |
| | | readonly |
| | | @click="!isReturned && (showDueDatePicker = true)" |
| | | :disabled="isReturned" |
| | | /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="!isReturned && (showDueDatePicker = true)"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åé
ç®ç" prop="borrowPurpose" required> |
| | | <up-input |
| | | v-model="form.borrowPurpose" |
| | | placeholder="请è¾å
¥åé
ç®ç" |
| | | clearable |
| | | :disabled="isReturned" |
| | | /> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="夿³¨"> |
| | | <up-textarea |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨ä¿¡æ¯" |
| | | height="80" |
| | | border="none" |
| | | :disabled="isReturned" |
| | | /> |
| | | </up-form-item> |
| | | </up-form> |
| | | |
| | | <FooterButtons |
| | | v-if="!isReturned" |
| | | :loading="loading" |
| | | :confirmText="isEdit ? 'ä¿å' : 'æ°å¢'" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" |
| | | /> |
| | | |
| | | <!-- åé
æ¥æéæ©å¨ --> |
| | | <up-popup :show="showBorrowDatePicker" mode="bottom" @close="showBorrowDatePicker = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="borrowDateValue" |
| | | @confirm="onBorrowDateConfirm" |
| | | @cancel="showBorrowDatePicker = false" |
| | | mode="date" |
| | | /> |
| | | </up-popup> |
| | | |
| | | <!-- åºå½è¿æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDueDatePicker" mode="bottom" @close="showDueDatePicker = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="dueReturnDateValue" |
| | | @confirm="onDueDateConfirm" |
| | | @cancel="showDueDatePicker = false" |
| | | mode="date" |
| | | /> |
| | | </up-popup> |
| | | |
| | | <!-- ææ¡£éæ©å¨ --> |
| | | <up-action-sheet |
| | | :show="showDocPicker" |
| | | :actions="documentOptions" |
| | | title="éæ©åé
书ç±" |
| | | @select="onDocSelect" |
| | | @close="showDocPicker = false" |
| | | /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { addBorrow, updateBorrow, getDocumentList } from "@/api/fileManagement/borrow"; |
| | | |
| | | const formRef = ref(); |
| | | const loading = ref(false); |
| | | const borrowId = ref(""); |
| | | const isEdit = ref(false); |
| | | |
| | | // å¼¹çªæ¾ç¤ºç¶æ |
| | | const showDocPicker = ref(false); |
| | | const showBorrowDatePicker = ref(false); |
| | | const showDueDatePicker = ref(false); |
| | | |
| | | // æ°æ® |
| | | const documentList = ref([]); |
| | | const borrowDateValue = ref(Date.now()); |
| | | const dueReturnDateValue = ref(Date.now()); |
| | | const displayDocName = ref(""); // ç¨äºæ¾ç¤ºçææ¡£åç§° |
| | | |
| | | const form = ref({ |
| | | id: "", |
| | | documentationId: "", |
| | | borrower: "", |
| | | borrowPurpose: "", |
| | | borrowDate: "", |
| | | dueReturnDate: "", |
| | | remark: "", |
| | | borrowStatus: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | borrower: [{ required: true, message: "请è¾å
¥åé
人", trigger: "blur" }], |
| | | documentationId: [{ required: true, message: "è¯·éæ©åé
书ç±", trigger: "change" }], |
| | | borrowPurpose: [{ required: true, message: "请è¾å
¥åé
ç®ç", trigger: "blur" }], |
| | | borrowDate: [{ required: true, message: "è¯·éæ©åé
æ¥æ", trigger: "change" }], |
| | | dueReturnDate: [{ required: true, message: "è¯·éæ©åºå½è¿æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | // 页颿 é¢ |
| | | const pageTitle = computed(() => { |
| | | if (isEdit.value) { |
| | | return form.value.borrowStatus === "å½è¿" ? "åé
详æ
" : "ç¼è¾åé
"; |
| | | } |
| | | return "æ°å¢åé
"; |
| | | }); |
| | | |
| | | // æ¯å¦å·²å½è¿ |
| | | const isReturned = computed(() => { |
| | | return form.value.borrowStatus === "å½è¿"; |
| | | }); |
| | | |
| | | // ææ¡£é项ï¼ä»
æ°å¢æ¨¡å¼ä½¿ç¨ï¼ |
| | | const documentOptions = computed(() => { |
| | | return documentList.value.map((item) => ({ |
| | | name: item.docName || item.name, |
| | | id: item.id, |
| | | })); |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.removeStorageSync("borrowEditData"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // å è½½ææ¡£å表ï¼ä»
æ°å¢æ¨¡å¼éè¦ï¼ |
| | | const loadDocumentList = async () => { |
| | | try { |
| | | const res = await getDocumentList(); |
| | | if (res.code === 200) { |
| | | documentList.value = res.data || []; |
| | | } |
| | | } catch (error) { |
| | | console.error("è·åææ¡£å表失败", error); |
| | | } |
| | | }; |
| | | |
| | | // ææ¡£éæ©ç¡®è®¤ |
| | | const onDocSelect = (e) => { |
| | | form.value.documentationId = e.id; |
| | | displayDocName.value = e.name; |
| | | showDocPicker.value = false; |
| | | }; |
| | | |
| | | // åé
æ¥æç¡®è®¤ |
| | | const onBorrowDateConfirm = (e) => { |
| | | const date = new Date(e.value); |
| | | form.value.borrowDate = formatDate(date); |
| | | showBorrowDatePicker.value = false; |
| | | }; |
| | | |
| | | // åºå½è¿æ¥æç¡®è®¤ |
| | | const onDueDateConfirm = (e) => { |
| | | const date = new Date(e.value); |
| | | form.value.dueReturnDate = formatDate(date); |
| | | showDueDatePicker.value = false; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (date) => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const handleSubmit = () => { |
| | | // 妿已å½è¿ï¼ç¦æ¢æäº¤ |
| | | if (isReturned.value) { |
| | | uni.showToast({ title: "å·²å½è¿çåé
è®°å½ä¸è½ç¼è¾", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | formRef.value |
| | | .validate() |
| | | .then(async () => { |
| | | try { |
| | | loading.value = true; |
| | | |
| | | if (isEdit.value) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | const res = await updateBorrow({ |
| | | id: form.value.id, |
| | | borrower: form.value.borrower, |
| | | borrowPurpose: form.value.borrowPurpose, |
| | | borrowDate: form.value.borrowDate, |
| | | dueReturnDate: form.value.dueReturnDate, |
| | | remark: form.value.remark, |
| | | }); |
| | | |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: "ç¼è¾æå", icon: "success" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | } else { |
| | | uni.showToast({ title: res.msg || "ç¼è¾å¤±è´¥", icon: "none" }); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | const res = await addBorrow({ |
| | | documentationId: form.value.documentationId, |
| | | borrower: form.value.borrower, |
| | | borrowPurpose: form.value.borrowPurpose, |
| | | borrowDate: form.value.borrowDate, |
| | | dueReturnDate: form.value.dueReturnDate, |
| | | borrowStatus: "åé
", |
| | | remark: form.value.remark, |
| | | }); |
| | | |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: "æ°å¢æå", icon: "success" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | } else { |
| | | uni.showToast({ title: res.msg || "æ°å¢å¤±è´¥", icon: "none" }); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "æä½å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | // éªè¯å¤±è´¥ |
| | | }); |
| | | }; |
| | | |
| | | // 页é¢å è½½ |
| | | onLoad((options) => { |
| | | if (options.id) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | isEdit.value = true; |
| | | borrowId.value = options.id; |
| | | |
| | | // ä» storage è·åç¼è¾æ°æ® |
| | | const editDataStr = uni.getStorageSync("borrowEditData"); |
| | | if (editDataStr) { |
| | | try { |
| | | const data = JSON.parse(editDataStr); |
| | | Object.assign(form.value, data); |
| | | borrowDateValue.value = new Date(data.borrowDate).getTime(); |
| | | dueReturnDateValue.value = new Date(data.dueReturnDate).getTime(); |
| | | // ç´æ¥ä½¿ç¨ä¼ éçææ¡£åç§°æ¾ç¤ºï¼å°è¯å¤ä¸ªå¯è½çåæ®µå |
| | | displayDocName.value = data.docName || data.documentationName || data.fileName || data.name || ""; |
| | | } catch (e) { |
| | | console.error("è§£æç¼è¾æ°æ®å¤±è´¥", e); |
| | | } |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼å è½½ææ¡£å表并设置é»è®¤æ¥æ |
| | | loadDocumentList(); |
| | | const today = new Date(); |
| | | form.value.borrowDate = formatDate(today); |
| | | borrowDateValue.value = today.getTime(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .borrow-edit { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="åé
管ç" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åçéåºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥åé
人æç´¢" |
| | | v-model="searchForm.borrower" |
| | | @change="getList" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="getList"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- åé
å表 --> |
| | | <view class="ledger-list" v-if="borrowList.length > 0"> |
| | | <view v-for="(item, index) in borrowList" :key="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.docName || '-' }}</text> |
| | | </view> |
| | | <view class="item-tag" :class="getStatusClass(item.borrowStatus)"> |
| | | <text class="tag-text">{{ item.borrowStatus }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åé
人</text> |
| | | <text class="detail-value">{{ item.borrower || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åé
ç®ç</text> |
| | | <text class="detail-value">{{ item.borrowPurpose || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åé
æ¥æ</text> |
| | | <text class="detail-value">{{ item.borrowDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºå½è¿æ¥æ</text> |
| | | <text class="detail-value">{{ item.dueReturnDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row" v-if="item.remark"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="detail-buttons"> |
| | | <u-button |
| | | v-if="item.borrowStatus !== 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="primary" |
| | | @click.stop="goEdit(item)" |
| | | > |
| | | ç¼è¾ |
| | | </u-button> |
| | | <u-button |
| | | v-if="item.borrowStatus !== 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="error" |
| | | plain |
| | | @click.stop="handleDelete(item)" |
| | | > |
| | | å é¤ |
| | | </u-button> |
| | | <u-button |
| | | v-if="item.borrowStatus === 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="primary" |
| | | plain |
| | | @click.stop="goView(item)" |
| | | > |
| | | æ¥ç |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <text>ææ åé
è®°å½</text> |
| | | </view> |
| | | |
| | | <!-- æµ®å¨æä½æé® --> |
| | | <view class="fab-button" @click="goAdd"> |
| | | <up-icon name="plus" size="24" color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { getBorrowList, deleteBorrow } from "@/api/fileManagement/borrow"; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | borrower: "", |
| | | }); |
| | | |
| | | // åé
åè¡¨æ°æ® |
| | | const borrowList = ref([]); |
| | | |
| | | // å页ç¸å
³ |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·åç¶ææ ·å¼ç±» |
| | | const getStatusClass = (status) => { |
| | | if (status === "å½è¿") return "tag-success"; |
| | | if (status === "åé
") return "tag-warning"; |
| | | return "tag-default"; |
| | | }; |
| | | |
| | | // å è½½åé
å表 |
| | | const getList = async () => { |
| | | uni.showLoading({ title: "å è½½ä¸...", mask: true }); |
| | | |
| | | const query = { |
| | | page: -1, |
| | | size: -1, |
| | | borrower: searchForm.borrower || undefined, |
| | | }; |
| | | |
| | | try { |
| | | const res = await getBorrowList(query); |
| | | if (res.code === 200) { |
| | | borrowList.value = res.data.records || []; |
| | | pagination.total = res.data.total || 0; |
| | | } else { |
| | | uni.showToast({ title: res.msg || "è·ååé
å表失败", icon: "none" }); |
| | | borrowList.value = []; |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "è·ååé
å表失败", icon: "none" }); |
| | | borrowList.value = []; |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }; |
| | | |
| | | // è·³è½¬å°æ°å¢é¡µé¢ |
| | | const goAdd = () => { |
| | | uni.navigateTo({ |
| | | url: "/pages/fileManagement/borrow/edit", |
| | | }); |
| | | }; |
| | | |
| | | // 跳转å°ç¼è¾é¡µé¢ |
| | | const goEdit = (item) => { |
| | | uni.setStorageSync("borrowEditData", JSON.stringify(item)); |
| | | uni.navigateTo({ |
| | | url: `/pages/fileManagement/borrow/edit?id=${item.id}`, |
| | | }); |
| | | }; |
| | | |
| | | // è·³è½¬å°æ¥ç页é¢ï¼å·²å½è¿è®°å½ï¼ |
| | | const goView = (item) => { |
| | | uni.setStorageSync("borrowEditData", JSON.stringify(item)); |
| | | uni.navigateTo({ |
| | | url: `/pages/fileManagement/borrow/edit?id=${item.id}`, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = (row) => { |
| | | uni.showModal({ |
| | | title: "å é¤ç¡®è®¤", |
| | | content: "éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", |
| | | confirmText: "确认", |
| | | cancelText: "åæ¶", |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | try { |
| | | uni.showLoading({ title: "å é¤ä¸...", mask: true }); |
| | | const result = await deleteBorrow([row.id]); |
| | | if (result.code === 200) { |
| | | uni.showToast({ title: "å 餿å", icon: "success" }); |
| | | getList(); |
| | | } else { |
| | | uni.showToast({ title: result.msg || "å é¤å¤±è´¥", icon: "none" }); |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "å é¤å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | onShow(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | // æ ç¾æ ·å¼ |
| | | .item-tag { |
| | | border-radius: 4px; |
| | | padding: 2px 8px; |
| | | |
| | | &.tag-success { |
| | | background: #4caf50; |
| | | } |
| | | |
| | | &.tag-warning { |
| | | background: #ff9800; |
| | | } |
| | | |
| | | &.tag-default { |
| | | background: #9e9e9e; |
| | | } |
| | | } |
| | | |
| | | .tag-text { |
| | | font-size: 11px; |
| | | color: #ffffff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | // æé®æ ·å¼ |
| | | .detail-buttons { |
| | | padding: 12px 0; |
| | | display: flex; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .detail-button { |
| | | flex: 1; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="return-edit"> |
| | | <PageHeader :title="pageTitle" @back="goBack" /> |
| | | |
| | | <up-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110" |
| | | > |
| | | <up-form-item label="ææ¡£" prop="borrowId" required> |
| | | <up-input |
| | | v-model="displayDocName" |
| | | placeholder="è¯·éæ©ææ¡£" |
| | | readonly |
| | | :disabled="isEdit" |
| | | @click="!isEdit && (showDocPicker = true)" |
| | | /> |
| | | <template #right> |
| | | <up-icon v-if="!isEdit" name="arrow-right" @click="showDocPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åé
人" prop="borrower"> |
| | | <up-input |
| | | v-model="form.borrower" |
| | | placeholder="éæ©ææ¡£åèªå¨å¸¦åº" |
| | | disabled |
| | | /> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="å½è¿äºº" prop="returner" required> |
| | | <up-input |
| | | v-model="form.returner" |
| | | placeholder="请è¾å
¥å½è¿äºº" |
| | | clearable |
| | | :disabled="isReturned" |
| | | /> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="å½è¿æ¥æ" prop="returnDate" required> |
| | | <up-input |
| | | v-model="form.returnDate" |
| | | placeholder="è¯·éæ©å½è¿æ¥æ" |
| | | readonly |
| | | @click="!isReturned && (showReturnDatePicker = true)" |
| | | :disabled="isReturned" |
| | | /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="!isReturned && (showReturnDatePicker = true)"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="åºå½è¿æ¥æ" prop="dueReturnDate"> |
| | | <up-input |
| | | v-model="form.dueReturnDate" |
| | | placeholder="éæ©ææ¡£åèªå¨å¸¦åº" |
| | | disabled |
| | | /> |
| | | </up-form-item> |
| | | |
| | | <up-form-item label="夿³¨è¯´æ" prop="remark"> |
| | | <up-textarea |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨è¯´æ" |
| | | height="80" |
| | | border="none" |
| | | :disabled="isReturned" |
| | | /> |
| | | </up-form-item> |
| | | </up-form> |
| | | |
| | | <FooterButtons |
| | | v-if="!isReturned" |
| | | :loading="loading" |
| | | :confirmText="isEdit ? 'ä¿å' : 'æ°å¢'" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" |
| | | /> |
| | | |
| | | <!-- ææ¡£éæ©å¨ --> |
| | | <up-action-sheet |
| | | :show="showDocPicker" |
| | | :actions="documentOptions" |
| | | title="éæ©ææ¡£" |
| | | @select="onDocSelect" |
| | | @close="showDocPicker = false" |
| | | /> |
| | | |
| | | <!-- å½è¿æ¥æéæ©å¨ --> |
| | | <up-popup :show="showReturnDatePicker" mode="bottom" @close="showReturnDatePicker = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="returnDateValue" |
| | | @confirm="onReturnDateConfirm" |
| | | @cancel="showReturnDatePicker = false" |
| | | mode="date" |
| | | /> |
| | | </up-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { returnDocument, reventUpdate, getDocumentList } from "@/api/fileManagement/return"; |
| | | |
| | | const formRef = ref(); |
| | | const loading = ref(false); |
| | | const returnId = ref(""); |
| | | const isEdit = ref(false); |
| | | |
| | | // å¼¹çªæ¾ç¤ºç¶æ |
| | | const showDocPicker = ref(false); |
| | | const showReturnDatePicker = ref(false); |
| | | |
| | | // æ°æ® |
| | | const documentList = ref([]); |
| | | const returnDateValue = ref(Date.now()); |
| | | const displayDocName = ref(""); // ç¨äºæ¾ç¤ºçææ¡£åç§° |
| | | |
| | | const form = ref({ |
| | | id: "", |
| | | borrowId: "", |
| | | documentationId: "", |
| | | borrower: "", |
| | | returner: "", |
| | | borrowStatus: "", |
| | | returnDate: "", |
| | | dueReturnDate: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | borrowId: [{ required: true, message: "è¯·éæ©ææ¡£", trigger: "change" }], |
| | | returner: [{ required: true, message: "请è¾å
¥å½è¿äºº", trigger: "blur" }], |
| | | returnDate: [{ required: true, message: "è¯·éæ©å½è¿æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | // 页颿 é¢ |
| | | const pageTitle = computed(() => { |
| | | if (isEdit.value) { |
| | | return form.value.borrowStatus === "å½è¿" ? "å½è¿è¯¦æ
" : "ç¼è¾å½è¿"; |
| | | } |
| | | return "æ°å¢å½è¿"; |
| | | }); |
| | | |
| | | // æ¯å¦å·²å½è¿ |
| | | const isReturned = computed(() => { |
| | | return form.value.borrowStatus === "å½è¿"; |
| | | }); |
| | | |
| | | // ææ¡£é项ï¼ä»
æ°å¢æ¨¡å¼ä½¿ç¨ï¼ |
| | | const documentOptions = computed(() => { |
| | | return documentList.value.map((item) => ({ |
| | | name: item.docName || item.name, |
| | | id: item.id, |
| | | borrower: item.borrower, |
| | | dueReturnDate: item.dueReturnDate, |
| | | })); |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.removeStorageSync("returnEditData"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // å è½½ææ¡£å表ï¼ä»
æ°å¢æ¨¡å¼éè¦ï¼ |
| | | const loadDocumentList = async () => { |
| | | try { |
| | | const res = await getDocumentList(); |
| | | if (res.code === 200) { |
| | | documentList.value = res.data || []; |
| | | } |
| | | } catch (error) { |
| | | console.error("è·åææ¡£å表失败", error); |
| | | } |
| | | }; |
| | | |
| | | // ææ¡£éæ©ç¡®è®¤ |
| | | const onDocSelect = (e) => { |
| | | form.value.borrowId = e.id; |
| | | form.value.documentationId = e.id; |
| | | displayDocName.value = e.name; |
| | | // èªå¨å¸¦åºåé
人ååºå½è¿æ¥æ |
| | | form.value.borrower = e.borrower || ""; |
| | | form.value.dueReturnDate = e.dueReturnDate || ""; |
| | | showDocPicker.value = false; |
| | | }; |
| | | |
| | | // å½è¿æ¥æç¡®è®¤ |
| | | const onReturnDateConfirm = (e) => { |
| | | const date = new Date(e.value); |
| | | form.value.returnDate = formatDate(date); |
| | | showReturnDatePicker.value = false; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (date) => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const handleSubmit = () => { |
| | | // 妿已å½è¿ï¼ç¦æ¢æäº¤ |
| | | if (isReturned.value) { |
| | | uni.showToast({ title: "å·²å½è¿çè®°å½ä¸è½ç¼è¾", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | formRef.value |
| | | .validate() |
| | | .then(async () => { |
| | | try { |
| | | loading.value = true; |
| | | |
| | | if (isEdit.value) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | const res = await reventUpdate({ |
| | | id: form.value.id, |
| | | documentationId: form.value.documentationId, |
| | | borrower: form.value.borrower, |
| | | returner: form.value.returner, |
| | | borrowStatus: form.value.borrowStatus, |
| | | returnDate: form.value.returnDate, |
| | | dueReturnDate: form.value.dueReturnDate, |
| | | remark: form.value.remark, |
| | | }); |
| | | |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: "ç¼è¾æå", icon: "success" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | } else { |
| | | uni.showToast({ title: res.msg || "ç¼è¾å¤±è´¥", icon: "none" }); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | const res = await returnDocument({ |
| | | borrowId: form.value.borrowId, |
| | | borrower: form.value.borrower, |
| | | returner: form.value.returner, |
| | | borrowStatus: "å½è¿", |
| | | returnDate: form.value.returnDate, |
| | | dueReturnDate: form.value.dueReturnDate, |
| | | remark: form.value.remark, |
| | | }); |
| | | |
| | | if (res.code === 200) { |
| | | uni.showToast({ title: "æ°å¢æå", icon: "success" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | } else { |
| | | uni.showToast({ title: res.msg || "æ°å¢å¤±è´¥", icon: "none" }); |
| | | } |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "æä½å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | // éªè¯å¤±è´¥ |
| | | }); |
| | | }; |
| | | |
| | | // 页é¢å è½½ |
| | | onLoad((options) => { |
| | | if (options.id) { |
| | | // ç¼è¾æ¨¡å¼ |
| | | isEdit.value = true; |
| | | returnId.value = options.id; |
| | | |
| | | // ä» storage è·åç¼è¾æ°æ® |
| | | const editDataStr = uni.getStorageSync("returnEditData"); |
| | | if (editDataStr) { |
| | | try { |
| | | const data = JSON.parse(editDataStr); |
| | | Object.assign(form.value, data); |
| | | returnDateValue.value = new Date(data.returnDate).getTime(); |
| | | // ç´æ¥ä½¿ç¨ä¼ éç docName æ¾ç¤º |
| | | displayDocName.value = data.docName || ""; |
| | | } catch (e) { |
| | | console.error("è§£æç¼è¾æ°æ®å¤±è´¥", e); |
| | | } |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼å è½½ææ¡£å表并设置é»è®¤æ¥æ |
| | | loadDocumentList(); |
| | | const today = new Date(); |
| | | form.value.returnDate = formatDate(today); |
| | | returnDateValue.value = today.getTime(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .return-edit { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="å½è¿ç®¡ç" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åçéåºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥åé
人æç´¢" |
| | | v-model="searchForm.borrower" |
| | | @change="getList" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="getList"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å½è¿å表 --> |
| | | <view class="ledger-list" v-if="returnList.length > 0"> |
| | | <view v-for="(item, index) in returnList" :key="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.docName || '-' }}</text> |
| | | </view> |
| | | <view class="item-tag" :class="getStatusClass(item.borrowStatus)"> |
| | | <text class="tag-text">{{ item.borrowStatus }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åé
人</text> |
| | | <text class="detail-value">{{ item.borrower || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½è¿äºº</text> |
| | | <text class="detail-value">{{ item.returner || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½è¿æ¥æ</text> |
| | | <text class="detail-value">{{ item.returnDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºå½è¿æ¥æ</text> |
| | | <text class="detail-value">{{ item.dueReturnDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row" v-if="item.remark"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="detail-buttons"> |
| | | <u-button |
| | | v-if="item.borrowStatus !== 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="primary" |
| | | @click.stop="goEdit(item)" |
| | | > |
| | | ç¼è¾ |
| | | </u-button> |
| | | <u-button |
| | | v-if="item.borrowStatus !== 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="error" |
| | | plain |
| | | @click.stop="handleDelete(item)" |
| | | > |
| | | å é¤ |
| | | </u-button> |
| | | <u-button |
| | | v-if="item.borrowStatus === 'å½è¿'" |
| | | class="detail-button" |
| | | size="small" |
| | | type="primary" |
| | | plain |
| | | @click.stop="goView(item)" |
| | | > |
| | | æ¥ç |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <text>ææ å½è¿è®°å½</text> |
| | | </view> |
| | | |
| | | <!-- æµ®å¨æä½æé® --> |
| | | <view class="fab-button" @click="goAdd"> |
| | | <up-icon name="plus" size="24" color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { getReturnListPage, deleteReturn } from "@/api/fileManagement/return"; |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | borrower: "", |
| | | }); |
| | | |
| | | // å½è¿åè¡¨æ°æ® |
| | | const returnList = ref([]); |
| | | |
| | | // å页ç¸å
³ |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·åç¶ææ ·å¼ç±» |
| | | const getStatusClass = (status) => { |
| | | if (status === "å½è¿") return "tag-success"; |
| | | if (status === "åé
") return "tag-warning"; |
| | | return "tag-default"; |
| | | }; |
| | | |
| | | // å è½½å½è¿å表 |
| | | const getList = async () => { |
| | | uni.showLoading({ title: "å è½½ä¸...", mask: true }); |
| | | |
| | | const query = { |
| | | page: -1, |
| | | size: -1, |
| | | borrower: searchForm.borrower || undefined, |
| | | }; |
| | | |
| | | try { |
| | | const res = await getReturnListPage(query); |
| | | if (res.code === 200) { |
| | | returnList.value = res.data.records || []; |
| | | pagination.total = res.data.total || 0; |
| | | } else { |
| | | uni.showToast({ title: res.msg || "è·åå½è¿å表失败", icon: "none" }); |
| | | returnList.value = []; |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "è·åå½è¿å表失败", icon: "none" }); |
| | | returnList.value = []; |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }; |
| | | |
| | | // è·³è½¬å°æ°å¢é¡µé¢ |
| | | const goAdd = () => { |
| | | uni.navigateTo({ |
| | | url: "/pages/fileManagement/return/edit", |
| | | }); |
| | | }; |
| | | |
| | | // 跳转å°ç¼è¾é¡µé¢ |
| | | const goEdit = (item) => { |
| | | uni.setStorageSync("returnEditData", JSON.stringify(item)); |
| | | uni.navigateTo({ |
| | | url: `/pages/fileManagement/return/edit?id=${item.id}`, |
| | | }); |
| | | }; |
| | | |
| | | // è·³è½¬å°æ¥ç页é¢ï¼å·²å½è¿è®°å½ï¼ |
| | | const goView = (item) => { |
| | | uni.setStorageSync("returnEditData", JSON.stringify(item)); |
| | | uni.navigateTo({ |
| | | url: `/pages/fileManagement/return/edit?id=${item.id}`, |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = (row) => { |
| | | uni.showModal({ |
| | | title: "å é¤ç¡®è®¤", |
| | | content: "éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", |
| | | confirmText: "确认", |
| | | cancelText: "åæ¶", |
| | | success: async (res) => { |
| | | if (res.confirm) { |
| | | try { |
| | | uni.showLoading({ title: "å é¤ä¸...", mask: true }); |
| | | const result = await deleteReturn([row.id]); |
| | | if (result.code === 200) { |
| | | uni.showToast({ title: "å 餿å", icon: "success" }); |
| | | getList(); |
| | | } else { |
| | | uni.showToast({ title: result.msg || "å é¤å¤±è´¥", icon: "none" }); |
| | | } |
| | | } catch (error) { |
| | | uni.showToast({ title: "å é¤å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | onShow(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | // æ ç¾æ ·å¼ |
| | | .item-tag { |
| | | border-radius: 4px; |
| | | padding: 2px 8px; |
| | | |
| | | &.tag-success { |
| | | background: #4caf50; |
| | | } |
| | | |
| | | &.tag-warning { |
| | | background: #ff9800; |
| | | } |
| | | |
| | | &.tag-default { |
| | | background: #9e9e9e; |
| | | } |
| | | } |
| | | |
| | | .tag-text { |
| | | font-size: 11px; |
| | | color: #ffffff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | // æé®æ ·å¼ |
| | | .detail-buttons { |
| | | padding: 12px 0; |
| | | display: flex; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .detail-button { |
| | | flex: 1; |
| | | } |
| | | </style> |
| | |
| | | <scroll-view class="scroll" scroll-y> |
| | | <!-- é¡¶é¨ Bannerï¼æ¾å
¥æ»å¨åºåï¼é页é¢ä¸èµ·æ»å¨ï¼ä¸åºå®å¨é¡¶é¨ --> |
| | | <view class="hero-section"> |
| | | <view class="bg-img"> |
| | | <view class="hero-content"> |
| | | <view class="hero-ornaments"> |
| | | <view class="hero-glow glow-left" /> |
| | | <view class="hero-glow glow-right" /> |
| | | <view class="hero-mist mist-top" /> |
| | | <view class="hero-mist mist-bottom" /> |
| | | <view class="hero-curve curve-main" /> |
| | | <view class="hero-curve curve-sub" /> |
| | | <view class="hero-banner"> |
| | | <view class="hero-top"> |
| | | <view class="hero-copy"> |
| | | <view class="hero-badge">ç»è¥çæ¿</view> |
| | | <text class="hero-title">{{ heroTitle }}</text> |
| | | <text class="hero-subtitle">{{ heroSubtitle }}</text> |
| | | <view class="hero-meta"> |
| | | <text |
| | | v-for="item in heroMetaItems" |
| | | :key="item" |
| | | class="hero-meta-item" |
| | | > |
| | | {{ item }} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | <view class="hero-avatar"> |
| | | <text class="hero-avatar-text">{{ heroInitial }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="hero-wave"></view> |
| | | <view class="hero-panels"> |
| | | <view |
| | | v-for="item in heroMetrics" |
| | | :key="item.label" |
| | | class="hero-panel" |
| | | > |
| | | <text class="hero-panel-label">{{ item.label }}</text> |
| | | <text class="hero-panel-value">{{ item.value }}</text> |
| | | <text class="hero-panel-hint">{{ item.hint }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å¿«æ·å
¥å£ --> |
| | | <view class="quick-section"> |
| | | <view v-if="quickTools.length" class="quick-section"> |
| | | <up-grid :border="false" col="4"> |
| | | <up-grid-item |
| | | v-for="item in quickTools" |
| | |
| | | </view> |
| | | |
| | | <!-- æ°æ®æ»è§ --> |
| | | <view class="section"> |
| | | <view v-if="hasOverviewSection" class="section"> |
| | | <view class="section-header"> |
| | | <view class="section-title"> |
| | | <view class="title-bar" /> |
| | |
| | | </view> |
| | | |
| | | <view v-show="overviewExpanded" class="overview"> |
| | | <view class="overview-card sales"> |
| | | <view v-if="canShowSalesOverview" class="overview-card sales"> |
| | | <view class="card-left"> |
| | | <text class="card-title">é宿°æ®</text> |
| | | <view class="card-metrics"> |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="overview-card purchase"> |
| | | <view v-if="canShowPurchaseOverview" class="overview-card purchase"> |
| | | <view class="card-left"> |
| | | <text class="card-title">éè´æ°æ®</text> |
| | | <view class="card-metrics"> |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="overview-card stock"> |
| | | <view v-if="canShowStockOverview" class="overview-card stock"> |
| | | <view class="card-left"> |
| | | <text class="card-title">åºåæ°æ®</text> |
| | | <view class="card-metrics"> |
| | |
| | | </view> |
| | | |
| | | <!-- 客æ·ååéé¢åæ --> |
| | | <view class="section"> |
| | | <view v-if="canShowContractAnalysis" class="section"> |
| | | <view class="section-header"> |
| | | <view class="section-title"> |
| | | <view class="title-bar" /> |
| | |
| | | import { analysisCustomerContractAmounts, getBusiness } from "@/api/viewIndex"; |
| | | import { createVersionUpgradeChecker } from "@/utils/versionUpgrade"; |
| | | import DownloadProgressMask from "@/components/DownloadProgressMask.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const imgNum1 = "/static/images/index/num1.png"; |
| | | const imgNum2 = "/static/images/index/num2.png"; |
| | | const imgNum3 = "/static/images/index/num3.png"; |
| | | |
| | | const quickTools = [ |
| | | const userStore = useUserStore(); |
| | | |
| | | const quickToolSource = [ |
| | | { |
| | | label: "ç产æ¥å·¥", |
| | | icon: "/static/images/icon/shengchanbaogong.svg", |
| | |
| | | route: "/pages/equipmentManagement/repair/index", |
| | | }, |
| | | ]; |
| | | const quickTools = ref([...quickToolSource]); |
| | | const allowedMenuTitles = ref(new Set()); |
| | | |
| | | const isCanvas2d = ref(false); |
| | | |
| | |
| | | uni.showToast({ title: "æ´å¤åè½å¾
æ¥å
¥", icon: "none" }); |
| | | } |
| | | |
| | | |
| | | function filterQuickToolsByRoutes() { |
| | | const routers = userStore.routers || []; |
| | | |
| | | if (!routers || routers.length === 0) { |
| | | allowedMenuTitles.value = new Set(); |
| | | quickTools.value = [...quickToolSource]; |
| | | return; |
| | | } |
| | | |
| | | const titles = new Set(); |
| | | const collectMenuTitles = (routes) => { |
| | | if (!Array.isArray(routes)) return; |
| | | routes.forEach((route) => { |
| | | if (route.meta && route.meta.title) { |
| | | titles.add(route.meta.title); |
| | | } |
| | | if (route.children && route.children.length > 0) { |
| | | collectMenuTitles(route.children); |
| | | } |
| | | }); |
| | | }; |
| | | collectMenuTitles(routers); |
| | | allowedMenuTitles.value = titles; |
| | | |
| | | quickTools.value = quickToolSource.filter((item) => |
| | | titles.has(item.label) |
| | | ); |
| | | } |
| | | |
| | | function hasAnyPermission(titles) { |
| | | const titleSet = allowedMenuTitles.value; |
| | | if (!titleSet || titleSet.size === 0) return true; |
| | | return titles.some((title) => titleSet.has(title)); |
| | | } |
| | | |
| | | const canShowSalesOverview = computed(() => hasAnyPermission(["éå®å°è´¦"])); |
| | | const canShowPurchaseOverview = computed(() => hasAnyPermission(["éè´å°è´¦"])); |
| | | const canShowStockOverview = computed(() => hasAnyPermission(["åºå管ç"])); |
| | | const hasOverviewSection = computed( |
| | | () => |
| | | canShowSalesOverview.value || |
| | | canShowPurchaseOverview.value || |
| | | canShowStockOverview.value |
| | | ); |
| | | const canShowContractAnalysis = computed(() => |
| | | hasAnyPermission(["éå®å°è´¦", "å®¢æ·æ¡£æ¡", "客æ·å¾æ¥"]) |
| | | ); |
| | | |
| | | const userDisplayName = computed( |
| | | () => userStore.nickName |
| | | ); |
| | | const heroInitial = computed(() => userDisplayName.value.slice(0, 1).toUpperCase()); |
| | | const heroTitle = computed(() => `ä½ å¥½ï¼${userDisplayName.value}`); |
| | | const heroSubtitle = computed( |
| | | () => userStore.currentFactoryName || "å½åè´¦å·å·²è¿å
¥ä¸å¡é¦é¡µ" |
| | | ); |
| | | const heroMetaItems = computed(() => { |
| | | const items = []; |
| | | if (userStore.roleName) items.push(userStore.roleName); |
| | | if (userStore.currentLoginTime) items.push(`ç»å½äº ${userStore.currentLoginTime}`); |
| | | if (!items.length) items.push("å½åä¸å¡æ¦è§"); |
| | | return items; |
| | | }); |
| | | const visibleSectionCount = computed(() => { |
| | | let count = 0; |
| | | if (quickTools.value.length > 0) count += 1; |
| | | if (hasOverviewSection.value) count += 1; |
| | | if (canShowContractAnalysis.value) count += 1; |
| | | return count; |
| | | }); |
| | | const heroMetrics = computed(() => { |
| | | const items = []; |
| | | |
| | | if (canShowSalesOverview.value) { |
| | | items.push({ |
| | | label: "éå®", |
| | | value: overviewCards.value.sales.today, |
| | | hint: "æ¬æè¥ä¸é¢", |
| | | }); |
| | | } |
| | | if (canShowPurchaseOverview.value) { |
| | | items.push({ |
| | | label: "éè´", |
| | | value: overviewCards.value.purchase.today, |
| | | hint: "æ¬æéè´é¢", |
| | | }); |
| | | } |
| | | if (canShowStockOverview.value) { |
| | | items.push({ |
| | | label: "åºå", |
| | | value: overviewCards.value.stock.today, |
| | | hint: "å½ååºåé", |
| | | }); |
| | | } |
| | | if (canShowContractAnalysis.value && items.length < 3) { |
| | | items.push({ |
| | | label: "åå", |
| | | value: contractSummaryView.value.sumText, |
| | | hint: "客æ·ååé¢", |
| | | }); |
| | | } |
| | | if (items.length < 3) { |
| | | items.push({ |
| | | label: "å¿«æ·", |
| | | value: String(quickTools.value.length), |
| | | hint: "å¯ç¨å
¥å£", |
| | | }); |
| | | } |
| | | if (items.length < 3) { |
| | | items.push({ |
| | | label: "模å", |
| | | value: String(visibleSectionCount.value), |
| | | hint: "å¯è§æ¿å", |
| | | }); |
| | | } |
| | | |
| | | return items.slice(0, 3); |
| | | }); |
| | | |
| | | function getByPath(obj, path) { |
| | | if (!obj || !path) return undefined; |
| | |
| | | async function loadHome() { |
| | | chartReady.value = false; |
| | | try { |
| | | const [bRes, cRes] = await Promise.all([getBusiness(), analysisCustomerContractAmounts()]); |
| | | const businessPromise = hasOverviewSection.value |
| | | ? getBusiness() |
| | | : Promise.resolve({ data: {} }); |
| | | const contractPromise = canShowContractAnalysis.value |
| | | ? analysisCustomerContractAmounts() |
| | | : Promise.resolve({ data: { item: [], sum: "0", chain: "0", yny: "0" } }); |
| | | const [bRes, cRes] = await Promise.all([businessPromise, contractPromise]); |
| | | businessRaw.value = bRes?.data || {}; |
| | | const cData = cRes?.data || {}; |
| | | contractSummary.value = { |
| | |
| | | isCanvas2d.value = false; |
| | | } |
| | | triggerVersionCheck("onMounted"); |
| | | loadHome(); |
| | | userStore |
| | | .getRouters() |
| | | .then(() => { |
| | | filterQuickToolsByRoutes(); |
| | | loadHome(); |
| | | }) |
| | | .catch(() => { |
| | | filterQuickToolsByRoutes(); |
| | | loadHome(); |
| | | }); |
| | | }); |
| | | |
| | | onShow(() => { |
| | | triggerVersionCheck("onShow"); |
| | | filterQuickToolsByRoutes(); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | } |
| | | } |
| | | .hero-section { |
| | | margin: 0 12px; |
| | | margin-bottom: 12px; |
| | | animation: fadeInUp 0.6s ease-out 0.1s both; |
| | | margin: 0 14px 12px; |
| | | animation: fadeInUp 0.6s ease-out 0.1s both; |
| | | } |
| | | |
| | | .bg-img { |
| | | width: 100%; |
| | | height: 10.25rem; |
| | | background: |
| | | linear-gradient(135deg, rgba(234, 245, 255, 0.98) 0%, rgba(220, 239, 255, 0.94) 42%, rgba(244, 250, 255, 0.96) 100%), |
| | | url("/static/images/banner/backview.png") center/cover no-repeat; |
| | | border-radius: 18px; |
| | | position: relative; |
| | | overflow: hidden; |
| | | box-shadow: 0 12px 30px rgba(118, 154, 186, 0.16); |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: |
| | | radial-gradient(circle at 14% 22%, rgba(255, 255, 255, 0.95) 0, rgba(255, 255, 255, 0) 28%), |
| | | radial-gradient(circle at 84% 18%, rgba(191, 226, 255, 0.7) 0, rgba(191, 226, 255, 0) 26%), |
| | | linear-gradient(180deg, rgba(255, 255, 255, 0.46) 0%, rgba(255, 255, 255, 0.16) 42%, rgba(206, 229, 247, 0.22) 100%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 18%; |
| | | bottom: -44px; |
| | | width: 64%; |
| | | height: 88px; |
| | | background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.72) 0%, rgba(255, 255, 255, 0) 72%); |
| | | border-radius: 50%; |
| | | filter: blur(10px); |
| | | pointer-events: none; |
| | | } |
| | | .hero-banner { |
| | | position: relative; |
| | | overflow: hidden; |
| | | border-radius: 18px; |
| | | padding: 18px 16px 16px; |
| | | min-height: 182px; |
| | | background: |
| | | linear-gradient(135deg, rgba(22, 74, 170, 0.92) 0%, rgba(33, 115, 185, 0.88) 48%, rgba(18, 156, 144, 0.82) 100%), |
| | | url("/static/images/banner/backview.png") center right / cover no-repeat; |
| | | box-shadow: 0 14px 34px rgba(29, 78, 137, 0.2); |
| | | border: 1px solid rgba(255, 255, 255, 0.18); |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: |
| | | linear-gradient(120deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.02) 32%, rgba(255, 255, 255, 0) 60%), |
| | | radial-gradient(circle at top right, rgba(255, 255, 255, 0.24) 0%, rgba(255, 255, 255, 0) 34%); |
| | | pointer-events: none; |
| | | } |
| | | |
| | | &::after { |
| | | content: ""; |
| | | position: absolute; |
| | | right: -28px; |
| | | bottom: -34px; |
| | | width: 156px; |
| | | height: 156px; |
| | | border-radius: 50%; |
| | | background: radial-gradient(circle, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 72%); |
| | | pointer-events: none; |
| | | } |
| | | } |
| | | |
| | | .hero-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | padding: 16px 16px 14px; |
| | | height: 100%; |
| | | backdrop-filter: blur(2px); |
| | | .hero-top { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .hero-ornaments { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | .hero-copy { |
| | | min-width: 0; |
| | | flex: 1; |
| | | } |
| | | |
| | | .hero-glow { |
| | | position: absolute; |
| | | border-radius: 50%; |
| | | filter: blur(4px); |
| | | background: radial-gradient(circle, rgba(255, 255, 255, 0.96) 0%, rgba(255, 255, 255, 0) 72%); |
| | | opacity: 0.9; |
| | | .hero-badge { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | height: 26px; |
| | | padding: 0 10px; |
| | | border-radius: 999px; |
| | | background: rgba(255, 255, 255, 0.16); |
| | | border: 1px solid rgba(255, 255, 255, 0.18); |
| | | color: rgba(255, 255, 255, 0.92); |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .hero-glow.glow-left { |
| | | left: -10px; |
| | | top: 8px; |
| | | width: 120px; |
| | | height: 120px; |
| | | .hero-title { |
| | | display: block; |
| | | margin-top: 12px; |
| | | color: #ffffff; |
| | | font-size: 24px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .hero-glow.glow-right { |
| | | right: -20px; |
| | | top: 4px; |
| | | width: 144px; |
| | | height: 144px; |
| | | background: radial-gradient(circle, rgba(207, 234, 255, 0.92) 0%, rgba(207, 234, 255, 0) 74%); |
| | | .hero-subtitle { |
| | | display: block; |
| | | margin-top: 8px; |
| | | color: rgba(255, 255, 255, 0.84); |
| | | font-size: 13px; |
| | | line-height: 1.45; |
| | | } |
| | | |
| | | .hero-mist { |
| | | position: absolute; |
| | | border-radius: 999px; |
| | | background: linear-gradient(90deg, rgba(255, 255, 255, 0.52), rgba(255, 255, 255, 0.08)); |
| | | border: 1px solid rgba(255, 255, 255, 0.34); |
| | | backdrop-filter: blur(10px); |
| | | box-shadow: 0 10px 24px rgba(154, 190, 219, 0.14); |
| | | .hero-meta { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .hero-mist.mist-top { |
| | | left: 18px; |
| | | top: 20px; |
| | | width: 112px; |
| | | height: 18px; |
| | | .hero-meta-item { |
| | | padding: 3px 10px; |
| | | border-radius: 999px; |
| | | background: rgba(255, 255, 255, 0.12); |
| | | color: rgba(255, 255, 255, 0.86); |
| | | font-size: 11px; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | .hero-mist.mist-bottom { |
| | | left: 18px; |
| | | top: 48px; |
| | | width: 72px; |
| | | height: 10px; |
| | | opacity: 0.82; |
| | | .hero-avatar { |
| | | position: relative; |
| | | z-index: 1; |
| | | width: 52px; |
| | | height: 52px; |
| | | flex: 0 0 52px; |
| | | border-radius: 16px; |
| | | background: rgba(255, 255, 255, 0.18); |
| | | border: 1px solid rgba(255, 255, 255, 0.22); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3); |
| | | } |
| | | |
| | | .hero-curve { |
| | | position: absolute; |
| | | border-radius: 999px; |
| | | border: 2px solid rgba(255, 255, 255, 0.72); |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0.12)); |
| | | box-shadow: |
| | | 0 10px 26px rgba(154, 190, 219, 0.16), |
| | | inset 0 1px 0 rgba(255, 255, 255, 0.8); |
| | | backdrop-filter: blur(10px); |
| | | .hero-avatar-text { |
| | | color: #ffffff; |
| | | font-size: 20px; |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .hero-curve.curve-main { |
| | | right: 18px; |
| | | bottom: 22px; |
| | | width: 176px; |
| | | height: 84px; |
| | | transform: rotate(-9deg); |
| | | border-top-left-radius: 90px; |
| | | border-bottom-right-radius: 90px; |
| | | opacity: 1; |
| | | .hero-panels { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: grid; |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | gap: 10px; |
| | | margin-top: 18px; |
| | | } |
| | | |
| | | .hero-curve.curve-sub { |
| | | right: 96px; |
| | | bottom: 60px; |
| | | width: 104px; |
| | | height: 40px; |
| | | transform: rotate(-9deg); |
| | | border-top-left-radius: 60px; |
| | | border-bottom-right-radius: 60px; |
| | | opacity: 0.9; |
| | | .hero-panel { |
| | | min-width: 0; |
| | | padding: 12px 10px; |
| | | border-radius: 14px; |
| | | background: rgba(11, 25, 48, 0.18); |
| | | border: 1px solid rgba(255, 255, 255, 0.14); |
| | | backdrop-filter: blur(10px); |
| | | } |
| | | |
| | | .hero-wave { |
| | | height: 1.1rem; |
| | | background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(244, 249, 253, 0.96) 100%); |
| | | margin-top: -1px; |
| | | position: relative; |
| | | .hero-panel-label { |
| | | display: block; |
| | | color: rgba(255, 255, 255, 0.7); |
| | | font-size: 11px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .hero-panel-value { |
| | | display: block; |
| | | margin-top: 8px; |
| | | color: #ffffff; |
| | | font-size: 18px; |
| | | font-weight: 700; |
| | | line-height: 1.2; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .hero-panel-hint { |
| | | display: block; |
| | | margin-top: 6px; |
| | | color: rgba(255, 255, 255, 0.72); |
| | | font-size: 11px; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | .safe-top { |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="attachment-page"> |
| | | <!-- 页é¢å¤´é¨ --> |
| | | <PageHeader :title="`æ¥çéä»¶ - ${taskInfo?.taskName || ''}`" @back="goBack" /> |
| | | |
| | | <!-- 页é¢å
容 --> |
| | | <view class="attachment-content"> |
| | | <!-- åç±»æ ç¾é¡µ --> |
| | | <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-list-container"> |
| | | <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="isImageFile(file)" |
| | | :src="file.url || file.downloadUrl" |
| | | class="attachment-preview" |
| | | mode="aspectFill" |
| | | /> |
| | | <view v-else class="attachment-video-preview"> |
| | | <u-icon name="video" size="40" 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"> |
| | | <u-icon name="folder-open" size="60" color="#ccc"></u-icon> |
| | | <text class="empty-text">该åç±»ææ éä»¶</text> |
| | | </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="20" 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> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import { onLoad } from '@dcloudio/uni-app'; |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import config from '@/config'; |
| | | |
| | | // ä»»å¡ä¿¡æ¯ |
| | | const taskInfo = ref(null); |
| | | |
| | | // éä»¶å表 |
| | | const attachmentList = ref([]); |
| | | |
| | | // å½åæ¥çç±»å |
| | | const currentViewType = ref('before'); // 'before', 'after', 'issue' |
| | | |
| | | // è§é¢é¢è§ç¸å
³ç¶æ |
| | | const showVideoDialog = ref(false); |
| | | const currentVideoFile = ref(null); |
| | | |
| | | // æä»¶è®¿é®åºç¡å |
| | | const filePreviewBase = config.fileUrl; |
| | | |
| | | // 页é¢å è½½ |
| | | onLoad((options) => { |
| | | if (options.taskInfo) { |
| | | try { |
| | | taskInfo.value = JSON.parse(decodeURIComponent(options.taskInfo)); |
| | | loadAttachments(); |
| | | } catch (e) { |
| | | console.error('è§£æä»»å¡ä¿¡æ¯å¤±è´¥:', e); |
| | | uni.showToast({ |
| | | title: 'å 载失败', |
| | | icon: 'error' |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // å è½½éä»¶æ°æ® |
| | | const loadAttachments = () => { |
| | | const task = taskInfo.value; |
| | | if (!task) return; |
| | | |
| | | attachmentList.value = []; |
| | | |
| | | // åç«¯åæ¾åæ®µ |
| | | const allList = Array.isArray(task?.commonFileList) ? task.commonFileList : []; |
| | | const beforeList = Array.isArray(task?.commonFileListBefore) |
| | | ? task.commonFileListBefore |
| | | : allList.filter((f) => f?.type === 10); |
| | | const afterList = Array.isArray(task?.commonFileListAfter) |
| | | ? task.commonFileListAfter |
| | | : allList.filter((f) => f?.type === 11); |
| | | const issueList = Array.isArray(task?.commonFileListIssue) |
| | | ? task.commonFileListIssue |
| | | : allList.filter((f) => f?.type === 12); |
| | | |
| | | const mapToViewFile = (file, viewType) => { |
| | | const u = normalizeFileUrl(file?.url || file?.downloadUrl || ''); |
| | | return { |
| | | ...file, |
| | | type: viewType, |
| | | name: file?.name || file?.originalFilename || file?.bucketFilename, |
| | | bucketFilename: file?.bucketFilename || file?.name, |
| | | originalFilename: file?.originalFilename || file?.name, |
| | | url: u, |
| | | downloadUrl: u, |
| | | size: file?.size || file?.byteSize, |
| | | }; |
| | | }; |
| | | |
| | | attachmentList.value.push(...beforeList.map((f) => mapToViewFile(f, 0))); |
| | | attachmentList.value.push(...afterList.map((f) => mapToViewFile(f, 1))); |
| | | attachmentList.value.push(...issueList.map((f) => mapToViewFile(f, 2))); |
| | | }; |
| | | |
| | | // å°å端è¿åçæä»¶å°åè§èæå¯è®¿é®URL |
| | | const normalizeFileUrl = (rawUrl) => { |
| | | try { |
| | | if (!rawUrl || typeof rawUrl !== 'string') return ''; |
| | | const url = rawUrl.trim(); |
| | | if (!url) return ''; |
| | | if (/^https?:\/\//i.test(url)) return url; |
| | | if (url.startsWith('/')) return `${filePreviewBase}${url}`; |
| | | |
| | | // Windows path -> web path |
| | | if (/^[a-zA-Z]:\\/.test(url)) { |
| | | const normalized = url.replace(/\\/g, '/'); |
| | | const idx = normalized.indexOf('/prod/'); |
| | | if (idx >= 0) { |
| | | const relative = normalized.slice(idx + '/prod/'.length); |
| | | return `${filePreviewBase}/${relative}`; |
| | | } |
| | | return normalized; |
| | | } |
| | | |
| | | return `${filePreviewBase}/${url.replace(/^\//, '')}`; |
| | | } catch (e) { |
| | | return rawUrl || ''; |
| | | } |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // 忢æ¥çç±»å |
| | | 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) => { |
| | | if (file.contentType && file.contentType.startsWith('image/')) { |
| | | return true; |
| | | } |
| | | 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 = () => { |
| | | uni.showToast({ |
| | | title: 'è§é¢ææ¾å¤±è´¥', |
| | | icon: 'error', |
| | | }); |
| | | }; |
| | | |
| | | // æ ¼å¼åæä»¶å¤§å° |
| | | 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'; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .attachment-page { |
| | | min-height: 100vh; |
| | | background-color: #f5f5f5; |
| | | } |
| | | |
| | | .attachment-content { |
| | | padding: 15px; |
| | | } |
| | | |
| | | /* æ ç¾é¡µæ ·å¼ */ |
| | | .attachment-tabs { |
| | | display: flex; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | margin-bottom: 15px; |
| | | padding: 4px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 12px 8px; |
| | | font-size: 14px; |
| | | color: #666; |
| | | border-radius: 8px; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background: #409eff; |
| | | color: #fff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* éä»¶åè¡¨æ ·å¼ */ |
| | | .attachment-list-container { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | min-height: 400px; |
| | | } |
| | | |
| | | .attachment-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .attachment-item { |
| | | width: calc(33.33% - 10px); |
| | | background: #f8f9fa; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .attachment-item:active { |
| | | transform: scale(0.98); |
| | | } |
| | | |
| | | .attachment-preview-container { |
| | | width: 100%; |
| | | height: 120px; |
| | | background: #e9ecef; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .attachment-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .attachment-video-preview { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .video-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .attachment-info { |
| | | padding: 10px; |
| | | } |
| | | |
| | | .attachment-name { |
| | | font-size: 12px; |
| | | color: #333; |
| | | display: block; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .attachment-size { |
| | | font-size: 10px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* ç©ºç¶ææ ·å¼ */ |
| | | .attachment-empty { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 80px 20px; |
| | | color: #999; |
| | | } |
| | | |
| | | .empty-text { |
| | | margin-top: 15px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | /* è§é¢å¼¹çªæ ·å¼ */ |
| | | .video-modal-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.9); |
| | | z-index: 10000; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .video-modal-container { |
| | | width: 100%; |
| | | max-width: 800px; |
| | | background: #000; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .video-modal-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 15px 20px; |
| | | background: #1a1a1a; |
| | | } |
| | | |
| | | .video-modal-title { |
| | | font-size: 16px; |
| | | color: #fff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .close-btn-video { |
| | | width: 32px; |
| | | height: 32px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: rgba(255, 255, 255, 0.1); |
| | | border-radius: 50%; |
| | | } |
| | | |
| | | .video-modal-body { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .video-player { |
| | | width: 100%; |
| | | height: 400px; |
| | | border-radius: 8px; |
| | | } |
| | | </style> |
| | |
| | | > |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">ä¸ä¼ </text> |
| | | <text class="popup-title">å·¡æ£è®°å½ä¸ä¼ </text> |
| | | </view> |
| | | |
| | | <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="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | <view class="title">å·¡æ£ç¶æ</view> |
| | | <view class="exception-section"> |
| | | <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 class="option-text">æ£å¸¸</text> |
| | | </view> |
| | | <view |
| | | class="exception-option" |
| | | :class="{ active: hasException === true }" |
| | | @click="setExceptionStatus(true)" |
| | | > |
| | | <u-icon name="close-circle" size="20" color="#ff4d4f"></u-icon> |
| | | <text class="option-text">åå¨å¼å¸¸</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å¼å¸¸æè¿°ï¼ä»
å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> |
| | | <view class="form-container" v-if="hasException === true"> |
| | | <view class="title">å¼å¸¸æè¿°</view> |
| | | <u-input |
| | | v-model="exceptionDescription" |
| | | type="textarea" |
| | | :maxlength="500" |
| | | placeholder="请æè¿°å¼å¸¸æ
åµ..." |
| | | :customStyle="{ padding: '10px', backgroundColor: '#f5f5f5' }" |
| | | /> |
| | | </view> |
| | | |
| | | <view class="form-container"> |
| | | <view class="title">ç产å</view> |
| | | <u-upload |
| | | :fileList="afterModelValue" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | name="after" |
| | | multiple |
| | | :maxCount="10" |
| | | :maxSize="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | </view> |
| | | |
| | | <view class="form-container"> |
| | | <view class="title">ç产é®é¢</view> |
| | | <u-upload |
| | | :fileList="issueModelValue" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | name="issue" |
| | | multiple |
| | | :maxCount="10" |
| | | :maxSize="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | <!-- ä¸ä¼ åºåï¼ä»
å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> |
| | | <template v-if="hasException === true"> |
| | | <view class="form-container"> |
| | | <view class="title">ç产å</view> |
| | | <u-upload |
| | | :fileList="beforeModelValue" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | name="before" |
| | | multiple |
| | | :maxCount="10" |
| | | :maxSize="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | </view> |
| | | |
| | | <view class="form-container"> |
| | | <view class="title">ç产å</view> |
| | | <u-upload |
| | | :fileList="afterModelValue" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | name="after" |
| | | multiple |
| | | :maxCount="10" |
| | | :maxSize="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | </view> |
| | | |
| | | <view class="form-container"> |
| | | <view class="title">ç产é®é¢</view> |
| | | <u-upload |
| | | :fileList="issueModelValue" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | name="issue" |
| | | multiple |
| | | :maxCount="10" |
| | | :maxSize="5 * 1024 * 1024" |
| | | accept="image/*" |
| | | :previewFullImage="true" |
| | | ></u-upload> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- æ£å¸¸ç¶ææç¤º --> |
| | | <view class="form-container normal-tip" v-if="hasException === false"> |
| | | <u-icon name="info-circle" size="40" color="#52c41a"></u-icon> |
| | | <text class="tip-text">设å¤è¿è¡æ£å¸¸ï¼æ éä¸ä¼ ç
§ç</text> |
| | | </view> |
| | | </view> |
| | | |
| | |
| | | const afterModelValue = ref([]) |
| | | const issueModelValue = ref([]) |
| | | const infoData = ref(null) |
| | | |
| | | // å¼å¸¸ç¶æï¼null=æªéæ©, false=æ£å¸¸, true=å¼å¸¸ |
| | | const hasException = ref(null) |
| | | // å¼å¸¸æè¿° |
| | | const exceptionDescription = ref('') |
| | | |
| | | // 计ç®ä¸ä¼ URL |
| | | const uploadFileUrl = computed(() => { |
| | |
| | | } |
| | | } |
| | | |
| | | // 设置å¼å¸¸ç¶æ |
| | | const setExceptionStatus = (status) => { |
| | | hasException.value = status |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = async () => { |
| | | try { |
| | | // æ£æ¥æ¯å¦éæ©äºå·¡æ£ç¶æ |
| | | if (hasException.value === null) { |
| | | uni.showToast({ |
| | | title: 'è¯·éæ©å·¡æ£ç¶æ', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | |
| | | // 妿æ¯å¼å¸¸ç¶æï¼æ£æ¥æ¯å¦æä¸ä¼ æä»¶ |
| | | if (hasException.value === true) { |
| | | const totalFiles = beforeModelValue.value.length + afterModelValue.value.length + issueModelValue.value.length |
| | | if (totalFiles === 0) { |
| | | uni.showToast({ |
| | | title: '请ä¸ä¼ å¼å¸¸ç
§ç', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | // æ£æ¥æ¯å¦å¡«åäºå¼å¸¸æè¿° |
| | | if (!exceptionDescription.value.trim()) { |
| | | uni.showToast({ |
| | | title: '请填åå¼å¸¸æè¿°', |
| | | icon: 'none' |
| | | }) |
| | | return |
| | | } |
| | | } |
| | | |
| | | let arr = [] |
| | | if (beforeModelValue.value.length > 0) { |
| | | arr.push(...beforeModelValue.value.map(item => ({ ...item, statusType: 0 }))) |
| | |
| | | |
| | | // æäº¤æ°æ® |
| | | infoData.value.storageBlobDTO = arr |
| | | infoData.value.hasException = hasException.value |
| | | infoData.value.exceptionDescription = exceptionDescription.value |
| | | await submitInspectionRecord({ ...infoData.value }) |
| | | |
| | | uni.showToast({ |
| | |
| | | beforeModelValue.value = [] |
| | | afterModelValue.value = [] |
| | | issueModelValue.value = [] |
| | | hasException.value = null |
| | | exceptionDescription.value = '' |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | |
| | | border-top: 1px solid #f0f0f0; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | // å¼å¸¸ç¶æéæ©æ ·å¼ |
| | | .exception-section { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .exception-options { |
| | | display: flex; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .exception-option { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 8px; |
| | | padding: 15px 20px; |
| | | border: 2px solid #e0e0e0; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | background-color: #fff; |
| | | |
| | | &.active { |
| | | border-color: #1890ff; |
| | | background-color: #e6f7ff; |
| | | } |
| | | |
| | | &:active { |
| | | opacity: 0.8; |
| | | } |
| | | } |
| | | |
| | | .option-text { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | // æ£å¸¸ç¶ææç¤ºæ ·å¼ |
| | | .normal-tip { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | padding: 40px 20px; |
| | | background-color: #f6ffed; |
| | | border: 1px dashed #b7eb8f; |
| | | border-radius: 8px; |
| | | |
| | | .tip-text { |
| | | margin-top: 15px; |
| | | font-size: 14px; |
| | | color: #52c41a; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <text class="detail-value">{{ item.taskId || item.id }}</text> |
| | | </view> |
| | | <view class="detail-item"> |
| | | <text class="detail-label">å·¡æ£é¡¹ç®</text> |
| | | <text class="detail-value">{{ item.inspectionProject || 'æ ' }}</text> |
| | | </view> |
| | | <view class="detail-item"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remarks || 'æ ' }}</text> |
| | | </view> |
| | |
| | | size="small" |
| | | type="primary" |
| | | inverted></uni-tag> |
| | | <uni-tag v-else="" |
| | | <uni-tag v-else |
| | | text="æªå·¡æ£" |
| | | size="small" |
| | | type="warning" |
| | |
| | | <view v-if="taskTableData?.length === 0" |
| | | class="no-data"> |
| | | <text>ææ æ°æ®</text> |
| | | </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="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ marginRight: '10px', flex: 1 }"> |
| | | <u-icon name="camera" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></u-icon> |
| | | {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç
§' }} |
| | | </u-button> |
| | | <u-button type="success" |
| | | @click="chooseMedia('video')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }"> |
| | | <uni-icons type="videocam" |
| | | name="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></uni-icons> |
| | | {{ uploading ? 'ä¸ä¼ ä¸...' : 'æè§é¢' }} |
| | | </u-button> |
| | | </view> |
| | | <!-- ä¸ä¼ è¿åº¦ --> |
| | | <view v-if="uploading" |
| | | class="upload-progress"> |
| | | <u-line-progress :percentage="uploadProgress" |
| | | :showText="true" |
| | | activeColor="#409eff"></u-line-progress> |
| | | </view> |
| | | <!-- å½ååç±»çæä»¶å表 --> |
| | | <view v-if="getCurrentFiles().length > 0" |
| | | class="file-list"> |
| | | <view v-for="(file, index) in getCurrentFiles()" |
| | | :key="index" |
| | | class="file-item"> |
| | | <view class="file-preview-container"> |
| | | <image v-if="file.type === 'image' || (file.type !== 'video' && !file.type)" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | class="file-preview" |
| | | mode="aspectFill" /> |
| | | <view v-else-if="file.type === 'video'" |
| | | class="video-preview"> |
| | | <uni-icons type="videocam" |
| | | name="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px;"></uni-icons> |
| | | <text class="video-text">è§é¢</text> |
| | | </view> |
| | | <!-- å é¤æé® --> |
| | | <view class="delete-btn" |
| | | @click="removeFile(index)"> |
| | | <u-icon name="close" |
| | | size="12" |
| | | color="#fff"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ file.bucketFilename || file.name || (file.type === 'image' ? 'å¾ç' : 'è§é¢') |
| | | }}</text> |
| | | <text class="file-size">{{ formatFileSize(file.size) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="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> |
| | | <!-- è§é¢é¢è§å¼¹çª --> |
| | |
| | | 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: "/file/upload", |
| | | limit: 10, |
| | | fileSize: 50, // MB |
| | | fileType: ["jpg", "jpeg", "png", "mp4", "mov"], |
| | | maxVideoDuration: 60, // ç§ |
| | | }; |
| | | |
| | | // 计ç®ä¸ä¼ URL |
| | | const uploadFileUrl = computed(() => { |
| | | const baseUrl = config.baseUrl; |
| | | |
| | | return baseUrl + uploadConfig.action; |
| | | }); |
| | | |
| | | // 计ç®è¯·æ±å¤´ |
| | | const headers = computed(() => { |
| | | const token = getToken(); |
| | | return token ? { Authorization: "Bearer " + token } : {}; |
| | | }); |
| | | |
| | | // 请æ±åæ¶æ å¿ï¼ç¨äºåæ¶æ£å¨è¿è¡çè¯·æ± |
| | | let isRequestCancelled = false; |
| | |
| | | onUnmounted(() => { |
| | | // è®¾ç½®åæ¶æ å¿ï¼é»æ¢åç»ç弿¥æä½ |
| | | isRequestCancelled = true; |
| | | |
| | | // å
³éä¸ä¼ å¼¹çª |
| | | if (showUploadDialog.value) { |
| | | showUploadDialog.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); |
| | | } |
| | | |
| | | // ä¼ ç»å端çä¸´æ¶æä»¶IDå表ï¼tempFileIdsï¼ |
| | | // å
¼å®¹ï¼æäºæ¥å£å¯è½è¿å tempId / tempFileId / id |
| | | let tempFileIds = []; |
| | | if (arr !== null && arr.length > 0) { |
| | | tempFileIds = arr |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(v => v !== undefined && v !== null && v !== ""); |
| | | } |
| | | |
| | | // æäº¤æ°æ® |
| | | infoData.value.storageBlobDTO = arr; |
| | | // æ·»å å¼å¸¸ç¶æä¿¡æ¯ |
| | | infoData.value.hasException = hasException.value; |
| | | infoData.value.tempFileIds = tempFileIds; |
| | | const result = await uploadInspectionTask({ ...infoData.value }); |
| | | |
| | | // æ£æ¥æäº¤ç»æ |
| | | if (result && (result.code === 200 || result.success)) { |
| | | // æäº¤æå |
| | | closeToast(); // å
³éå è½½æç¤º |
| | | |
| | | uni.showToast({ |
| | | title: "æäº¤æå", |
| | | icon: "success", |
| | | }); |
| | | |
| | | // å
³éå¼¹çª |
| | | closeUploadDialog(); |
| | | |
| | | // å·æ°å表 |
| | | setTimeout(() => { |
| | | reloadPage(); |
| | | }, 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 taskData = encodeURIComponent(JSON.stringify(task)); |
| | | uni.navigateTo({ |
| | | url: `/pages/inspectionUpload/upload?taskInfo=${taskData}`, |
| | | }); |
| | | }; |
| | | |
| | | // å¾çä¸ä¼ (å¯éæ©å¾çä¸ä¼ æè
æ¯ç¸æºæç
§) |
| | | const startUploadForTask = async (task, type) => { |
| | | // ç´æ¥æå¼ä¸ä¼ å¼¹çª |
| | | // æå¼ä¸ä¼ é¡µé¢ |
| | | openUploadDialog(task); |
| | | }; |
| | | |
| | | // æ¥çéä»¶ |
| | | // æ¥çéä»¶ - 跳转å°éä»¶é¡µé¢ |
| | | const viewAttachments = async task => { |
| | | try { |
| | | currentViewTask.value = task; |
| | | currentViewType.value = "before"; |
| | | |
| | | // è§£ææ°çæ°æ®ç»æ |
| | | attachmentList.value = []; |
| | | |
| | | // åç«¯åæ¾åæ®µï¼ä½ æä¾çæ°æ®ç»æï¼ï¼ |
| | | // - commonFileListBeforeï¼ç产åï¼é常 type=10ï¼ |
| | | // - commonFileListAfterï¼ç产ä¸ï¼é常 type=11ï¼ |
| | | // - commonFileListï¼å¯è½æ¯å
¨é¨/å
åºï¼è¥å
å«ç产åï¼ä¸è¬ type=12ï¼ |
| | | const allList = Array.isArray(task?.commonFileList) |
| | | ? task.commonFileList |
| | | : []; |
| | | const beforeList = Array.isArray(task?.commonFileListBefore) |
| | | ? task.commonFileListBefore |
| | | : allList.filter(f => f?.type === 10); |
| | | const afterList = Array.isArray(task?.commonFileListAfter) |
| | | ? task.commonFileListAfter |
| | | : allList.filter(f => f?.type === 11); |
| | | // 妿å端åç»è¡¥äº commonFileListIssueï¼åä¼å
ç¨ï¼å¦åä» commonFileList éæ type=12 å
åº |
| | | const issueList = Array.isArray(task?.commonFileListIssue) |
| | | ? task.commonFileListIssue |
| | | : allList.filter(f => f?.type === 12); |
| | | |
| | | const mapToViewFile = (file, viewType) => { |
| | | const u = normalizeFileUrl(file?.url || file?.downloadUrl || ""); |
| | | return { |
| | | ...file, |
| | | // ç¨äºä¸æ ç¾é¡µåç»ï¼0=ç产å 1=çäº§ä¸ 2=ç产å |
| | | type: viewType, |
| | | name: file?.name || file?.originalFilename || file?.bucketFilename, |
| | | bucketFilename: file?.bucketFilename || file?.name, |
| | | originalFilename: file?.originalFilename || file?.name, |
| | | url: u, |
| | | downloadUrl: u, |
| | | size: file?.size || file?.byteSize, |
| | | }; |
| | | }; |
| | | |
| | | attachmentList.value.push(...beforeList.map(f => mapToViewFile(f, 0))); |
| | | attachmentList.value.push(...afterList.map(f => mapToViewFile(f, 1))); |
| | | attachmentList.value.push(...issueList.map(f => mapToViewFile(f, 2))); |
| | | |
| | | 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) || []; |
| | | }; |
| | | // è·åtypeå¼ |
| | | const getTabType = () => { |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | return 10; |
| | | case "after": |
| | | return 11; |
| | | case "issue": |
| | | return 12; |
| | | default: |
| | | return 10; |
| | | } |
| | | }; |
| | | // è·åå½åæ¥çç±»åçéä»¶ |
| | | const getCurrentViewAttachments = () => { |
| | | switch (currentViewType.value) { |
| | | case "before": |
| | | return getAttachmentsByType(0); |
| | | case "after": |
| | | return getAttachmentsByType(1); |
| | | case "issue": |
| | | return getAttachmentsByType(2); |
| | | default: |
| | | return []; |
| | | } |
| | | const taskData = encodeURIComponent(JSON.stringify(task)); |
| | | uni.navigateTo({ |
| | | url: `/pages/inspectionUpload/attachment?taskInfo=${taskData}`, |
| | | }); |
| | | }; |
| | | |
| | | // 夿æ¯å¦ä¸ºå¾çæä»¶ |
| | |
| | | title: "è§é¢ææ¾å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | }; |
| | | |
| | | // æç
§/æè§é¢ï¼çæºä¼å
ç¨ chooseMediaï¼ä¸æ¯æåéçº§ï¼ |
| | | const chooseMedia = type => { |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`, |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const remaining = uploadConfig.limit - getCurrentFiles().length; |
| | | |
| | | // ä¼å
ï¼chooseMediaï¼æ¯æ image/videoï¼ |
| | | if (typeof uni.chooseMedia === "function") { |
| | | uni.chooseMedia({ |
| | | count: Math.min(remaining, 1), |
| | | mediaType: [type || "image"], |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | try { |
| | | const files = res?.tempFiles || []; |
| | | if (!files.length) throw new Error("æªè·åå°æä»¶"); |
| | | |
| | | files.forEach((tf, idx) => { |
| | | const filePath = tf.tempFilePath || tf.path || ""; |
| | | const fileType = tf.fileType || type || "image"; |
| | | const ext = fileType === "video" ? "mp4" : "jpg"; |
| | | const file = { |
| | | tempFilePath: filePath, |
| | | path: filePath, |
| | | type: fileType, |
| | | name: `${fileType}_${Date.now()}_${idx}.${ext}`, |
| | | size: tf.size || 0, |
| | | duration: tf.duration || 0, |
| | | createTime: Date.now(), |
| | | uid: Date.now() + Math.random() + idx, |
| | | }; |
| | | handleBeforeUpload(file); |
| | | }); |
| | | } catch (e) { |
| | | console.error("å¤çææç»æå¤±è´¥:", e); |
| | | uni.showToast({ title: "å¤çæä»¶å¤±è´¥", icon: "error" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("ææå¤±è´¥:", err); |
| | | uni.showToast({ title: "ææå¤±è´¥", icon: "error" }); |
| | | }, |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // é级ï¼chooseImage / chooseVideo |
| | | if (type === "video") { |
| | | chooseVideo(); |
| | | } else { |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | const tempFilePath = res?.tempFilePaths?.[0]; |
| | | const tempFile = res?.tempFiles?.[0] || {}; |
| | | if (!tempFilePath) return; |
| | | handleBeforeUpload({ |
| | | tempFilePath, |
| | | path: tempFilePath, |
| | | type: "image", |
| | | name: `photo_${Date.now()}.jpg`, |
| | | size: tempFile.size || 0, |
| | | createTime: Date.now(), |
| | | uid: Date.now() + Math.random(), |
| | | }); |
| | | }, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // æç
§ |
| | | const chooseImage = () => { |
| | | if (uploadFiles.value.length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `æå¤åªè½ææ${uploadConfig.limit}个æä»¶`, |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | uni.chooseMedia({ |
| | | count: 1, |
| | | mediaType: ["image", "video"], |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | try { |
| | | if (!res.tempFiles || res.tempFiles.length === 0) { |
| | | throw new Error("æªè·åå°å¾çæä»¶"); |
| | | } |
| | | |
| | | const tempFilePath = res.tempFiles[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 => { |
| | | // æ ¡éªæä»¶ç±»å |
| | | 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; |
| | | }; |
| | | |
| | | // æä»¶ä¸ä¼ å¤çï¼çæºèµ° uni.uploadFileï¼ |
| | | const uploadFile = async file => { |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | number.value++; // å¢å ä¸ä¼ è®¡æ° |
| | | |
| | | // ç¡®ä¿tokenåå¨ |
| | | const token = getToken(); |
| | | if (!token) { |
| | | handleUploadError("ç¨æ·æªç»å½"); |
| | | return; |
| | | } |
| | | |
| | | const typeValue = getTabType(); // ç产å:10, ç产ä¸:11, ç产å:12 |
| | | |
| | | uploadWithUniUploadFile( |
| | | file, |
| | | file.tempFilePath || file.path || "", |
| | | typeValue, |
| | | token |
| | | ); |
| | | }; |
| | | |
| | | // 使ç¨uni.uploadFileä¸ä¼ ï¼éH5ç¯å¢æH5åéæ¹æ¡ï¼ |
| | | const uploadWithUniUploadFile = (file, filePath, typeValue, token) => { |
| | | if (!filePath) { |
| | | handleUploadError("æä»¶è·¯å¾ä¸åå¨"); |
| | | return; |
| | | } |
| | | |
| | | const uploadTask = uni.uploadFile({ |
| | | url: uploadFileUrl.value, |
| | | filePath: filePath, |
| | | name: "file", |
| | | formData: { |
| | | type: typeValue, |
| | | }, |
| | | header: { |
| | | Authorization: `Bearer ${token}`, |
| | | }, |
| | | success: res => { |
| | | try { |
| | | if (res.statusCode === 200) { |
| | | const response = JSON.parse(res.data); |
| | | if (response.code === 200) { |
| | | handleUploadSuccess(response, file); |
| | | uni.showToast({ |
| | | title: "ä¸ä¼ æå", |
| | | icon: "success", |
| | | }); |
| | | } else { |
| | | handleUploadError(response.msg || "æå¡å¨è¿åé误"); |
| | | } |
| | | } else { |
| | | handleUploadError(`æå¡å¨é误ï¼ç¶æç : ${res.statusCode}`); |
| | | } |
| | | } catch (e) { |
| | | console.error("è§£æååºå¤±è´¥:", e); |
| | | console.error("åå§ååºæ°æ®:", res.data); |
| | | handleUploadError("ååºæ°æ®è§£æå¤±è´¥: " + e.message); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("ä¸ä¼ 失败:", err.errMsg || err); |
| | | number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° |
| | | |
| | | let errorMessage = "ä¸ä¼ 失败"; |
| | | if (err.errMsg) { |
| | | if (err.errMsg.includes("statusCode: null")) { |
| | | errorMessage = "ç½ç»è¿æ¥å¤±è´¥ï¼è¯·æ£æ¥ç½ç»è®¾ç½®"; |
| | | } else if (err.errMsg.includes("timeout")) { |
| | | errorMessage = "ä¸ä¼ è¶
æ¶ï¼è¯·éè¯"; |
| | | } else if (err.errMsg.includes("fail")) { |
| | | errorMessage = "ä¸ä¼ 失败ï¼è¯·æ£æ¥ç½ç»è¿æ¥"; |
| | | } else { |
| | | errorMessage = err.errMsg; |
| | | } |
| | | } |
| | | |
| | | handleUploadError(errorMessage); |
| | | }, |
| | | complete: () => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | }, |
| | | }); |
| | | |
| | | // çå¬ä¸ä¼ è¿åº¦ |
| | | if (uploadTask && uploadTask.onProgressUpdate) { |
| | | uploadTask.onProgressUpdate(res => { |
| | | uploadProgress.value = res.progress; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ 失败å¤ç |
| | | const handleUploadError = (message = "ä¸ä¼ æä»¶å¤±è´¥", showRetry = false) => { |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | |
| | | if (showRetry) { |
| | | uni.showModal({ |
| | | title: "ä¸ä¼ 失败", |
| | | content: message + "ï¼æ¯å¦éè¯ï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | // ç¨æ·éæ©éè¯ï¼è¿éå¯ä»¥éæ°è§¦åä¸ä¼ |
| | | } |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ æååè° |
| | | const handleUploadSuccess = (res, file) => { |
| | | console.log("ä¸ä¼ æåååº:", res); |
| | | |
| | | // å¤çä¸åçæ°æ®ç»æï¼å¯è½æ¯æ°ç»ï¼ä¹å¯è½æ¯å个对象 |
| | | let uploadedFile = null; |
| | | uploadedFile = res.data; |
| | | |
| | | if (!uploadedFile) { |
| | | console.error("æ æ³è§£æä¸ä¼ ååºæ°æ®:", res); |
| | | number.value--; // ä¸ä¼ 失败æ¶åå°è®¡æ° |
| | | handleUploadError("ä¸ä¼ ååºæ°æ®æ ¼å¼é误", false); |
| | | return; |
| | | } |
| | | |
| | | // æ ¹æ®å½åä¸ä¼ ç±»å设置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 |
| | | tempId: uploadedFile.tempId ?? uploadedFile.tempFileId ?? uploadedFile.id, |
| | | url: |
| | | uploadedFile.url || |
| | | uploadedFile.downloadUrl || |
| | | file.tempFilePath || |
| | | file.path, |
| | | bucketFilename: |
| | | uploadedFile.bucketFilename || uploadedFile.originalFilename || file.name, |
| | | downloadUrl: uploadedFile.downloadUrl || uploadedFile.url, |
| | | size: uploadedFile.size || uploadedFile.byteSize || file.size, |
| | | createTime: uploadedFile.createTime || new Date().getTime(), |
| | | type: typeValue, // æ·»å ç±»ååæ®µï¼0=ç产å, 1=ç产ä¸, 2=ç产å |
| | | }; |
| | | |
| | | uploadList.value.push(fileData); |
| | | |
| | | // ç«å³æ·»å å°å¯¹åºçåç±»ï¼ä¸çå¾
æææä»¶ä¸ä¼ 宿 |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | beforeModelValue.value.push(fileData); |
| | | break; |
| | | case "after": |
| | | afterModelValue.value.push(fileData); |
| | | break; |
| | | case "issue": |
| | | issueModelValue.value.push(fileData); |
| | | break; |
| | | } |
| | | |
| | | // éç½®ä¸ä¼ å表ï¼å ä¸ºå·²ç»æ·»å å°å¯¹åºåç±»äºï¼ |
| | | uploadList.value = []; |
| | | number.value = 0; |
| | | }; |
| | | |
| | | // ä¸ä¼ ç»æå¤çï¼å·²åºå¼ï¼ç°å¨å¨handleUploadSuccessä¸ç´æ¥å¤çï¼ |
| | | const uploadedSuccessfully = () => { |
| | | // æ¤å½æ°å·²ä¸å使ç¨ï¼æä»¶ä¸ä¼ æååç«å³æ·»å å°å¯¹åºåç±» |
| | | }; |
| | | |
| | | // æ ¼å¼åæä»¶å¤§å° |
| | | 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"; |
| | | }; |
| | | </script> |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | | /* è§é¢é¢è§å¼¹çªæ ·å¼ */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="inspection-upload-page"> |
| | | <!-- 页é¢å¤´é¨ --> |
| | | <PageHeader title="ä¸ä¼ å·¡æ£è®°å½" |
| | | @back="goBack" /> |
| | | <!-- 页é¢å
容 --> |
| | | <view class="upload-content"> |
| | | <!-- ä»»å¡ä¿¡æ¯å¡ç --> |
| | | <view class="task-info-card" |
| | | v-if="taskInfo"> |
| | | <view class="task-info-header"> |
| | | <text class="task-name">{{ taskInfo.taskName }}</text> |
| | | </view> |
| | | <view class="task-info-body"> |
| | | <view class="info-item"> |
| | | <text class="info-label">ä»»å¡ID</text> |
| | | <text class="info-value">{{ taskInfo.taskId || taskInfo.id }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">å·¡æ£ä½ç½®</text> |
| | | <text class="info-value">{{ taskInfo.inspectionLocation || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="info-label">æ§è¡äºº</text> |
| | | <text class="info-value">{{ taskInfo.inspector || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å¼å¸¸ç¶æéæ© --> |
| | | <view class="section-card"> |
| | | <view class="section-title">å·¡æ£ç¶æ</view> |
| | | <view class="exception-options"> |
| | | <view class="exception-option" |
| | | :class="{ active: hasException === false }" |
| | | @click="setExceptionStatus(false)"> |
| | | <u-icon name="checkmark-circle" |
| | | size="20" |
| | | color="#52c41a"></u-icon> |
| | | <text class="option-text">æ£å¸¸</text> |
| | | </view> |
| | | <view class="exception-option" |
| | | :class="{ active: hasException === true }" |
| | | @click="setExceptionStatus(true)"> |
| | | <u-icon name="close-circle" |
| | | size="20" |
| | | color="#ff4d4f"></u-icon> |
| | | <text class="option-text">åå¨å¼å¸¸</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å¼å¸¸æè¿°ï¼ä»
å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> |
| | | <view class="section-card" |
| | | v-if="hasException === true"> |
| | | <view class="section-title">å¼å¸¸æè¿°</view> |
| | | <textarea v-model="abnormalDescription" |
| | | class="exception-textarea" |
| | | maxlength="500" |
| | | placeholder="请æè¿°å¼å¸¸æ
åµ..." /> |
| | | </view> |
| | | <!-- åç±»æ ç¾é¡µï¼ä»
å¨å¼å¸¸æ¶æ¾ç¤ºï¼ --> |
| | | <view class="section-card" |
| | | v-if="hasException === true"> |
| | | <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="upload-area"> |
| | | <view class="upload-buttons"> |
| | | <u-button type="primary" |
| | | @click="chooseMedia('image')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ marginRight: '10px', flex: 1 }"> |
| | | <u-icon name="camera" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></u-icon> |
| | | {{ uploading ? 'ä¸ä¼ ä¸...' : 'æç
§' }} |
| | | </u-button> |
| | | <u-button type="success" |
| | | @click="chooseMedia('video')" |
| | | :loading="uploading" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit" |
| | | :customStyle="{ flex: 1 }"> |
| | | <uni-icons type="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></uni-icons> |
| | | {{ 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' || (file.type !== 'video' && !file.type)" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | class="file-preview" |
| | | mode="aspectFill" /> |
| | | <view v-else-if="file.type === 'video'" |
| | | class="video-preview"> |
| | | <uni-icons type="videocam" |
| | | size="18" |
| | | color="#fff" |
| | | style="margin-right: 5px"></uni-icons> |
| | | <text class="video-text">è§é¢</text> |
| | | </view> |
| | | <!-- å é¤æé® --> |
| | | <view class="delete-btn" |
| | | @click="removeFile(index)"> |
| | | <u-icon name="close" |
| | | size="12" |
| | | color="#fff"></u-icon> |
| | | </view> |
| | | </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> |
| | | <!-- ç»è®¡ä¿¡æ¯ --> |
| | | <view class="upload-summary"> |
| | | <text class="summary-text"> |
| | | ç产å: {{ beforeModelValue.length }}个æä»¶ | |
| | | ç产ä¸: {{ afterModelValue.length }}个æä»¶ | |
| | | ç产å: {{ issueModelValue.length }}个æä»¶ |
| | | </text> |
| | | </view> |
| | | </view> |
| | | <!-- æ£å¸¸ç¶ææç¤º --> |
| | | <view class="normal-tip-card" |
| | | v-if="hasException === false"> |
| | | <u-icon name="info-circle" |
| | | size="60" |
| | | color="#52c41a"></u-icon> |
| | | <text class="tip-text">设å¤è¿è¡æ£å¸¸ï¼æ éä¸ä¼ ç
§ç</text> |
| | | </view> |
| | | </view> |
| | | <!-- åºé¨æé® --> |
| | | <view class="footer-buttons"> |
| | | <u-button @click="goBack" |
| | | :customStyle="{ marginRight: '10px' }">åæ¶</u-button> |
| | | <u-button v-if="hasException === true" |
| | | type="warning" |
| | | @click="goToRepair" |
| | | :customStyle="{ marginRight: '10px' }"> |
| | | æ°å¢æ¥ä¿® |
| | | </u-button> |
| | | <u-button type="primary" |
| | | @click="submitUpload">æäº¤</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { uploadInspectionTask } from "@/api/inspectionManagement"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import config from "@/config"; |
| | | |
| | | // ä»»å¡ä¿¡æ¯ |
| | | const taskInfo = ref(null); |
| | | |
| | | // ä¸ä¼ ç¸å
³ç¶æ |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | |
| | | // ä¸ä¸ªåç±»çä¸ä¼ ç¶æ |
| | | const beforeModelValue = ref([]); // ç产å |
| | | const afterModelValue = ref([]); // çäº§ä¸ |
| | | const issueModelValue = ref([]); // ç产å |
| | | |
| | | // å½åæ¿æ´»çä¸ä¼ ç±»å |
| | | const currentUploadType = ref("before"); // 'before', 'after', 'issue' |
| | | |
| | | // å¼å¸¸ç¶æ |
| | | const hasException = ref(null); // null: æªéæ©, true: åå¨å¼å¸¸, false: æ£å¸¸ |
| | | // å¼å¸¸æè¿° |
| | | const abnormalDescription = ref(""); |
| | | |
| | | // ä¸ä¼ é
ç½® |
| | | const uploadConfig = { |
| | | action: "/common/upload", |
| | | limit: 10, |
| | | fileSize: 50, // MB |
| | | fileType: ["jpg", "jpeg", "png", "mp4", "mov"], |
| | | maxVideoDuration: 60, // ç§ |
| | | }; |
| | | |
| | | // 计ç®ä¸ä¼ URL |
| | | const uploadFileUrl = computed(() => { |
| | | const baseUrl = config.baseUrl; |
| | | return baseUrl + uploadConfig.action; |
| | | }); |
| | | |
| | | // 页é¢å è½½ |
| | | onLoad(options => { |
| | | if (options.taskInfo) { |
| | | try { |
| | | const info = JSON.parse(decodeURIComponent(options.taskInfo)); |
| | | taskInfo.value = info; |
| | | |
| | | // åæ¾é»è¾ï¼ä» taskInfo 䏿¢å¤å·²ä¸ä¼ çæä»¶ |
| | | const mapFiles = list => { |
| | | if (!list || !Array.isArray(list)) return []; |
| | | return list.map(item => { |
| | | // å¤ç URLï¼å»é¤å¯è½çç©ºæ ¼ |
| | | const finalUrl = (item.url || item.previewURL || "").trim(); |
| | | // èªå¨æ¨ææä»¶ç±»å |
| | | let fileType = item.type; |
| | | if (!fileType && item.contentType) { |
| | | fileType = item.contentType.startsWith("video") ? "video" : "image"; |
| | | } else if (!fileType) { |
| | | fileType = "image"; // é»è®¤å¾ç |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | url: finalUrl, |
| | | name: item.name || item.originalFilename, |
| | | tempId: item.tempId || item.id || item.tempFileId, |
| | | size: item.size || item.byteSize || 0, // æ å°å¤§å°å段 |
| | | type: fileType, |
| | | status: "success", |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | // æ ¹æ®ç¨æ·è¦æ±æ å°ï¼AfterDTO(ç产å), DTO(ç产ä¸), BeforeDTO(ç产å) |
| | | if ( |
| | | info.commonFileListAfterVO && |
| | | Array.isArray(info.commonFileListAfterVO) |
| | | ) { |
| | | beforeModelValue.value = mapFiles(info.commonFileListAfterVO); |
| | | } |
| | | console.log(beforeModelValue.value, "beforeModelValue"); |
| | | |
| | | if (info.commonFileListVO && Array.isArray(info.commonFileListVO)) { |
| | | afterModelValue.value = mapFiles(info.commonFileListVO); |
| | | } |
| | | if ( |
| | | info.commonFileListBeforeVO && |
| | | Array.isArray(info.commonFileListBeforeVO) |
| | | ) { |
| | | issueModelValue.value = mapFiles(info.commonFileListBeforeVO); |
| | | } |
| | | |
| | | // 妿æå¼å¸¸æè¿°ï¼ä¹æ¢å¤ |
| | | if (info.abnormalDescription) { |
| | | abnormalDescription.value = info.abnormalDescription; |
| | | } |
| | | // 妿æå¼å¸¸ç¶æï¼ä¹æ¢å¤ |
| | | if (info.hasException !== undefined && info.hasException !== null) { |
| | | hasException.value = info.hasException; |
| | | } else if ( |
| | | info.inspectionResult !== undefined && |
| | | info.inspectionResult !== null |
| | | ) { |
| | | // 0-å¼å¸¸ï¼1-æ£å¸¸ |
| | | hasException.value = String(info.inspectionResult) === "0"; |
| | | } |
| | | |
| | | // èªå¨å
åºï¼å¦æåå¨å·²ä¸ä¼ æä»¶ï¼åå¿
ç¶æ¯å¼å¸¸ç¶æï¼ç¡®ä¿ UI æ£å¸¸æ¾ç¤º |
| | | if ( |
| | | !hasException.value && |
| | | (beforeModelValue.value.length > 0 || |
| | | afterModelValue.value.length > 0 || |
| | | issueModelValue.value.length > 0) |
| | | ) { |
| | | hasException.value = true; |
| | | } |
| | | } catch (e) { |
| | | console.error("è§£æä»»å¡ä¿¡æ¯å¤±è´¥:", e); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // 忢ä¸ä¼ ç±»å |
| | | const 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 setExceptionStatus = status => { |
| | | hasException.value = status; |
| | | }; |
| | | |
| | | // è·³è½¬å°æ°å¢æ¥ä¿®é¡µé¢ |
| | | const goToRepair = () => { |
| | | try { |
| | | const taskData = { |
| | | taskId: taskInfo.value?.taskId || taskInfo.value?.id, |
| | | taskName: taskInfo.value?.taskName, |
| | | inspectionLocation: taskInfo.value?.inspectionLocation, |
| | | inspector: taskInfo.value?.inspector, |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, // 0-å¼å¸¸ï¼1-æ£å¸¸ |
| | | commonFileListAfterDTO: beforeModelValue.value, |
| | | commonFileListDTO: afterModelValue.value, |
| | | commonFileListBeforeDTO: issueModelValue.value, |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: afterModelValue.value, |
| | | issue: issueModelValue.value, |
| | | }, |
| | | }; |
| | | |
| | | uni.setStorageSync("repairTaskInfo", JSON.stringify(taskData)); |
| | | |
| | | uni.navigateTo({ |
| | | url: "/pages/equipmentManagement/repair/add", |
| | | }); |
| | | } catch (error) { |
| | | console.error("跳转æ¥ä¿®é¡µé¢å¤±è´¥:", error); |
| | | uni.showToast({ |
| | | title: "跳转失败ï¼è¯·éè¯", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // æäº¤ä¸ä¼ |
| | | const submitUpload = async () => { |
| | | try { |
| | | // æ£æ¥æ¯å¦éæ©äºå¼å¸¸ç¶æ |
| | | if (hasException.value === null) { |
| | | uni.showToast({ |
| | | title: "è¯·éæ©å·¡æ£ç¶æ", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // 妿æ¯å¼å¸¸ç¶æï¼æ£æ¥æ¯å¦æä¸ä¼ æä»¶åæè¿° |
| | | if (hasException.value === true) { |
| | | const totalFiles = |
| | | beforeModelValue.value.length + |
| | | afterModelValue.value.length + |
| | | issueModelValue.value.length; |
| | | if (totalFiles === 0) { |
| | | uni.showToast({ |
| | | title: "请ä¸ä¼ å¼å¸¸ç
§ç", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | // æ£æ¥æ¯å¦å¡«åäºå¼å¸¸æè¿° |
| | | if (!abnormalDescription.value.trim()) { |
| | | uni.showToast({ |
| | | title: "请填åå¼å¸¸æè¿°", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // æ¾ç¤ºæäº¤ä¸çå è½½æç¤º |
| | | uni.showLoading({ |
| | | title: "æäº¤ä¸...", |
| | | mask: true, |
| | | }); |
| | | |
| | | // æç
§é»è¾åå¹¶ææåç±»çæä»¶ç¨äºæåID |
| | | const allFiles = [ |
| | | ...beforeModelValue.value, |
| | | ...afterModelValue.value, |
| | | ...issueModelValue.value, |
| | | ]; |
| | | |
| | | // ä¼ ç»å端çä¸´æ¶æä»¶IDå表 |
| | | let tempFileIds = []; |
| | | if (allFiles.length > 0) { |
| | | tempFileIds = allFiles |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(v => v !== undefined && v !== null && v !== ""); |
| | | } |
| | | |
| | | // æäº¤æ°æ® |
| | | const submitData = { |
| | | ...taskInfo.value, |
| | | commonFileListAfterDTO: beforeModelValue.value, // ç产å |
| | | commonFileListDTO: afterModelValue.value, // çäº§ä¸ |
| | | commonFileListBeforeDTO: issueModelValue.value, // ç产å |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, // 0-å¼å¸¸ï¼1-æ£å¸¸ |
| | | abnormalDescription: abnormalDescription.value, |
| | | tempFileIds: tempFileIds, |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(submitData); |
| | | |
| | | // æ£æ¥æäº¤ç»æ |
| | | if (result && (result.code === 200 || result.success)) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: "æäº¤æå", |
| | | icon: "success", |
| | | }); |
| | | |
| | | // è¿ååè¡¨é¡µå¹¶å·æ° |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 500); |
| | | } else { |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: result?.msg || result?.message || "æäº¤å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.error("æäº¤ä¸ä¼ 失败:", error); |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: error?.message || "æäº¤å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // æ ¼å¼åæä»¶å¤§å° |
| | | const formatFileSize = size => { |
| | | if (!size) return "0 B"; |
| | | const units = ["B", "KB", "MB", "GB"]; |
| | | let index = 0; |
| | | let fileSize = size; |
| | | while (fileSize >= 1024 && index < units.length - 1) { |
| | | fileSize /= 1024; |
| | | index++; |
| | | } |
| | | return `${fileSize.toFixed(2)} ${units[index]}`; |
| | | }; |
| | | |
| | | // æç
§/æè§é¢ |
| | | const chooseMedia = type => { |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | uni.showToast({ |
| | | title: `æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`, |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | const remaining = uploadConfig.limit - getCurrentFiles().length; |
| | | |
| | | // ä¼å
ä½¿ç¨ chooseMedia |
| | | if (typeof uni.chooseMedia === "function") { |
| | | uni.chooseMedia({ |
| | | count: Math.min(remaining, 1), |
| | | mediaType: [type || "image"], |
| | | sizeType: ["compressed", "original"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | try { |
| | | const files = res?.tempFiles || []; |
| | | if (!files.length) throw new Error("æªè·åå°æä»¶"); |
| | | |
| | | files.forEach((tf, idx) => { |
| | | const filePath = tf.tempFilePath || tf.path || ""; |
| | | const fileType = tf.fileType || type || "image"; |
| | | const ext = fileType === "video" ? "mp4" : "jpg"; |
| | | const file = { |
| | | tempFilePath: filePath, |
| | | path: filePath, |
| | | type: fileType, |
| | | name: `${fileType}_${Date.now()}_${idx}.${ext}`, |
| | | size: tf.size || 0, |
| | | duration: tf.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | } catch (err) { |
| | | uni.showToast({ title: err.message || "å¤çæä»¶å¤±è´¥", icon: "none" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("éæ©åªä½å¤±è´¥:", err); |
| | | uni.showToast({ title: "éæ©å¤±è´¥", icon: "none" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | // éçº§æ¹æ¡ |
| | | if (type === "video") { |
| | | uni.chooseVideo({ |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | const file = { |
| | | tempFilePath: res.tempFilePath, |
| | | path: res.tempFilePath, |
| | | type: "video", |
| | | name: `video_${Date.now()}.mp4`, |
| | | size: res.size || 0, |
| | | duration: res.duration || 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: "éæ©è§é¢å¤±è´¥", icon: "none" }); |
| | | }, |
| | | }); |
| | | } else { |
| | | uni.chooseImage({ |
| | | count: Math.min(remaining, 9), |
| | | sizeType: ["compressed"], |
| | | sourceType: ["camera"], |
| | | success: res => { |
| | | const list = res.tempFilePaths || res.tempFiles || []; |
| | | list.forEach((src, idx) => { |
| | | const path = typeof src === "string" ? src : src.path; |
| | | const file = { |
| | | tempFilePath: path, |
| | | path: path, |
| | | type: "image", |
| | | name: `image_${Date.now()}_${idx}.jpg`, |
| | | size: 0, |
| | | createTime: Date.now(), |
| | | }; |
| | | uploadFile(file); |
| | | }); |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: "éæ©å¾ç失败", icon: "none" }); |
| | | }, |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ å个æä»¶ |
| | | const uploadFile = file => { |
| | | const token = getToken(); |
| | | if (!token) { |
| | | uni.showToast({ title: "ç¨æ·æªç»å½", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | |
| | | const uploadTask = uni.uploadFile({ |
| | | url: uploadFileUrl.value, |
| | | filePath: file.tempFilePath, |
| | | name: "files", |
| | | header: { |
| | | Authorization: `Bearer ${token}`, |
| | | }, |
| | | formData: { |
| | | type: getTabType(), |
| | | }, |
| | | success: res => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | // å
¼å®¹ CommonUpload.vue çå¤çé»è¾ |
| | | const resultData = Array.isArray(data.data) |
| | | ? data.data[0] |
| | | : data.data; |
| | | |
| | | // å¤ç url å name èµå¼ |
| | | const finalUrl = resultData.url || resultData.previewURL; |
| | | const finalName = resultData.name || resultData.originalFilename; |
| | | const finalId = |
| | | resultData.tempId || resultData.id || resultData.tempFileId; |
| | | |
| | | const uploadedFile = { |
| | | ...file, |
| | | ...resultData, // å
å«å端è¿åçææåæ®µ |
| | | url: finalUrl, |
| | | name: finalName, |
| | | tempId: finalId, |
| | | status: "success", |
| | | }; |
| | | |
| | | // æ ¹æ®å½åç±»åæ·»å å°å¯¹åºæ°ç» |
| | | if (currentUploadType.value === "before") { |
| | | beforeModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === "after") { |
| | | afterModelValue.value.push(uploadedFile); |
| | | } else if (currentUploadType.value === "issue") { |
| | | issueModelValue.value.push(uploadedFile); |
| | | } |
| | | |
| | | uni.showToast({ title: "ä¸ä¼ æå", icon: "success" }); |
| | | } else { |
| | | uni.showToast({ title: data.msg || "ä¸ä¼ 失败", icon: "none" }); |
| | | } |
| | | } catch (e) { |
| | | uni.showToast({ title: "è§£æååºå¤±è´¥", icon: "none" }); |
| | | } |
| | | }, |
| | | fail: err => { |
| | | console.error("ä¸ä¼ 失败:", err); |
| | | uni.showToast({ title: "ä¸ä¼ 失败", icon: "none" }); |
| | | }, |
| | | complete: () => { |
| | | uploading.value = false; |
| | | }, |
| | | }); |
| | | |
| | | // çå¬ä¸ä¼ è¿åº¦ |
| | | uploadTask.onProgressUpdate(res => { |
| | | uploadProgress.value = res.progress; |
| | | }); |
| | | }; |
| | | |
| | | // è·åtypeå¼ |
| | | const getTabType = () => { |
| | | switch (currentUploadType.value) { |
| | | case "before": |
| | | return 10; |
| | | case "after": |
| | | return 11; |
| | | case "issue": |
| | | return 12; |
| | | default: |
| | | return 10; |
| | | } |
| | | }; |
| | | |
| | | // å 餿件 |
| | | const removeFile = index => { |
| | | const files = getCurrentFiles(); |
| | | files.splice(index, 1); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inspection-upload-page { |
| | | min-height: 100vh; |
| | | background-color: #f5f5f5; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .upload-content { |
| | | padding: 15px; |
| | | } |
| | | |
| | | /* ä»»å¡ä¿¡æ¯å¡ç */ |
| | | .task-info-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .task-info-header { |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .task-name { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .task-info-body { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .info-label { |
| | | font-size: 13px; |
| | | color: #999; |
| | | } |
| | | |
| | | .info-value { |
| | | font-size: 13px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* éç¨å¡çæ ·å¼ */ |
| | | .section-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 15px; |
| | | margin-bottom: 15px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .section-title { |
| | | 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: 14px 16px; |
| | | background: #f8f9fa; |
| | | border: 2px solid #e9ecef; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .exception-option.active { |
| | | border-color: #409eff; |
| | | background: #f0f8ff; |
| | | } |
| | | |
| | | .option-text { |
| | | font-size: 14px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* å¼å¸¸æè¿° */ |
| | | .exception-textarea { |
| | | width: 100%; |
| | | min-height: 100px; |
| | | padding: 12px; |
| | | background: #f8f9fa; |
| | | border: 1px solid #e9ecef; |
| | | border-radius: 8px; |
| | | font-size: 14px; |
| | | color: #333; |
| | | resize: none; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .exception-textarea:focus { |
| | | outline: none; |
| | | border-color: #409eff; |
| | | background: #fff; |
| | | } |
| | | |
| | | /* åç±»æ ç¾é¡µ */ |
| | | .upload-tabs { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .tab-item { |
| | | flex: 1; |
| | | padding: 10px; |
| | | text-align: center; |
| | | background: #f5f5f5; |
| | | border-radius: 6px; |
| | | font-size: 13px; |
| | | color: #666; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .tab-item.active { |
| | | background: #409eff; |
| | | color: #fff; |
| | | } |
| | | |
| | | /* ä¸ä¼ åºå */ |
| | | .upload-area { |
| | | padding: 10px 0; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 10px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .upload-progress { |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | /* æä»¶å表 */ |
| | | .file-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .file-item { |
| | | width: calc(33.33% - 7px); |
| | | } |
| | | |
| | | .file-preview-container { |
| | | position: relative; |
| | | width: 100%; |
| | | aspect-ratio: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f5f5f5; |
| | | } |
| | | |
| | | .file-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | } |
| | | |
| | | .video-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #333; |
| | | } |
| | | |
| | | .video-text { |
| | | font-size: 12px; |
| | | color: #fff; |
| | | margin-top: 5px; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 5px; |
| | | right: 5px; |
| | | width: 22px; |
| | | height: 22px; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .file-info { |
| | | margin-top: 5px; |
| | | } |
| | | |
| | | .file-name { |
| | | display: block; |
| | | font-size: 11px; |
| | | color: #666; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .file-size { |
| | | display: block; |
| | | font-size: 10px; |
| | | color: #999; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .empty-state { |
| | | text-align: center; |
| | | padding: 30px; |
| | | color: #999; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | /* ç»è®¡ä¿¡æ¯ */ |
| | | .upload-summary { |
| | | margin-top: 15px; |
| | | padding: 10px; |
| | | background: #f8f9fa; |
| | | border-radius: 6px; |
| | | border-left: 3px solid #409eff; |
| | | } |
| | | |
| | | .summary-text { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | /* æ£å¸¸ç¶ææç¤º */ |
| | | .normal-tip-card { |
| | | background: #f6ffed; |
| | | border: 2px dashed #b7eb8f; |
| | | border-radius: 12px; |
| | | padding: 50px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .normal-tip-card .tip-text { |
| | | margin-top: 15px; |
| | | font-size: 16px; |
| | | color: #52c41a; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | /* åºé¨æé® */ |
| | | .footer-buttons { |
| | | position: fixed; |
| | | bottom: 0; |
| | | left: 0; |
| | | right: 0; |
| | | display: flex; |
| | | padding: 15px; |
| | | background: #fff; |
| | | border-top: 1px solid #f0f0f0; |
| | | box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="record-container"> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥äº§å大类" |
| | | v-model="searchForm.productName" |
| | | @confirm="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view scroll-y class="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore"> |
| | | <view v-for="item in tableData" :key="item.id" 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.productName }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¹å·</text> |
| | | <text class="detail-value">{{ item.batchNo }}</text> |
| | | </view> |
| | | |
| | | <view class="quantity-section"> |
| | | <view class="quantity-box qualified"> |
| | | <text class="q-label">åæ ¼åºå</text> |
| | | <text class="q-value">{{ item.qualifiedQuantity }}</text> |
| | | </view> |
| | | <view class="quantity-box unqualified"> |
| | | <text class="q-label">ä¸åæ ¼åºå</text> |
| | | <text class="q-value">{{ item.unQualifiedQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="quantity-section"> |
| | | <view class="quantity-box locked"> |
| | | <text class="q-label">åæ ¼å»ç»</text> |
| | | <text class="q-value">{{ item.qualifiedLockedQuantity }}</text> |
| | | </view> |
| | | <view class="quantity-box locked"> |
| | | <text class="q-label">ä¸åæ ¼å»ç»</text> |
| | | <text class="q-value">{{ item.unQualifiedLockedQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºåé¢è¦</text> |
| | | <text class="detail-value">{{ item.warnNum }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ´æ°æ¶é´</text> |
| | | <text class="detail-value">{{ item.updateTime }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" /> |
| | | </scroll-view> |
| | | <view v-else-if="!loading" class="no-data"> |
| | | <up-empty mode="data" text="ææ åºåæ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue'; |
| | | import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js"; |
| | | |
| | | const props = defineProps({ |
| | | productId: { |
| | | type: Number, |
| | | required: true |
| | | } |
| | | }); |
| | | |
| | | const tableData = ref([]); |
| | | const loading = ref(false); |
| | | const loadStatus = ref('loadmore'); |
| | | const page = reactive({ current: 1, size: 10 }); |
| | | const total = ref(0); |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | topParentProductId: props.productId |
| | | }); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | loadStatus.value = 'loading'; |
| | | |
| | | getStockInventoryListPageCombined({ |
| | | ...searchForm, |
| | | current: page.current, |
| | | size: page.size |
| | | }).then(res => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | tableData.value = page.current === 1 ? records : [...tableData.value, ...records]; |
| | | total.value = res.data.total; |
| | | loadStatus.value = tableData.value.length >= total.value ? 'nomore' : 'loadmore'; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = 'loadmore'; |
| | | }); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loadStatus.value === 'loadmore') { |
| | | page.current++; |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .record-container { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .search-section { |
| | | padding: 20rpx; |
| | | background-color: #ffffff; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | background-color: #f2f2f2; |
| | | border-radius: 40rpx; |
| | | padding: 0 30rpx; |
| | | height: 80rpx; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | } |
| | | |
| | | .search-text { |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .filter-button { |
| | | padding-left: 20rpx; |
| | | } |
| | | |
| | | .ledger-list { |
| | | flex: 1; |
| | | padding: 20rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .ledger-item { |
| | | background-color: #ffffff; |
| | | border-radius: 16rpx; |
| | | padding: 30rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .document-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background: linear-gradient(135deg, #2979ff, #1565c0); |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .item-details { |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 16rpx; |
| | | font-size: 26rpx; |
| | | |
| | | .detail-label { |
| | | color: #909399; |
| | | } |
| | | |
| | | .detail-value { |
| | | color: #303133; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .quantity-section { |
| | | display: flex; |
| | | gap: 20rpx; |
| | | margin: 20rpx 0; |
| | | |
| | | .quantity-box { |
| | | flex: 1; |
| | | padding: 16rpx; |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .q-label { |
| | | font-size: 22rpx; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .q-value { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | &.qualified { |
| | | background-color: #ecf5ff; |
| | | color: #409eff; |
| | | } |
| | | |
| | | &.unqualified { |
| | | background-color: #fef0f0; |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | &.locked { |
| | | background-color: #f4f4f5; |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="app-container"> |
| | | <PageHeader title="åºå管ç" @back="goBack" /> |
| | | <up-tabs :list="tabs" @click="handleTabClick" :current="activeTab"/> |
| | | <swiper class="swiper-box" :current="activeTab" @change="handleSwiperChange"> |
| | | <swiper-item class="swiper-item"> |
| | | <qualified-record /> |
| | | </swiper-item> |
| | | <swiper-item class="swiper-item"> |
| | | <unqualified-record /> |
| | | </swiper-item> |
| | | </swiper> |
| | | <PageHeader title="åºå管ç" |
| | | @back="goBack" /> |
| | | <view v-if="loading" |
| | | class="loading-state"> |
| | | <up-loading-icon text="å è½½ä¸..."></up-loading-icon> |
| | | </view> |
| | | <template v-else> |
| | | <up-tabs :list="tabs" |
| | | @click="handleTabClick" |
| | | :current="activeTab" /> |
| | | <swiper class="swiper-box" |
| | | :current="activeTab" |
| | | @change="handleSwiperChange"> |
| | | <swiper-item class="swiper-item" |
| | | v-for="tab in products" |
| | | :key="tab.id"> |
| | | <record :product-id="tab.id" |
| | | v-if="activeTab === products.indexOf(tab) || initializedTabs.includes(tab.id)" /> |
| | | </swiper-item> |
| | | </swiper> |
| | | </template> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import QualifiedRecord from "./Qualified.vue"; |
| | | import UnqualifiedRecord from "./Unqualified.vue"; |
| | | import { ref, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import Record from "./Record.vue"; |
| | | import { productTreeList } from "@/api/basicData/product.js"; |
| | | |
| | | const activeTab = ref(0); |
| | | const tabs = ref([ |
| | | { name: 'åæ ¼åºå' }, |
| | | { name: 'ä¸åæ ¼åºå' } |
| | | ]); |
| | | const activeTab = ref(0); |
| | | const tabs = ref([]); |
| | | const products = ref([]); |
| | | const loading = ref(false); |
| | | const initializedTabs = ref([]); |
| | | |
| | | const handleTabClick = (item) => { |
| | | activeTab.value = item.index; |
| | | }; |
| | | const handleTabClick = item => { |
| | | activeTab.value = item.index; |
| | | if (!initializedTabs.value.includes(products.value[item.index].id)) { |
| | | initializedTabs.value.push(products.value[item.index].id); |
| | | } |
| | | }; |
| | | |
| | | const handleSwiperChange = (e) => { |
| | | activeTab.value = e.detail.current; |
| | | }; |
| | | const handleSwiperChange = e => { |
| | | const index = e.detail.current; |
| | | activeTab.value = index; |
| | | if (!initializedTabs.value.includes(products.value[index].id)) { |
| | | initializedTabs.value.push(products.value[index].id); |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | const fetchProducts = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await productTreeList(); |
| | | // è¿æ»¤æ ¹èç¹äº§å |
| | | products.value = res |
| | | .filter(item => item.parentId === null) |
| | | .map(({ id, productName }) => ({ id, productName })); |
| | | tabs.value = products.value.map(p => ({ name: p.productName })); |
| | | |
| | | if (products.value.length > 0) { |
| | | activeTab.value = 0; |
| | | initializedTabs.value = [products.value[0].id]; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | fetchProducts(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | background-color: #f8f9fa; |
| | | } |
| | | .swiper-box { |
| | | flex: 1; |
| | | } |
| | | .swiper-item { |
| | | height: 100%; |
| | | } |
| | | :deep(.up-tabs) { |
| | | background-color: #fff; |
| | | } |
| | | .app-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | background-color: #f8f9fa; |
| | | } |
| | | .loading-state { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .swiper-box { |
| | | flex: 1; |
| | | } |
| | | .swiper-item { |
| | | height: 100%; |
| | | } |
| | | :deep(.up-tabs) { |
| | | background-color: #fff; |
| | | } |
| | | </style> |
| | |
| | | </up-form-item> |
| | | <up-form-item label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | required |
| | | > |
| | | required> |
| | | <up-input v-model="form.supplierName" |
| | | readonly |
| | | :disabled="isReadOnly" |
| | |
| | | placeholder="请è¾å
¥" |
| | | disabled /> |
| | | </up-form-item> |
| | | <view class="approval-process"> |
| | | <view class="approval-header"> |
| | | <text class="approval-title">å®¡æ ¸æµç¨</text> |
| | | <text class="approval-desc">æ¯ä¸ªæ¥éª¤åªè½éæ©ä¸ä¸ªå®¡æ¹äºº</text> |
| | | </view> |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审æ¹äºº</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | | </view> |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | </view> |
| | | <view class="delete-approver-btn" |
| | | v-if="!isReadOnly" |
| | | @click="removeApprover(stepIndex)">Ã</view> |
| | | </view> |
| | | <view v-else-if="!isReadOnly" |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">鿩审æ¹äºº</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1 && !isReadOnly" |
| | | @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> |
| | | </view> |
| | | </view> |
| | | <view class="add-step-btn" v-if="!isReadOnly"> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">æ°å¢èç¹</u-button> |
| | | </view> |
| | | </view> |
| | | <up-popup :show="showTimePicker" |
| | | mode="bottom" |
| | | @close="showTimePicker = false"> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="basic-parameters-edit"> |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | <up-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | :errorType="['none']" |
| | | label-width="110"> |
| | | <up-form-item label="åæ°ç¼ç " |
| | | prop="paramCode"> |
| | | <up-input v-model="form.paramCode" |
| | | disabled |
| | | placeholder="èªå¨çæ" /> |
| | | </up-form-item> |
| | | <up-form-item label="åæ°åç§°" |
| | | prop="paramName" |
| | | required> |
| | | <up-input v-model="form.paramName" |
| | | placeholder="请è¾å
¥åæ°åç§°" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="åæ°ç±»å" |
| | | prop="paramType" |
| | | required> |
| | | <up-input v-model="paramTypeText" |
| | | placeholder="è¯·éæ©åæ°ç±»å" |
| | | readonly |
| | | @click="showParamTypeSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showParamTypeSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="åä½" |
| | | prop="unit" |
| | | :required="form.paramType === 1"> |
| | | <up-input v-model="form.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å弿 ¼å¼" |
| | | v-if="form.paramType === 1 || form.paramType === 2" |
| | | prop="paramFormat"> |
| | | <up-input v-model="form.paramFormat" |
| | | placeholder="请è¾å
¥å弿 ¼å¼" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="䏿åå
¸" |
| | | v-else-if="form.paramType === 3" |
| | | prop="paramFormat"> |
| | | <up-input v-model="dictTypeText" |
| | | placeholder="è¯·éæ©ä¸æåå
¸" |
| | | readonly |
| | | @click="showDictTypeSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showDictTypeSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æ¶é´æ ¼å¼" |
| | | v-else-if="form.paramType === 4" |
| | | prop="paramFormat"> |
| | | <up-input v-model="form.paramFormat" |
| | | placeholder="è¯·éæ©æ¶é´æ ¼å¼" |
| | | readonly |
| | | @click="showTimeFormatSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTimeFormatSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æ¯å¦å¿
å¡«" |
| | | prop="isRequired"> |
| | | <view style="display: flex; justify-content: flex-end; width: 100%;"> |
| | | <up-switch v-model="form.isRequired" |
| | | :activeValue="1" |
| | | :inactiveValue="0" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <up-textarea v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | autoHeight /> |
| | | </up-form-item> |
| | | </up-form> |
| | | <FooterButtons :loading="loading" |
| | | :confirmText="paramId ? 'ä¿å' : 'æ°å¢'" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" /> |
| | | <!-- åæ°ç±»åéæ© --> |
| | | <up-action-sheet :show="showParamTypeSheet" |
| | | title="éæ©åæ°ç±»å" |
| | | :actions="paramTypeActions" |
| | | @select="onSelectParamType" |
| | | @close="showParamTypeSheet = false" /> |
| | | <!-- 䏿åå
¸éæ© --> |
| | | <up-action-sheet :show="showDictTypeSheet" |
| | | title="鿩䏿åå
¸" |
| | | :actions="dictTypeActions" |
| | | @select="onSelectDictType" |
| | | @close="showDictTypeSheet = false" /> |
| | | <!-- æ¶é´æ ¼å¼éæ© --> |
| | | <up-action-sheet :show="showTimeFormatSheet" |
| | | title="éæ©æ¶é´æ ¼å¼" |
| | | :actions="timeFormatActions" |
| | | @select="onSelectTimeFormat" |
| | | @close="showTimeFormatSheet = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, nextTick, onMounted, ref } from "vue"; |
| | | import { onLoad, onReady } from "@dcloudio/uni-app"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { |
| | | addBaseParam, |
| | | editBaseParam, |
| | | } from "@/api/basicData/parameterMaintenance"; |
| | | import { listType } from "@/api/system/dict/type"; |
| | | |
| | | const formRef = ref(); |
| | | const loading = ref(false); |
| | | const paramId = ref(""); |
| | | const showParamTypeSheet = ref(false); |
| | | const showDictTypeSheet = ref(false); |
| | | const showTimeFormatSheet = ref(false); |
| | | const dictTypes = ref([]); |
| | | |
| | | const form = ref({ |
| | | id: null, |
| | | paramCode: "", |
| | | paramName: "", |
| | | paramType: "", |
| | | unit: "", |
| | | remark: "", |
| | | isRequired: 0, |
| | | paramFormat: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | paramName: [{ required: true, message: "请è¾å
¥åæ°åç§°" }], |
| | | paramType: [{ required: true, message: "è¯·éæ©åæ°ç±»å" }], |
| | | unit: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (form.value.paramType === 1 && !value) { |
| | | callback(new Error("æ°å¼ç±»åå¿
须填ååä½")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const paramTypeActions = [ |
| | | { name: "æ°å¼æ ¼å¼", value: 1 }, |
| | | { name: "ææ¬æ ¼å¼", value: 2 }, |
| | | { name: "䏿é项", value: 3 }, |
| | | { name: "æ¶é´æ ¼å¼", value: 4 }, |
| | | ]; |
| | | |
| | | const timeFormatActions = [ |
| | | { name: "YYYY-MM-DD", value: "YYYY-MM-DD" }, |
| | | { name: "YYYY-MM-DD HH:mm:ss", value: "YYYY-MM-DD HH:mm:ss" }, |
| | | ]; |
| | | |
| | | const dictTypeActions = computed(() => { |
| | | return dictTypes.value.map(item => ({ |
| | | name: item.dictName, |
| | | value: item.dictType, |
| | | })); |
| | | }); |
| | | |
| | | const pageTitle = computed(() => (paramId.value ? "ç¼è¾åæ°" : "æ°å¢åæ°")); |
| | | |
| | | const paramTypeText = computed(() => { |
| | | const action = paramTypeActions.find( |
| | | item => item.value === form.value.paramType |
| | | ); |
| | | return action ? action.name : ""; |
| | | }); |
| | | |
| | | const dictTypeText = computed(() => { |
| | | const action = dictTypes.value.find( |
| | | item => item.dictType === form.value.paramFormat |
| | | ); |
| | | return action ? action.dictName : form.value.paramFormat || ""; |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const getDictTypes = () => { |
| | | listType({ pageNum: 1, pageSize: 1000 }).then(res => { |
| | | dictTypes.value = res.rows || []; |
| | | }); |
| | | }; |
| | | |
| | | const onSelectParamType = action => { |
| | | form.value.paramType = action.value; |
| | | if (action.value === 1) { |
| | | form.value.paramFormat = "#.00000"; |
| | | } else if (action.value === 4) { |
| | | form.value.paramFormat = "YYYY-MM-DD HH:mm:ss"; |
| | | } else { |
| | | form.value.paramFormat = ""; |
| | | } |
| | | showParamTypeSheet.value = false; |
| | | }; |
| | | |
| | | const onSelectDictType = action => { |
| | | form.value.paramFormat = action.value; |
| | | showDictTypeSheet.value = false; |
| | | }; |
| | | |
| | | const onSelectTimeFormat = action => { |
| | | form.value.paramFormat = action.value; |
| | | showTimeFormatSheet.value = false; |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value |
| | | .validate() |
| | | .then(() => { |
| | | if (form.value.paramType === 3 && !form.value.paramFormat) { |
| | | uni.showToast({ title: "è¯·éæ©ä¸æåå
¸", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | loading.value = true; |
| | | const action = paramId.value ? editBaseParam : addBaseParam; |
| | | action({ ...form.value, id: paramId.value || undefined }) |
| | | .then(() => { |
| | | uni.showToast({ title: "ä¿åæå", icon: "success" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "ä¿å失败", icon: "none" }); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }) |
| | | .catch(errors => { |
| | | if (errors && errors.length > 0) { |
| | | uni.showToast({ |
| | | title: errors[0].message, |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onReady(() => { |
| | | if (formRef.value) { |
| | | formRef.value.setRules(rules); |
| | | } |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | getDictTypes(); |
| | | }); |
| | | |
| | | onLoad(options => { |
| | | if (options?.item) { |
| | | const item = JSON.parse(decodeURIComponent(options.item)); |
| | | paramId.value = item.id; |
| | | if (item.paramType) { |
| | | item.paramType = Number(item.paramType); |
| | | } |
| | | Object.assign(form.value, item); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .basic-parameters-edit { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <PageHeader title="åºç¡åæ°" |
| | | @back="goBack" /> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | v-model="paramName" |
| | | placeholder="请è¾å
¥åæ°åç§°" |
| | | clearable |
| | | @change="handleSearch" /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleSearch"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="list.length > 0" |
| | | class="ledger-list"> |
| | | <view v-for="item in list" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="setting-fill" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.paramName || "-" }}</text> |
| | | </view> |
| | | <text class="item-index">{{ item.paramCode || "-" }}</text> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åæ°ç±»å</text> |
| | | <up-tag :text="getParamTypeLabel(item.paramType)" |
| | | :type="getParamTypeTag(item.paramType)" |
| | | size="mini" /> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¯å¦å¿
å¡«</text> |
| | | <up-tag :text="item.isRequired === 1 ? 'æ¯' : 'å¦'" |
| | | :type="item.isRequired === 1 ? 'success' : 'info'" |
| | | size="mini" /> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å弿 ¼å¼</text> |
| | | <text class="detail-value">{{ item.paramFormat || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click="goEdit(item)">ç¼è¾</up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="error" |
| | | @click="handleDelete(item)">å é¤</up-button> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="page.status" /> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ åºç¡åæ°æ°æ®</text> |
| | | </view> |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | | <up-icon name="plus" |
| | | size="28" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import { |
| | | getBaseParamList, |
| | | removeBaseParam, |
| | | } from "@/api/basicData/parameterMaintenance"; |
| | | |
| | | const paramName = ref(""); |
| | | const list = ref([]); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | status: "loadmore", // loadmore, loading, nomore |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const getParamTypeLabel = type => { |
| | | const map = { |
| | | 1: "æ°å¼æ ¼å¼", |
| | | 2: "ææ¬æ ¼å¼", |
| | | 3: "䏿é项", |
| | | 4: "æ¶é´æ ¼å¼", |
| | | }; |
| | | return map[type] || type; |
| | | }; |
| | | |
| | | const getParamTypeTag = type => { |
| | | const map = { |
| | | 1: "primary", |
| | | 2: "info", |
| | | 3: "warning", |
| | | 4: "success", |
| | | }; |
| | | return map[type] || "info"; |
| | | }; |
| | | |
| | | const goAdd = () => { |
| | | uni.navigateTo({ url: "/pages/productionDesign/basicParameters/edit" }); |
| | | }; |
| | | |
| | | const goEdit = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionDesign/basicParameters/edit?item=${encodeURIComponent( |
| | | JSON.stringify(item) |
| | | )}`, |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = item => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "ç¡®å®è¦å é¤è¯¥åæ°åï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | removeBaseParam(item.id).then(() => { |
| | | uni.showToast({ title: "å 餿å" }); |
| | | handleSearch(); |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | page.current = 1; |
| | | page.status = "loadmore"; |
| | | list.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (page.status === "loading" || page.status === "nomore") return; |
| | | |
| | | page.status = "loading"; |
| | | getBaseParamList({ |
| | | current: page.current, |
| | | size: page.size, |
| | | paramName: paramName.value, |
| | | }) |
| | | .then(res => { |
| | | const records = res?.data?.records || res?.records || []; |
| | | const total = res?.data?.total || res?.total || 0; |
| | | |
| | | if (page.current === 1) { |
| | | list.value = records; |
| | | } else { |
| | | list.value = [...list.value, ...records]; |
| | | } |
| | | |
| | | page.total = total; |
| | | if (list.value.length >= total) { |
| | | page.status = "nomore"; |
| | | } else { |
| | | page.status = "loadmore"; |
| | | page.current++; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error" }); |
| | | page.status = "loadmore"; |
| | | }); |
| | | }; |
| | | |
| | | onReachBottom(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | onShow(() => { |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | padding-bottom: 30rpx; |
| | | } |
| | | |
| | | .action-btn { |
| | | width: 140rpx; |
| | | margin: 0 !important; |
| | | } |
| | | |
| | | .fab-button { |
| | | position: fixed; |
| | | right: 40rpx; |
| | | bottom: 60rpx; |
| | | width: 100rpx; |
| | | height: 100rpx; |
| | | background: #2979ff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.4); |
| | | z-index: 100; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="structure-item-wrapper" |
| | | :class="{ 'is-root': level === 0, 'is-last': isLast }"> |
| | | <!-- æ å½¢è¿æ¥çº¿ (éæ ¹èç¹æ¾ç¤º) --> |
| | | <template v-if="level > 0"> |
| | | <view class="line-v"></view> |
| | | <view class="line-h"></view> |
| | | </template> |
| | | <view class="structure-item-card" |
| | | :class="{ 'has-children': hasChildren }"> |
| | | <view class="card-main"> |
| | | <view class="item-header" |
| | | @click="toggleExpand"> |
| | | <view class="header-left"> |
| | | <view v-if="hasChildren" |
| | | class="expand-icon" |
| | | :class="{ 'is-expanded': isExpanded }"> |
| | | <up-icon name="arrow-right" |
| | | size="14" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | <view v-else |
| | | class="dot-icon"></view> |
| | | <text class="item-title">{{ item.productName || 'æªéæ©äº§å' }}</text> |
| | | </view> |
| | | <up-tag v-if="hasChildren" |
| | | text="ç»å" |
| | | type="primary" |
| | | size="mini" |
| | | plain |
| | | shape="circle" /> |
| | | </view> |
| | | <view class="item-body"> |
| | | <view class="info-grid"> |
| | | <view class="info-item"> |
| | | <text class="label">è§æ ¼åå·ï¼</text> |
| | | <text class="value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">æ¶èå·¥åºï¼</text> |
| | | <text class="value">{{ getProcessName(item.processId) }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å使°éï¼</text> |
| | | <text class="value highlight">{{ item.unitQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">éæ±æ»éï¼</text> |
| | | <text class="value highlight">{{ item.demandedQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">åä½ï¼</text> |
| | | <text class="value">{{ item.unit || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">çæ°ï¼</text> |
| | | <text class="value">{{ item.diskQuantity || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- éå½å±ç¤ºåèç¹ --> |
| | | <view v-if="hasChildren && isExpanded" |
| | | class="children-container"> |
| | | <BomStructureItem v-for="(child, index) in item.children" |
| | | :key="index" |
| | | :item="child" |
| | | :level="level + 1" |
| | | :isLast="index === item.children.length - 1" |
| | | :processOptions="processOptions" /> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, defineProps } from "vue"; |
| | | |
| | | const props = defineProps({ |
| | | item: { |
| | | type: Object, |
| | | required: true, |
| | | }, |
| | | level: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | isLast: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | processOptions: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | }); |
| | | |
| | | const isExpanded = ref(true); |
| | | const hasChildren = computed( |
| | | () => props.item.children && props.item.children.length > 0 |
| | | ); |
| | | |
| | | const toggleExpand = () => { |
| | | if (hasChildren.value) { |
| | | isExpanded.value = !isExpanded.value; |
| | | } |
| | | }; |
| | | |
| | | const getProcessName = id => { |
| | | const process = props.processOptions.find(p => p.id === id); |
| | | return process ? process.name : "-"; |
| | | }; |
| | | </script> |
| | | |
| | | <script> |
| | | export default { |
| | | name: "BomStructureItem", |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .structure-item-wrapper { |
| | | position: relative; |
| | | padding-left: 44rpx; |
| | | |
| | | &.is-root { |
| | | padding-left: 0; |
| | | } |
| | | } |
| | | |
| | | // åç´è¿æ¥çº¿æ®µ |
| | | .line-v { |
| | | position: absolute; |
| | | left: 18rpx; // å±
ä¸äº 44rpx ç缩è¿å
|
| | | top: -20rpx; // åä¸å»¶ä¼¸è¦çä¸ä¸ä¸ªèç¹ç margin-bottom |
| | | bottom: 0; |
| | | width: 2rpx; |
| | | background-color: #ddd; |
| | | z-index: 1; |
| | | } |
| | | |
| | | // æåä¸ä¸ªèç¹çåç´çº¿åªå»¶ä¼¸å°æ°´å¹³çº¿ä½ç½® |
| | | .is-last > .line-v { |
| | | bottom: auto; |
| | | height: 60rpx; // 20rpx (top offset) + 40rpx (to horizontal line) |
| | | } |
| | | |
| | | // æ°´å¹³è¿æ¥çº¿ |
| | | .line-h { |
| | | position: absolute; |
| | | left: 18rpx; |
| | | top: 40rpx; // 对é½å°å¡çå
é¨å¾æ ä¸å¿ (padding 24 + icon 32/2) |
| | | width: 26rpx; |
| | | height: 2rpx; |
| | | background-color: #ddd; |
| | | z-index: 1; |
| | | } |
| | | |
| | | .structure-item-card { |
| | | position: relative; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 20rpx; |
| | | padding: 24rpx; |
| | | box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | border: 1rpx solid #f0f0f0; |
| | | transition: all 0.3s; |
| | | z-index: 2; |
| | | |
| | | &:active { |
| | | background-color: #f9f9f9; |
| | | } |
| | | |
| | | &.has-children { |
| | | border-left: 6rpx solid #3c9cff; |
| | | } |
| | | } |
| | | |
| | | .card-main { |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .header-left { |
| | | display: flex; |
| | | align-items: center; |
| | | flex: 1; |
| | | |
| | | .expand-icon { |
| | | margin-right: 12rpx; |
| | | transition: transform 0.3s; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 32rpx; |
| | | height: 32rpx; |
| | | |
| | | &.is-expanded { |
| | | transform: rotate(90deg); |
| | | } |
| | | } |
| | | |
| | | .dot-icon { |
| | | width: 12rpx; |
| | | height: 12rpx; |
| | | border-radius: 50%; |
| | | background-color: #ccc; |
| | | margin-right: 20rpx; |
| | | margin-left: 10rpx; |
| | | } |
| | | |
| | | .item-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | line-height: 1.4; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-body { |
| | | .info-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 12rpx 20rpx; |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | font-size: 24rpx; |
| | | line-height: 1.5; |
| | | |
| | | .label { |
| | | color: #999; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .value { |
| | | color: #666; |
| | | word-break: break-all; |
| | | |
| | | &.highlight { |
| | | color: #3c9cff; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .children-container { |
| | | position: relative; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="bom-list"> |
| | | <PageHeader title="BOM管ç" |
| | | @back="goBack" /> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | v-model="queryParams.productName" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | clearable |
| | | @change="handleSearch" /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleSearch"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="list.length > 0" |
| | | class="ledger-list"> |
| | | <view v-for="item in list" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="list-dot" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.bomNo || "-" }}</text> |
| | | </view> |
| | | <up-tag :text="'V' + (item.version || '1.0')" |
| | | type="primary" |
| | | size="mini" /> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value">{{ item.productName || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.productModelName || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click="goStructure(item)">æ¥ç详æ
</up-button> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="pageStatus" /> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty text="ææ BOMæ°æ®" |
| | | mode="list"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import { listPage } from "@/api/productionManagement/bom"; |
| | | |
| | | const queryParams = reactive({ |
| | | productName: "", |
| | | }); |
| | | const list = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 3, |
| | | total: 0, |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | page.current = 1; |
| | | pageStatus.value = "loadmore"; |
| | | list.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (pageStatus.value === "loading" || pageStatus.value === "nomore") return; |
| | | |
| | | pageStatus.value = "loading"; |
| | | listPage({ |
| | | current: page.current, |
| | | size: page.size, |
| | | productName: queryParams.productName, |
| | | }) |
| | | .then(res => { |
| | | const records = res?.data?.records || res?.records || []; |
| | | const total = res?.data?.total || res?.total || 0; |
| | | |
| | | if (page.current === 1) { |
| | | list.value = records; |
| | | } else { |
| | | list.value = [...list.value, ...records]; |
| | | } |
| | | |
| | | page.total = total; |
| | | if (list.value.length >= total) { |
| | | pageStatus.value = "nomore"; |
| | | } else { |
| | | pageStatus.value = "loadmore"; |
| | | page.current++; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error" }); |
| | | pageStatus.value = "loadmore"; |
| | | }); |
| | | }; |
| | | |
| | | const goStructure = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionDesign/bom/structure?id=${ |
| | | item.id |
| | | }&bomNo=${encodeURIComponent(item.bomNo)}&productName=${encodeURIComponent( |
| | | item.productName || "" |
| | | )}&productModelName=${encodeURIComponent( |
| | | item.productModelName || "" |
| | | )}&remark=${encodeURIComponent( |
| | | item.remark || "" |
| | | )}&version=${encodeURIComponent(item.version || 1)}`, |
| | | }); |
| | | }; |
| | | |
| | | onReachBottom(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | onShow(() => { |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 15rpx; |
| | | padding: 0 30rpx 30rpx; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .action-btn { |
| | | width: calc(50% - 15rpx); |
| | | margin: 0 !important; |
| | | margin-bottom: 15rpx !important; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="structure-page"> |
| | | <PageHeader :title="'BOMç»æ - ' + bomNo" |
| | | @back="goBack" /> |
| | | <view class="info-card"> |
| | | <view class="info-row"> |
| | | <text class="info-label">产ååç§°ï¼</text> |
| | | <text class="info-value">{{ productName }}-{{ productModelName }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="structure-list" |
| | | v-if="dataList.length > 0"> |
| | | <BomStructureItem v-for="(item, index) in dataList" |
| | | :key="index" |
| | | :item="item" |
| | | :level="0" |
| | | :isLast="index === dataList.length - 1" |
| | | :processOptions="processOptions" /> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty text="ææ ç»ææ°æ®" |
| | | mode="list"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { queryStructureList } from "@/api/productionManagement/bom"; |
| | | import { list as getProcessList } from "@/api/productionManagement/processManagement"; |
| | | import BomStructureItem from "./BomStructureItem.vue"; |
| | | |
| | | const bomId = ref(null); |
| | | const bomNo = ref(""); |
| | | const productName = ref(""); |
| | | const dataList = ref([]); |
| | | const processOptions = ref([]); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const fetchData = () => { |
| | | queryStructureList(bomId.value).then(res => { |
| | | dataList.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const fetchProcess = () => { |
| | | getProcessList().then(res => { |
| | | processOptions.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const productModelName = ref(""); |
| | | |
| | | onLoad(options => { |
| | | bomId.value = options.id; |
| | | bomNo.value = decodeURIComponent(options.bomNo); |
| | | productName.value = decodeURIComponent(options.productName); |
| | | productModelName.value = decodeURIComponent(options.productModelName); |
| | | fetchData(); |
| | | fetchProcess(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .structure-page { |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | padding-bottom: 120rpx; |
| | | } |
| | | |
| | | .info-card { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | margin-bottom: 20rpx; |
| | | .info-row { |
| | | display: flex; |
| | | font-size: 28rpx; |
| | | .info-label { |
| | | color: #666; |
| | | } |
| | | .info-value { |
| | | color: #333; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .structure-list { |
| | | padding: 20rpx; |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="process-edit"> |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | <up-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | :errorType="['none']" |
| | | label-width="110"> |
| | | <up-form-item label="å·¥åºç¼ç " |
| | | prop="no"> |
| | | <up-input v-model="form.no" |
| | | placeholder="请è¾å
¥å·¥åºç¼ç " |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥åºåç§°" |
| | | prop="name" |
| | | required> |
| | | <up-input v-model="form.name" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥èµå®é¢" |
| | | prop="salaryQuota"> |
| | | <up-input v-model="form.salaryQuota" |
| | | type="number" |
| | | placeholder="请è¾å
¥å·¥èµå®é¢" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="计费类å" |
| | | prop="type"> |
| | | <up-input v-model="typeText" |
| | | placeholder="è¯·éæ©è®¡è´¹ç±»å" |
| | | readonly |
| | | @click="showTypeSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTypeSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æ¯å¦è´¨æ£" |
| | | prop="isQuality"> |
| | | <view style="display: flex; justify-content: flex-end; width: 100%;"> |
| | | <up-switch v-model="form.isQuality" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="æ¯å¦ç产" |
| | | prop="isProduction"> |
| | | <view style="display: flex; justify-content: flex-end; width: 100%;"> |
| | | <up-switch v-model="form.isProduction" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="å
³è设å¤" |
| | | prop="deviceLedgerId"> |
| | | <up-input v-model="deviceText" |
| | | placeholder="è¯·éæ©å
³è设å¤" |
| | | readonly |
| | | @click="showDeviceSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showDeviceSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥åºæè¿°" |
| | | prop="remark"> |
| | | <up-textarea v-model="form.remark" |
| | | placeholder="请è¾å
¥å·¥åºæè¿°" |
| | | autoHeight /> |
| | | </up-form-item> |
| | | </up-form> |
| | | <FooterButtons :loading="loading" |
| | | :confirmText="processId ? 'ä¿å' : 'æ°å¢'" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" /> |
| | | <!-- 计费类åéæ© --> |
| | | <up-action-sheet :show="showTypeSheet" |
| | | title="éæ©è®¡è´¹ç±»å" |
| | | :actions="typeActions" |
| | | @select="onSelectType" |
| | | @close="showTypeSheet = false" /> |
| | | <!-- 设å¤éæ© --> |
| | | <up-action-sheet :show="showDeviceSheet" |
| | | title="éæ©å
³è设å¤" |
| | | :actions="deviceActions" |
| | | @select="onSelectDevice" |
| | | @close="showDeviceSheet = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, computed, onMounted } from "vue"; |
| | | import { onLoad, onReady } from "@dcloudio/uni-app"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { |
| | | add, |
| | | update, |
| | | getDeviceLedger, |
| | | } from "@/api/productionManagement/processManagement"; |
| | | |
| | | const formRef = ref(null); |
| | | const loading = ref(false); |
| | | const processId = ref(null); |
| | | const pageTitle = computed(() => (processId.value ? "ç¼è¾å·¥åº" : "æ°å¢å·¥åº")); |
| | | |
| | | const form = ref({ |
| | | no: "", |
| | | name: "", |
| | | salaryQuota: "", |
| | | isQuality: false, |
| | | isProduction: false, |
| | | remark: "", |
| | | deviceLedgerId: null, |
| | | type: 0, |
| | | }); |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: "请è¾å
¥å·¥åºåç§°" }], |
| | | salaryQuota: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value !== "" && value !== null && (isNaN(value) || value < 0)) { |
| | | callback(new Error("å·¥èµå®é¢å¿
é¡»æ¯éè´æ°å")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const showTypeSheet = ref(false); |
| | | const typeActions = [ |
| | | { name: "计æ¶", value: 0 }, |
| | | { name: "计件", value: 1 }, |
| | | ]; |
| | | const typeText = computed(() => { |
| | | const action = typeActions.find(a => a.value === form.value.type); |
| | | return action ? action.name : ""; |
| | | }); |
| | | |
| | | const showDeviceSheet = ref(false); |
| | | const deviceActions = ref([]); |
| | | const deviceText = ref(""); |
| | | |
| | | const onSelectType = e => { |
| | | form.value.type = e.value; |
| | | showTypeSheet.value = false; |
| | | }; |
| | | |
| | | const onSelectDevice = e => { |
| | | form.value.deviceLedgerId = e.id; |
| | | deviceText.value = e.name; |
| | | showDeviceSheet.value = false; |
| | | }; |
| | | |
| | | const loadDevices = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceActions.value = (data || []).map(item => ({ |
| | | name: item.deviceName, |
| | | id: item.id, |
| | | })); |
| | | if (form.value.deviceLedgerId) { |
| | | const device = deviceActions.value.find( |
| | | d => d.id === Number(form.value.deviceLedgerId) |
| | | ); |
| | | if (device) deviceText.value = device.name; |
| | | } |
| | | } catch (error) { |
| | | console.error("å 载设å¤å¤±è´¥", error); |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value |
| | | .validate() |
| | | .then(() => { |
| | | loading.value = true; |
| | | const promise = processId.value ? update(form.value) : add(form.value); |
| | | promise |
| | | .then(() => { |
| | | uni.showToast({ title: processId.value ? "ä¿åæå" : "æ°å¢æå" }); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1500); |
| | | }) |
| | | .catch(err => { |
| | | uni.showToast({ title: err.msg || "æäº¤å¤±è´¥", icon: "error" }); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }) |
| | | .catch(errors => { |
| | | if (errors && errors.length > 0) { |
| | | uni.showToast({ |
| | | title: errors[0].message, |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | onLoad(option => { |
| | | if (option.item) { |
| | | const item = JSON.parse(decodeURIComponent(option.item)); |
| | | processId.value = item.id; |
| | | Object.assign(form.value, item); |
| | | // å¤çç±»å转æ¢ï¼ç¡®ä¿æ¯æ°å |
| | | form.value.type = Number(form.value.type); |
| | | form.value.isQuality = !!form.value.isQuality; |
| | | form.value.isProduction = !!form.value.isProduction; |
| | | } |
| | | }); |
| | | |
| | | onReady(() => { |
| | | formRef.value.setRules(rules); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | loadDevices(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .process-edit { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <PageHeader title="å·¥åºç®¡ç" |
| | | @back="goBack" /> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | v-model="queryParams.name" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | clearable |
| | | @change="handleSearch" /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleSearch"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="list.length > 0" |
| | | class="ledger-list"> |
| | | <view v-for="item in list" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="list-dot" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.name || "-" }}</text> |
| | | </view> |
| | | <text class="item-index">{{ item.no || "-" }}</text> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å
³è设å¤</text> |
| | | <text class="detail-value">{{ getDeviceName(item.deviceLedgerId) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥èµå®é¢</text> |
| | | <text class="detail-value highlight">Â¥{{ item.salaryQuota || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åºç¶æ</text> |
| | | <view class="detail-value"> |
| | | <up-tag :text="item.isQuality ? 'è´¨æ£' : 'éè´¨æ£'" |
| | | :type="item.isQuality ? 'warning' : 'info'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | <up-tag :text="item.isProduction ? 'ç产' : 'ä¸ç产'" |
| | | :type="item.isProduction ? 'warning' : 'info'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | <up-tag v-if="item.type !== null && item.type !== undefined" |
| | | :text="item.type == 0 ? '计æ¶' : '计件'" |
| | | :type="item.type == 1 ? 'primary' : 'success'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | </view> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click="goEdit(item)">ç¼è¾</up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="warning" |
| | | @click="goParams(item)">åæ°é
ç½®</up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="error" |
| | | @click="handleDelete(item)">å é¤</up-button> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="pageStatus" /> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ å·¥åºæ°æ®</text> |
| | | </view> |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | | <up-icon name="plus" |
| | | size="28" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import { |
| | | getProcessList, |
| | | del, |
| | | getDeviceLedger, |
| | | } from "@/api/productionManagement/processManagement"; |
| | | |
| | | const queryParams = reactive({ |
| | | name: "", |
| | | }); |
| | | const list = ref([]); |
| | | const deviceOptions = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const getDeviceName = deviceId => { |
| | | if (!deviceId) return "æªå
³è"; |
| | | const device = deviceOptions.value.find(item => item.id === Number(deviceId)); |
| | | return device?.deviceName || "æªå
³è"; |
| | | }; |
| | | |
| | | const loadDevices = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (error) { |
| | | console.error("å 载设å¤å表失败", error); |
| | | } |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | page.current = 1; |
| | | pageStatus.value = "loadmore"; |
| | | list.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (pageStatus.value === "loading" || pageStatus.value === "nomore") return; |
| | | |
| | | pageStatus.value = "loading"; |
| | | getProcessList({ |
| | | current: page.current, |
| | | size: page.size, |
| | | name: queryParams.name, |
| | | }) |
| | | .then(res => { |
| | | const records = res?.data?.records || res?.records || []; |
| | | const total = res?.data?.total || res?.total || 0; |
| | | |
| | | if (page.current === 1) { |
| | | list.value = records; |
| | | } else { |
| | | list.value = [...list.value, ...records]; |
| | | } |
| | | |
| | | page.total = total; |
| | | if (list.value.length >= total) { |
| | | pageStatus.value = "nomore"; |
| | | } else { |
| | | pageStatus.value = "loadmore"; |
| | | page.current++; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "æ¥è¯¢å¤±è´¥", icon: "error" }); |
| | | pageStatus.value = "loadmore"; |
| | | }); |
| | | }; |
| | | |
| | | const goAdd = () => { |
| | | uni.navigateTo({ url: "/pages/productionDesign/processManagement/edit" }); |
| | | }; |
| | | |
| | | const goEdit = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionDesign/processManagement/edit?item=${encodeURIComponent( |
| | | JSON.stringify(item) |
| | | )}`, |
| | | }); |
| | | }; |
| | | |
| | | const goParams = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionDesign/processManagement/params?id=${item.id}&name=${encodeURIComponent(item.name)}`, |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = item => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "ç¡®å®è¦å é¤è¯¥å·¥åºåï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | del([item.id]).then(() => { |
| | | uni.showToast({ title: "å 餿å" }); |
| | | handleSearch(); |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | onReachBottom(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | onShow(async () => { |
| | | await loadDevices(); |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | padding-bottom: 30rpx; |
| | | } |
| | | |
| | | .action-btn { |
| | | flex: 1; |
| | | margin: 0 !important; |
| | | } |
| | | |
| | | .fab-button { |
| | | position: fixed; |
| | | right: 40rpx; |
| | | bottom: 60rpx; |
| | | width: 100rpx; |
| | | height: 100rpx; |
| | | background: #2979ff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.4); |
| | | z-index: 100; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="process-params"> |
| | | <PageHeader :title="processName + ' - åæ°é
ç½®'" |
| | | @back="goBack" /> |
| | | <view class="ledger-list"> |
| | | <view v-if="paramList.length > 0"> |
| | | <view v-for="item in paramList" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="setting-fill" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.paramName || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ åå¼</text> |
| | | <text class="detail-value highlight">{{ item.standardValue || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åæ°ç±»å</text> |
| | | <up-tag :text="getParamTypeText(item.paramType)" |
| | | :type="getParamTypeTag(item.paramType)" |
| | | size="mini" /> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å弿 ¼å¼</text> |
| | | <text class="detail-value">{{ item.paramFormat || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click="handleEditParam(item)">ç¼è¾</up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="error" |
| | | @click="handleDeleteParam(item)">å é¤</up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty text="ææ åæ°é
ç½®" |
| | | icon="account" |
| | | iconSize="60"></up-empty> |
| | | </view> |
| | | </view> |
| | | <!-- æµ®å¨æ°å¢æé® --> |
| | | <view class="fab-button" |
| | | @click="openSelectModal"> |
| | | <up-icon name="plus" |
| | | size="28" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <!-- éæ©åæ°å¼¹çª --> |
| | | <up-modal :show="selectModalVisible" |
| | | title="鿩忰" |
| | | width="650rpx" |
| | | @confirm="handleSelectSubmit" |
| | | @cancel="selectModalVisible = false" |
| | | :closeOnClickOverlay="false" |
| | | showCancelButton> |
| | | <view class="modal-content"> |
| | | <view class="search-box"> |
| | | <up-input v-model="searchKeyword" |
| | | placeholder="æç´¢åºç¡åæ°åç§°" |
| | | clearable |
| | | @confirm="handleSearch" |
| | | @change="handleSearch" /> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="param-scroll-list" |
| | | @scrolltolower="loadMoreParams"> |
| | | <view v-for="param in availableParams" |
| | | :key="param.id" |
| | | class="param-select-item" |
| | | :class="{ active: selectedBaseParam?.id === param.id }" |
| | | @click="selectParam(param)"> |
| | | <view class="param-main"> |
| | | <text class="param-name">{{ param.paramName }}</text> |
| | | <up-tag :text="getParamTypeText(param.paramType)" |
| | | :type="getParamTypeTag(param.paramType)" |
| | | size="mini" /> |
| | | </view> |
| | | <text class="param-code">{{ param.paramCode }}</text> |
| | | </view> |
| | | <up-loadmore :status="availablePageStatus" /> |
| | | </scroll-view> |
| | | <view v-if="selectedBaseParam" |
| | | class="standard-input-box"> |
| | | <text class="label">æ åå¼ï¼</text> |
| | | <up-input v-model="selectedStandardValue" |
| | | placeholder="请è¾å
¥è¯¥å·¥åºçæ åå¼" /> |
| | | </view> |
| | | </view> |
| | | </up-modal> |
| | | <!-- ç¼è¾åæ°æ åå¼å¼¹çª --> |
| | | <up-modal :show="editModalVisible" |
| | | title="ç¼è¾æ åå¼" |
| | | width="500rpx" |
| | | @confirm="handleEditSubmit" |
| | | @cancel="editModalVisible = false" |
| | | :closeOnClickOverlay="false" |
| | | showCancelButton> |
| | | <view class="modal-content"> |
| | | <view class="edit-info"> |
| | | <text class="edit-label">åæ°ï¼{{ currentEditParam?.paramName }}</text> |
| | | <up-input v-model="currentEditValue" |
| | | placeholder="请è¾å
¥æ°çæ åå¼" /> |
| | | </view> |
| | | </view> |
| | | </up-modal> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { |
| | | getProcessParamList, |
| | | addProcessParam, |
| | | editProcessParam, |
| | | deleteProcessParam, |
| | | getBaseParamList, |
| | | } from "@/api/productionManagement/processManagement"; |
| | | |
| | | const processId = ref(null); |
| | | const processName = ref(""); |
| | | const paramList = ref([]); |
| | | const loading = ref(false); |
| | | |
| | | // 鿩忰ç¸å
³ |
| | | const selectModalVisible = ref(false); |
| | | const availableParams = ref([]); |
| | | const searchKeyword = ref(""); |
| | | const selectedBaseParam = ref(null); |
| | | const selectedStandardValue = ref(""); |
| | | const availablePage = reactive({ current: 1, size: 20, total: 0 }); |
| | | const availablePageStatus = ref("loadmore"); |
| | | |
| | | // ç¼è¾åæ°ç¸å
³ |
| | | const editModalVisible = ref(false); |
| | | const currentEditParam = ref(null); |
| | | const currentEditValue = ref(""); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const getParamList = () => { |
| | | loading.value = true; |
| | | getProcessParamList({ technologyOperationId: processId.value }) |
| | | .then(res => { |
| | | paramList.value = res?.data || []; |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "è·åå表失败", icon: "none" }); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const openSelectModal = () => { |
| | | searchKeyword.value = ""; |
| | | selectedBaseParam.value = null; |
| | | selectedStandardValue.value = ""; |
| | | availableParams.value = []; |
| | | availablePage.current = 1; |
| | | availablePageStatus.value = "loadmore"; |
| | | selectModalVisible.value = true; |
| | | loadAvailableParams(true); |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | availablePage.current = 1; |
| | | availableParams.value = []; |
| | | availablePageStatus.value = "loadmore"; |
| | | loadAvailableParams(true); |
| | | }; |
| | | |
| | | const loadMoreParams = () => { |
| | | if ( |
| | | availablePageStatus.value === "nomore" || |
| | | availablePageStatus.value === "loading" |
| | | ) |
| | | return; |
| | | loadAvailableParams(false); |
| | | }; |
| | | |
| | | const loadAvailableParams = (isReset = false) => { |
| | | if (availablePageStatus.value === "loading") return; |
| | | if (isReset) { |
| | | availablePage.current = 1; |
| | | availableParams.value = []; |
| | | availablePageStatus.value = "loading"; |
| | | } else if (availablePageStatus.value === "nomore") { |
| | | return; |
| | | } else { |
| | | availablePageStatus.value = "loading"; |
| | | } |
| | | getBaseParamList({ |
| | | paramName: searchKeyword.value, |
| | | current: availablePage.current, |
| | | size: availablePage.size, |
| | | }) |
| | | .then(res => { |
| | | const records = res?.data?.records || res?.records || []; |
| | | const total = res?.data?.total || res?.total || 0; |
| | | |
| | | if (isReset || availablePage.current === 1) { |
| | | availableParams.value = records; |
| | | } else { |
| | | availableParams.value = [...availableParams.value, ...records]; |
| | | } |
| | | |
| | | availablePage.total = total; |
| | | if (availableParams.value.length >= total) { |
| | | availablePageStatus.value = "nomore"; |
| | | } else { |
| | | availablePageStatus.value = "loadmore"; |
| | | availablePage.current++; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | availablePageStatus.value = "loadmore"; |
| | | }); |
| | | }; |
| | | |
| | | const selectParam = param => { |
| | | selectedBaseParam.value = param; |
| | | selectedStandardValue.value = param.standardValue || ""; |
| | | }; |
| | | |
| | | const handleSelectSubmit = () => { |
| | | if (!selectedBaseParam.value) { |
| | | uni.showToast({ title: "è¯·éæ©ä¸ä¸ªåºç¡åæ°", icon: "none" }); |
| | | return; |
| | | } |
| | | if (!selectedStandardValue.value) { |
| | | uni.showToast({ title: "请è¾å
¥æ åå¼", icon: "none" }); |
| | | return; |
| | | } |
| | | addProcessParam({ |
| | | technologyOperationId: processId.value, |
| | | technologyParamId: selectedBaseParam.value.id, |
| | | standardValue: selectedStandardValue.value, |
| | | }) |
| | | .then(() => { |
| | | uni.showToast({ title: "æ·»å æå" }); |
| | | selectModalVisible.value = false; |
| | | getParamList(); |
| | | }) |
| | | .catch(err => { |
| | | uni.showToast({ title: err.msg || "æ·»å 失败", icon: "error" }); |
| | | }); |
| | | }; |
| | | |
| | | const handleEditParam = item => { |
| | | currentEditParam.value = item; |
| | | currentEditValue.value = item.standardValue; |
| | | editModalVisible.value = true; |
| | | }; |
| | | |
| | | const handleEditSubmit = () => { |
| | | if (!currentEditValue.value) { |
| | | uni.showToast({ title: "请è¾å
¥æ åå¼", icon: "none" }); |
| | | return; |
| | | } |
| | | editProcessParam({ |
| | | id: currentEditParam.value.id, |
| | | technologyOperationId: processId.value, |
| | | technologyParamId: currentEditParam.value.technologyParamId, |
| | | standardValue: currentEditValue.value, |
| | | }) |
| | | .then(() => { |
| | | uni.showToast({ title: "ä¿®æ¹æå" }); |
| | | editModalVisible.value = false; |
| | | getParamList(); |
| | | }) |
| | | .catch(err => { |
| | | uni.showToast({ title: err.msg || "ä¿®æ¹å¤±è´¥", icon: "error" }); |
| | | }); |
| | | }; |
| | | |
| | | const handleDeleteParam = item => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "ç¡®å®è¦å é¤è¯¥åæ°é
ç½®åï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | deleteProcessParam(item.id).then(() => { |
| | | uni.showToast({ title: "å 餿å" }); |
| | | getParamList(); |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const getParamTypeText = type => { |
| | | const typeMap = { 1: "æ°å¼", 2: "ææ¬", 3: "䏿", 4: "æ¶é´" }; |
| | | return typeMap[type] || "æªç¥"; |
| | | }; |
| | | |
| | | const getParamTypeTag = type => { |
| | | const typeMap = { 1: "primary", 2: "info", 3: "warning", 4: "success" }; |
| | | return typeMap[type] || "default"; |
| | | }; |
| | | |
| | | onLoad(option => { |
| | | if (option.id) { |
| | | processId.value = option.id; |
| | | processName.value = decodeURIComponent(option.name || ""); |
| | | getParamList(); |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .process-params { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | } |
| | | |
| | | .modal-content { |
| | | padding: 20rpx 0; |
| | | width: 100%; |
| | | } |
| | | |
| | | .param-scroll-list { |
| | | height: 500rpx; |
| | | margin-top: 20rpx; |
| | | border: 1px solid #eee; |
| | | border-radius: 8rpx; |
| | | } |
| | | |
| | | .param-select-item { |
| | | padding: 20rpx; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | width: 100%; |
| | | &.active { |
| | | background-color: #e3f2fd; |
| | | } |
| | | } |
| | | |
| | | .param-main { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .param-name { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .param-code { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .standard-input-box { |
| | | margin-top: 30rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | .label { |
| | | width: 120rpx; |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .edit-info { |
| | | .edit-label { |
| | | display: block; |
| | | margin-bottom: 20rpx; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | .fab-button { |
| | | position: fixed; |
| | | right: 40rpx; |
| | | bottom: 60rpx; |
| | | width: 100rpx; |
| | | height: 100rpx; |
| | | background: #2979ff; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 4rpx 12rpx rgba(41, 121, 255, 0.4); |
| | | z-index: 100; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-plan-detail"> |
| | | <PageHeader title="计å详æ
" |
| | | @back="goBack" /> |
| | | <view class="detail-container" |
| | | v-if="detailData"> |
| | | <!-- åºæ¬ä¿¡æ¯å¡ç --> |
| | | <view class="detail-card"> |
| | | <view class="card-title"> |
| | | <up-icon name="info-circle" |
| | | size="18" |
| | | color="#3c9cff"></up-icon> |
| | | <text class="title-text">åºæ¬ä¿¡æ¯</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-item"> |
| | | <text class="label">主ç产计åå·</text> |
| | | <text class="value">{{ detailData.mpsNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">æ¥æº</text> |
| | | <up-tag :text="detailData.source === 'éå®' ? 'éå®' : 'å
é¨'" |
| | | :type="detailData.source === 'éå®' ? 'primary' : 'info'" |
| | | size="mini" /> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">ä¸åç¶æ</text> |
| | | <up-tag :text="getStatusText(detailData.status)" |
| | | :type="getStatusType(detailData.status)" |
| | | size="mini" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 产åä¿¡æ¯å¡ç --> |
| | | <view class="detail-card"> |
| | | <view class="card-title"> |
| | | <up-icon name="order" |
| | | size="18" |
| | | color="#3c9cff"></up-icon> |
| | | <text class="title-text">产åä¿¡æ¯</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-item"> |
| | | <text class="label">产ååç§°</text> |
| | | <text class="value font-bold">{{ detailData.productName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">è§æ ¼åå·</text> |
| | | <text class="value">{{ detailData.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">æéæ°é</text> |
| | | <text class="value highlight">{{ detailData.qtyRequired || 0 }} {{ detailData.unit || 'æ¹' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å·²ä¸åæ°é</text> |
| | | <text class="value">{{ detailData.quantityIssued || 0 }} {{ detailData.unit || 'æ¹' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- æ¥æä¸å
³èå¡ç --> |
| | | <view class="detail-card"> |
| | | <view class="card-title"> |
| | | <up-icon name="calendar" |
| | | size="18" |
| | | color="#3c9cff"></up-icon> |
| | | <text class="title-text">æ¥æä¸å
³è</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-item"> |
| | | <text class="label">éæ±æ¥æ</text> |
| | | <text class="value">{{ formatDate(detailData.requiredDate) }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">æ¿è¯ºæ¥æ</text> |
| | | <text class="value">{{ formatDate(detailData.promisedDeliveryDate) }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">éå®ååå·</text> |
| | | <text class="value">{{ detailData.salesContractNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">客æ·åç§°</text> |
| | | <text class="value">{{ detailData.customerName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">项ç®åç§°</text> |
| | | <text class="value">{{ detailData.projectName || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 夿³¨ä¿¡æ¯ --> |
| | | <view class="detail-card"> |
| | | <view class="card-title"> |
| | | <up-icon name="edit-pen" |
| | | size="18" |
| | | color="#3c9cff"></up-icon> |
| | | <text class="title-text">夿³¨</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="remark-box"> |
| | | <text class="remark-text">{{ detailData.remark || 'æ 夿³¨' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ è¯¦æ
æ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import dayjs from "dayjs"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const detailData = ref(null); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = date => { |
| | | return date ? dayjs(date).format("YYYY-MM-DD") : "-"; |
| | | }; |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 0: "å¾
ä¸å", |
| | | 1: "é¨åä¸å", |
| | | 2: "å·²ä¸å", |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | // è·åç¶æç±»å |
| | | const getStatusType = status => { |
| | | const typeMap = { |
| | | 0: "warning", |
| | | 1: "primary", |
| | | 2: "info", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | onLoad(options => { |
| | | if (options.data) { |
| | | try { |
| | | detailData.value = JSON.parse(decodeURIComponent(options.data)); |
| | | } catch (e) { |
| | | console.error("è§£ææ°æ®å¤±è´¥", e); |
| | | uni.showToast({ |
| | | title: "æ°æ®å 载失败", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .production-plan-detail { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 40rpx; |
| | | } |
| | | |
| | | .detail-container { |
| | | padding: 20rpx; |
| | | } |
| | | |
| | | .detail-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 24rpx; |
| | | overflow: hidden; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03); |
| | | |
| | | .card-title { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 24rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | background: #fafafa; |
| | | |
| | | .title-text { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-left: 12rpx; |
| | | } |
| | | } |
| | | |
| | | .card-content { |
| | | padding: 10rpx 24rpx; |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | border-bottom: 1rpx solid #f9f9f9; |
| | | |
| | | &:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | text-align: right; |
| | | max-width: 70%; |
| | | |
| | | &.font-bold { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | &.highlight { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .remark-box { |
| | | padding: 20rpx 0; |
| | | |
| | | .remark-text { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | line-height: 1.5; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="main-production-plan"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="主ç产计å" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥è®¡åå·æäº§ååç§°" |
| | | v-model="searchForm.keyword" |
| | | @change="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å表åºå --> |
| | | <scroll-view scroll-y class="list-container" v-if="tableData.length > 0" @scrolltolower="loadMore"> |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" @click="goDetail(item)"> |
| | | <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.mpsNo }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <up-tag :text="getStatusText(item.status)" :type="getStatusType(item.status)" size="mini" /> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æéæ°é</text> |
| | | <text class="detail-value highlight">{{ item.qtyRequired || 0 }} {{ item.unit || 'æ¹' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éæ±æ¥æ</text> |
| | | <text class="detail-value">{{ formatDate(item.requiredDate) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥æº</text> |
| | | <text class="detail-value">{{ item.source === 'éå®' ? 'éå®' : 'å
é¨' }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="item-footer"> |
| | | <text class="more-detail">æ¥ç详æ
</text> |
| | | <up-icon name="arrow-right" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" v-if="tableData.length >= page.size" /> |
| | | </scroll-view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <up-empty mode="data" text="ææ ä¸»çäº§è®¡åæ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import dayjs from "dayjs"; |
| | | import { productionPlanListPage } from "@/api/productionManagement/productionPlan.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const loadStatus = ref('loadmore'); |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | keyword: "", |
| | | mpsNo: "", |
| | | productName: "" |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (date) => { |
| | | return date ? dayjs(date).format('YYYY-MM-DD') : '-'; |
| | | }; |
| | | |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 0: "å¾
ä¸å", |
| | | 1: "é¨åä¸å", |
| | | 2: "å·²ä¸å", |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | // è·åç¶æç±»å (uView tag type) |
| | | const getStatusType = (status) => { |
| | | const typeMap = { |
| | | 0: "warning", |
| | | 1: "primary", |
| | | 2: "info", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | // å è½½æ´å¤ |
| | | const loadMore = () => { |
| | | if (loadStatus.value === 'nomore' || loading.value) return; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | loadStatus.value = 'loading'; |
| | | |
| | | // æé 请æ±åæ° |
| | | // PC端æ¥å£æ¯æ mpsNo, productName çï¼è¿éç®åå¤çï¼å¦æ keyword åå¨ï¼åå°è¯å¹é
|
| | | const params = { |
| | | current: page.current, |
| | | size: page.size, |
| | | mpsNo: searchForm.value.keyword, // ç®åå¤çï¼æç´¢å· |
| | | productName: searchForm.value.keyword // ç®åå¤çï¼æç´¢åç§° |
| | | }; |
| | | |
| | | productionPlanListPage(params).then((res) => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | if (page.current === 1) { |
| | | tableData.value = records; |
| | | } else { |
| | | tableData.value = [...tableData.value, ...records]; |
| | | } |
| | | |
| | | if (records.length < page.size) { |
| | | loadStatus.value = 'nomore'; |
| | | } else { |
| | | loadStatus.value = 'loadmore'; |
| | | } |
| | | page.total = res.data.total || 0; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = 'loadmore'; |
| | | uni.showToast({ |
| | | title: 'å 载失败', |
| | | icon: 'error' |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 跳转详æ
|
| | | const goDetail = (item) => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/mainProductionPlan/detail?data=${encodeURIComponent(JSON.stringify(item))}` |
| | | }); |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/styles/sales-common.scss'; |
| | | |
| | | .main-production-plan { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .list-container { |
| | | flex: 1; |
| | | height: 0; |
| | | } |
| | | |
| | | .ledger-item { |
| | | background: #fff; |
| | | margin: 20rpx; |
| | | padding: 20rpx; |
| | | border-radius: 12rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 10rpx; |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .document-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background: #3c9cff; |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-details { |
| | | padding: 10rpx 0; |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | |
| | | &.highlight { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | padding-top: 16rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | |
| | | .more-detail { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-right: 8rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="process-route"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="å·¥èºè·¯çº¿" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥è§æ ¼åç§°æç´¢" |
| | | v-model="searchForm.model" |
| | | @change="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å表åºå --> |
| | | <scroll-view scroll-y |
| | | class="list-container" |
| | | v-if="tableData.length > 0" |
| | | @scrolltolower="loadMore"> |
| | | <view v-for="(item, index) in tableData" |
| | | :key="item.id || index" |
| | | @click="goDetail(item)"> |
| | | <view class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="share-square" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.processRouteCode }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value font-bold">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åç§°</text> |
| | | <text class="detail-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">BOMç¼å·</text> |
| | | <text class="detail-value">{{ item.bomNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æè¿°</text> |
| | | <text class="detail-value">{{ item.description || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="item-footer"> |
| | | <text class="more-detail">路线项ç®</text> |
| | | <up-icon name="arrow-right" |
| | | size="14" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" |
| | | v-if="tableData.length >= page.size" /> |
| | | </scroll-view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ å·¥èºè·¯çº¿æ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { listPage } from "@/api/productionManagement/processRoute.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const loadStatus = ref("loadmore"); |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | model: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | // å è½½æ´å¤ |
| | | const loadMore = () => { |
| | | if (loadStatus.value === "nomore" || loading.value) return; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | loadStatus.value = "loading"; |
| | | |
| | | const params = { |
| | | current: page.current, |
| | | size: page.size, |
| | | model: searchForm.value.model, |
| | | }; |
| | | |
| | | listPage(params) |
| | | .then(res => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | if (page.current === 1) { |
| | | tableData.value = records; |
| | | } else { |
| | | tableData.value = [...tableData.value, ...records]; |
| | | } |
| | | |
| | | if (records.length < page.size) { |
| | | loadStatus.value = "nomore"; |
| | | } else { |
| | | loadStatus.value = "loadmore"; |
| | | } |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = "loadmore"; |
| | | uni.showToast({ |
| | | title: "å 载失败", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // è·³è½¬è·¯çº¿é¡¹ç® |
| | | const goDetail = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/processRoute/items?id=${ |
| | | item.id |
| | | }&processRouteCode=${ |
| | | item.processRouteCode |
| | | }&productName=${encodeURIComponent( |
| | | item.productName || "" |
| | | )}&model=${encodeURIComponent(item.model || "")}&bomNo=${ |
| | | item.bomNo || "" |
| | | }&bomId=${item.bomId || ""}&description=${encodeURIComponent( |
| | | item.description || "" |
| | | )}`, |
| | | }); |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | .process-route { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .list-container { |
| | | flex: 1; |
| | | height: 0; |
| | | } |
| | | |
| | | .ledger-item { |
| | | background: #fff; |
| | | margin: 20rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 12rpx; |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .document-icon { |
| | | width: 44rpx; |
| | | height: 44rpx; |
| | | background: #3c9cff; |
| | | border-radius: 10rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-details { |
| | | padding: 16rpx 0; |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | min-width: 140rpx; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | text-align: right; |
| | | flex: 1; |
| | | |
| | | &.font-bold { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | padding-top: 16rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | |
| | | .more-detail { |
| | | font-size: 24rpx; |
| | | color: #3c9cff; |
| | | margin-right: 8rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="process-route-items"> |
| | | <PageHeader title="路线项ç®" |
| | | @back="goBack" /> |
| | | <!-- 路线åºç¡ä¿¡æ¯å¡ç --> |
| | | <view class="route-info-card"> |
| | | <view class="info-row"> |
| | | <text class="label">å·¥èºè·¯çº¿ç¼å·</text> |
| | | <text class="value">{{ routeInfo.processRouteCode || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">产ååç§°</text> |
| | | <text class="value">{{ routeInfo.productName || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">è§æ ¼åç§°</text> |
| | | <text class="value">{{ routeInfo.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">BOMç¼å·</text> |
| | | <text class="value">{{ routeInfo.bomNo || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- é项å¡åæ¢ --> |
| | | <view class="tabs-box"> |
| | | <up-tabs :list="tabsList" |
| | | @click="handleTabClick" |
| | | :current="currentTab"></up-tabs> |
| | | </view> |
| | | <!-- å·¥åºé¡¹ç®å表 --> |
| | | <scroll-view scroll-y |
| | | class="content-scroll" |
| | | v-if="currentTab === 0"> |
| | | <view v-if="itemsList.length > 0"> |
| | | <view v-for="(item, index) in itemsList" |
| | | :key="index" |
| | | class="process-card"> |
| | | <view class="card-header"> |
| | | <view class="index-badge">{{ index + 1 }}</view> |
| | | <text class="process-name">{{ item.technologyOperationName || item.operationName || '-' }}</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å
³è产å</text> |
| | | <text class="detail-value">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit || '-' }}</text> |
| | | </view> |
| | | <view class="tag-row"> |
| | | <up-tag v-if="item.isQuality" |
| | | text="è´¨æ£" |
| | | type="primary" |
| | | size="mini" |
| | | plain /> |
| | | <up-tag v-if="item.isProduction" |
| | | text="ç产" |
| | | type="success" |
| | | size="mini" |
| | | plain /> |
| | | <up-tag v-if="item.type==0" |
| | | text="计æ¶" |
| | | type="info" |
| | | size="mini" |
| | | plain /> |
| | | <up-tag v-else |
| | | text="计件" |
| | | type="warning" |
| | | size="mini" |
| | | plain /> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer" |
| | | @click="showParams(item)"> |
| | | <text class="action-text">æ¥çåæ°å表</text> |
| | | <up-icon name="arrow-right" |
| | | size="14" |
| | | color="#3c9cff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ è·¯çº¿é¡¹ç®"></up-empty> |
| | | </view> |
| | | </scroll-view> |
| | | <!-- BOM ç»æå±ç¤º --> |
| | | <scroll-view scroll-y |
| | | class="content-scroll" |
| | | v-if="currentTab === 1"> |
| | | <view v-if="bomList.length > 0" |
| | | class="bom-tree"> |
| | | <view v-for="(node, nIndex) in flatBomList" |
| | | :key="nIndex" |
| | | class="bom-node" |
| | | :style="{ paddingLeft: (node.level * 40) + 'rpx' }"> |
| | | <view class="bom-node-inner"> |
| | | <view class="bom-line" |
| | | v-if="node.level > 0"></view> |
| | | <view class="bom-content"> |
| | | <view class="bom-header"> |
| | | <text class="bom-product">{{ node.productName }}</text> |
| | | <text class="bom-model" |
| | | v-if="node.model">({{ node.model }})</text> |
| | | </view> |
| | | <view class="bom-details"> |
| | | <text class="bom-info">å·¥åº: {{ node.operationName || '-' }}</text> |
| | | <text class="bom-info">æé: {{ node.unitQuantity || 0 }} {{ node.unit || '' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ BOM ç»æ"></up-empty> |
| | | </view> |
| | | </scroll-view> |
| | | <!-- åæ°åè¡¨å¼¹çª --> |
| | | <up-popup :show="showPopup" |
| | | mode="bottom" |
| | | @close="showPopup = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="title">åæ°å表 - {{ currentItem.technologyOperationName || currentItem.operationName }}</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="showPopup = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="param-list"> |
| | | <view v-if="paramList.length > 0"> |
| | | <view v-for="(param, pIndex) in paramList" |
| | | :key="pIndex" |
| | | class="param-item"> |
| | | <view class="param-row"> |
| | | <text class="param-label">åæ°åç§°ï¼</text> |
| | | <text class="param-value">{{ param.paramName || '-' }}</text> |
| | | </view> |
| | | <view class="param-row"> |
| | | <text class="param-label">æ åå¼ï¼</text> |
| | | <text class="param-value">{{ param.standardValue || '-' }}</text> |
| | | </view> |
| | | <view class="param-row"> |
| | | <text class="param-label">åä½ï¼</text> |
| | | <text class="param-value">{{ param.unit || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-record"> |
| | | <text>ææ åæ°è®°å½</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { |
| | | findProcessRouteItemList, |
| | | getProcessParamList, |
| | | queryBomList, |
| | | } from "@/api/productionManagement/processRoute.js"; |
| | | import { |
| | | queryOrderBomList, |
| | | findProcessParamListOrder, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const routeInfo = ref({}); |
| | | const itemsList = ref([]); |
| | | const bomList = ref([]); |
| | | const loading = ref(false); |
| | | const pageType = ref("route"); // route | order |
| | | |
| | | // éé¡¹å¡ |
| | | const tabsList = reactive([{ name: "路线项ç®" }, { name: "BOMç»æ" }]); |
| | | const currentTab = ref(0); |
| | | |
| | | // å¼¹çªç¸å
³ |
| | | const showPopup = ref(false); |
| | | const currentItem = ref({}); |
| | | const paramList = ref([]); |
| | | const paramLoading = ref(false); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const handleTabClick = item => { |
| | | currentTab.value = item.index; |
| | | if (item.index === 1 && bomList.value.length === 0) { |
| | | fetchBom(); |
| | | } |
| | | }; |
| | | |
| | | // æå¹³å BOM æ ç¨äºå±ç¤º |
| | | const flatBomList = computed(() => { |
| | | const result = []; |
| | | const flatten = (nodes, level = 0) => { |
| | | nodes.forEach(node => { |
| | | result.push({ ...node, level }); |
| | | if (node.children && node.children.length > 0) { |
| | | flatten(node.children, level + 1); |
| | | } |
| | | }); |
| | | }; |
| | | flatten(bomList.value); |
| | | return result; |
| | | }); |
| | | |
| | | onLoad(options => { |
| | | if (options.id) { |
| | | pageType.value = options.type || "route"; |
| | | routeInfo.value = { |
| | | id: options.id, |
| | | processRouteCode: options.processRouteCode || "", |
| | | productName: decodeURIComponent(options.productName || ""), |
| | | model: decodeURIComponent(options.model || ""), |
| | | bomNo: options.bomNo || "", |
| | | bomId: options.bomId || "", |
| | | description: decodeURIComponent(options.description || ""), |
| | | orderId: options.orderId || "", |
| | | }; |
| | | fetchItems(options.id); |
| | | } |
| | | }); |
| | | |
| | | const fetchItems = id => { |
| | | loading.value = true; |
| | | findProcessRouteItemList({ routeId: id, orderId: routeInfo.value.orderId }) |
| | | .then(res => { |
| | | itemsList.value = res.data || []; |
| | | loading.value = false; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | uni.showToast({ |
| | | title: "è·å项ç®å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const fetchBom = () => { |
| | | console.log(routeInfo.value.bomId, "routeInfo.value.bomId"); |
| | | |
| | | if (!routeInfo.value.bomId) return; |
| | | loading.value = true; |
| | | const api = pageType.value === "order" ? queryOrderBomList : queryBomList; |
| | | api(routeInfo.value.bomId) |
| | | .then(res => { |
| | | bomList.value = res.data || []; |
| | | loading.value = false; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | uni.showToast({ |
| | | title: "è·å BOM 失败", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const showParams = item => { |
| | | currentItem.value = item; |
| | | showPopup.value = true; |
| | | paramLoading.value = true; |
| | | paramList.value = []; |
| | | |
| | | const api = |
| | | pageType.value === "order" |
| | | ? findProcessParamListOrder |
| | | : getProcessParamList; |
| | | const params = |
| | | pageType.value === "order" |
| | | ? { |
| | | productionOrderRoutingOperationId: item.id, |
| | | productionOrderId: routeInfo.value.orderId, |
| | | } |
| | | : { technologyRoutingOperationId: item.id }; |
| | | |
| | | api(params) |
| | | .then(res => { |
| | | paramList.value = res.data || []; |
| | | paramLoading.value = false; |
| | | }) |
| | | .catch(() => { |
| | | paramLoading.value = false; |
| | | uni.showToast({ |
| | | title: "è·ååæ°å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .process-route-items { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .route-info-card { |
| | | background: #fff; |
| | | margin: 20rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | } |
| | | .value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .tabs-box { |
| | | background: #fff; |
| | | margin-bottom: 10rpx; |
| | | } |
| | | |
| | | .content-scroll { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 0 20rpx; |
| | | } |
| | | |
| | | .process-card { |
| | | background: #fff; |
| | | margin-bottom: 24rpx; |
| | | border-radius: 16rpx; |
| | | overflow: hidden; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 24rpx; |
| | | background: #fcfcfc; |
| | | border-bottom: 1rpx solid #f5f5f5; |
| | | |
| | | .index-badge { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background: #3c9cff; |
| | | color: #fff; |
| | | border-radius: 20rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | font-size: 24rpx; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .process-name { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .card-content { |
| | | padding: 24rpx; |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | .detail-value { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | .tag-row { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | margin-top: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .card-footer { |
| | | padding: 16rpx 24rpx; |
| | | border-top: 1rpx dashed #eee; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .action-text { |
| | | font-size: 24rpx; |
| | | color: #3c9cff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* BOM æ æ ·å¼ */ |
| | | .bom-tree { |
| | | padding: 20rpx 0; |
| | | } |
| | | |
| | | .bom-node { |
| | | position: relative; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .bom-node-inner { |
| | | background: #fff; |
| | | padding: 20rpx; |
| | | border-radius: 12rpx; |
| | | box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03); |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .bom-line { |
| | | position: absolute; |
| | | left: -20rpx; |
| | | top: 50%; |
| | | width: 20rpx; |
| | | height: 2rpx; |
| | | background: #ddd; |
| | | } |
| | | |
| | | .bom-content { |
| | | flex: 1; |
| | | } |
| | | |
| | | .bom-header { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 8rpx; |
| | | |
| | | .bom-product { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .bom-model { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-left: 10rpx; |
| | | } |
| | | } |
| | | |
| | | .bom-details { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | |
| | | .bom-info { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | } |
| | | |
| | | /* å¼¹çªæ ·å¼ */ |
| | | .popup-content { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 70vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 30rpx; |
| | | border-bottom: 1rpx solid #eee; |
| | | |
| | | .title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .param-list { |
| | | flex: 1; |
| | | height: 0; |
| | | padding-top: 20rpx; |
| | | } |
| | | |
| | | .param-item { |
| | | padding: 20rpx; |
| | | background: #f9f9f9; |
| | | border-radius: 12rpx; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .param-row { |
| | | display: flex; |
| | | margin-bottom: 8rpx; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .param-label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | width: 140rpx; |
| | | } |
| | | .param-value { |
| | | font-size: 24rpx; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-record { |
| | | padding: 100rpx 0; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 26rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="process-statistics"> |
| | | <PageHeader title="å·¥åºç产å®åµ" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="date-picker-container" |
| | | @click="showCalendar = true"> |
| | | <view class="date-input"> |
| | | <up-icon name="calendar" |
| | | size="20" |
| | | color="#999"></up-icon> |
| | | <text class="date-text" |
| | | :class="{ 'placeholder': !searchForm.startDate }">{{ dateRangeText }}</text> |
| | | <view v-if="searchForm.startDate" |
| | | class="clear-icon-wrapper" |
| | | @click.stop="handleClearDate"> |
| | | <up-icon name="close-circle-fill" |
| | | size="18" |
| | | color="#c0c4cc"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="search-btn-wrapper"> |
| | | <up-button type="primary" |
| | | size="small" |
| | | text="æç´¢" |
| | | @click.stop="handleQuery"></up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ç»è®¡å¡çå表 --> |
| | | <scroll-view scroll-y |
| | | class="stats-list"> |
| | | <view v-if="loading" |
| | | class="loading-box"> |
| | | <up-loading-icon text="å è½½ä¸..."></up-loading-icon> |
| | | </view> |
| | | <view v-else-if="statsData.length > 0" |
| | | class="card-grid"> |
| | | <view v-for="(item, index) in statsData" |
| | | :key="index" |
| | | class="stats-card"> |
| | | <view class="card-header"> |
| | | <text class="process-tag">{{ item.name }}</text> |
| | | <view class="header-details"> |
| | | <view class="detail-row"> |
| | | <text class="label">è®¡åæ°</text> |
| | | <text class="value">{{ item.planned }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="label">è¯åæ°</text> |
| | | <text class="value good">{{ item.good }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="label">ä¸è¯å</text> |
| | | <text class="value bad">{{ item.bad }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="card-body"> |
| | | <view class="main-stat"> |
| | | <text class="big-number">{{ item.total }}</text> |
| | | <text class="sub-label">çäº§ä»»å¡æ°</text> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer"> |
| | | <view class="progress-section"> |
| | | <view class="progress-header"> |
| | | <text class="progress-label">ç产è¿åº¦</text> |
| | | <text class="percentage-text">{{ item.percentage }}%</text> |
| | | </view> |
| | | <up-line-progress :percentage="Math.min(item.percentage, 100)" |
| | | :activeColor="getProgressColor(item.percentage)" |
| | | :show-text="false" |
| | | height="8"></up-line-progress> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ å·¥åºç»è®¡æ°æ®"></up-empty> |
| | | </view> |
| | | </scroll-view> |
| | | <!-- æ¥åéæ©å¨ --> |
| | | <up-calendar :show="showCalendar" |
| | | mode="range" |
| | | :maxDate="maxDate" |
| | | minDate="2026-01-01" |
| | | :monthNum="monthNum" |
| | | @confirm="onDateConfirm" |
| | | @close="showCalendar = false"></up-calendar> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { getOperationStatistics } from "@/api/productionManagement/workOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const loading = ref(false); |
| | | const showCalendar = ref(false); |
| | | const dateRange = ref([]); |
| | | const maxDate = dayjs().format("YYYY-MM-DD"); |
| | | const monthNum = computed(() => { |
| | | const min = dayjs("2022-02-01"); |
| | | const max = dayjs(maxDate); |
| | | return max.diff(min, "month") + 1; |
| | | }); |
| | | // const minDate = dayjs().subtract(7, "day").format("YYYY-MM-DD"); |
| | | |
| | | const searchForm = reactive({ |
| | | startDate: "", |
| | | endDate: "", |
| | | }); |
| | | |
| | | const statsData = ref([]); |
| | | |
| | | const dateRangeText = computed(() => { |
| | | if (searchForm.startDate && searchForm.endDate) { |
| | | return `${searchForm.startDate} è³ ${searchForm.endDate}`; |
| | | } |
| | | return "è¯·éæ©æ¥æåºé´"; |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const getProgressColor = percentage => { |
| | | if (percentage >= 100) return "#67c23a"; |
| | | if (percentage >= 50) return "#3c9cff"; |
| | | if (percentage >= 25) return "#e6a23c"; |
| | | return "#f56c6c"; |
| | | }; |
| | | |
| | | const onDateConfirm = e => { |
| | | searchForm.startDate = e[0]; |
| | | searchForm.endDate = e[e.length - 1]; |
| | | showCalendar.value = false; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | loading.value = true; |
| | | const params = { |
| | | startDate: searchForm.startDate, |
| | | endDate: searchForm.endDate, |
| | | }; |
| | | getOperationStatistics(params) |
| | | .then(res => { |
| | | statsData.value = (res.data || []).map(item => ({ |
| | | name: item.operationName || "-", |
| | | total: item.productionTaskCount || 0, |
| | | planned: item.planQuantity || 0, |
| | | good: item.goodQuantity || 0, |
| | | bad: item.scrapQty || 0, |
| | | percentage: Number(item.completionStatus || 0), |
| | | })); |
| | | }) |
| | | .finally(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | getList(); |
| | | }; |
| | | |
| | | const handleClearDate = () => { |
| | | searchForm.startDate = ""; |
| | | searchForm.endDate = ""; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // é»è®¤æ¶é´ç½®ç©º |
| | | searchForm.startDate = ""; |
| | | searchForm.endDate = ""; |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .process-statistics { |
| | | min-height: 100vh; |
| | | background-color: #f5f7fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .search-section { |
| | | background-color: #fff; |
| | | padding: 24rpx 30rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02); |
| | | } |
| | | |
| | | .date-picker-container { |
| | | display: flex; |
| | | align-items: center; |
| | | width: 100%; |
| | | |
| | | .date-input { |
| | | flex: 1; |
| | | height: 80rpx; |
| | | background-color: #f5f7fa; |
| | | border: 1rpx solid #e4e7ed; |
| | | border-radius: 12rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 24rpx; |
| | | margin-right: 20rpx; |
| | | transition: all 0.3s; |
| | | |
| | | &:active { |
| | | background-color: #ebedf0; |
| | | } |
| | | |
| | | .date-text { |
| | | font-size: 28rpx; |
| | | color: #303133; |
| | | margin-left: 16rpx; |
| | | flex: 1; |
| | | |
| | | &.placeholder { |
| | | color: #c0c4cc; |
| | | } |
| | | } |
| | | |
| | | .clear-icon { |
| | | padding: 10rpx; |
| | | margin-right: -10rpx; |
| | | } |
| | | } |
| | | |
| | | .search-btn-wrapper { |
| | | width: 140rpx; |
| | | } |
| | | } |
| | | |
| | | .stats-list { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 0 24rpx 40rpx; |
| | | } |
| | | |
| | | .loading-box { |
| | | display: flex; |
| | | justify-content: center; |
| | | padding-top: 100rpx; |
| | | } |
| | | |
| | | .card-grid { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24rpx; |
| | | } |
| | | |
| | | .stats-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .process-tag { |
| | | background-color: #e6f7ff; |
| | | color: #1890ff; |
| | | padding: 6rpx 16rpx; |
| | | border-radius: 8rpx; |
| | | font-size: 26rpx; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .header-details { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4rpx; |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | |
| | | .label { |
| | | font-size: 22rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 24rpx; |
| | | color: #333; |
| | | font-weight: bold; |
| | | min-width: 60rpx; |
| | | text-align: right; |
| | | |
| | | &.good { |
| | | color: #52c41a; |
| | | } |
| | | &.bad { |
| | | color: #f56c6c; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-body { |
| | | padding-bottom: 30rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .main-stat { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .big-number { |
| | | font-size: 56rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .sub-label { |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | margin-top: 12rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-footer { |
| | | padding-top: 24rpx; |
| | | |
| | | .progress-section { |
| | | .progress-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | |
| | | .progress-label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .percentage-text { |
| | | font-size: 24rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-accounting"> |
| | | <PageHeader title="çäº§æ ¸ç®" |
| | | @back="goBack" /> |
| | | <!-- çéåºå --> |
| | | <view class="filter-section"> |
| | | <view class="date-type-selector"> |
| | | <up-tabs :list="dateTypeList" |
| | | :current="currentDateTypeIndex" |
| | | @change="handleDateTypeChange" |
| | | :activeStyle="{ color: '#2979ff', fontWeight: 'bold' }" |
| | | lineWidth="30" |
| | | lineHeight="3" /> |
| | | </view> |
| | | <view class="date-picker-bar" |
| | | @click="showDatePicker = true"> |
| | | <view class="date-display"> |
| | | <up-icon name="calendar" |
| | | size="20" |
| | | color="#2979ff"></up-icon> |
| | | <text class="date-text">{{ dateDisplayText }}</text> |
| | | </view> |
| | | <up-icon name="arrow-right" |
| | | size="16" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <!-- æ±æ»å表 --> |
| | | <view class="summary-section" |
| | | v-if="!showDetail"> |
| | | <view class="section-header"> |
| | | <text class="section-title">çäº§äººåæ±æ»</text> |
| | | </view> |
| | | <view class="ledger-list" |
| | | v-if="summaryList.length > 0"> |
| | | <view v-for="(item, index) in summaryList" |
| | | :key="index" |
| | | class="ledger-item" |
| | | @click="handleRowClick(item)"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="user-icon"> |
| | | <up-icon name="account" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.schedulingUserName || 'æªç¥' }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <up-icon name="arrow-right" |
| | | size="16" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-grid"> |
| | | <view class="grid-item"> |
| | | <text class="grid-label">产é</text> |
| | | <text class="grid-value">{{ item.finishedNum || 0 }}</text> |
| | | </view> |
| | | <view class="grid-item"> |
| | | <text class="grid-label">å·¥èµ</text> |
| | | <text class="grid-value highlight">Â¥{{ item.wages || 0 }}</text> |
| | | </view> |
| | | <view class="grid-item"> |
| | | <text class="grid-label">åæ ¼ç</text> |
| | | <text class="grid-value">{{ formatOutputRate(item.outputRate) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ æ±æ»æ°æ®" /> |
| | | </view> |
| | | </view> |
| | | <!-- æç»å表 (ç¹å»æ±æ»è¡åæ¾ç¤º) --> |
| | | <view class="detail-section" |
| | | v-else> |
| | | <view class="section-header back-bar" |
| | | @click="showDetail = false"> |
| | | <up-icon name="arrow-left" |
| | | size="16" |
| | | color="#2979ff"></up-icon> |
| | | <text class="back-text">è¿åæ±æ» ({{ currentUserName }})</text> |
| | | </view> |
| | | <view class="ledger-list" |
| | | v-if="detailList.length > 0"> |
| | | <view v-for="(item, index) in detailList" |
| | | :key="index" |
| | | class="ledger-item no-click"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="product-icon"> |
| | | <up-icon name="shopping-cart" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.productName }}</text> |
| | | </view> |
| | | <view class="item-tag"> |
| | | <text class="tag-text">{{ item.schedulingDate }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">çäº§æ¥æ</text> |
| | | <text class="detail-value">{{ item.schedulingDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产人</text> |
| | | <text class="detail-value">{{ item.schedulingUserName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.productModelName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åº</text> |
| | | <text class="detail-value">{{ item.process }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产æ°é</text> |
| | | <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶(h)</text> |
| | | <text class="detail-value">{{ item.workHour || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶å®é¢</text> |
| | | <text class="detail-value">{{ item.workHours }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥èµ</text> |
| | | <text class="detail-value highlight">Â¥{{ item.wages }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" |
| | | @loadmore="getDetailList" /> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ æç»æ°æ®" /> |
| | | </view> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker :show="showDatePicker" |
| | | v-model="pickerValue" |
| | | :mode="currentDateType === 'day' ? 'date' : 'year-month'" |
| | | @confirm="handleDateConfirm" |
| | | @cancel="showDatePicker = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { |
| | | salesLedgerProductionAccountingList, |
| | | salesLedgerProductionAccountingListProductionDetails, |
| | | } from "@/api/productionManagement/productionCosting"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | // çéç¸å
³ |
| | | const dateTypeList = [{ name: "æ¥" }, { name: "æ" }]; |
| | | const currentDateTypeIndex = ref(0); |
| | | const currentDateType = computed(() => |
| | | currentDateTypeIndex.value === 0 ? "day" : "month" |
| | | ); |
| | | const showDatePicker = ref(false); |
| | | const pickerValue = ref(Date.now()); |
| | | const selectedDate = ref(dayjs().format("YYYY-MM-DD")); |
| | | |
| | | const dateDisplayText = computed(() => { |
| | | return currentDateType.value === "day" |
| | | ? selectedDate.value |
| | | : dayjs(selectedDate.value).format("YYYY-MM"); |
| | | }); |
| | | |
| | | // æ°æ®ç¸å
³ |
| | | const summaryList = ref([]); |
| | | const detailList = ref([]); |
| | | const showDetail = ref(false); |
| | | const currentUserName = ref(""); |
| | | const loadStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }); |
| | | |
| | | const page1 = reactive({ |
| | | current: 1, |
| | | size: 20, |
| | | total: 0, |
| | | }); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // åæ¢æ¥æç±»å |
| | | const handleDateTypeChange = index => { |
| | | currentDateTypeIndex.value = index.index; |
| | | if (currentDateType.value === "day") { |
| | | selectedDate.value = dayjs().format("YYYY-MM-DD"); |
| | | } else { |
| | | selectedDate.value = dayjs().startOf("month").format("YYYY-MM-DD"); |
| | | } |
| | | reloadData(); |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const handleDateConfirm = e => { |
| | | selectedDate.value = dayjs(e.value).format("YYYY-MM-DD"); |
| | | showDatePicker.value = false; |
| | | reloadData(); |
| | | }; |
| | | |
| | | // æ ¼å¼ååæ ¼ç |
| | | const formatOutputRate = val => { |
| | | if (val == null || val === "") return "-"; |
| | | return parseFloat(val).toFixed(2) + "%"; |
| | | }; |
| | | |
| | | // å è½½æ±æ»å表 |
| | | const getSummaryList = () => { |
| | | uni.showLoading({ title: "å è½½ä¸..." }); |
| | | const params = { |
| | | dateType: currentDateType.value, |
| | | entryDate: currentDateType.value === "day" ? selectedDate.value : undefined, |
| | | entryDateStart: |
| | | currentDateType.value === "month" |
| | | ? dayjs(selectedDate.value).startOf("month").format("YYYY-MM-DD") |
| | | : undefined, |
| | | entryDateEnd: |
| | | currentDateType.value === "month" |
| | | ? dayjs(selectedDate.value).endOf("month").format("YYYY-MM-DD") |
| | | : undefined, |
| | | pageNum: page.current, |
| | | pageSize: page.size, |
| | | }; |
| | | |
| | | salesLedgerProductionAccountingList(params) |
| | | .then(res => { |
| | | summaryList.value = res.data.records || []; |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | uni.hideLoading(); |
| | | }); |
| | | }; |
| | | |
| | | // å è½½æç»å表 |
| | | const getDetailList = (isLoadMore = false) => { |
| | | if (!isLoadMore) { |
| | | page1.current = 1; |
| | | detailList.value = []; |
| | | } |
| | | loadStatus.value = "loading"; |
| | | |
| | | const params = { |
| | | schedulingUserName: currentUserName.value, |
| | | dateType: currentDateType.value, |
| | | entryDate: currentDateType.value === "day" ? selectedDate.value : undefined, |
| | | entryDateStart: |
| | | currentDateType.value === "month" |
| | | ? dayjs(selectedDate.value).startOf("month").format("YYYY-MM-DD") |
| | | : undefined, |
| | | entryDateEnd: |
| | | currentDateType.value === "month" |
| | | ? dayjs(selectedDate.value).endOf("month").format("YYYY-MM-DD") |
| | | : undefined, |
| | | pageNum: page1.current, |
| | | pageSize: page1.size, |
| | | }; |
| | | |
| | | salesLedgerProductionAccountingListProductionDetails(params) |
| | | .then(res => { |
| | | const records = res.data.records || []; |
| | | detailList.value = isLoadMore |
| | | ? [...detailList.value, ...records] |
| | | : records; |
| | | page1.total = res.data.total || 0; |
| | | |
| | | if (detailList.value.length >= page1.total) { |
| | | loadStatus.value = "nomore"; |
| | | } else { |
| | | loadStatus.value = "loadmore"; |
| | | page1.current++; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | loadStatus.value = "loadmore"; |
| | | }); |
| | | }; |
| | | |
| | | // ç¹å»æ±æ»è¡ |
| | | const handleRowClick = item => { |
| | | currentUserName.value = item.schedulingUserName; |
| | | showDetail.value = true; |
| | | getDetailList(); |
| | | }; |
| | | |
| | | // éæ°å è½½æ°æ® |
| | | const reloadData = () => { |
| | | page.current = 1; |
| | | showDetail.value = false; |
| | | getSummaryList(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSummaryList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .production-accounting { |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | padding-bottom: 30rpx; |
| | | |
| | | .filter-section { |
| | | background-color: #ffffff; |
| | | padding: 20rpx 30rpx; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .date-type-selector { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .date-picker-bar { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | background-color: #f0f4ff; |
| | | padding: 16rpx 24rpx; |
| | | border-radius: 8rpx; |
| | | |
| | | .date-display { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | |
| | | .date-text { |
| | | font-size: 28rpx; |
| | | color: #2979ff; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .section-header { |
| | | padding: 20rpx 30rpx; |
| | | |
| | | .section-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | border-left: 8rpx solid #2979ff; |
| | | padding-left: 16rpx; |
| | | } |
| | | |
| | | &.back-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10rpx; |
| | | background-color: #ffffff; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .back-text { |
| | | font-size: 28rpx; |
| | | color: #2979ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .ledger-list { |
| | | padding: 0 20rpx; |
| | | |
| | | .ledger-item { |
| | | background-color: #ffffff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | &:active { |
| | | background-color: #f9f9f9; |
| | | } |
| | | |
| | | &.no-click:active { |
| | | background-color: #ffffff; |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12rpx; |
| | | |
| | | .user-icon, |
| | | .product-icon { |
| | | width: 48rpx; |
| | | height: 48rpx; |
| | | background: linear-gradient(135deg, #2979ff, #64a1ff); |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .item-tag { |
| | | background-color: #f0f4ff; |
| | | padding: 4rpx 12rpx; |
| | | border-radius: 4rpx; |
| | | |
| | | .tag-text { |
| | | font-size: 24rpx; |
| | | color: #2979ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-details { |
| | | padding-top: 10rpx; |
| | | |
| | | .detail-grid { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | |
| | | .grid-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | flex: 1; |
| | | |
| | | .grid-label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .grid-value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | |
| | | &.highlight { |
| | | color: #ff5a5f; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | |
| | | &.highlight { |
| | | color: #ff5a5f; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 100rpx; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <up-popup |
| | | v-model:show="show" |
| | | mode="bottom" |
| | | :round="20" |
| | | :safeAreaInsetBottom="true" |
| | | @close="handleClose" |
| | | @open="handleOpen" |
| | | > |
| | | <view class="dispatch-modal"> |
| | | <!-- å¤´é¨ --> |
| | | <view class="modal-header"> |
| | | <text class="modal-title">ç产派工</text> |
| | | <view class="close-btn" @click="handleClose"> |
| | | <up-icon name="close" size="20" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 表åå
容 --> |
| | | <view class="modal-content"> |
| | | <up-form |
| | | :model="form" |
| | | ref="formRef" |
| | | :rules="rules" |
| | | labelWidth="120" |
| | | > |
| | | <!-- 项ç®åºæ¬ä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">项ç®ä¿¡æ¯</text> |
| | | <up-form-item label="项ç®åç§°" prop="projectName"> |
| | | <up-input |
| | | v-model="form.projectName" |
| | | disabled |
| | | placeholder="项ç®åç§°" |
| | | /> |
| | | </up-form-item> |
| | | <up-form-item label="产å大类" prop="productCategory"> |
| | | <up-input |
| | | v-model="form.productCategory" |
| | | disabled |
| | | placeholder="产å大类" |
| | | /> |
| | | </up-form-item> |
| | | </view> |
| | | |
| | | <!-- æ°éä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">æ°éä¿¡æ¯</text> |
| | | <up-form-item label="æ»æ°é" prop="quantity"> |
| | | <up-input |
| | | v-model="form.quantity" |
| | | disabled |
| | | placeholder="æ»æ°é" |
| | | /> |
| | | </up-form-item> |
| | | <up-form-item label="å¾
æäº§æ°é" prop="pendingQuantity"> |
| | | <up-input |
| | | v-model="form.pendingQuantity" |
| | | disabled |
| | | placeholder="å¾
æäº§æ°é" |
| | | /> |
| | | </up-form-item> |
| | | <up-form-item label="æ¬æ¬¡æäº§æ°é" prop="schedulingNum" required> |
| | | <up-number-box |
| | | v-model="form.schedulingNum" |
| | | :min="0" |
| | | :max="form.pendingQuantity" |
| | | :step="0.1" |
| | | :precision="2" |
| | | @change="handleNumChange" |
| | | /> |
| | | </up-form-item> |
| | | </view> |
| | | |
| | | <!-- æ´¾å·¥ä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">派工信æ¯</text> |
| | | <up-form-item label="派工人" prop="schedulingUserId" required> |
| | | <up-input |
| | | v-model="selectedUserName" |
| | | placeholder="è¯·éæ©æ´¾å·¥äºº" |
| | | readonly |
| | | @click="showUserPicker = true" |
| | | suffixIcon="arrow-down" |
| | | /> |
| | | </up-form-item> |
| | | <up-form-item label="æ´¾å·¥æ¥æ" prop="schedulingDate" required> |
| | | <up-input |
| | | v-model="form.schedulingDate" |
| | | placeholder="è¯·éæ©æ´¾å·¥æ¥æ" |
| | | readonly |
| | | @click="showDatePicker = true" |
| | | suffixIcon="calendar" |
| | | /> |
| | | </up-form-item> |
| | | </view> |
| | | </up-form> |
| | | </view> |
| | | |
| | | <!-- åºé¨æé® --> |
| | | <view class="modal-footer"> |
| | | <up-button |
| | | @click="handleClose" |
| | | text="åæ¶" |
| | | type="info" |
| | | plain |
| | | :customStyle="{ marginRight: '12px', flex: 1 }" |
| | | /> |
| | | <up-button |
| | | @click="handleConfirm" |
| | | text="确认派工" |
| | | type="primary" |
| | | :customStyle="{ flex: 1 }" |
| | | :loading="submitting" |
| | | /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 人åéæ©å¨ --> |
| | | <up-picker |
| | | v-model="showUserPicker" |
| | | :columns="userColumns" |
| | | @confirm="handleUserSelect" |
| | | @cancel="showUserPicker = false" |
| | | /> |
| | | |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker |
| | | v-model="showDatePicker" |
| | | mode="date" |
| | | @confirm="handleDateSelect" |
| | | @cancel="showDatePicker = false" |
| | | /> |
| | | </up-popup> |
| | | <up-popup v-model:show="show" |
| | | mode="bottom" |
| | | :round="20" |
| | | :safeAreaInsetBottom="true" |
| | | @close="handleClose" |
| | | @open="handleOpen"> |
| | | <view class="dispatch-modal"> |
| | | <!-- å¤´é¨ --> |
| | | <view class="modal-header"> |
| | | <text class="modal-title">ç产派工</text> |
| | | <view class="close-btn" |
| | | @click="handleClose"> |
| | | <up-icon name="close" |
| | | size="20" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <!-- 表åå
容 --> |
| | | <view class="modal-content"> |
| | | <up-form :model="form" |
| | | ref="formRef" |
| | | :rules="rules" |
| | | labelWidth="120"> |
| | | <!-- 项ç®åºæ¬ä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">项ç®ä¿¡æ¯</text> |
| | | <up-form-item label="项ç®åç§°" |
| | | prop="projectName"> |
| | | <up-input v-model="form.projectName" |
| | | disabled |
| | | placeholder="项ç®åç§°" /> |
| | | </up-form-item> |
| | | <up-form-item label="产å大类" |
| | | prop="productCategory"> |
| | | <up-input v-model="form.productCategory" |
| | | disabled |
| | | placeholder="产å大类" /> |
| | | </up-form-item> |
| | | </view> |
| | | <!-- æ°éä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">æ°éä¿¡æ¯</text> |
| | | <up-form-item label="æ»æ°é" |
| | | prop="quantity"> |
| | | <up-input v-model="form.quantity" |
| | | disabled |
| | | placeholder="æ»æ°é" /> |
| | | </up-form-item> |
| | | <up-form-item label="å¾
æäº§æ°é" |
| | | prop="pendingQuantity"> |
| | | <up-input v-model="form.pendingQuantity" |
| | | disabled |
| | | placeholder="å¾
æäº§æ°é" /> |
| | | </up-form-item> |
| | | <up-form-item label="æ¬æ¬¡æäº§æ°é" |
| | | prop="schedulingNum" |
| | | required> |
| | | <up-number-box v-model="form.schedulingNum" |
| | | :min="0" |
| | | :max="form.pendingQuantity" |
| | | :step="0.1" |
| | | :precision="2" |
| | | @change="handleNumChange" /> |
| | | </up-form-item> |
| | | </view> |
| | | <!-- æ´¾å·¥ä¿¡æ¯ --> |
| | | <view class="form-section"> |
| | | <text class="section-title">派工信æ¯</text> |
| | | <up-form-item label="派工人" |
| | | prop="schedulingUserId" |
| | | required> |
| | | <up-input v-model="selectedUserName" |
| | | placeholder="è¯·éæ©æ´¾å·¥äºº" |
| | | readonly |
| | | @click="showUserPicker = true" |
| | | suffixIcon="arrow-down" /> |
| | | </up-form-item> |
| | | <up-form-item label="æ´¾å·¥æ¥æ" |
| | | prop="schedulingDate" |
| | | required> |
| | | <up-input v-model="form.schedulingDate" |
| | | placeholder="è¯·éæ©æ´¾å·¥æ¥æ" |
| | | readonly |
| | | @click="showDatePicker = true" |
| | | suffixIcon="calendar" /> |
| | | </up-form-item> |
| | | </view> |
| | | </up-form> |
| | | </view> |
| | | <!-- åºé¨æé® --> |
| | | <view class="modal-footer"> |
| | | <up-button @click="handleClose" |
| | | text="åæ¶" |
| | | type="info" |
| | | plain |
| | | :customStyle="{ marginRight: '12px', flex: 1 }" /> |
| | | <up-button @click="handleConfirm" |
| | | text="确认派工" |
| | | type="primary" |
| | | :customStyle="{ flex: 1 }" |
| | | :loading="submitting" /> |
| | | </view> |
| | | </view> |
| | | <!-- 人åéæ©å¨ --> |
| | | <up-picker v-model="showUserPicker" |
| | | :columns="userColumns" |
| | | @confirm="handleUserSelect" |
| | | @cancel="showUserPicker = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker v-model="showDatePicker" |
| | | mode="date" |
| | | @confirm="handleDateSelect" |
| | | @cancel="showDatePicker = false" /> |
| | | </up-popup> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, getCurrentInstance } from 'vue'; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { productionDispatch } from "@/api/productionManagement/productionOrder.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | import { ref, reactive, computed, getCurrentInstance } from "vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | // import { productionDispatch } from "@/api/productionManagement/productionOrder.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const emit = defineEmits(['confirm']); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const emit = defineEmits(["confirm"]); |
| | | |
| | | // å¼¹çªæ¾ç¤ºç¶æ |
| | | const show = ref(false); |
| | | const submitting = ref(false); |
| | | // å¼¹çªæ¾ç¤ºç¶æ |
| | | const show = ref(false); |
| | | const submitting = ref(false); |
| | | |
| | | // 鿩卿¾ç¤ºç¶æ |
| | | const showUserPicker = ref(false); |
| | | const showDatePicker = ref(false); |
| | | // 鿩卿¾ç¤ºç¶æ |
| | | const showUserPicker = ref(false); |
| | | const showDatePicker = ref(false); |
| | | |
| | | // ç¨æ·å表 |
| | | const userList = ref([]); |
| | | const userColumns = computed(() => [ |
| | | userList.value.map(user => ({ |
| | | label: user.nickName, |
| | | value: user.userId |
| | | })) |
| | | ]); |
| | | // ç¨æ·å表 |
| | | const userList = ref([]); |
| | | const userColumns = computed(() => [ |
| | | userList.value.map(user => ({ |
| | | label: user.nickName, |
| | | value: user.userId, |
| | | })), |
| | | ]); |
| | | |
| | | // éä¸çç¨æ·åç§°ï¼ç¨äºæ¾ç¤ºï¼ |
| | | const selectedUserName = computed(() => { |
| | | const user = userList.value.find(u => u.userId === form.schedulingUserId); |
| | | return user ? user.nickName : ''; |
| | | }); |
| | | // éä¸çç¨æ·åç§°ï¼ç¨äºæ¾ç¤ºï¼ |
| | | const selectedUserName = computed(() => { |
| | | const user = userList.value.find(u => u.userId === form.schedulingUserId); |
| | | return user ? user.nickName : ""; |
| | | }); |
| | | |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: 0, |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: 0, |
| | | id: "" // åå§è®°å½ID |
| | | }); |
| | | // è¡¨åæ°æ® |
| | | const form = reactive({ |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: 0, |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: 0, |
| | | id: "", // åå§è®°å½ID |
| | | }); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = reactive({ |
| | | schedulingNum: [ |
| | | { required: true, message: "请è¾å
¥æäº§æ°é", trigger: "blur" } |
| | | ], |
| | | schedulingUserId: [ |
| | | { required: true, message: "è¯·éæ©æ´¾å·¥äºº", trigger: "change" } |
| | | ], |
| | | schedulingDate: [ |
| | | { required: true, message: "è¯·éæ©æ´¾å·¥æ¥æ", trigger: "change" } |
| | | ] |
| | | }); |
| | | // 表åéªè¯è§å |
| | | const rules = reactive({ |
| | | schedulingNum: [ |
| | | { required: true, message: "请è¾å
¥æäº§æ°é", trigger: "blur" }, |
| | | ], |
| | | schedulingUserId: [ |
| | | { required: true, message: "è¯·éæ©æ´¾å·¥äºº", trigger: "change" }, |
| | | ], |
| | | schedulingDate: [ |
| | | { required: true, message: "è¯·éæ©æ´¾å·¥æ¥æ", trigger: "change" }, |
| | | ], |
| | | }); |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // æå¼å¼¹çª |
| | | const open = async (rowData) => { |
| | | try { |
| | | // å è½½ç¨æ·å表 |
| | | const res = await userListNoPageByTenantId(); |
| | | userList.value = res.data || []; |
| | | |
| | | // å¡«å
è¡¨åæ°æ® |
| | | Object.assign(form, { |
| | | ...rowData, |
| | | schedulingNum: 0, |
| | | schedulingUserId: userStore.id, |
| | | schedulingDate: dayjs().format("YYYY-MM-DD") |
| | | }); |
| | | |
| | | show.value = true; |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: 'å è½½ç¨æ·å表失败', |
| | | icon: 'error' |
| | | }); |
| | | } |
| | | }; |
| | | // æå¼å¼¹çª |
| | | const open = async rowData => { |
| | | try { |
| | | // å è½½ç¨æ·å表 |
| | | const res = await userListNoPageByTenantId(); |
| | | userList.value = res.data || []; |
| | | |
| | | // å¤çæ°éåå |
| | | const handleNumChange = (value) => { |
| | | if (value > form.pendingQuantity) { |
| | | form.schedulingNum = form.pendingQuantity; |
| | | uni.showToast({ |
| | | title: 'æäº§æ°éä¸å¯å¤§äºå¾
æäº§æ°é', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | }; |
| | | // å¡«å
è¡¨åæ°æ® |
| | | Object.assign(form, { |
| | | ...rowData, |
| | | schedulingNum: 0, |
| | | schedulingUserId: userStore.id, |
| | | schedulingDate: dayjs().format("YYYY-MM-DD"), |
| | | }); |
| | | |
| | | // å¤çç¨æ·éæ© |
| | | const handleUserSelect = (params) => { |
| | | if (params.value && params.value.length > 0) { |
| | | form.schedulingUserId = params.value[0]; |
| | | } |
| | | showUserPicker.value = false; |
| | | }; |
| | | show.value = true; |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: "å è½½ç¨æ·å表失败", |
| | | icon: "error", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å¤çæ¥æéæ© |
| | | const handleDateSelect = (params) => { |
| | | if (params.value) { |
| | | form.schedulingDate = dayjs(params.value).format("YYYY-MM-DD"); |
| | | } |
| | | showDatePicker.value = false; |
| | | }; |
| | | // å¤çæ°éåå |
| | | const handleNumChange = value => { |
| | | if (value > form.pendingQuantity) { |
| | | form.schedulingNum = form.pendingQuantity; |
| | | uni.showToast({ |
| | | title: "æäº§æ°éä¸å¯å¤§äºå¾
æäº§æ°é", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // 确认派工 |
| | | const handleConfirm = async () => { |
| | | try { |
| | | // 表åéªè¯ |
| | | const valid = await formRef.value?.validate(); |
| | | if (!valid) return; |
| | | |
| | | if (form.schedulingNum <= 0) { |
| | | uni.showToast({ |
| | | title: 'æäº§æ°éå¿
须大äº0', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | submitting.value = true; |
| | | |
| | | // æäº¤æ´¾å·¥æ°æ® |
| | | await productionDispatch(form); |
| | | |
| | | uni.showToast({ |
| | | title: '派工æå', |
| | | icon: 'success' |
| | | }); |
| | | |
| | | handleClose(); |
| | | emit('confirm'); |
| | | |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: '派工失败', |
| | | icon: 'error' |
| | | }); |
| | | } finally { |
| | | submitting.value = false; |
| | | } |
| | | }; |
| | | // å¤çç¨æ·éæ© |
| | | const handleUserSelect = params => { |
| | | if (params.value && params.value.length > 0) { |
| | | form.schedulingUserId = params.value[0]; |
| | | } |
| | | showUserPicker.value = false; |
| | | }; |
| | | |
| | | // å¼¹çªæå¼äºä»¶ |
| | | const handleOpen = () => { |
| | | // å¼¹çªæå¼æ¶çå¤ç |
| | | }; |
| | | // å¤çæ¥æéæ© |
| | | const handleDateSelect = params => { |
| | | if (params.value) { |
| | | form.schedulingDate = dayjs(params.value).format("YYYY-MM-DD"); |
| | | } |
| | | showDatePicker.value = false; |
| | | }; |
| | | |
| | | // å
³éå¼¹çª |
| | | const handleClose = () => { |
| | | show.value = false; |
| | | showUserPicker.value = false; |
| | | showDatePicker.value = false; |
| | | |
| | | // é置表å |
| | | Object.assign(form, { |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: 0, |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: 0, |
| | | id: "" |
| | | }); |
| | | }; |
| | | // 确认派工 |
| | | const handleConfirm = async () => { |
| | | try { |
| | | // 表åéªè¯ |
| | | const valid = await formRef.value?.validate(); |
| | | if (!valid) return; |
| | | |
| | | // æ´é²æ¹æ³ |
| | | defineExpose({ |
| | | open |
| | | }); |
| | | if (form.schedulingNum <= 0) { |
| | | uni.showToast({ |
| | | title: "æäº§æ°éå¿
须大äº0", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | submitting.value = true; |
| | | |
| | | // æäº¤æ´¾å·¥æ°æ® |
| | | // await productionDispatch(form); |
| | | |
| | | uni.showToast({ |
| | | title: "派工æå", |
| | | icon: "success", |
| | | }); |
| | | |
| | | handleClose(); |
| | | emit("confirm"); |
| | | } catch (error) { |
| | | uni.showToast({ |
| | | title: "派工失败", |
| | | icon: "error", |
| | | }); |
| | | } finally { |
| | | submitting.value = false; |
| | | } |
| | | }; |
| | | |
| | | // å¼¹çªæå¼äºä»¶ |
| | | const handleOpen = () => { |
| | | // å¼¹çªæå¼æ¶çå¤ç |
| | | }; |
| | | |
| | | // å
³éå¼¹çª |
| | | const handleClose = () => { |
| | | show.value = false; |
| | | showUserPicker.value = false; |
| | | showDatePicker.value = false; |
| | | |
| | | // é置表å |
| | | Object.assign(form, { |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: 0, |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: 0, |
| | | id: "", |
| | | }); |
| | | }; |
| | | |
| | | // æ´é²æ¹æ³ |
| | | defineExpose({ |
| | | open, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .dispatch-modal { |
| | | background: #ffffff; |
| | | border-radius: 20px 20px 0 0; |
| | | max-height: 80vh; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | .dispatch-modal { |
| | | background: #ffffff; |
| | | border-radius: 20px 20px 0 0; |
| | | max-height: 80vh; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .modal-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 20px 20px 0 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | padding-bottom: 16px; |
| | | margin-bottom: 20px; |
| | | } |
| | | .modal-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 20px 20px 0 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | padding-bottom: 16px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .modal-title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .modal-title { |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .close-btn { |
| | | padding: 4px; |
| | | |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | | } |
| | | .close-btn { |
| | | padding: 4px; |
| | | |
| | | .modal-content { |
| | | flex: 1; |
| | | padding: 0 20px; |
| | | overflow-y: auto; |
| | | } |
| | | &:active { |
| | | opacity: 0.7; |
| | | } |
| | | } |
| | | |
| | | .form-section { |
| | | margin-bottom: 24px; |
| | | } |
| | | .modal-content { |
| | | flex: 1; |
| | | padding: 0 20px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .section-title { |
| | | display: block; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 16px; |
| | | padding-left: 8px; |
| | | border-left: 3px solid #2979ff; |
| | | } |
| | | .form-section { |
| | | margin-bottom: 24px; |
| | | } |
| | | |
| | | .modal-footer { |
| | | display: flex; |
| | | gap: 12px; |
| | | padding: 20px; |
| | | border-top: 1px solid #f0f0f0; |
| | | background: #fafafa; |
| | | } |
| | | .section-title { |
| | | display: block; |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | margin-bottom: 16px; |
| | | padding-left: 8px; |
| | | border-left: 3px solid #2979ff; |
| | | } |
| | | |
| | | // uView ç»ä»¶æ ·å¼è°æ´ |
| | | :deep(.up-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | .modal-footer { |
| | | display: flex; |
| | | gap: 12px; |
| | | padding: 20px; |
| | | border-top: 1px solid #f0f0f0; |
| | | background: #fafafa; |
| | | } |
| | | |
| | | :deep(.up-input) { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | // uView ç»ä»¶æ ·å¼è°æ´ |
| | | :deep(.up-form-item) { |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | :deep(.up-input--disabled) { |
| | | background: #f0f0f0; |
| | | color: #999; |
| | | } |
| | | :deep(.up-input) { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | :deep(.up-number-box) { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | :deep(.up-input--disabled) { |
| | | background: #f0f0f0; |
| | | color: #999; |
| | | } |
| | | |
| | | :deep(.up-number-box) { |
| | | background: #f8f9fa; |
| | | border-radius: 8px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="ç产派工" |
| | | width="50%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="ç产派工" |
| | | width="50%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="项ç®åç§°ï¼" prop="projectName"> |
| | | <el-input v-model="form.projectName" placeholder="请è¾å
¥" clearable disabled/> |
| | | <el-form-item label="项ç®åç§°ï¼" |
| | | prop="projectName"> |
| | | <el-input v-model="form.projectName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input v-model="form.productCategory" placeholder="请è¾å
¥" clearable disabled/> |
| | | <el-form-item label="产å大类ï¼" |
| | | prop="productCategory"> |
| | | <el-input v-model="form.productCategory" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ»æ°éï¼" prop="quantity"> |
| | | <el-input v-model="form.quantity" placeholder="请è¾å
¥" clearable disabled/> |
| | | <el-form-item label="æ»æ°éï¼" |
| | | prop="quantity"> |
| | | <el-input v-model="form.quantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¾
æäº§æ°éï¼" prop="pendingQuantity"> |
| | | <el-input v-model="form.pendingQuantity" placeholder="请è¾å
¥" clearable disabled/> |
| | | </el-form-item> |
| | | <el-form-item label="å¾
æäº§æ°éï¼" |
| | | prop="pendingQuantity"> |
| | | <el-input v-model="form.pendingQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¬æ¬¡æäº§æ°éï¼" prop="schedulingNum"> |
| | | <el-input-number |
| | | v-model="form.schedulingNum" |
| | | placeholder="请è¾å
¥" |
| | | :min="0" |
| | | :step="0.1" |
| | | :precision="2" |
| | | clearable |
| | | @change="changeNum" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¬æ¬¡æäº§æ°éï¼" |
| | | prop="schedulingNum"> |
| | | <el-input-number v-model="form.schedulingNum" |
| | | placeholder="请è¾å
¥" |
| | | :min="0" |
| | | :step="0.1" |
| | | :precision="2" |
| | | clearable |
| | | @change="changeNum" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="派工人ï¼" prop="schedulingUserId"> |
| | | <el-select |
| | | v-model="form.schedulingUserId" |
| | | placeholder="éæ©äººå" |
| | | style="width: 100%;" |
| | | > |
| | | <el-option |
| | | v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ´¾å·¥æ¥æï¼" prop="schedulingDate"> |
| | | <el-date-picker |
| | | v-model="form.schedulingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="派工人ï¼" |
| | | prop="schedulingUserId"> |
| | | <el-select v-model="form.schedulingUserId" |
| | | placeholder="éæ©äººå" |
| | | style="width: 100%;"> |
| | | <el-option v-for="user in userList" |
| | | :key="user.userId" |
| | | :label="user.nickName" |
| | | :value="user.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ´¾å·¥æ¥æï¼" |
| | | prop="schedulingDate"> |
| | | <el-date-picker v-model="form.schedulingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref} from "vue"; |
| | | import {getStaffJoinInfo, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | import {productionDispatch} from "@/api/productionManagement/productionOrder.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import { ref } from "vue"; |
| | | import { |
| | | getStaffJoinInfo, |
| | | staffJoinAdd, |
| | | staffJoinUpdate, |
| | | } from "@/api/personnelManagement/onboarding.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | // import {productionDispatch} from "@/api/productionManagement/productionOrder.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import dayjs from "dayjs"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const data = reactive({ |
| | | form: { |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: "", |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: "", |
| | | }, |
| | | rules: { |
| | | schedulingNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" },], |
| | | schedulingUserId: [{ required: true, message: "è¯·éæ©", trigger: "change" },], |
| | | schedulingDate: [{ required: true, message: "è¯·éæ©", trigger: "change" },], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const userList = ref([]) |
| | | const userStore = useUserStore() |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const data = reactive({ |
| | | form: { |
| | | projectName: "", |
| | | productCategory: "", |
| | | quantity: "", |
| | | schedulingNum: "", |
| | | schedulingUserId: "", |
| | | schedulingDate: "", |
| | | pendingQuantity: "", |
| | | }, |
| | | rules: { |
| | | schedulingNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | schedulingUserId: [ |
| | | { required: true, message: "è¯·éæ©", trigger: "change" }, |
| | | ], |
| | | schedulingDate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const userList = ref([]); |
| | | const userStore = useUserStore(); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = {...row} |
| | | form.value.schedulingNum = 0 |
| | | form.value.schedulingUserId = userStore.id |
| | | form.value.schedulingDate = dayjs().format("YYYY-MM-DD"); |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value = { ...row }; |
| | | form.value.schedulingNum = 0; |
| | | form.value.schedulingUserId = userStore.id; |
| | | form.value.schedulingDate = dayjs().format("YYYY-MM-DD"); |
| | | }; |
| | | |
| | | // |
| | | const changeNum = (value) => { |
| | | if (value > form.value.pendingQuantity) { |
| | | form.value.schedulingNum = form.value.pendingQuantity; |
| | | proxy.$modal.msgWarning('æäº§æ°éä¸å¯å¤§äºå¾
æäº§æ°é') |
| | | } |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | productionDispatch(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | // |
| | | const changeNum = value => { |
| | | if (value > form.value.pendingQuantity) { |
| | | form.value.schedulingNum = form.value.pendingQuantity; |
| | | proxy.$modal.msgWarning("æäº§æ°éä¸å¯å¤§äºå¾
æäº§æ°é"); |
| | | } |
| | | }) |
| | | } |
| | | }; |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | // productionDispatch(form.value).then(res => { |
| | | // proxy.$modal.msgSuccess("æäº¤æå"); |
| | | // closeDia(); |
| | | // }) |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | <template> |
| | | <view class="production-dispatching"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="ç产派工" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°æç´¢" |
| | | v-model="searchForm.customerName" |
| | | @change="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- ç产派工å表 --> |
| | | <view class="ledger-list" v-if="tableData.length > 0"> |
| | | <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.salesContractNo }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½å
¥æ¥æ</text> |
| | | <text class="detail-value">{{ item.entryDate }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·ååå·</text> |
| | | <text class="detail-value">{{ item.customerContractNo }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·åç§°</text> |
| | | <text class="detail-value">{{ item.customerName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">项ç®åç§°</text> |
| | | <text class="detail-value">{{ item.projectName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产å大类</text> |
| | | <text class="detail-value">{{ item.productCategory }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.specificationModel }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ»æ°é</text> |
| | | <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æäº§æ°é</text> |
| | | <text class="detail-value highlight">{{ item.schedulingNum }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å¾
ææ°é</text> |
| | | <text class="detail-value" :class="{ 'danger': item.pendingQuantity <= 0 }">{{ item.pendingQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æä½æé®åºå --> |
| | | <view class="action-buttons"> |
| | | <up-button |
| | | type="primary" |
| | | size="small" |
| | | @click="handleDispatch(item)" |
| | | class="action-btn" |
| | | :disabled="item.pendingQuantity <= 0" |
| | | > |
| | | ç产派工 |
| | | </up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="no-data"> |
| | | <text>ææ çäº§æ´¾å·¥æ°æ®</text> |
| | | </view> |
| | | |
| | | <!-- æ´¾å·¥å¼¹çª --> |
| | | <DispatchModal ref="dispatchModalRef" @confirm="handleDispatchConfirm" /> |
| | | </view> |
| | | <view class="production-dispatching"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="ç产派工" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°æç´¢" |
| | | v-model="searchForm.customerName" |
| | | @change="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- ç产派工å表 --> |
| | | <view class="ledger-list" |
| | | v-if="tableData.length > 0"> |
| | | <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.salesContractNo }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½å
¥æ¥æ</text> |
| | | <text class="detail-value">{{ item.entryDate }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·ååå·</text> |
| | | <text class="detail-value">{{ item.customerContractNo }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·åç§°</text> |
| | | <text class="detail-value">{{ item.customerName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">项ç®åç§°</text> |
| | | <text class="detail-value">{{ item.projectName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产å大类</text> |
| | | <text class="detail-value">{{ item.productCategory }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.specificationModel }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ»æ°é</text> |
| | | <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æäº§æ°é</text> |
| | | <text class="detail-value highlight">{{ item.schedulingNum }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å¾
ææ°é</text> |
| | | <text class="detail-value" |
| | | :class="{ 'danger': item.pendingQuantity <= 0 }">{{ item.pendingQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- æä½æé®åºå --> |
| | | <view class="action-buttons"> |
| | | <up-button type="primary" |
| | | size="small" |
| | | @click="handleDispatch(item)" |
| | | class="action-btn" |
| | | :disabled="item.pendingQuantity <= 0"> |
| | | ç产派工 |
| | | </up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ çäº§æ´¾å·¥æ°æ®</text> |
| | | </view> |
| | | <!-- æ´¾å·¥å¼¹çª --> |
| | | <DispatchModal ref="dispatchModalRef" |
| | | @confirm="handleDispatchConfirm" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import dayjs from "dayjs"; |
| | | import {schedulingListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import DispatchModal from "./components/DispatchModal.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import dayjs from "dayjs"; |
| | | // import {schedulingListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import DispatchModal from "./components/DispatchModal.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: -1, |
| | | size: -1, |
| | | }); |
| | | |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: -1, |
| | | size: -1, |
| | | }); |
| | | // 派工弹çªå¼ç¨ |
| | | const dispatchModalRef = ref(); |
| | | |
| | | // 派工弹çªå¼ç¨ |
| | | const dispatchModalRef = ref(); |
| | | // éç¨æç¤ºå½æ° |
| | | const showLoadingToast = message => { |
| | | uni.showLoading({ |
| | | title: message, |
| | | mask: true, |
| | | }); |
| | | }; |
| | | |
| | | // éç¨æç¤ºå½æ° |
| | | const showLoadingToast = (message) => { |
| | | uni.showLoading({ |
| | | title: message, |
| | | mask: true |
| | | }); |
| | | }; |
| | | const closeToast = () => { |
| | | uni.hideLoading(); |
| | | }; |
| | | |
| | | const closeToast = () => { |
| | | uni.hideLoading(); |
| | | }; |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | getList(); |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | getList(); |
| | | }; |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | showLoadingToast("å è½½ä¸..."); |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | showLoadingToast('å è½½ä¸...'); |
| | | |
| | | // æé 请æ±åæ° |
| | | const params = { ...searchForm.value, ...page }; |
| | | |
| | | schedulingListPage(params).then((res) => { |
| | | loading.value = false; |
| | | closeToast(); |
| | | |
| | | // å¤çæ¯æ¡æ°æ®ï¼å¢å pendingQuantityåæ®µ |
| | | tableData.value = (res.data.records || []).map(item => ({ |
| | | ...item, |
| | | pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0) |
| | | })); |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | closeToast(); |
| | | uni.showToast({ |
| | | title: 'å 载失败', |
| | | icon: 'error' |
| | | }); |
| | | }); |
| | | }; |
| | | // æé 请æ±åæ° |
| | | const params = { ...searchForm.value, ...page }; |
| | | |
| | | // å¤ç派工æä½ |
| | | const handleDispatch = (item) => { |
| | | if (item.pendingQuantity <= 0) { |
| | | uni.showToast({ |
| | | title: 'è¯¥é¡¹ç®æ éåæ´¾å·¥', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | dispatchModalRef.value?.open(item); |
| | | }; |
| | | // schedulingListPage(params).then((res) => { |
| | | // loading.value = false; |
| | | // closeToast(); |
| | | |
| | | // å¤ç派工确认 |
| | | const handleDispatchConfirm = () => { |
| | | getList(); // å·æ°å表 |
| | | }; |
| | | // // å¤çæ¯æ¡æ°æ®ï¼å¢å pendingQuantityåæ®µ |
| | | // tableData.value = (res.data.records || []).map(item => ({ |
| | | // ...item, |
| | | // pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0) |
| | | // })); |
| | | // }).catch(() => { |
| | | // loading.value = false; |
| | | // closeToast(); |
| | | // uni.showToast({ |
| | | // title: 'å 载失败', |
| | | // icon: 'error' |
| | | // }); |
| | | // }); |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | // å è½½åè¡¨æ°æ® |
| | | getList(); |
| | | }); |
| | | // å¤ç派工æä½ |
| | | const handleDispatch = item => { |
| | | if (item.pendingQuantity <= 0) { |
| | | uni.showToast({ |
| | | title: "è¯¥é¡¹ç®æ éåæ´¾å·¥", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | dispatchModalRef.value?.open(item); |
| | | }; |
| | | |
| | | // å¤ç派工确认 |
| | | const handleDispatchConfirm = () => { |
| | | getList(); // å·æ°å表 |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | // å è½½åè¡¨æ°æ® |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/styles/sales-common.scss'; |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | // çäº§æ´¾å·¥é¡µé¢æ ·å¼ |
| | | .production-dispatching { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | position: relative; |
| | | } |
| | | // çäº§æ´¾å·¥é¡µé¢æ ·å¼ |
| | | .production-dispatching { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | position: relative; |
| | | } |
| | | |
| | | // åè¡¨é¡¹æ ·å¼ |
| | | .ledger-item { |
| | | .detail-value.highlight { |
| | | color: #ff6b35; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .detail-value.danger { |
| | | color: #ee0a24; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | // åè¡¨é¡¹æ ·å¼ |
| | | .ledger-item { |
| | | .detail-value.highlight { |
| | | color: #ff6b35; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | // éé
uView ç»ä»¶æ ·å¼ |
| | | :deep(.up-input) { |
| | | background: transparent; |
| | | } |
| | | .detail-value.danger { |
| | | color: #ee0a24; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | |
| | | // éé
uView ç»ä»¶æ ·å¼ |
| | | :deep(.up-input) { |
| | | background: transparent; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="production-order"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="ç产订å" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°æç´¢" |
| | | v-model="searchForm.customerName" |
| | | @change="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- ç产订åå表 --> |
| | | <view class="ledger-list" v-if="tableData.length > 0"> |
| | | <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.salesContractNo }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å½å
¥æ¥æ</text> |
| | | <text class="detail-value">{{ item.entryDate }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·ååå·</text> |
| | | <text class="detail-value">{{ item.customerContractNo }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·åç§°</text> |
| | | <text class="detail-value">{{ item.customerName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">项ç®åç§°</text> |
| | | <text class="detail-value">{{ item.projectName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产å大类</text> |
| | | <text class="detail-value">{{ item.productCategory }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.specificationModel }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ°é</text> |
| | | <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æäº§æ°é</text> |
| | | <text class="detail-value highlight">{{ item.schedulingNum }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®å·¥æ°é</text> |
| | | <text class="detail-value highlight">{{ item.successNum }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="no-data"> |
| | | <text>ææ çäº§è®¢åæ°æ®</text> |
| | | </view> |
| | | </view> |
| | | <view class="production-order"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader title="ç产订å" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥è®¢åå·æäº§ååç§°" |
| | | v-model="searchForm.keyword" |
| | | @change="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å表åºå --> |
| | | <scroll-view scroll-y |
| | | class="list-container" |
| | | v-if="tableData.length > 0" |
| | | @scrolltolower="loadMore"> |
| | | <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.npsNo }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <up-tag :text="getStatusText(item.status)" |
| | | :type="getStatusType(item.status)" |
| | | size="mini" /> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value font-bold">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è®¢åæ°é</text> |
| | | <text class="detail-value">{{ item.quantity || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">宿è¿åº¦</text> |
| | | <view class="progress-box"> |
| | | <up-line-progress :percentage="toProgressPercentage(item.completionStatus)" |
| | | :activeColor="progressColor(item.completionStatus)" |
| | | height="10"></up-line-progress> |
| | | <text class="progress-text">{{ item.completeQuantity || 0 }} / {{ item.quantity || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | <!-- å·¥åºç产è¿åº¦å±ç¤º --> |
| | | <view class="detail-row process-row"> |
| | | <text class="detail-label">å·¥åºè¿åº¦</text> |
| | | <scroll-view scroll-x |
| | | class="process-scroll"> |
| | | <view class="process-container"> |
| | | <view v-for="(process, pIdx) in item.processRouteStatus" |
| | | :key="pIdx" |
| | | class="process-item"> |
| | | <view class="process-node"> |
| | | <view class="node-circle" |
| | | :class="{ 'is-complete': process.percentage >= 100 }"> |
| | | <text class="node-percentage" |
| | | :style="{ color: process.percentage >= 100 ? '#52c41a' : (process.percentage >= 70 ? '#f56c6c' : '#3c9cff') }">{{ process.percentage }}%</text> |
| | | </view> |
| | | <text class="node-name">{{ process.name }}</text> |
| | | </view> |
| | | <view v-if="pIdx < item.processRouteStatus.length - 1" |
| | | class="node-line"></view> |
| | | </view> |
| | | <view v-if="!item.processRouteStatus || !item.processRouteStatus.length" |
| | | class="no-process">-</view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">计å宿</text> |
| | | <text class="detail-value">{{ formatDate(item.planCompleteTime) }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="item-footer"> |
| | | <view class="action-btns"> |
| | | <up-button type="info" |
| | | size="small" |
| | | plain |
| | | text="ç产追溯" |
| | | @click="goTraceability(item)"></up-button> |
| | | <up-button type="info" |
| | | size="small" |
| | | plain |
| | | text="å·¥èºè·¯çº¿" |
| | | @click="goProcessRoute(item)"></up-button> |
| | | <up-button type="primary" |
| | | size="small" |
| | | plain |
| | | text="æ¥æº" |
| | | @click="goSource(item)"></up-button> |
| | | <up-button type="success" |
| | | size="small" |
| | | plain |
| | | text="é¢æè¯¦æ
" |
| | | @click="goPickingDetail(item)"></up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" |
| | | v-if="tableData.length >= page.size" /> |
| | | </scroll-view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ çäº§è®¢åæ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import dayjs from "dayjs"; |
| | | import {schedulingListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | productOrderListPage, |
| | | getOrderProcessRouteMain, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: -1, |
| | | size: -1, |
| | | total: 0, |
| | | }); |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const loadStatus = ref("loadmore"); |
| | | // åè¡¨æ°æ® |
| | | const tableData = ref([]); |
| | | |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | customerName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | // å页é
ç½® |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | // éç¨æç¤ºå½æ° |
| | | const showLoadingToast = (message) => { |
| | | uni.showLoading({ |
| | | title: message, |
| | | mask: true |
| | | }); |
| | | }; |
| | | // æç´¢è¡¨åæ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | keyword: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const closeToast = () => { |
| | | uni.hideLoading(); |
| | | }; |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = date => { |
| | | return date ? dayjs(date).format("YYYY-MM-DD") : "-"; |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; // éç½®åè¡¨æ°æ® |
| | | getList(); |
| | | }; |
| | | // è·åç¶æææ¬ |
| | | const getStatusText = status => { |
| | | const statusMap = { |
| | | 1: "å¾
å¼å§", |
| | | 2: "è¿è¡ä¸", |
| | | 3: "已宿", |
| | | 4: "已忶", |
| | | 5: "å·²ç»æ", |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | showLoadingToast('å è½½ä¸...'); |
| | | |
| | | // æé 请æ±åæ° |
| | | const params = { ...searchForm.value, ...page }; |
| | | |
| | | schedulingListPage(params).then((res) => { |
| | | loading.value = false; |
| | | closeToast(); |
| | | |
| | | tableData.value = res.data.records || []; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | closeToast(); |
| | | uni.showToast({ |
| | | title: 'å 载失败', |
| | | icon: 'error' |
| | | }); |
| | | }); |
| | | }; |
| | | // è·åç¶æç±»å |
| | | const getStatusType = status => { |
| | | const typeMap = { |
| | | 1: "primary", |
| | | 2: "warning", |
| | | 3: "success", |
| | | 4: "info", |
| | | 5: "error", |
| | | }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | // å è½½åè¡¨æ°æ® |
| | | getList(); |
| | | }); |
| | | // 宿è¿åº¦ç¾åæ¯ |
| | | const toProgressPercentage = val => { |
| | | const n = Number(val); |
| | | if (!Number.isFinite(n)) return 0; |
| | | if (n <= 0) return 0; |
| | | if (n >= 100) return 100; |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | // è¿åº¦æ¡é¢è² |
| | | const progressColor = percentage => { |
| | | const p = toProgressPercentage(percentage); |
| | | if (p < 30) return "#f56c6c"; |
| | | if (p < 50) return "#e6a23c"; |
| | | if (p < 80) return "#409eff"; |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | // å è½½æ´å¤ |
| | | const loadMore = () => { |
| | | if (loadStatus.value === "nomore" || loading.value) return; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | // è·ååè¡¨æ°æ® |
| | | const getList = () => { |
| | | loading.value = true; |
| | | loadStatus.value = "loading"; |
| | | |
| | | const params = { |
| | | current: page.current, |
| | | size: page.size, |
| | | npsNo: searchForm.value.keyword, |
| | | productName: searchForm.value.keyword, |
| | | }; |
| | | |
| | | productOrderListPage(params) |
| | | .then(async res => { |
| | | const records = res.data.records || []; |
| | | |
| | | // 为æ¯ä¸ªè®¢åå¹¶è¡æ¥è¯¢å·¥åºè¿åº¦ |
| | | const processPromises = records.map(async item => { |
| | | if (item.npsNo) { |
| | | try { |
| | | const workOrderRes = await productWorkOrderPage({ |
| | | npsNo: item.npsNo, |
| | | size: 100, |
| | | }); |
| | | const workOrders = workOrderRes.data.records || []; |
| | | const processRouteStatus = workOrders.map(wo => ({ |
| | | name: wo.operationName || "æªç¥å·¥åº", |
| | | percentage: |
| | | Number(wo.completionStatus) > 100 |
| | | ? 100 |
| | | : Number(wo.completionStatus || 0), |
| | | })); |
| | | return { ...item, processRouteStatus }; |
| | | } catch (error) { |
| | | console.error(`è·åå·¥å ${item.npsNo} è¿åº¦å¤±è´¥:`, error); |
| | | return { ...item, processRouteStatus: [] }; |
| | | } |
| | | } |
| | | return { ...item, processRouteStatus: [] }; |
| | | }); |
| | | |
| | | const updatedRecords = await Promise.all(processPromises); |
| | | |
| | | loading.value = false; |
| | | if (page.current === 1) { |
| | | tableData.value = updatedRecords; |
| | | } else { |
| | | tableData.value = [...tableData.value, ...updatedRecords]; |
| | | } |
| | | |
| | | if (updatedRecords.length < page.size) { |
| | | loadStatus.value = "nomore"; |
| | | } else { |
| | | loadStatus.value = "loadmore"; |
| | | } |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = "loadmore"; |
| | | uni.showToast({ |
| | | title: "å 载失败", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 跳转工èºè·¯çº¿ (BOM) |
| | | const goProcessRoute = item => { |
| | | getOrderProcessRouteMain(item.id) |
| | | .then(res => { |
| | | const data = res.data || {}; |
| | | if (!data.id) { |
| | | uni.showToast({ title: "æªæ¾å°å·¥èºè·¯çº¿", icon: "none" }); |
| | | return; |
| | | } |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/processRoute/items?id=${ |
| | | data.id |
| | | }&bomId=${data.orderBomId}&processRouteCode=${ |
| | | data.processRouteCode || "" |
| | | }&productName=${encodeURIComponent( |
| | | item.productName || "" |
| | | )}&model=${encodeURIComponent(item.model || "")}&orderId=${ |
| | | item.id |
| | | }&type=order`, |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "è·å路线失败", icon: "none" }); |
| | | }); |
| | | }; |
| | | |
| | | // è·³è½¬æ¥æº |
| | | const goSource = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/productionOrder/source?id=${ |
| | | item.id |
| | | }&productName=${encodeURIComponent( |
| | | item.productName |
| | | )}&model=${encodeURIComponent(item.model)}&quantity=${item.quantity}`, |
| | | }); |
| | | }; |
| | | |
| | | // è·³è½¬é¢æè¯¦æ
|
| | | const goPickingDetail = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/productionOrder/pickingDetail?id=${item.id}&npsNo=${item.npsNo}`, |
| | | }); |
| | | }; |
| | | |
| | | // 跳转ç产追溯 |
| | | const goTraceability = item => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/productionTraceability/index?npsNo=${item.npsNo}`, |
| | | }); |
| | | }; |
| | | |
| | | // 页颿¾ç¤ºæ¶å è½½æ°æ® |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/styles/sales-common.scss'; |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | // ç产订å页颿 ·å¼ |
| | | .production-order { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | position: relative; |
| | | } |
| | | .production-order { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | // éåé¨åæ ·å¼ä»¥éé
ç产订å |
| | | .ledger-item { |
| | | .detail-value.highlight { |
| | | color: #ff6b35; |
| | | font-weight: 600; |
| | | } |
| | | } |
| | | .list-container { |
| | | flex: 1; |
| | | height: 0; |
| | | } |
| | | |
| | | // éé
uView ç»ä»¶æ ·å¼ |
| | | :deep(.up-input) { |
| | | background: transparent; |
| | | } |
| | | .ledger-item { |
| | | background: #fff; |
| | | margin: 20rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06); |
| | | |
| | | :deep(.up-datetime-picker) { |
| | | width: 100%; |
| | | } |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 12rpx; |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .document-icon { |
| | | width: 44rpx; |
| | | height: 44rpx; |
| | | background: #3c9cff; |
| | | border-radius: 10rpx; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-right: 20rpx; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-details { |
| | | padding: 16rpx 0; |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | min-width: 140rpx; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | text-align: right; |
| | | |
| | | &.font-bold { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .progress-box { |
| | | flex: 1; |
| | | margin-left: 40rpx; |
| | | |
| | | .progress-text { |
| | | font-size: 22rpx; |
| | | color: #999; |
| | | margin-top: 4rpx; |
| | | display: block; |
| | | text-align: right; |
| | | } |
| | | } |
| | | |
| | | &.process-row { |
| | | flex-direction: column; |
| | | margin: 20rpx 0; |
| | | |
| | | .process-scroll { |
| | | width: 100%; |
| | | margin-top: 16rpx; |
| | | |
| | | .process-container { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | padding: 10rpx 0; |
| | | min-height: 120rpx; |
| | | |
| | | .process-item { |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .process-node { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | width: 100rpx; |
| | | |
| | | .node-circle { |
| | | width: 60rpx; |
| | | height: 60rpx; |
| | | border-radius: 50%; |
| | | border: 2rpx solid #3c9cff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #fff; |
| | | margin-bottom: 8rpx; |
| | | |
| | | .node-percentage { |
| | | font-size: 18rpx; |
| | | color: #3c9cff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | &.is-complete { |
| | | border-color: #52c41a; |
| | | background: #f6ffed; |
| | | |
| | | .node-percentage { |
| | | color: #52c41a; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .node-name { |
| | | font-size: 20rpx; |
| | | color: #666; |
| | | text-align: center; |
| | | width: 120rpx; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | } |
| | | |
| | | .node-line { |
| | | width: 40rpx; |
| | | height: 2rpx; |
| | | background: #e8e8e8; |
| | | margin: -30rpx 0 0 0; |
| | | } |
| | | } |
| | | |
| | | .no-process { |
| | | font-size: 24rpx; |
| | | color: #ccc; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-footer { |
| | | padding-top: 20rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | |
| | | .action-btns { |
| | | display: flex; |
| | | gap: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="picking-detail"> |
| | | <PageHeader title="é¢æè¯¦æ
" |
| | | @back="goBack" /> |
| | | <scroll-view scroll-y |
| | | class="detail-list" |
| | | v-if="detailList.length > 0"> |
| | | <view v-for="(item, index) in detailList" |
| | | :key="index" |
| | | class="material-card"> |
| | | <view class="card-header"> |
| | | <text class="material-name">{{ item.materialName || item.productName || '-' }}</text> |
| | | <up-tag :text="item.operationName || '-'" |
| | | type="info" |
| | | size="mini" |
| | | plain /> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-grid"> |
| | | <view class="info-item"> |
| | | <text class="label">è§æ ¼åå·</text> |
| | | <text class="value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">åä½</text> |
| | | <text class="value">{{ item.unit || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">é颿°é</text> |
| | | <text class="value highlight">{{ item.qtyRequired || item.demandedQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">已颿°é</text> |
| | | <text class="value success">{{ item.qtyPicked || item.pickQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">è¡¥ææ°é</text> |
| | | <view class="value link" |
| | | @click="showSupplementDetail(item)"> |
| | | {{ item.qtySupplement || item.feedingQty || 0 }} |
| | | </view> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">éææ°é</text> |
| | | <text class="value">{{ item.returnQty || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="remark-row" |
| | | v-if="item.remark"> |
| | | <text class="label">夿³¨ï¼</text> |
| | | <text class="value">{{ item.remark }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ é¢æè¯¦æ
"></up-empty> |
| | | </view> |
| | | <!-- è¡¥æè®°å½å¼¹çª --> |
| | | <up-popup :show="showPopup" |
| | | mode="bottom" |
| | | @close="showPopup = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="title">è¡¥æè®°å½</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="showPopup = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="record-list"> |
| | | <view v-if="supplementRecords.length > 0"> |
| | | <view v-for="(record, rIndex) in supplementRecords" |
| | | :key="rIndex" |
| | | class="record-item"> |
| | | <view class="record-row"> |
| | | <text class="record-label">è¡¥ææ°éï¼</text> |
| | | <text class="record-value highlight">{{ record.pickQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="record-row"> |
| | | <text class="record-label">è¡¥æäººï¼</text> |
| | | <text class="record-value">{{ record.supplementUserName || '-' }}</text> |
| | | </view> |
| | | <view class="record-row"> |
| | | <text class="record-label">è¡¥ææ¥æï¼</text> |
| | | <text class="record-value">{{ record.supplementTime || '-' }}</text> |
| | | </view> |
| | | <view class="record-row"> |
| | | <text class="record-label">è¡¥æåå ï¼</text> |
| | | <text class="record-value">{{ record.feedingReason || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-record"> |
| | | <text>ææ è¡¥æè®°å½</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { |
| | | listMaterialPickingDetail, |
| | | listMaterialSupplementRecord, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const npsNo = ref(""); |
| | | const productionOrderId = ref(""); |
| | | const detailList = ref([]); |
| | | const loading = ref(false); |
| | | |
| | | // å¼¹çªç¸å
³ |
| | | const showPopup = ref(false); |
| | | const supplementRecords = ref([]); |
| | | const recordLoading = ref(false); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const calculatePending = item => { |
| | | const required = Number(item.qtyRequired || item.demandedQuantity || 0); |
| | | const picked = Number(item.qtyPicked || item.pickQuantity || 0); |
| | | return Math.max(0, required - picked); |
| | | }; |
| | | |
| | | onLoad(options => { |
| | | if (options.id) { |
| | | productionOrderId.value = options.id; |
| | | npsNo.value = options.npsNo || ""; |
| | | fetchDetail(options.id); |
| | | } |
| | | }); |
| | | |
| | | const fetchDetail = id => { |
| | | loading.value = true; |
| | | listMaterialPickingDetail(id) |
| | | .then(res => { |
| | | detailList.value = res.data?.records || res.data || []; |
| | | loading.value = false; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | uni.showToast({ |
| | | title: "è·å详æ
失败", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const showSupplementDetail = item => { |
| | | const qty = Number(item.qtySupplement || item.feedingQty || 0); |
| | | if (qty <= 0) return; |
| | | |
| | | showPopup.value = true; |
| | | recordLoading.value = true; |
| | | supplementRecords.value = []; |
| | | |
| | | listMaterialSupplementRecord({ |
| | | pickId: item.id, |
| | | productionOrderId: productionOrderId.value, |
| | | }) |
| | | .then(res => { |
| | | supplementRecords.value = res.data || []; |
| | | recordLoading.value = false; |
| | | }) |
| | | .catch(() => { |
| | | recordLoading.value = false; |
| | | uni.showToast({ |
| | | title: "è·åè¡¥æè®°å½å¤±è´¥", |
| | | icon: "error", |
| | | }); |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .picking-detail { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .detail-list { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 20rpx; |
| | | } |
| | | |
| | | .material-card { |
| | | background: #fff; |
| | | margin-bottom: 24rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 20rpx; |
| | | border-bottom: 1rpx solid #f5f5f5; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .material-name { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .info-grid { |
| | | display: grid; |
| | | grid-template-columns: repeat(2, 1fr); |
| | | gap: 20rpx; |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | |
| | | &.highlight { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | &.success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | &.warning { |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | &.link { |
| | | color: #3c9cff; |
| | | text-decoration: underline; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .remark-row { |
| | | margin-top: 20rpx; |
| | | padding-top: 16rpx; |
| | | border-top: 1rpx dashed #eee; |
| | | display: flex; |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | .value { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | |
| | | /* å¼¹çªæ ·å¼ */ |
| | | .popup-content { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 70vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 30rpx; |
| | | border-bottom: 1rpx solid #eee; |
| | | |
| | | .title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .record-list { |
| | | flex: 1; |
| | | height: 0; |
| | | padding-top: 20rpx; |
| | | } |
| | | |
| | | .record-item { |
| | | padding: 24rpx; |
| | | background: #f9f9f9; |
| | | border-radius: 12rpx; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .record-row { |
| | | display: flex; |
| | | margin-bottom: 10rpx; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .record-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | width: 140rpx; |
| | | } |
| | | |
| | | .record-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | flex: 1; |
| | | |
| | | &.highlight { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-record { |
| | | padding: 100rpx 0; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-order-source"> |
| | | <PageHeader title="æ¥æºæ°æ®" @back="goBack" /> |
| | | |
| | | <view class="summary-card" v-if="summary"> |
| | | <view class="summary-item"> |
| | | <text class="label">产ååç§°</text> |
| | | <up-tag :text="summary.productName || '-'" type="primary" size="mini" /> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="label">è§æ ¼åå·</text> |
| | | <text class="value">{{ summary.model || '-' }}</text> |
| | | </view> |
| | | <view class="summary-item"> |
| | | <text class="label">éæ±æ°é</text> |
| | | <text class="value highlight">{{ summary.quantity || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view scroll-y class="source-list" v-if="sourceList.length > 0"> |
| | | <view v-for="(item, index) in sourceList" :key="index" class="source-card"> |
| | | <view class="card-header"> |
| | | <text class="plan-no">计åå·: {{ item.mpsNo || '-' }}</text> |
| | | <up-tag :text="item.source || 'æªç¥'" :type="item.source === 'éå®' ? 'primary' : 'warning'" size="mini" /> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="info-row"> |
| | | <text class="info-label">ååå·</text> |
| | | <text class="info-value">{{ item.salesContractNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="info-label">客æ·åç§°</text> |
| | | <text class="info-value">{{ item.customerName || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="info-label">项ç®åç§°</text> |
| | | <text class="info-value">{{ item.projectName || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="info-label">éæ±æ°é</text> |
| | | <text class="info-value">{{ item.qtyRequired || 0 }} {{ item.unit || '' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="info-label">éæ±æ¥æ</text> |
| | | <text class="info-value">{{ formatDate(item.requiredDate) }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <up-empty mode="data" text="ææ æ¥æºæ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { onLoad } from '@dcloudio/uni-app'; |
| | | import dayjs from "dayjs"; |
| | | import { getProductOrderSource } from "@/api/productionManagement/productionOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const summary = ref(null); |
| | | const sourceList = ref([]); |
| | | const loading = ref(false); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const formatDate = (date) => { |
| | | return date ? dayjs(date).format('YYYY-MM-DD') : '-'; |
| | | }; |
| | | |
| | | onLoad((options) => { |
| | | if (options.id) { |
| | | summary.value = { |
| | | productName: decodeURIComponent(options.productName || ''), |
| | | model: decodeURIComponent(options.model || ''), |
| | | quantity: options.quantity || 0 |
| | | }; |
| | | fetchSource(options.id); |
| | | } |
| | | }); |
| | | |
| | | const fetchSource = (id) => { |
| | | loading.value = true; |
| | | getProductOrderSource(id).then(res => { |
| | | sourceList.value = res.data || []; |
| | | loading.value = false; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | uni.showToast({ |
| | | title: 'è·åæ¥æºå¤±è´¥', |
| | | icon: 'error' |
| | | }); |
| | | }); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .production-order-source { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .summary-card { |
| | | background: #fff; |
| | | margin: 20rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); |
| | | |
| | | .summary-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 12rpx; |
| | | &:last-child { margin-bottom: 0; } |
| | | |
| | | .label { font-size: 26rpx; color: #999; } |
| | | .value { font-size: 26rpx; color: #333; } |
| | | .highlight { color: #f56c6c; font-weight: bold; } |
| | | } |
| | | } |
| | | |
| | | .source-list { |
| | | flex: 1; |
| | | height: 0; |
| | | padding: 0 20rpx; |
| | | } |
| | | |
| | | .source-card { |
| | | background: #fff; |
| | | margin-bottom: 20rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.03); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding-bottom: 16rpx; |
| | | border-bottom: 1rpx solid #f9f9f9; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .plan-no { font-size: 28rpx; font-weight: bold; color: #333; } |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 12rpx; |
| | | &:last-child { margin-bottom: 0; } |
| | | |
| | | .info-label { font-size: 24rpx; color: #999; } |
| | | .info-value { font-size: 24rpx; color: #666; } |
| | | } |
| | | } |
| | | |
| | | .no-data { padding-top: 100rpx; } |
| | | </style> |
| | |
| | | placeholder="èªå¨å¡«å
" |
| | | disabled /> |
| | | </u-form-item> |
| | | <u-form-item label="æ¬æ¬¡ç产æ°é" |
| | | <u-form-item label="çäº§åæ ¼æ°é" |
| | | prop="quantity" |
| | | required> |
| | | <u-input v-model="form.quantity" |
| | |
| | | @click="openProducerPicker" |
| | | suffix-icon="arrow-down" /> |
| | | </u-form-item> |
| | | <!-- å·¥æ¶ --> |
| | | <u-form-item label="å·¥æ¶" |
| | | v-if="form.type == 0" |
| | | prop="workHour"> |
| | | <u-input v-model="form.workHour" |
| | | placeholder="请è¾å
¥å·¥æ¶" |
| | | type="number" /> |
| | | <text class="param-unit">h</text> |
| | | </u-form-item> |
| | | </view> |
| | | <!-- 卿忰åºå --> |
| | | <view class="form-section" |
| | | v-if="params.length > 0"> |
| | | <view class="section-title">å·¥åºåæ°</view> |
| | | <u-form-item v-for="param in params" |
| | | :key="param.id" |
| | | :label="param.paramName" |
| | | :label-width="110" |
| | | :required="param.required === '1'"> |
| | | <!-- æ°åç±»å --> |
| | | <template v-if="param.paramType == '1'"> |
| | | <u-input v-model="form.paramGroups[param.id]" |
| | | type="number" |
| | | :placeholder="'请è¾å
¥' + param.paramName" |
| | | :key="param.id" /> |
| | | <text v-if="param.unit && param.unit != '/'" |
| | | class="param-unit">{{ param.unit }}</text> |
| | | </template> |
| | | <!-- ææ¬ç±»å --> |
| | | <template v-else-if="param.paramType == '2'"> |
| | | <u-input v-model="form.paramGroups[param.id]" |
| | | :placeholder="'请è¾å
¥' + param.paramName" |
| | | :key="param.id" /> |
| | | <text v-if="param.unit && param.unit != '/'" |
| | | class="param-unit">{{ param.unit }}</text> |
| | | </template> |
| | | <!-- éæ©ç±»å --> |
| | | <template v-else-if="param.paramType == '3'"> |
| | | <u-input v-model="form.paramGroups[param.id]" |
| | | readonly |
| | | :placeholder="'è¯·éæ©' + param.paramName" |
| | | @click="openParamSelect(param)" |
| | | suffix-icon="arrow-down" /> |
| | | </template> |
| | | <!-- æ¥æç±»å --> |
| | | <template v-else-if="param.paramType == '4'"> |
| | | <u-input v-model="form.paramGroups[param.id]" |
| | | readonly |
| | | :placeholder="'è¯·éæ©' + param.paramName" |
| | | @click="openDateParamPicker(param)" |
| | | suffix-icon="arrow-down" /> |
| | | <text v-if="param.unit && param.unit != '/'" |
| | | class="param-unit">{{ param.unit }}</text> |
| | | </template> |
| | | <!-- é»è®¤ææ¬ --> |
| | | <template v-else> |
| | | <u-input v-model="form.paramGroups[param.id]" |
| | | :placeholder="'请è¾å
¥' + param.paramName" |
| | | :key="param.id" /> |
| | | </template> |
| | | </u-form-item> |
| | | </view> |
| | | <!-- 使ç¨FooterButtonsç»ä»¶ --> |
| | | <FooterButtons @cancel="goBack" |
| | |
| | | title="éæ©ç产人" |
| | | @select="onProducerConfirm" |
| | | @close="showProducerPicker = false" /> |
| | | <!-- åæ°éæ©å¨ --> |
| | | <up-action-sheet :show="showParamSelect" |
| | | :actions="paramOptions" |
| | | :title="currentParam?.paramName || 'éæ©'" |
| | | @select="onParamConfirm" |
| | | @close="showParamSelect = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker :show="showDatePicker" |
| | | v-model="datePickerValue" |
| | | :mode="datePickerMode" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDatePicker = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | import { addProductMain } from "@/api/productionManagement/productionReporting"; |
| | | import { getInfo } from "@/api/login"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { |
| | | findProcessParamListOrder, |
| | | listMaterialPickingDetail, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import { getDicts } from "@/api/system/dict/data"; |
| | | import { formatDateToYMD, parseTime } from "@/utils/ruoyi"; |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(); |
| | | |
| | | // è¡¨åæ°æ® |
| | | let form = ref({ |
| | | planQuantity: "", |
| | | quantity: "", |
| | | scrapQty: "", |
| | | userName: "", |
| | | workOrderId: "", |
| | | productProcessRouteItemId: "", |
| | | userId: "", |
| | | schedulingUserId: "", |
| | | reportWork: "", |
| | | productionOrderRoutingOperationId: "", |
| | | productionOrderId: "", |
| | | workHour: 0, |
| | | type: null, |
| | | paramGroups: {}, |
| | | }); |
| | | |
| | | // çäº§äººéæ©å¨ç¶æ |
| | | const showProducerPicker = ref(false); |
| | | const producerList = ref([]); |
| | | |
| | | // æå¼çäº§äººéæ©å¨ |
| | | const params = ref([]); |
| | | const dictOptions = ref({}); |
| | | const showParamSelect = ref(false); |
| | | const currentParam = ref(null); |
| | | const paramOptions = ref([]); |
| | | |
| | | const showDatePicker = ref(false); |
| | | const datePickerValue = ref(Date.now()); |
| | | const datePickerMode = ref("date"); |
| | | const currentDateParam = ref(null); |
| | | |
| | | const openProducerPicker = async () => { |
| | | if (producerList.value.length === 0) { |
| | | // 妿å表为空ï¼å
å è½½ç¨æ·å表 |
| | | try { |
| | | const res = await userListNoPageByTenantId(); |
| | | const users = res.data || []; |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | producerList.value = users.map(user => ({ |
| | | name: user.nickName || user.userName, |
| | | value: user.userId, |
| | |
| | | showProducerPicker.value = true; |
| | | }; |
| | | |
| | | // çäº§äººéæ©ç¡®è®¤ |
| | | const onProducerConfirm = e => { |
| | | form.value.schedulingUserId = e.value; |
| | | form.value.userName = e.name; |
| | | form.value.userId = e.value; // åæ¶æ´æ° userId |
| | | form.value.userId = e.value; |
| | | showProducerPicker.value = false; |
| | | }; |
| | | |
| | | // æäº¤ç¶æ |
| | | const openParamSelect = async param => { |
| | | currentParam.value = param; |
| | | if (param.paramType == "3" && param.paramFormat) { |
| | | const options = await getDictOptions(param.paramFormat); |
| | | paramOptions.value = options.map(opt => ({ |
| | | name: opt.dictLabel, |
| | | value: opt.dictLabel, |
| | | })); |
| | | } |
| | | showParamSelect.value = true; |
| | | }; |
| | | |
| | | const onParamConfirm = e => { |
| | | if (currentParam.value) { |
| | | form.value.paramGroups[currentParam.value.id] = e.value; |
| | | } |
| | | showParamSelect.value = false; |
| | | }; |
| | | |
| | | const openDateParamPicker = param => { |
| | | currentDateParam.value = param; |
| | | const currentValue = form.value.paramGroups[param.id]; |
| | | datePickerValue.value = currentValue |
| | | ? new Date(currentValue).getTime() |
| | | : Date.now(); |
| | | // åç
§ PC 端é»è¾ï¼å¦ææ ¼å¼æ¯ yyyy-MM-dd å为 date 模å¼ï¼å¦å为 datetime æ¨¡å¼ |
| | | datePickerMode.value = |
| | | param.paramFormat === "yyyy-MM-dd" ? "date" : "datetime"; |
| | | showDatePicker.value = true; |
| | | }; |
| | | |
| | | const onDateConfirm = e => { |
| | | if (currentDateParam.value) { |
| | | const format = |
| | | currentDateParam.value.paramFormat === "yyyy-MM-dd" |
| | | ? "{y}-{m}-{d}" |
| | | : "{y}-{m}-{d} {h}:{i}:{s}"; |
| | | form.value.paramGroups[currentDateParam.value.id] = parseTime( |
| | | e.value, |
| | | format |
| | | ); |
| | | } |
| | | showDatePicker.value = false; |
| | | }; |
| | | |
| | | const getDictOptions = async dictType => { |
| | | if (!dictType) return []; |
| | | if (dictOptions.value[dictType]) return dictOptions.value[dictType]; |
| | | try { |
| | | const res = await getDicts(dictType); |
| | | if (res.code === 200) { |
| | | dictOptions.value[dictType] = res.data; |
| | | return res.data; |
| | | } |
| | | return []; |
| | | } catch (error) { |
| | | console.error("è·ååå
¸æ°æ®å¤±è´¥:", error); |
| | | return []; |
| | | } |
| | | }; |
| | | |
| | | const loadParams = (productionOrderRoutingOperationId, productionOrderId) => { |
| | | findProcessParamListOrder({ |
| | | productionOrderRoutingOperationId, |
| | | productionOrderId, |
| | | }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | console.log(res.data, "res.data========"); |
| | | |
| | | const paramList = res.data || []; |
| | | params.value = paramList; |
| | | form.value.paramGroups = {}; |
| | | paramList.forEach(param => { |
| | | if (!form.value.paramGroups[param.id]) { |
| | | form.value.paramGroups[param.id] = ""; |
| | | } |
| | | if (param.paramType == "3" && param.paramFormat) { |
| | | getDictOptions(param.paramFormat); |
| | | } |
| | | }); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.error("è·åå·¥åºåæ°å¤±è´¥:", err); |
| | | }); |
| | | }; |
| | | |
| | | const submitting = ref(false); |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | // æäº¤è¡¨å |
| | | |
| | | const submitForm = async () => { |
| | | submitting.value = true; |
| | | // æ ¡éªè¡¨å |
| | | |
| | | if (!form.value.quantity) { |
| | | submitting.value = false; |
| | | showToast("请è¾å
¥æ¬æ¬¡ç产æ°é"); |
| | | showToast("请è¾å
¥çäº§åæ ¼æ°é"); |
| | | return; |
| | | } |
| | | |
| | | if (!form.value.schedulingUserId) { |
| | | submitting.value = false; |
| | | showToast("è¯·éæ©ç产人"); |
| | | return; |
| | | } |
| | | // 转æ¢ä¸ºæ°åè¿è¡æ¯è¾ |
| | | |
| | | const quantity = Number(form.value.quantity) || 0; |
| | | const scrapQty = Number(form.value.scrapQty) || 0; |
| | | const planQuantity = Number(form.value.planQuantity); |
| | | // éªè¯ç产æ°éåæ¥åºæ°éçåä¸è½è¶
è¿å¾
ç产æ°é |
| | | if (quantity + scrapQty > planQuantity) { |
| | | |
| | | if (quantity < 0) { |
| | | submitting.value = false; |
| | | showToast("ç产æ°éåæ¥åºæ°éçåä¸è½è¶
è¿å¾
ç产æ°é"); |
| | | showToast("çäº§åæ ¼æ°éå¿
须大äºçäº0"); |
| | | return; |
| | | } |
| | | if (quantity > planQuantity) { |
| | | |
| | | // if (quantity + scrapQty > planQuantity) { |
| | | // submitting.value = false; |
| | | // showToast("ç产æ°éåæ¥åºæ°éçåä¸è½è¶
è¿å¾
ç产æ°é"); |
| | | // return; |
| | | // } |
| | | |
| | | if (scrapQty < 0) { |
| | | submitting.value = false; |
| | | showToast("æ¬æ¬¡ç产æ°éä¸è½å¤§äºå¾
ç产æ°é"); |
| | | showToast("æ¥åºæ°éä¸è½å°äº0"); |
| | | return; |
| | | } |
| | | // åå¤æäº¤æ°æ®ï¼ç¡®ä¿æ°éåæ®µä¸ºæ°åç±»å |
| | | |
| | | // if (scrapQty > quantity) { |
| | | // submitting.value = false; |
| | | // showToast("æ¥åºæ°éä¸è½å¤§äºæ¬æ¬¡ç产æ°é"); |
| | | // return; |
| | | // } |
| | | |
| | | const productionOperationParamList = params.value.map(param => ({ |
| | | ...param, |
| | | inputValue: form.value.paramGroups[param.id] ?? "", |
| | | })); |
| | | |
| | | const submitData = { |
| | | ...form.value, |
| | | quantity: Number(form.value.quantity), |
| | | scrapQty: Number(form.value.scrapQty) || 0, |
| | | planQuantity: Number(form.value.planQuantity) || 0, |
| | | quantity: quantity, |
| | | scrapQty: scrapQty, |
| | | userId: form.value.userId, |
| | | userName: form.value.userName, |
| | | productionOperationTaskId: form.value.workOrderId, |
| | | reportWork: form.value.reportWork, |
| | | productionOrderRoutingOperationId: |
| | | form.value.productionOrderRoutingOperationId, |
| | | productionOrderId: form.value.productionOrderId, |
| | | workHour: form.value.workHour, |
| | | productionOperationParamList: productionOperationParamList, |
| | | }; |
| | | |
| | | console.log(submitData, "submitData"); |
| | | |
| | | addProductMain(submitData).then(res => { |
| | | if (res.code === 200) { |
| | | showToast("æ¥å·¥æå"); |
| | | addProductMain(submitData) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | showToast("æ¥å·¥æå"); |
| | | submitting.value = false; |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1000); |
| | | } else { |
| | | showToast(res.msg || "æ¥å·¥å¤±è´¥"); |
| | | submitting.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | showToast("æ¥å·¥å¤±è´¥"); |
| | | submitting.value = false; |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 1000); |
| | | } else { |
| | | showToast(res.msg || "æ¥å·¥å¤±è´¥"); |
| | | submitting.value = false; |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 页é¢å è½½æ¶åå§åæ°æ® |
| | | onLoad(options => { |
| | | onLoad(async options => { |
| | | console.log(options, "options"); |
| | | // å¦ææ²¡æ orderRow åæ°ï¼è¯´ææ¯ä»é¦é¡µç´æ¥è·³è½¬ï¼éè¦ç¨æ·æå¨éæ©è®¢å |
| | | if (!options.orderRow) { |
| | | console.log("ä»é¦é¡µè·³è½¬ï¼æ è®¢åæ°æ®"); |
| | | getInfo().then(res => { |
| | | // é»è®¤ä½¿ç¨å½åç»å½ç¨æ· |
| | | form.value.userId = res.user.userId; |
| | | form.value.userName = res.user.userName; |
| | | form.value.userName = res.user.nickName || res.user.userName; |
| | | form.value.schedulingUserId = res.user.userId; |
| | | }); |
| | | return; |
| | | } |
| | | try { |
| | | const orderRow = JSON.parse(options.orderRow); |
| | | const orderRow = JSON.parse(decodeURIComponent(options.orderRow)); |
| | | console.log("æé çorderRow:", orderRow); |
| | | console.log(orderRow, "orderRow======########"); |
| | | // ç¡®ä¿ planQuantity 转æ¢ä¸ºå符串ï¼ä»¥ä¾¿å¨ u-input 䏿£ç¡®æ¾ç¤º |
| | | form.value.planQuantity = orderRow.planQuantity != null ? String(orderRow.planQuantity) : ""; |
| | | form.value.productProcessRouteItemId = orderRow.productProcessRouteItemId || ""; |
| | | |
| | | // åç
§ PC 端é»è¾ï¼æªé¢ææ æ³æ¥å·¥ |
| | | if (orderRow.productionOrderId) { |
| | | try { |
| | | const res = await listMaterialPickingDetail(orderRow.productionOrderId); |
| | | const records = Array.isArray(res.data) |
| | | ? res.data |
| | | : res.data?.records || []; |
| | | if (res.code === 200 && records.length === 0) { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "æªé¢ææ æ³æ¥å·¥", |
| | | showCancel: false, |
| | | success: () => { |
| | | goBack(); |
| | | }, |
| | | }); |
| | | return; |
| | | } |
| | | } catch (error) { |
| | | console.error("æ¥è¯¢é¢æè¯¦æ
失败:", error); |
| | | } |
| | | } |
| | | |
| | | const planQuantity = Number(orderRow.planQuantity || 0); |
| | | const completeQuantity = Number(orderRow.completeQuantity || 0); |
| | | form.value.planQuantity = String( |
| | | Math.max(0, planQuantity - completeQuantity) |
| | | ); |
| | | form.value.workOrderId = orderRow.id || ""; |
| | | form.value.reportWork = orderRow.reportWork || ""; |
| | | form.value.productionOrderRoutingOperationId = |
| | | orderRow.productionOrderRoutingOperationId || ""; |
| | | form.value.productionOrderId = orderRow.productionOrderId || ""; |
| | | form.value.type = orderRow.type; |
| | | |
| | | if (orderRow.type == 0) { |
| | | form.value.workHour = orderRow.workHour || 0; |
| | | } else { |
| | | form.value.workHour = 0; |
| | | } |
| | | |
| | | getInfo().then(res => { |
| | | // é»è®¤ä½¿ç¨å½åç»å½ç¨æ·ï¼ä½å
è®¸ç¨æ·ä¿®æ¹ |
| | | form.value.userId = res.user.userId; |
| | | form.value.userName = res.user.userName; |
| | | form.value.userName = res.user.nickName || res.user.userName; |
| | | form.value.schedulingUserId = res.user.userId; |
| | | }); |
| | | // ä½¿ç¨ nextTick ç¡®ä¿ DOM æ´æ° |
| | | console.log(orderRow, "orderRow====="); |
| | | |
| | | if ( |
| | | orderRow.productionOrderRoutingOperationId && |
| | | orderRow.productionOrderId |
| | | ) { |
| | | nextTick(() => { |
| | | loadParams( |
| | | orderRow.productionOrderRoutingOperationId, |
| | | orderRow.productionOrderId |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | nextTick(() => { |
| | | console.log("form.value after assignment:", form.value); |
| | | }); |
| | |
| | | console.error("订åè§£æå¤±è´¥:", error); |
| | | showToast("订åè§£æå¤±è´¥"); |
| | | goBack(); |
| | | return; |
| | | } |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .form-section { |
| | | background: #fff; |
| | | margin-bottom: 12px; |
| | | padding: 0 16px; |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | padding: 24rpx 0 16rpx; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .param-unit { |
| | | margin-left: 8rpx; |
| | | color: #909399; |
| | | font-size: 24rpx; |
| | | } |
| | | </style> |
| | | |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="reporting-ledger"> |
| | | <PageHeader title="æ¥å·¥å°è´¦" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå - åèéè´å°è´¦æ ·å¼ --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥å·¥åå·æç´¢" |
| | | v-model="searchForm.keyword" |
| | | @change="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å表åºå --> |
| | | <scroll-view scroll-y |
| | | class="list-container" |
| | | v-if="tableData.length > 0" |
| | | @scrolltolower="loadMore"> |
| | | <view 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.productNo || '-' }}</text> |
| | | </view> |
| | | <view class="item-tag"> |
| | | <text class="create-time">{{ formatDate(item.createTime) }}</text> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥å·¥äººå</text> |
| | | <text class="detail-value highlight">{{ item.nickName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶(h)</text> |
| | | <text class="detail-value">{{ item.workHour || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå±å·¥åº</text> |
| | | <view class="detail-value"> |
| | | <up-tag :text="item.process || '-'" |
| | | type="primary" |
| | | size="mini" |
| | | plain /> |
| | | </view> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åç¼å·</text> |
| | | <text class="detail-value">{{ item.workOrderNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éå®ååå·</text> |
| | | <text class="detail-value">{{ item.salesContractNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value font-bold">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.productModelName || '-' }}</text> |
| | | </view> |
| | | <view class="quantity-section"> |
| | | <view class="qty-item"> |
| | | <text class="qty-label">äº§åºæ°é</text> |
| | | <text class="qty-value success">{{ item.quantity || 0 }} {{ item.unit || '' }}</text> |
| | | </view> |
| | | <view class="qty-item"> |
| | | <text class="qty-label">æ¥åºæ°é</text> |
| | | <text class="qty-value error">{{ item.scrapQty || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="item-footer"> |
| | | <view class="action-buttons"> |
| | | <up-button type="primary" |
| | | size="small" |
| | | plain |
| | | text="æ¥çæå
¥" |
| | | @click="handleShowInput(item)"></up-button> |
| | | <up-button type="info" |
| | | size="small" |
| | | plain |
| | | text="åæ°è¯¦æ
" |
| | | @click="handleShowParams(item)"></up-button> |
| | | <!-- <up-button type="error" |
| | | size="small" |
| | | plain |
| | | text="å é¤" |
| | | @click="handleDelete(item)"></up-button> --> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" |
| | | v-if="tableData.length >= page.size" /> |
| | | </scroll-view> |
| | | <view v-else |
| | | class="empty-state"> |
| | | <up-empty mode="data" |
| | | text="ææ æ¥å·¥å°è´¦æ°æ®"></up-empty> |
| | | </view> |
| | | <!-- æå
¥è¯¦æ
å¼¹çª --> |
| | | <up-modal :show="inputVisible" |
| | | title="æå
¥è¯¦æ
" |
| | | @confirm="inputVisible = false"> |
| | | <view class="modal-content scroll-view"> |
| | | <view v-if="inputList.length > 0"> |
| | | <view v-for="(input, idx) in inputList" |
| | | :key="idx" |
| | | class="detail-item"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥å·¥åå·</text> |
| | | <text class="detail-value">{{ input.productNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥äº§ååç§°</text> |
| | | <text class="detail-value font-bold">{{ input.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥äº§ååå·</text> |
| | | <text class="detail-value">{{ input.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥æ°é</text> |
| | | <text class="detail-value highlight">{{ input.quantity || 0 }} {{ input.unit || '' }}</text> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | </view> |
| | | </view> |
| | | <up-empty v-else |
| | | mode="data" |
| | | text="ææ æå
¥æ°æ®" /> |
| | | </view> |
| | | </up-modal> |
| | | <!-- åæ°è¯¦æ
å¼¹çª --> |
| | | <up-modal :show="paramsVisible" |
| | | title="åæ°è¯¦æ
" |
| | | @confirm="paramsVisible = false"> |
| | | <view class="modal-content"> |
| | | <view v-if="currentParams.length > 0"> |
| | | <view v-for="(param, idx) in currentParams" |
| | | :key="idx" |
| | | class="detail-row"> |
| | | <text class="detail-label">{{ param.paramName }}</text> |
| | | <text class="detail-value">{{ param.inputValue }} {{ param.unit && param.unit !== '/' ? '(' + param.unit + ')' : '' }}</text> |
| | | </view> |
| | | </view> |
| | | <up-empty v-else |
| | | mode="data" |
| | | text="ææ åæ°æ°æ®" /> |
| | | </view> |
| | | </up-modal> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { |
| | | productionProductMainListPage, |
| | | productionReportDelete, |
| | | productionProductInputListPage, |
| | | } from "@/api/productionManagement/productionProductMain.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import modal from "@/plugins/modal"; |
| | | |
| | | const tableData = ref([]); |
| | | const loading = ref(false); |
| | | const loadStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const searchForm = reactive({ |
| | | keyword: "", |
| | | }); |
| | | |
| | | // æå
¥è¯¦æ
ç¸å
³ |
| | | const inputVisible = ref(false); |
| | | const inputList = ref([]); |
| | | |
| | | // åæ°è¯¦æ
ç¸å
³ |
| | | const paramsVisible = ref(false); |
| | | const currentParams = ref([]); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const formatDate = date => { |
| | | return date ? dayjs(date).format("YYYY-MM-DD HH:mm") : "-"; |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loadStatus.value === "nomore" || loading.value) return; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | loading.value = true; |
| | | loadStatus.value = "loading"; |
| | | |
| | | const params = { |
| | | current: page.current, |
| | | size: page.size, |
| | | workOrderNo: searchForm.keyword, |
| | | }; |
| | | |
| | | productionProductMainListPage(params) |
| | | .then(res => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | if (page.current === 1) { |
| | | tableData.value = records; |
| | | } else { |
| | | tableData.value = [...tableData.value, ...records]; |
| | | } |
| | | |
| | | if (records.length < page.size) { |
| | | loadStatus.value = "nomore"; |
| | | } else { |
| | | loadStatus.value = "loadmore"; |
| | | } |
| | | page.total = res.data.total || 0; |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = "loadmore"; |
| | | modal.msgError("å 载失败"); |
| | | }); |
| | | }; |
| | | |
| | | const handleShowInput = item => { |
| | | modal.loading("å è½½ä¸..."); |
| | | productionProductInputListPage({ |
| | | productMainId: item.id, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | .then(res => { |
| | | modal.closeLoading(); |
| | | inputList.value = res.data.records || []; |
| | | inputVisible.value = true; |
| | | }) |
| | | .catch(() => { |
| | | modal.closeLoading(); |
| | | modal.msgError("å è½½æå
¥æ°æ®å¤±è´¥"); |
| | | }); |
| | | }; |
| | | |
| | | const handleShowParams = item => { |
| | | currentParams.value = item.productionOperationParamList || []; |
| | | paramsVisible.value = true; |
| | | }; |
| | | |
| | | const handleDelete = item => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "ç¡®å®è¦å é¤è¯¥æ¥å·¥è®°å½åï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | productionReportDelete({ id: item.id }).then(res => { |
| | | if (res.code === 200) { |
| | | modal.msgSuccess("å 餿å"); |
| | | handleQuery(); |
| | | } else { |
| | | modal.msgError(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .reporting-ledger { |
| | | min-height: 100vh; |
| | | background-color: #f8f9fa; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .list-container { |
| | | flex: 1; |
| | | height: 0; |
| | | } |
| | | |
| | | .ledger-item { |
| | | .item-header { |
| | | .item-tag { |
| | | .create-time { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-details { |
| | | .quantity-section { |
| | | display: flex; |
| | | background-color: #f9f9f9; |
| | | border-radius: 8rpx; |
| | | padding: 20rpx; |
| | | margin: 20rpx 0; |
| | | |
| | | .qty-item { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | &:first-child { |
| | | border-right: 1rpx solid #eee; |
| | | } |
| | | |
| | | .qty-label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .qty-value { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | |
| | | &.success { |
| | | color: #52c41a; |
| | | } |
| | | |
| | | &.error { |
| | | color: #f56c6c; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-footer { |
| | | padding-top: 20rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .modal-content { |
| | | width: 100%; |
| | | padding: 20rpx 0; |
| | | max-height: 60vh; |
| | | overflow-y: auto; |
| | | |
| | | .detail-item { |
| | | padding-bottom: 20rpx; |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 16rpx; |
| | | |
| | | .detail-label { |
| | | font-size: 26rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 26rpx; |
| | | color: #333; |
| | | |
| | | &.font-bold { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | &.highlight { |
| | | color: #3c9cff; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-state { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-scheduling"> |
| | | <!-- éç¨é¡µé¢å¤´é¨ --> |
| | | <PageHeader title="ç产æäº§" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | placeholder="请è¾å
¥å·¥åç¼å·" |
| | | v-model="searchForm.workOrderNo" |
| | | @confirm="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" |
| | | @click="handleQuery"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å表 --> |
| | | <scroll-view scroll-y |
| | | class="ledger-list" |
| | | v-if="tableData.length > 0" |
| | | @scrolltolower="loadMore"> |
| | | <view v-for="(item, index) in tableData" |
| | | :key="item.id || index" |
| | | 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.workOrderNo }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <up-tag :text="item.workOrderType" |
| | | size="mini" |
| | | type="primary" |
| | | plain></up-tag> |
| | | </view> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产订åå·</text> |
| | | <text class="detail-value">{{ item.npsNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value">{{ item.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åºåç§°</text> |
| | | <text class="detail-value">{{ item.operationName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éæ±æ°é</text> |
| | | <text class="detail-value">{{ item.planQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">宿æ°é</text> |
| | | <text class="detail-value">{{ item.completeQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="progress-section"> |
| | | <text class="detail-label">宿è¿åº¦</text> |
| | | <view class="progress-bar"> |
| | | <up-line-progress :percentage="toProgressPercentage(item.completionStatus)" |
| | | :activeColor="progressColor(item.completionStatus)" |
| | | :showText="true"></up-line-progress> |
| | | </view> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">计åå¼å§</text> |
| | | <text class="detail-value">{{ item.planStartTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">计åç»æ</text> |
| | | <text class="detail-value">{{ item.planEndTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
å¼å§</text> |
| | | <text class="detail-value">{{ item.actualStartTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
ç»æ</text> |
| | | <text class="detail-value">{{ item.actualEndTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå®æ¥å·¥äºº</text> |
| | | <view class="detail-value tags-box"> |
| | | <template v-if="item.userNames"> |
| | | <up-tag v-for="(name, idx) in item.userNames.split(',')" |
| | | :key="idx" |
| | | :text="name" |
| | | size="mini" |
| | | type="info" |
| | | plain |
| | | class="user-tag"></up-tag> |
| | | </template> |
| | | <text v-else>-</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" /> |
| | | </scroll-view> |
| | | <view v-else-if="!loading" |
| | | class="no-data"> |
| | | <up-empty mode="data" |
| | | text="ææ æ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const loading = ref(false); |
| | | const tableData = ref([]); |
| | | const loadStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | workOrderNo: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | |
| | | const params = { |
| | | ...searchForm.value, |
| | | ...page, |
| | | }; |
| | | |
| | | productWorkOrderPage(params) |
| | | .then(res => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | tableData.value = |
| | | page.current === 1 ? records : [...tableData.value, ...records]; |
| | | page.total = res.data.total; |
| | | |
| | | if (tableData.value.length >= page.total) { |
| | | loadStatus.value = "nomore"; |
| | | } else { |
| | | loadStatus.value = "loadmore"; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | loading.value = false; |
| | | uni.showToast({ title: "å 载失败", icon: "error" }); |
| | | }); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loadStatus.value === "nomore" || loading.value) return; |
| | | page.current++; |
| | | getList(); |
| | | }; |
| | | |
| | | const toProgressPercentage = val => { |
| | | const n = Number(val); |
| | | if (!Number.isFinite(n)) return 0; |
| | | if (n <= 0) return 0; |
| | | if (n >= 100) return 100; |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | const progressColor = percentage => { |
| | | const p = toProgressPercentage(percentage); |
| | | if (p < 30) return "#f56c6c"; |
| | | if (p < 50) return "#e6a23c"; |
| | | if (p < 80) return "#409eff"; |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/sales-common.scss"; |
| | | |
| | | .production-scheduling { |
| | | padding-bottom: 20rpx; |
| | | } |
| | | .progress-bar { |
| | | margin-top: 20rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .tags-box { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8rpx; |
| | | justify-content: flex-end; |
| | | } |
| | | |
| | | .user-tag { |
| | | margin-bottom: 4rpx; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="production-traceability"> |
| | | <PageHeader title="ç产追溯" |
| | | @back="goBack" /> |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar" |
| | | @click="openNpsNoSelector"> |
| | | <view class="search-input"> |
| | | <text v-if="!selectedNpsNo" |
| | | class="placeholder">è¯·éæ©ç产订åå·</text> |
| | | <text v-else |
| | | class="value">{{ selectedNpsNoLabel }}</text> |
| | | </view> |
| | | <view class="search-button"> |
| | | <up-icon name="arrow-down" |
| | | size="20" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å
容åºå --> |
| | | <view class="content-container" |
| | | v-if="rowData.productionOrderDto"> |
| | | <!-- åºç¡ä¿¡æ¯ --> |
| | | <view class="info-card"> |
| | | <view class="card-title">åºç¡ä¿¡æ¯</view> |
| | | <view class="base-info"> |
| | | <view class="info-row"> |
| | | <text class="label">ç产订åå·ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.npsNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">产ååç§°ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.productName || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">è§æ ¼åå·ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">è®¡åæ°éï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.quantity || 0 }} {{ rowData.productionOrderDto?.unit }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">å½åç¶æï¼</text> |
| | | <view class="value"> |
| | | <up-tag :text="getStatusText(rowData.productionOrderDto?.status)" |
| | | :type="getStatusType(rowData.productionOrderDto?.status)" |
| | | size="mini" /> |
| | | </view> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">客æ·åç§°ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.customerName || '-' }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">å¼å§æ¥æï¼</text> |
| | | <text class="value">{{ formatDate(rowData.productionOrderDto?.startTime) }}</text> |
| | | </view> |
| | | <view class="info-row"> |
| | | <text class="label">宿è¿åº¦ï¼</text> |
| | | <view class="value progress-box"> |
| | | <up-line-progress :percentage="formatProgress(rowData.productionOrderDto?.completionStatus)" |
| | | :activeColor="progressColor(formatProgress(rowData.productionOrderDto?.completionStatus))" |
| | | :showText="true" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- å·¥åä¿¡æ¯ --> |
| | | <view class="work-order-section" |
| | | v-if="rowData.productionRecords && rowData.productionRecords.length > 0"> |
| | | <view class="section-title">å·¥åä¿¡æ¯</view> |
| | | <view v-for="(item, index) in rowData.productionRecords" |
| | | :key="index" |
| | | class="work-order-card"> |
| | | <view class="card-header"> |
| | | <text class="work-order-no">{{ item.workOrder.workOrderNo }}</text> |
| | | <text class="progress-tag" |
| | | :style="{ color: progressColor(item.workOrder.completionStatus) }">{{ item.workOrder.completionStatus || 0 }}%</text> |
| | | </view> |
| | | <view class="card-content"> |
| | | <view class="content-row"> |
| | | <text class="label">产å/è§æ ¼ï¼</text> |
| | | <text class="value">{{ item.workOrder.productName }} / {{ item.workOrder.model }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">å½åå·¥åºï¼</text> |
| | | <text class="value">{{ item.workOrder.operationName || '-' }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">éæ±/宿ï¼</text> |
| | | <text class="value">{{ item.workOrder.planQuantity }} / {{ item.workOrder.completeQuantity }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">æ¥åºæ°éï¼</text> |
| | | <text class="value error-text">{{ item.workOrder.scrapQty || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer"> |
| | | <up-button type="primary" |
| | | size="small" |
| | | plain |
| | | text="æ¥å·¥è®°å½" |
| | | @click="handleShowReports(item)"></up-button> |
| | | <up-button type="success" |
| | | size="small" |
| | | plain |
| | | text="è´¨æ£ä¿¡æ¯" |
| | | @click="handleShowQuality(item)"></up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data-minor"> |
| | | <up-empty mode="data" |
| | | text="ææ å·¥åä¿¡æ¯" |
| | | icon-size="40"></up-empty> |
| | | </view> |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <up-empty mode="search" |
| | | text="è¯·éæ©ç产订åå·æ¥ç追溯信æ¯"></up-empty> |
| | | </view> |
| | | <!-- ç产订åå·éæ©å¼¹çª --> |
| | | <up-popup :show="showNpsNoSelector" |
| | | mode="bottom" |
| | | @close="showNpsNoSelector = false" |
| | | round="10"> |
| | | <view class="selector-popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©ç产订åå·</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="showNpsNoSelector = false"></up-icon> |
| | | </view> |
| | | <view class="search-box"> |
| | | <up-search placeholder="è¾å
¥å
³é®åæç´¢" |
| | | v-model="npsNoQuery" |
| | | :show-action="false" |
| | | @change="handleNpsNoSearch" |
| | | @search="handleNpsNoSearch" |
| | | :loading="npsNoLoading"></up-search> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="options-list"> |
| | | <view v-for="item in npsNoOptions" |
| | | :key="item.id" |
| | | class="option-item" |
| | | @click="onSelectNpsNo(item)"> |
| | | <view class="option-main"> |
| | | <text class="nps-no">{{ item.npsNo }}</text> |
| | | <text class="product-info">{{ item.productName }} / {{ item.model }}</text> |
| | | </view> |
| | | <up-icon v-if="selectedNpsNo === item.id" |
| | | name="checkbox-mark" |
| | | color="#3c9cff" |
| | | size="20"></up-icon> |
| | | </view> |
| | | <view v-if="npsNoOptions.length === 0" |
| | | class="no-options"> |
| | | <text>{{ npsNoLoading ? 'å è½½ä¸...' : 'ææ é项' }}</text> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- æ¥å·¥è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="reportPopupVisible" |
| | | mode="bottom" |
| | | @close="reportPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">ç产æ¥å·¥è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="reportPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view class="detail-info"> |
| | | <view class="info-row"><text class="label">å·¥åå·ï¼</text><text class="value">{{ detailData.workOrder.workOrderNo }}</text></view> |
| | | <view class="info-row"><text class="label">计å/宿ï¼</text><text class="value">{{ detailData.workOrder.planQuantity }} / {{ detailData.workOrder.completeQuantity }}</text></view> |
| | | <view class="info-row"><text class="label">å®é
æ¶é´ï¼</text><text class="value">{{ formatDate(detailData.workOrder.actualStartTime) }} è³ {{ formatDate(detailData.workOrder.actualEndTime) }}</text></view> |
| | | </view> |
| | | <view class="list-title">æ¥å·¥æç»</view> |
| | | <view v-for="(report, idx) in detailData.reports" |
| | | :key="idx" |
| | | class="detail-item"> |
| | | <view class="item-main"> |
| | | <view class="item-row"><text class="label">æ¥å·¥åå·ï¼</text><text class="value">{{ report.productNo }}</text></view> |
| | | <view class="item-row"><text class="label">äº§åºæ°éï¼</text><text class="value">{{ report.quantity || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">æ¥åºæ°éï¼</text><text class="value error-text">{{ report.scrapQty || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">å·¥æ¶(h)ï¼</text><text class="value">{{ report.workHour || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">å建人ï¼</text><text class="value">{{ report.userName }}</text></view> |
| | | <view class="item-row"><text class="label">å建æ¶é´ï¼</text><text class="value">{{ formatDate(report.createTime, '{y}-{m}-{d} {h}:{i}') }}</text></view> |
| | | </view> |
| | | <view class="item-actions"> |
| | | <text class="action-link" |
| | | @click="showParams(report.productionOperationParamList)">åæ°è¯¦æ
</text> |
| | | <text class="action-link green" |
| | | @click="handleShowInput(report.id)">æå
¥è¯¦æ
</text> |
| | | </view> |
| | | </view> |
| | | <view v-if="!detailData.reports || detailData.reports.length === 0" |
| | | class="no-data-minor">ææ æ¥å·¥æç»</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- æå
¥è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="inputPopupVisible" |
| | | mode="bottom" |
| | | @close="inputPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">æå
¥ä¿¡æ¯è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="inputPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view class="input-list-popup"> |
| | | <view v-for="(item, idx) in inputListData" |
| | | :key="idx" |
| | | class="quality-record"> |
| | | <view class="record-title">æå
¥è®°å½ {{ idx + 1 }}</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"><text class="label">æ¥å·¥åå·</text><text class="value">{{ item.productNo || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">æå
¥æ°é</text><text class="value">{{ item.quantity || 0 }} {{ item.unit || '' }}</text></view> |
| | | <view class="info-item full-width"><text class="label">æå
¥äº§ååç§°</text><text class="value">{{ item.productName || '-' }}</text></view> |
| | | <view class="info-item full-width"><text class="label">æå
¥äº§ååå·</text><text class="value">{{ item.model || '-' }}</text></view> |
| | | </view> |
| | | </view> |
| | | <view v-if="!inputListData || inputListData.length === 0" |
| | | class="no-data-minor">{{ inputLoading ? 'å è½½ä¸...' : 'ææ æå
¥è®°å½' }}</view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- è´¨æ£è¯¦æ
å¼¹çª --> |
| | | <up-popup :show="qualityPopupVisible" |
| | | mode="bottom" |
| | | @close="qualityPopupVisible = false" |
| | | round="10"> |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">è´¨æ£è¯¦æ
</text> |
| | | <up-icon name="close" |
| | | size="20" |
| | | @click="qualityPopupVisible = false"></up-icon> |
| | | </view> |
| | | <scroll-view scroll-y |
| | | class="popup-scroll"> |
| | | <view v-for="(record, idx) in qualityRecords" |
| | | :key="idx" |
| | | class="quality-record"> |
| | | <view class="record-title">æ£æµè®°å½ {{ idx + 1 }}</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"><text class="label">æ£æµæ¥æ</text><text class="value">{{ formatDate(record.createTime) }}</text></view> |
| | | <view class="info-item"><text class="label">æ£æµç»æ</text><up-tag style="width:100rpx" |
| | | :text="record.checkResult || 'å¾
æ£æµ'" |
| | | :type="record.checkResult === 'åæ ¼' ? 'success' : 'error'" |
| | | size="mini" /></view> |
| | | <view class="info-item"><text class="label">æ£éªå</text><text class="value">{{ record.userName }}</text></view> |
| | | <view class="info-item"><text class="label">æ°é</text><text class="value">{{ record.quantity }} {{ record.unit }}</text></view> |
| | | <view class="info-item"><text class="label">æ¥å·¥åå·</text><text class="value">{{ record.reportNo || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">产ååç§°</text><text class="value">{{ record.productName || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">è§æ ¼åå·</text><text class="value">{{ record.model || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">æ£æµåä½</text><text class="value">{{ record.checkCompany || '-' }}</text></view> |
| | | </view> |
| | | <view class="params-table"> |
| | | <view class="table-header"> |
| | | <text class="col">ææ </text> |
| | | <text class="col">åä½</text> |
| | | <text class="col">æ åå¼</text> |
| | | <text class="col">å
æ§å¼</text> |
| | | <text class="col">å®é
å¼</text> |
| | | </view> |
| | | <view v-for="(param, pIdx) in record.inspectParamList" |
| | | :key="pIdx" |
| | | class="table-row"> |
| | | <text class="col">{{ param.parameterItem }}</text> |
| | | <text class="col">{{ param.unit || '-' }}</text> |
| | | <text class="col">{{ param.standardValue }}</text> |
| | | <text class="col">{{ param.controlValue || '-' }}</text> |
| | | <text class="col" |
| | | :class="{ 'error-text': param.testValue != param.standardValue }">{{ param.testValue }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-if="!qualityRecords || qualityRecords.length === 0" |
| | | class="no-data-minor">ææ è´¨æ£è®°å½</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | <!-- åæ°è¯¦æ
å¼¹çª --> |
| | | <up-modal :show="paramModalVisible" |
| | | title="åæ°è¯¦æ
" |
| | | @confirm="paramModalVisible = false"> |
| | | <view class="modal-content"> |
| | | <view v-for="(param, idx) in currentParams" |
| | | :key="idx" |
| | | class="param-row"> |
| | | <text class="label">{{ param.paramName }}ï¼</text> |
| | | <text class="value">{{ param.inputValue }} {{ param.unit && param.unit !== '/' ? param.unit : '' }}</text> |
| | | </view> |
| | | <view v-if="!currentParams || currentParams.length === 0" |
| | | class="no-data-minor">ææ åæ°æ°æ®</view> |
| | | </view> |
| | | </up-modal> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import { |
| | | getOrderDetail, |
| | | productOrderListPage, |
| | | } from "@/api/productionManagement/productionOrder"; |
| | | import { productionProductInputListPage } from "@/api/productionManagement/productionProductMain"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | // éæ©å¨ç¸å
³ |
| | | const showNpsNoSelector = ref(false); |
| | | const npsNoQuery = ref(""); |
| | | const npsNoOptions = ref([]); |
| | | const npsNoLoading = ref(false); |
| | | const selectedNpsNo = ref(null); |
| | | const selectedNpsNoLabel = ref(""); |
| | | |
| | | const rowData = reactive({ |
| | | productionOrderDto: null, |
| | | productionRecords: [], |
| | | }); |
| | | |
| | | // æ¥å·¥è¯¦æ
|
| | | const reportPopupVisible = ref(false); |
| | | const detailData = ref({ workOrder: {}, reports: [] }); |
| | | |
| | | // æå
¥è¯¦æ
|
| | | const inputPopupVisible = ref(false); |
| | | const inputListData = ref([]); |
| | | const inputLoading = ref(false); |
| | | |
| | | // è´¨æ£è¯¦æ
|
| | | const qualityPopupVisible = ref(false); |
| | | const qualityRecords = ref([]); |
| | | |
| | | // åæ°è¯¦æ
|
| | | const paramModalVisible = ref(false); |
| | | const currentParams = ref([]); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const openNpsNoSelector = () => { |
| | | showNpsNoSelector.value = true; |
| | | if (npsNoOptions.value.length === 0) { |
| | | handleNpsNoSearch(); |
| | | } |
| | | }; |
| | | |
| | | const handleNpsNoSearch = async () => { |
| | | npsNoLoading.value = true; |
| | | try { |
| | | const res = await productOrderListPage({ |
| | | npsNo: npsNoQuery.value || "", |
| | | pageNum: 1, |
| | | pageSize: 50, |
| | | }); |
| | | npsNoOptions.value = res.data?.records || res.rows || []; |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | npsNoLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const onSelectNpsNo = async item => { |
| | | selectedNpsNo.value = item.id; |
| | | selectedNpsNoLabel.value = item.npsNo; |
| | | showNpsNoSelector.value = false; |
| | | |
| | | uni.showLoading({ title: "å è½½ä¸..." }); |
| | | try { |
| | | const res = await getOrderDetail(item.npsNo); |
| | | if (res.code === 200 && res.data) { |
| | | const { productionOrder, workOrderList } = res.data; |
| | | rowData.productionOrderDto = productionOrder || item; |
| | | rowData.productionRecords = workOrderList || []; |
| | | } else { |
| | | rowData.productionOrderDto = item; |
| | | rowData.productionRecords = []; |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | rowData.productionOrderDto = item; |
| | | rowData.productionRecords = []; |
| | | uni.showToast({ title: "è·å详æ
失败", icon: "none" }); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | }; |
| | | |
| | | onLoad(async options => { |
| | | if (options.npsNo) { |
| | | uni.showLoading({ title: "å è½½ä¸..." }); |
| | | try { |
| | | const res = await productOrderListPage({ |
| | | npsNo: options.npsNo, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const records = res.data?.records || res.rows || []; |
| | | if (records.length > 0) { |
| | | onSelectNpsNo(records[0]); |
| | | } else { |
| | | uni.showToast({ title: "æªæ¾å°ç¸å
³è®¢å", icon: "none" }); |
| | | } |
| | | } catch (error) { |
| | | console.error(error); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const getStatusText = status => { |
| | | const statusMap = { 1: "å¾
å¼å§", 2: "è¿è¡ä¸", 3: "已宿", 5: "å·²ç»æ" }; |
| | | return statusMap[status] || "已忶"; |
| | | }; |
| | | |
| | | const getStatusType = status => { |
| | | const typeMap = { 1: "primary", 2: "warning", 3: "success", 5: "error" }; |
| | | return typeMap[status] || "info"; |
| | | }; |
| | | |
| | | const formatDate = (date, pattern = "{y}-{m}-{d}") => { |
| | | return parseTime(date, pattern) || "-"; |
| | | }; |
| | | |
| | | const formatProgress = val => { |
| | | const p = parseFloat(val || 0); |
| | | return p >= 100 ? 100 : p; |
| | | }; |
| | | |
| | | const progressColor = percentage => { |
| | | if (percentage < 30) return "#f56c6c"; |
| | | if (percentage < 70) return "#e6a23c"; |
| | | return "#67c23a"; |
| | | }; |
| | | |
| | | const handleShowReports = row => { |
| | | detailData.value = { |
| | | workOrder: row.workOrder || {}, |
| | | reports: (row.reportList || []).map(r => ({ |
| | | ...r.reportMain, |
| | | ...(r.reportOutputList ? r.reportOutputList[0] : {}), |
| | | id: r.reportMain.id, |
| | | productionOperationParamList: r.reportParamList || [], |
| | | })), |
| | | }; |
| | | reportPopupVisible.value = true; |
| | | }; |
| | | |
| | | const handleShowInput = async reportId => { |
| | | inputPopupVisible.value = true; |
| | | inputLoading.value = true; |
| | | inputListData.value = []; |
| | | try { |
| | | const res = await productionProductInputListPage({ |
| | | productMainId: reportId, |
| | | pageNum: 1, |
| | | pageSize: 100, |
| | | }); |
| | | inputListData.value = res.data?.records || res.rows || []; |
| | | } catch (error) { |
| | | console.error(error); |
| | | uni.showToast({ title: "è·åæå
¥ä¿¡æ¯å¤±è´¥", icon: "none" }); |
| | | } finally { |
| | | inputLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const handleShowQuality = row => { |
| | | const inspects = row.inspectList || []; |
| | | qualityRecords.value = inspects.map(i => ({ |
| | | ...i.inspect, |
| | | reportNo: i.reportNo, |
| | | productName: row.workOrder?.productName || "-", |
| | | model: row.workOrder?.model || "-", |
| | | userName: i.reportMain?.userName || "-", |
| | | inspectParamList: i.inspectParamList || [], |
| | | })); |
| | | qualityPopupVisible.value = true; |
| | | }; |
| | | |
| | | const showParams = params => { |
| | | currentParams.value = params || []; |
| | | paramModalVisible.value = true; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/styles/procurement-common.scss"; |
| | | |
| | | .production-traceability { |
| | | min-height: 100vh; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .search-section { |
| | | background-color: #fff; |
| | | padding: 20rpx 24rpx; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | background-color: #f2f2f2; |
| | | border-radius: 8rpx; |
| | | padding: 0 20rpx; |
| | | height: 80rpx; |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | .placeholder { |
| | | font-size: 28rpx; |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | |
| | | .search-button { |
| | | padding: 0 10rpx; |
| | | } |
| | | } |
| | | |
| | | .selector-popup { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 70vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 24rpx; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .search-box { |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .options-list { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .option-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 24rpx 0; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .option-main { |
| | | flex: 1; |
| | | .nps-no { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | .product-info { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-options { |
| | | text-align: center; |
| | | padding: 40rpx; |
| | | color: #999; |
| | | font-size: 26rpx; |
| | | } |
| | | } |
| | | |
| | | .content-container { |
| | | padding: 0 24rpx 40rpx; |
| | | } |
| | | |
| | | .info-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 24rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-bottom: 24rpx; |
| | | padding-left: 16rpx; |
| | | border-left: 8rpx solid #3c9cff; |
| | | } |
| | | } |
| | | |
| | | .info-grid { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | |
| | | .info-item { |
| | | width: 50%; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .progress-container { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 20rpx; |
| | | |
| | | up-line-progress { |
| | | flex: 1; |
| | | } |
| | | |
| | | .progress-text { |
| | | font-size: 24rpx; |
| | | color: #666; |
| | | min-width: 60rpx; |
| | | } |
| | | } |
| | | |
| | | .section-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin: 32rpx 0 20rpx; |
| | | } |
| | | |
| | | .work-order-card { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | padding-bottom: 16rpx; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .work-order-no { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | color: #3c9cff; |
| | | } |
| | | |
| | | .progress-tag { |
| | | font-size: 28rpx; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .card-content { |
| | | .content-row { |
| | | margin-bottom: 12rpx; |
| | | font-size: 26rpx; |
| | | |
| | | .label { |
| | | color: #999; |
| | | } |
| | | |
| | | .value { |
| | | color: #333; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .card-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | margin-top: 20rpx; |
| | | } |
| | | } |
| | | |
| | | .base-info { |
| | | background: #fff; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .info-row { |
| | | margin-bottom: 16rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 180rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | |
| | | &.progress-box { |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .info-grid { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | padding: 10rpx 0; |
| | | |
| | | .info-item { |
| | | width: 50%; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popup-content { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | | max-height: 80vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | border-radius: 20rpx 20rpx 0 0; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .popup-title { |
| | | font-size: 34rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .popup-scroll { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | } |
| | | } |
| | | |
| | | .detail-info { |
| | | background: #f8f9fa; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 30rpx; |
| | | flex-direction: column; |
| | | |
| | | .info-row { |
| | | margin-bottom: 12rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 140rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .list-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | margin-bottom: 20rpx; |
| | | color: #333; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | width: 6rpx; |
| | | height: 28rpx; |
| | | background: #3c9cff; |
| | | margin-right: 12rpx; |
| | | border-radius: 4rpx; |
| | | } |
| | | } |
| | | |
| | | .detail-item { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .item-main { |
| | | flex: 1; |
| | | .item-row { |
| | | font-size: 26rpx; |
| | | margin-bottom: 8rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 130rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .item-actions { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 16rpx; |
| | | padding-left: 20rpx; |
| | | border-left: 1rpx solid #f0f0f0; |
| | | |
| | | .action-link { |
| | | font-size: 26rpx; |
| | | color: #3c9cff; |
| | | white-space: nowrap; |
| | | |
| | | &.green { |
| | | color: #52c41a; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .quality-record { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 16rpx; |
| | | padding: 24rpx; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .record-title { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #3c9cff; |
| | | margin-bottom: 24rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | } |
| | | |
| | | .params-table { |
| | | margin-top: 24rpx; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | overflow: hidden; |
| | | |
| | | .table-header { |
| | | display: flex; |
| | | background: #f8f9fa; |
| | | padding: 20rpx 16rpx; |
| | | font-size: 26rpx; |
| | | font-weight: bold; |
| | | color: #666; |
| | | } |
| | | |
| | | .table-row { |
| | | display: flex; |
| | | padding: 20rpx 16rpx; |
| | | font-size: 26rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | color: #333; |
| | | |
| | | &:nth-child(even) { |
| | | background: #fafafa; |
| | | } |
| | | } |
| | | |
| | | .col { |
| | | flex: 1; |
| | | text-align: center; |
| | | word-break: break-all; |
| | | } |
| | | } |
| | | |
| | | .modal-content { |
| | | padding: 30rpx; |
| | | .param-row { |
| | | margin-bottom: 20rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #666; |
| | | min-width: 160rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | font-weight: 500; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .input-list-popup { |
| | | .input-item { |
| | | background: #fff; |
| | | border: 1rpx solid #f0f0f0; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 20rpx; |
| | | |
| | | .input-row { |
| | | display: flex; |
| | | font-size: 26rpx; |
| | | margin-bottom: 8rpx; |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | .label { |
| | | color: #999; |
| | | min-width: 160rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .error-text { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .no-data-minor { |
| | | text-align: center; |
| | | padding: 60rpx 40rpx; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | </style> |
| src/pages/qualityManagement/finalInspection/add.vue
src/pages/qualityManagement/finalInspection/detail.vue
src/pages/qualityManagement/finalInspection/index.vue
src/pages/qualityManagement/materialInspection/add.vue
src/pages/qualityManagement/materialInspection/detail.vue
src/pages/qualityManagement/materialInspection/index.vue
src/pages/qualityManagement/processInspection/add.vue
src/pages/qualityManagement/processInspection/detail.vue
src/pages/qualityManagement/processInspection/index.vue
src/pages/sales/salesAccount/goOut.vue
src/pages/sales/salesQuotation/detail.vue
src/pages/sales/salesQuotation/edit.vue
src/pages/sales/salesQuotation/index.vue
src/pages/works.vue
src/static/images/icon/baogongtaizhang.svg
src/static/images/icon/bom.svg
src/static/images/icon/gongxuguanli.svg
src/static/images/icon/gongyiluxian.svg
src/static/images/icon/guihuandengji.svg
src/static/images/icon/jichucanshu.svg
src/static/images/icon/jieyuedengji.svg
src/static/images/icon/kucunguanli.svg
src/static/images/icon/shengchandingdan.svg
src/static/images/icon/shengchanhesuan.svg
src/static/images/icon/shengchanjihua.svg
src/static/images/icon/shengchanpaichan.svg
src/static/images/icon/shengchanshikuang.svg
src/static/images/icon/shengchanzhuisu.svg
src/utils/versionUpgrade.js |