| | |
| | |
|
| | | # ä¸å°ä¼ä¸æ°åå转åäºçº§å¥é¤å
/å¼åç¯å¢
|
| | | VITE_APP_BASE_API = '/dev-api'
|
| | |
|
| | | VITE_BASE_API = 'http://localhost:9003'
|
| | |
| | | yarn-error.log* |
| | | **/*.log |
| | | |
| | | .claude/ |
| | | |
| | | tests/**/coverage/ |
| | | tests/e2e/reports |
| | | selenium-debug.log |
| | |
| | | *.njsproj |
| | | *.sln |
| | | *.local |
| | | |
| | | # Claude Code |
| | | .claude/ |
| | | |
| | | package-lock.json |
| | | yarn.lock |
| | |
| | | "element-plus": "2.7.7", |
| | | "file-saver": "2.0.5", |
| | | "fuse.js": "6.6.2", |
| | | "html2canvas": "^1.4.1", |
| | | "js-beautify": "1.14.11", |
| | | "js-cookie": "3.0.5", |
| | | "jsencrypt": "3.3.2", |
| | |
| | | }) |
| | | } |
| | | |
| | | // æµå
¥å
¬æµ· |
| | | // åé客æ·å°é宿¡£æ¡ |
| | | export function backCustomer(id) { |
| | | return request({ |
| | | url: '/basic/customer/back/' + id, |
| | |
| | | import request from "@/utils/request.js"; |
| | | |
| | | /** å®¡æ¹æ¨¡æ¿ç±»åçéç¨æä¸¾ï¼TypeEnumsï¼ */ |
| | | export function getTypeEnums() { |
| | | return request({ |
| | | url: '/basic/enum/TypeEnums', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | export function findAllStockRecordTypeOptions() { |
| | | return request({ |
| | | url: '/basic/enum/stockRecordType', |
| | |
| | | import request from "@/utils/request"; |
| | | import { getToken } from '@/utils/auth'; |
| | | |
| | | /** |
| | | * ç¥è¯åºç®¡çæ¥å£ |
| | | * ä¼ åè§è: |
| | | * - GET请æ±: ä½¿ç¨ params |
| | | * - POST请æ±: ä½¿ç¨ data |
| | | * - DELETE请æ±: ä½¿ç¨ data |
| | | * - æµå¼è¯·æ±: ä½¿ç¨ Fetch API |
| | | */ |
| | | |
| | | // ==================== ç¥è¯åºCRUDæ¥å£ ==================== |
| | | |
| | | /** |
| | | * æ¥è¯¢ç¥è¯åºå表 |
| | | * @param {Object} query - æ¥è¯¢åæ° |
| | | * @param {number} query.current - å½å页ç (å¿
å¡«) |
| | | * @param {number} query.size - æ¯é¡µæ¡æ°(å¿
å¡«) |
| | | * @param {string} [query.title] - ç¥è¯æ é¢(å¯é,æ¨¡ç³æç´¢) |
| | | * @param {string} [query.type] - ç¥è¯ç±»å(å¯é,精确å¹é
) |
| | | * @returns {Promise} |
| | | */ |
| | | // æ¥è¯¢ç¥è¯åºå表 |
| | | export function listKnowledgeBase(query) { |
| | | return request({ |
| | | url: "/knowledgeBase/getList", |
| | | method: "get", |
| | | params: query, // GET请æ±ä½¿ç¨params |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ°å¢ç¥è¯åº |
| | | * @param {Object} data - ç¥è¯åºæ°æ® |
| | | * @param {string} data.title - ç¥è¯æ é¢(å¿
å¡«) |
| | | * @param {string} data.type - ç¥è¯ç±»å(å¿
å¡«) |
| | | * @param {string} [data.scenario] - éç¨åºæ¯(å¯é) |
| | | * @param {string} [data.efficiency] - è§£å³æç(å¯é) |
| | | * @param {string} data.problem - é®é¢æè¿°(å¿
å¡«) |
| | | * @param {string} data.solution - è§£å³æ¹æ¡(å¿
å¡«) |
| | | * @param {string} [data.keyPoints] - å
³é®è¦ç¹(å¯é) |
| | | * @param {string} [data.creator] - å建人(å¯é) |
| | | * @param {number} [data.usageCount=0] - ä½¿ç¨æ¬¡æ°(å¯é) |
| | | * @returns {Promise} |
| | | */ |
| | | // æ¥è¯¢ç¥è¯åºè¯¦ç» |
| | | // export function getKnowledgeBase(knowledgeBaseId) { |
| | | // return request({ |
| | | // url: "/collaborativeApproval/knowledgeBase/" + knowledgeBaseId, |
| | | // method: "get", |
| | | // }); |
| | | // } |
| | | |
| | | // æ°å¢ç¥è¯åº |
| | | export function addKnowledgeBase(data) { |
| | | return request({ |
| | | url: "/knowledgeBase/add", |
| | | method: "post", |
| | | data: data, // POST请æ±ä½¿ç¨data |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * ä¿®æ¹ç¥è¯åº |
| | | * @param {Object} data - ç¥è¯åºæ°æ® |
| | | * @param {number} data.id - ç¥è¯åºID(å¿
å¡«) |
| | | * @param {string} data.title - ç¥è¯æ é¢(å¿
å¡«) |
| | | * @param {string} data.type - ç¥è¯ç±»å(å¿
å¡«) |
| | | * @returns {Promise} |
| | | */ |
| | | // ä¿®æ¹ç¥è¯åº |
| | | export function updateKnowledgeBase(data) { |
| | | return request({ |
| | | url: "/knowledgeBase/update", |
| | | method: "post", |
| | | data: data, // POST请æ±ä½¿ç¨data |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * å é¤ç¥è¯åº(æ¯ææ¹éå é¤) |
| | | * @param {number[]} ids - ç¥è¯åºIDæ°ç» |
| | | * @returns {Promise} |
| | | */ |
| | | export function delKnowledgeBase(ids) { |
| | | // å é¤ç¥è¯åº |
| | | export function delKnowledgeBase(query) { |
| | | return request({ |
| | | url: "/knowledgeBase/delete", |
| | | method: "delete", |
| | | data: ids, // DELETE请æ±ä½¿ç¨dataä¼ éæ°ç» |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ==================== æä»¶ç®¡çæ¥å£ ==================== |
| | | |
| | | /** |
| | | * æ¥è¯¢ç¥è¯åºæä»¶åéåç¶æ |
| | | * @param {number} knowledgeBaseId - ç¥è¯åºID |
| | | * @returns {Promise} è¿åæä»¶å表ååéåç¶æ |
| | | */ |
| | | export function getVectorStatus(knowledgeBaseId) { |
| | | // æ¹éå é¤ç¥è¯åº |
| | | export function delKnowledgeBaseBatch(knowledgeBaseIds) { |
| | | return request({ |
| | | url: `/knowledgeBase/vector/status/${knowledgeBaseId}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * ä¿åç¥è¯åºæä»¶å
³è(触ååéå) |
| | | * @param {Object} data - æä»¶å
³èæ°æ® |
| | | * @param {number} data.knowledgeBaseId - ç¥è¯åºID(å¿
å¡«) |
| | | * @param {number[]} data.storageBlobIds - æä»¶blob IDæ°ç»(å¿
å¡«) |
| | | * @returns {Promise} |
| | | */ |
| | | export function saveKnowledgeBaseFiles(data) { |
| | | return request({ |
| | | url: "/knowledgeBase/file/save", |
| | | method: "post", |
| | | data: data, // POST请æ±ä½¿ç¨data |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * å é¤ç¥è¯åºæä»¶ |
| | | * @param {number[]} ids - åéè®°å½IDæ°ç» |
| | | * @returns {Promise} |
| | | */ |
| | | export function deleteKnowledgeBaseFile(ids) { |
| | | return request({ |
| | | url: "/knowledgeBase/file/delete", |
| | | url: "/knowledgeBase/batch", |
| | | method: "delete", |
| | | data: ids, // DELETE请æ±ä½¿ç¨dataä¼ éæ°ç» |
| | | data: knowledgeBaseIds, |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * éæ°åéåæä»¶ |
| | | * @param {number} vectorId - åéè®°å½ID |
| | | * @returns {Promise} |
| | | */ |
| | | export function reprocessVector(vectorId) { |
| | | return request({ |
| | | url: `/knowledgeBase/vector/reprocess/${vectorId}`, |
| | | method: "post", |
| | | }); |
| | | } |
| | | |
| | | // ==================== ç¥è¯é®çæ¥å£ ==================== |
| | | |
| | | /** |
| | | * ç¥è¯åºé®ç(æµå¼) |
| | | * å端æ¥å£: POST /ai/knowledge/chat |
| | | * ååºç±»å: text/stream;charset=utf-8 (Spring Flux<String>) |
| | | * |
| | | * @param {Object} data - é®çåæ° |
| | | * @param {number} data.knowledgeBaseId - ç¥è¯åºID(å¿
å¡«) |
| | | * @param {string} data.memoryId - ä¼è¯ID(å¿
å¡«,ç¨äºä¿æä¸ä¸æ) |
| | | * @param {string} data.question - ç¨æ·é®é¢(å¿
å¡«) |
| | | * @returns {Promise<Response>} è¿åFetch Response对象 |
| | | */ |
| | | export function knowledgeChat(data) { |
| | | const token = getToken(); |
| | | return fetch(import.meta.env.VITE_APP_BASE_API + '/ai/knowledge/chat', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | 'Authorization': 'Bearer ' + token |
| | | }, |
| | | body: JSON.stringify(data) |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢ç¥è¯åºé®çåå² |
| | | * @param {string} memoryId - ä¼è¯ID |
| | | * @returns {Promise} |
| | | */ |
| | | export function getKnowledgeHistory(memoryId) { |
| | | return request({ |
| | | url: `/ai/knowledge/history/${memoryId}`, |
| | | method: "get", |
| | | }); |
| | | } |
| | |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // å®åå详æ
|
| | | export function getAfterSalesServiceById(id) { |
| | | return request({ |
| | | url: '/afterSalesService/getById', |
| | | method: 'get', |
| | | params: { id } |
| | | }) |
| | | } |
| | |
| | | url: `/accountInvoiceApplication/deleteAccountInvoiceApplication?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | | |
| | | /** æ ¹æ®å®¢æ·idæ¥è¯¢å¼ç¥¨ç³è¯·å°è´¦ */ |
| | | export function getAccountInvoiceApplicationList(query) { |
| | | return request({ |
| | | url: '/accountInvoiceApplication/getAccountInvoiceApplicationList', |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | } |
| | |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // æ¹éå审åºåºè®°å½ |
| | | export const batchUnapproveStockOutRecords = (data) => { |
| | | return request({ |
| | | url: "/stockOutRecord/reAudit", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | export function staffSalaryMainCalculateByEmployeeId(data) { |
| | | return request({ |
| | | url: "/staffSalaryMain/calculateByEmployeeId", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | export function staffSalaryMainAdd(data) { |
| | | return request({ |
| | | url: "/staffSalaryMain/add", |
| | |
| | | }); |
| | | } |
| | | |
| | | // è·åå¯ç¨é¨é¨åè¡¨ï¼æ ¹æ®æä»½æ¥è¯¢ï¼æé¤å·²åæ¾å·¥èµè¡¨çé¨é¨ï¼ |
| | | export function staffSalaryMainAvailableDepts(salaryMonth) { |
| | | return request({ |
| | | url: "/staffSalaryMain/availableDepts", |
| | | method: "get", |
| | | params: { salaryMonth }, |
| | | }); |
| | | } |
| | |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** 仿¬¾å°è´¦ - 仿¬¾ç»è®°å表 */ |
| | | export function registrationList(params) { |
| | | return request({ |
| | | url: "/purchase/report/registration", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | |
| | | params: query, |
| | | }); |
| | | } |
| | | // éè´éè´§å¯ééè´å°è´¦ |
| | | export function purchaseReturnableList(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/returnableList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ¥è¯¢ååå· |
| | | export function getSalesNo(query) { |
| | | return request({ |
| | |
| | | }); |
| | | } |
| | | |
| | | export function createPurchaseNo(entryDate) { |
| | | export function createPurchaseNo() { |
| | | return request({ |
| | | url: "/purchase/ledger/createPurchaseNo", |
| | | method: "get", |
| | | params: { entryDate }, |
| | | }); |
| | | } |
| | | export function updateApprovalStatus(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/updateApprovalStatus", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | return request({ |
| | | url: "/purchase/ledger/updateApprovalStatus", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ä¿åéè´æ¨¡æ¿ |
| | | export function addPurchaseTemplate(data) { |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹éè´æ¨¡æ¿ |
| | | export function updatePurchaseTemplate(data) { |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢éè´æ¨¡æ¿ |
| | | export function getPurchaseTemplateList(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseTemplateList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseTemplateList", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å é¤éè´æ¨¡æ¿ |
| | | export function delPurchaseTemplate(id) { |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/delete", |
| | | method: "delete", |
| | | data: id, |
| | | }); |
| | | return request({ |
| | | url: "/purchaseLedgerTemplate/delete", |
| | | method: "delete", |
| | | data: id, |
| | | }); |
| | | } |
| | | // æ¥è¯¢éè´è¯¦æ
|
| | | export function getPurchaseByCode(id) { |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseByCode", |
| | | method: "get", |
| | | params: id, |
| | | }); |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseByCode", |
| | | method: "get", |
| | | params: id, |
| | | }); |
| | | } |
| | | |
| | | export function batchGeneratePurchaseInboundSteps(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/batchInsertPurchaseSteps", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // è·åéè´åæå°æ°æ® |
| | | export function getPrintData(id) { |
| | | return request({ |
| | | url: '/purchase/ledger/printData/' + id, |
| | | method: 'get', |
| | | }); |
| | | } |
| | |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // è·åéå®åæå°æ°æ® |
| | | export function getPrintData(id) { |
| | | return request({ |
| | | url: '/sales/ledger/printData/' + id, |
| | | method: 'get', |
| | | }); |
| | | } |
| | |
| | | |
| | | export const productionOrderProgress = (params = {}) => { |
| | | const safePageNum = Math.max(1, Number(params.pageNum || 1)); |
| | | const safeTab = ["all", "inProgress", "completed", "end"].includes(params.tab) |
| | | const safePageSize = Math.min(50, Math.max(1, Number(params.pageSize || 10))); |
| | | const safeTab = ["all", "inProgress", "completed", "paused"].includes(params.tab) |
| | | ? params.tab |
| | | : "all"; |
| | | return request({ |
| | |
| | | ...params, |
| | | tab: safeTab, |
| | | pageNum: safePageNum, |
| | | pageSize: params.pageSize, |
| | | pageSize: safePageSize, |
| | | }, |
| | | headers: { |
| | | handleAuthError: false, |
| | |
| | | & .nest-menu .el-sub-menu > .el-sub-menu__title, |
| | | & .el-sub-menu .el-menu-item { |
| | | min-width: 0 !important; |
| | | width: calc(100% - 32px) !important; |
| | | margin: 0 16px 6px !important; |
| | | height: 40px; |
| | | line-height: 40px; |
| | | padding-left: 12px !important; |
| | | padding-right: 12px !important; |
| | | border-radius: 8px; |
| | | width: calc(100% - 24px) !important; |
| | | margin: 0 12px 8px !important; |
| | | height: 46px; |
| | | line-height: 46px; |
| | | padding-left: 14px !important; |
| | | padding-right: 14px !important; |
| | | border-radius: 12px; |
| | | transition: all 0.24s ease; |
| | | color: var(--sidebar-text); |
| | | border: none !important; |
| | | background: transparent; |
| | | font-size: 13px; |
| | | border: 1px solid rgba(255, 255, 255, 0.06) !important; |
| | | background: linear-gradient(128deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.01)); |
| | | |
| | | &:hover { |
| | | background: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.12) !important; |
| | | transform: translateX(4px); |
| | | background: linear-gradient(128deg, rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.24), rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.07)) !important; |
| | | border-color: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.3) !important; |
| | | transform: translateX(2px); |
| | | } |
| | | |
| | | &.is-active { |
| | | background: rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.85) !important; |
| | | background: var(--menu-active-bg, linear-gradient(135deg, var(--el-color-primary), var(--el-color-primary-light-3))) !important; |
| | | background-size: 180% 180%; |
| | | color: #fff !important; |
| | | font-weight: 500; |
| | | box-shadow: 0 4px 12px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.25); |
| | | box-shadow: var(--menu-active-glow, 0 10px 24px rgba(var(--el-color-primary-rgb, 37, 99, 235), 0.34)); |
| | | animation: sidebarActiveFlow 4.6s ease infinite; |
| | | } |
| | | } |
| | | |
| | | // åèå容卿 ·å¼ - å¢å 缩è¿åè§è§å±æ¬¡ |
| | | & .el-sub-menu .el-menu { |
| | | padding: 4px 0 8px; |
| | | margin-left: 8px; |
| | | border-left: 2px solid rgba(255, 255, 255, 0.08); |
| | | } |
| | | } |
| | | |
| | |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ è´¢å¡ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥çç»è¥é©¾é©¶è±', |
| | | 'æ¥çæ¬æç»è¥é©¾é©¶è±', |
| | | 'çææ¬å¨ç»è¥å¨æ¥ï¼å©æ¶¦ä¸ç°éæµï¼', |
| | | 'åææ¬æå©æ¶¦ä¸éåå ', |
| | | 'æ¥è¯¢è¿30å¤©äºæè®¢å', |
| | | 'è¿30天åªä¸ªå®¢æ·å©æ¶¦è´¡ç®æé«', |
| | | 'æ¥çæ¬æç»è¥é©¾é©¶è±', |
| | | 'æ¥è¯¢è¿30å¤©äºæè®¢å', |
| | | 'åæè¿30天åºåèµéå ç¨', |
| | | '颿µæªæ¥3个æç°éæµ', |
| | | 'æ¥è¯¢åºæ¶å款é£é©', |
| | | 'åªä¸ªå·¥åºææ¬æé«' |
| | | ] |
| | | } |
| | |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ ç产ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥è¯¢ç产ç°åºæ°æ®', |
| | | 'æ¥è¯¢çäº§è®¡åæ§è¡æ
åµ', |
| | | 'æ¥è¯¢å·¥åæ§è¡æ
åµ', |
| | | 'æ¥è¯¢è®¾å¤è¿è¡æ
åµ', |
| | | 'æ¥è¯¢è´¨éå¼å¸¸æ°æ®', |
| | | 'æ¥è¯¢ç©æç¸å
³æ°æ®', |
| | | 'æ¥çå¶é é¢è¦çæ¿', |
| | | 'åææ¬æå¶é ç»è¥æ
åµ', |
| | | 'åæè¿7天ç产å¼å¸¸', |
| | | 'çæå¶é åç建议' |
| | | 'æ¥è¯¢æ¬æç产计å', |
| | | 'æ¥çæè¿10æ¡å·¥å', |
| | | 'æ¥è®¾å¤A-01çç»´ä¿®æ
åµ', |
| | | 'æ¥è´¨éä¸åæ ¼è®°å½', |
| | | 'æ¥ä½åºåç©æ', |
| | | 'æ¥è¿7天å¼å¸¸å¤ç', |
| | | 'çæå¶é é¢è¦çæ¿', |
| | | 'åææ¬æçäº§å®æçåå¼å¸¸ç', |
| | | 'ç»åºå·¥å龿å设å¤å¾
ä¿®çåç建议' |
| | | ] |
| | | } |
| | |
| | | fileAnalyzeUrl: '/purchase-ai/analyze-files', |
| | | emptySessionText: 'ææ éè´ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥è¯¢éè´å°è´¦å表', |
| | | 'æ¥è¯¢æè¿10æ¡éè´å°è´¦', |
| | | 'ç»è®¡æ¬æéè´æ°æ®', |
| | | 'æ¥è¯¢æ¬æéè´ç©æé颿è¡', |
| | | 'æ¥è¯¢æªå
¥åºéè´è®¢å', |
| | | 'æ¥è¯¢éè´å°è´§å¼å¸¸', |
| | | 'æ¥è¯¢å¾
仿¬¾éè´å', |
| | | 'æ¥è¯¢æ¬æéè´éè´§æ
åµ', |
| | | 'æ¥è¯¢æä¸ªéè´å°è´¦è¯¦æ
', |
| | | 'åæä¾åºåéè´é颿å' |
| | | 'æ¬æéè´é颿åååçç©ææåªäºï¼', |
| | | 'åªäºéè´è®¢åè¿æªå
¥åºï¼', |
| | | 'æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼', |
| | | '帮æç»è®¡å¾
仿¬¾éè´åï¼', |
| | | 'ååºæ¬æéè´éè´§æ
åµ' |
| | | ] |
| | | } |
| | |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ éå®ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥è¯¢ç§æµ·å®¢æ·æ¡£æ¡å10æ¡', |
| | | 'æ¥è¯¢å
¬æµ·å®¢æ·æ¡£æ¡', |
| | | 'æ¥è¯¢é宿¡£æ¡å10æ¡', |
| | | 'æ¥è¯¢å®¢æ·æ¡£æ¡', |
| | | 'æ¥è¯¢æ¬æé宿¥ä»·', |
| | | 'æ¥è¯¢æ¬æéå®å°è´¦', |
| | | 'æ¥è¯¢è¿30天éå®éè´§', |
| | |
| | | clearInitTimer() |
| | | }) |
| | | |
| | | defineExpose({ |
| | | resize: windowResizeListener, |
| | | }) |
| | | |
| | | // Watch all reactive props that affect the chart |
| | | watch( |
| | | () => [props.xAxis, props.yAxis, props.series, props.legend, props.tooltip, props.visualMap], |
| | |
| | | :highlight-current-row="highlightCurrentRow" |
| | | :row-class-name="rowClassName" |
| | | :row-style="rowStyle" |
| | | :cell-style="cellStyle" |
| | | :row-key="rowKey" |
| | | :style="tableStyle" |
| | | tooltip-effect="dark" |
| | | :tooltip-options="{ popperOptions: { strategy: 'absolute' } }" |
| | | :tooltip-options="{ appendTo: 'body' }" |
| | | :expand-row-keys="expandRowKeys" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summaryMethod" |
| | |
| | | <el-button v-show="o.type != 'upload'" |
| | | v-if="o.showHide ? o.showHide(scope.row) : true" |
| | | :disabled="isOperationDisabled(o, scope.row)" |
| | | :loading="isOperationLoading(o, scope.row)" |
| | | :plain="o.plain" |
| | | type="primary" |
| | | :style="{ |
| | |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | cellStyle: { |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [], |
| | |
| | | : !!operation.disabled; |
| | | }; |
| | | |
| | | const isOperationLoading = (operation, row) => { |
| | | if (!operation?.loading) return false; |
| | | return typeof operation.loading === "function" |
| | | ? !!operation.loading(row) |
| | | : !!operation.loading; |
| | | }; |
| | | |
| | | const parseHexToRgb = hex => { |
| | | const normalized = String(hex || "") |
| | | .trim() |
| | |
| | | }, |
| | | pageSizes: { |
| | | type: Array, |
| | | default: () => [10, 20, 30, 50, 100] |
| | | default: () => [10, 20, 30, 50, 100, 200, 500] |
| | | }, |
| | | pagerCount: { |
| | | type: Number, |
| | |
| | | size: paramPage.value.size, |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | const existingParamIds = new Set( |
| | | (props.paramList || []) |
| | | .map(item => Number(item.paramId || item.technologyOperationParamId)) |
| | | .filter(id => Number.isFinite(id) && id > 0) |
| | | ); |
| | | filteredParamList.value = (res.data?.records || []).filter( |
| | | item => !existingParamIds.has(Number(item.id)) |
| | | ); |
| | | filteredParamList.value = res.data?.records || []; |
| | | paramPage.value.total = res.data.total || 0; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | |
| | | addProcessRouteItemParamOrder({ |
| | | productionOrderId: Number(props.orderId), |
| | | productionOrderRoutingOperationId: props.process.id, |
| | | technologyRoutingOperationParamId: props.process.id, |
| | | paramId: selectedParam.value.id, |
| | | standardValue: selectedParam.value.standardValue || "", |
| | | isRequired: selectedParam.value.isRequired || 0, |
| | |
| | | .detail-text { |
| | | font-weight: 500; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | </script>
|
| | |
|
| | | <style lang="scss" scoped>
|
| | | .sidebar-item-wrapper {
|
| | | :deep(.menu-icon) {
|
| | | width: 26px;
|
| | | height: 26px;
|
| | | margin-right: 12px;
|
| | | display: inline-flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | flex-shrink: 0;
|
| | | transition: color 0.2s ease;
|
| | | color: var(--sidebar-text);
|
| | | opacity: 0.88;
|
| | | }
|
| | | |
| | | :deep(.el-menu-item:hover .menu-icon),
|
| | | :deep(.el-sub-menu__title:hover .menu-icon) {
|
| | | color: #ffffff;
|
| | | opacity: 1;
|
| | | }
|
| | | .sidebar-item-wrapper { |
| | | :deep(.menu-icon) { |
| | | width: 26px; |
| | | height: 26px; |
| | | margin-right: 12px; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-shrink: 0; |
| | | transition: color 0.2s ease; |
| | | color: var(--sidebar-text); |
| | | opacity: 0.88; |
| | | } |
| | | |
| | | :deep(.el-menu-item:hover .menu-icon), |
| | | :deep(.el-sub-menu__title:hover .menu-icon) { |
| | | color: #ffffff; |
| | | opacity: 1; |
| | | } |
| | |
|
| | | :deep(.el-menu-item.is-active .menu-icon) {
|
| | | color: var(--menu-active-text) !important;
|
| | | opacity: 1;
|
| | | }
|
| | |
|
| | | :deep(.menu-title) {
|
| | | font-weight: 500;
|
| | | transition: color 0.2s ease;
|
| | | color: var(--sidebar-text);
|
| | | opacity: 0.82;
|
| | | }
|
| | | |
| | | :deep(.el-menu-item:hover .menu-title),
|
| | | :deep(.el-sub-menu__title:hover .menu-title) {
|
| | | color: #ffffff;
|
| | | opacity: 1;
|
| | | }
|
| | | |
| | | :deep(.el-menu-item.is-active .menu-title) {
|
| | | color: var(--menu-active-text) !important;
|
| | | opacity: 1;
|
| | | }
|
| | | |
| | | :deep(.nest-menu) {
|
| | | .menu-icon {
|
| | | width: 18px;
|
| | | height: 18px;
|
| | | margin-right: 8px;
|
| | | opacity: 0.7;
|
| | | }
|
| | | |
| | | .menu-title { |
| | | font-size: 13px;
|
| | | opacity: 0.85;
|
| | | }
|
| | | |
| | | .el-menu-item.is-active {
|
| | | .menu-icon {
|
| | | opacity: 1;
|
| | | }
|
| | | .menu-title {
|
| | | opacity: 1;
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | :deep(.menu-title) { |
| | | font-weight: 500; |
| | | transition: color 0.2s ease; |
| | | color: var(--sidebar-text); |
| | | opacity: 0.82; |
| | | } |
| | | |
| | | :deep(.el-menu-item:hover .menu-title), |
| | | :deep(.el-sub-menu__title:hover .menu-title) { |
| | | color: #ffffff; |
| | | opacity: 1; |
| | | } |
| | | |
| | | :deep(.el-menu-item.is-active .menu-title) { |
| | | color: var(--menu-active-text) !important; |
| | | opacity: 1; |
| | | } |
| | | |
| | | :deep(.nest-menu) { |
| | | .menu-icon { |
| | | width: 22px; |
| | | height: 22px; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | .menu-title { font-size: 13px; } |
| | | } |
| | | } |
| | | </style>
|
| | |
| | | size: Cookies.get("size") || "default", |
| | | }); |
| | | app._context.components.ElDialog.props.closeOnClickModal.default = false; |
| | | const ElTable = app._context.components.ElTable; |
| | | if (ElTable?.props?.tooltipOptions) { |
| | | if (ElTable.props.tooltipOptions === Object) { |
| | | ElTable.props.tooltipOptions = { |
| | | type: Object, |
| | | default: () => ({ |
| | | appendTo: "body", |
| | | }), |
| | | }; |
| | | } else if (typeof ElTable.props.tooltipOptions === "object") { |
| | | ElTable.props.tooltipOptions.default = |
| | | ElTable.props.tooltipOptions.default || |
| | | (() => ({ |
| | | appendTo: "body", |
| | | })); |
| | | } |
| | | } |
| | | |
| | | app.mount("#app"); |
| | |
| | | }, |
| | | ], |
| | | }, |
| | | // æå¦æ¼ç¤ºæ¨¡å |
| | | { |
| | | path: "/productionManagement/teachingDemo", |
| | | component: Layout, |
| | | hidden: true, |
| | | children: [ |
| | | { |
| | | path: "", |
| | | component: () => import("@/views/productionManagement/teachingDemo/index.vue"), |
| | | name: "TeachingDemo", |
| | | meta: { title: "å·¥èºè·¯çº¿ä¸BOMæå¦æ¼ç¤º", icon: "education" }, |
| | | }, |
| | | { |
| | | path: "bom", |
| | | component: () => import("@/views/productionManagement/teachingDemo/BomDemo.vue"), |
| | | name: "BomDemo", |
| | | meta: { title: "BOMç»ææ¼ç¤º", activeMenu: "/productionManagement/teachingDemo" }, |
| | | }, |
| | | { |
| | | path: "processRoute", |
| | | component: () => import("@/views/productionManagement/teachingDemo/ProcessRouteDemo.vue"), |
| | | name: "ProcessRouteDemo", |
| | | meta: { title: "å·¥èºè·¯çº¿æ¼ç¤º", activeMenu: "/productionManagement/teachingDemo" }, |
| | | }, |
| | | { |
| | | path: "combined", |
| | | component: () => import("@/views/productionManagement/teachingDemo/CombinedDemo.vue"), |
| | | name: "CombinedDemo", |
| | | meta: { title: "è卿¼ç¤º", activeMenu: "/productionManagement/teachingDemo" }, |
| | | }, |
| | | { |
| | | path: "factory", |
| | | component: () => import("@/views/productionManagement/teachingDemo/FactoryDemo.vue"), |
| | | name: "FactoryDemo", |
| | | meta: { title: "å¨æå·¥åæ¼ç¤º", activeMenu: "/productionManagement/teachingDemo" }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/user", |
| | | component: Layout, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import html2canvas from 'html2canvas' |
| | | |
| | | function esc(s) { |
| | | if (!s) return '' |
| | | return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') |
| | | } |
| | | |
| | | function fmtDate(v) { |
| | | if (!v) return '' |
| | | const d = new Date(v) |
| | | const y = d.getFullYear() |
| | | const m = String(d.getMonth() + 1).padStart(2, '0') |
| | | const day = String(d.getDate()).padStart(2, '0') |
| | | return y + '-' + m + '-' + day |
| | | } |
| | | |
| | | function fmtMoney(v) { |
| | | if (v == null) return '0.00' |
| | | return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) |
| | | } |
| | | |
| | | function digitUppercase(n) { |
| | | const digits = ['é¶', '壹', 'è´°', 'å', 'è', 'ä¼', 'é', 'æ', 'æ', 'ç'] |
| | | const radices = ['', 'æ¾', 'ä½°', 'ä»', 'ä¸', 'æ¾', 'ä½°', 'ä»', '亿'] |
| | | let num = Math.abs(n) |
| | | let result = '' |
| | | const intPart = Math.floor(num) |
| | | const intStr = String(intPart) |
| | | const len = intStr.length |
| | | if (intPart === 0) { |
| | | result = 'é¶' |
| | | } else { |
| | | let zeroFlag = false |
| | | for (let i = 0; i < len; i++) { |
| | | const d = parseInt(intStr[i]) |
| | | const pos = len - 1 - i |
| | | if (d === 0) { |
| | | zeroFlag = true |
| | | if (pos % 4 === 0) { |
| | | result += radices[pos] |
| | | zeroFlag = false |
| | | } |
| | | } else { |
| | | if (zeroFlag) { |
| | | result += 'é¶' |
| | | zeroFlag = false |
| | | } |
| | | result += digits[d] + radices[pos] |
| | | } |
| | | } |
| | | if (result.endsWith('é¶')) result = result.slice(0, -1) |
| | | } |
| | | result += 'å
' |
| | | const decPart = Math.round((num - intPart) * 100) |
| | | if (decPart === 0) { |
| | | result += 'æ´' |
| | | } else { |
| | | const jiao = Math.floor(decPart / 10) |
| | | const fen = decPart % 10 |
| | | if (jiao > 0) result += digits[jiao] + 'è§' |
| | | if (fen > 0) result += digits[fen] + 'å' |
| | | } |
| | | return result |
| | | } |
| | | |
| | | function coreCss() { |
| | | return `*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:30px 36px;color:#333;background:#fff}.title{text-align:center;font-size:18px;font-weight:bold;letter-spacing:6px;margin-bottom:4px}.sub{text-align:center;font-size:12px;color:#999;margin-bottom:18px}.info-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px dashed #ccc}.info-top .left,.info-top .right{line-height:2}.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 36px;font-size:13px;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid #ccc}.info-grid .l{color:#666}.info-grid .v{font-weight:500}table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:14px}th,td{border:1px solid #333;padding:5px 6px;text-align:center}th{background:#eee;font-weight:500}td.r{text-align:right}.summary{text-align:right;font-size:13px;margin-bottom:16px;line-height:2.2}.summary .cn{font-weight:bold;color:#cf1322}` |
| | | } |
| | | |
| | | function productRows(products) { |
| | | if (!products || !products.length) return '' |
| | | let html = '' |
| | | products.forEach((p, i) => { |
| | | const name = p.productCategory || '' |
| | | const spec = p.specificationModel || '' |
| | | html += '<tr>' |
| | | html += '<td>' + (i + 1) + '</td>' |
| | | html += '<td style="text-align:left">' + esc(name) |
| | | if (spec) html += '<br><span style="color:#888;font-size:12px">' + esc(spec) + '</span>' |
| | | html += '</td>' |
| | | html += '<td>' + esc(p.unit || '') + '</td>' |
| | | html += '<td class="r">' + (p.quantity != null ? Number(p.quantity).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '') + '</td>' |
| | | html += '<td class="r">' + fmtMoney(p.taxInclusiveUnitPrice) + '</td>' |
| | | html += '<td class="r">' + fmtMoney(p.taxInclusiveTotalPrice) + '</td>' |
| | | html += '<td>' + (p.taxRate != null ? p.taxRate + '%' : '') + '</td>' |
| | | html += '<td>' + esc(p.invoiceType || '') + '</td>' |
| | | html += '</tr>' |
| | | }) |
| | | return html |
| | | } |
| | | |
| | | function calcTotals(products) { |
| | | let total = 0, totalNoTax = 0 |
| | | if (products) { |
| | | products.forEach(p => { |
| | | if (p.taxInclusiveTotalPrice != null) total += Number(p.taxInclusiveTotalPrice) |
| | | if (p.taxExclusiveTotalPrice != null) totalNoTax += Number(p.taxExclusiveTotalPrice) |
| | | }) |
| | | } |
| | | return { total, totalNoTax } |
| | | } |
| | | |
| | | function buildSalesHtml(companyName, ledger, forPrint) { |
| | | const products = ledger.productData || [] |
| | | const { total, totalNoTax } = calcTotals(products) |
| | | const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">æå° / å¦å为 PDF</button></div>' : '' |
| | | const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : '' |
| | | const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : '' |
| | | |
| | | return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éå®å - ${esc(ledger.salesContractNo)} - ${esc(companyName)}</title> |
| | | <style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body> |
| | | ${toolbar} |
| | | <div class="title">${esc(companyName)}</div> |
| | | <div class="sub">éå®åå / éå®å</div> |
| | | <div class="info-top"> |
| | | <div class="left"><div><strong>éæ¹åç§°ï¼</strong>${esc(companyName)}</div></div> |
| | | <div class="right"><div><strong>ååç¼å·ï¼</strong>${esc(ledger.salesContractNo)}</div><div><strong>ç¾è®¢æ¥æï¼</strong>${fmtDate(ledger.executionDate)}</div></div> |
| | | </div> |
| | | <div class="info-grid"> |
| | | <div><span class="l">客æ·åç§°ï¼</span><span class="v">${esc(ledger.customerName)}</span></div> |
| | | <div><span class="l">项ç®åç§°ï¼</span><span class="v">${esc(ledger.projectName)}</span></div> |
| | | <div><span class="l">ä¸å¡åï¼</span><span class="v">${esc(ledger.salesman)}</span></div> |
| | | <div><span class="l">仿¬¾æ¹å¼ï¼</span><span class="v">${esc(ledger.paymentMethod)}</span></div> |
| | | <div><span class="l">å½å
¥æ¥æï¼</span><span class="v">${fmtDate(ledger.entryDate)}</span></div> |
| | | <div><span class="l">äº¤è´§æ¥æï¼</span><span class="v">${fmtDate(ledger.deliveryDate)}</span></div> |
| | | ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">夿³¨ï¼</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''} |
| | | </div> |
| | | <table><thead><tr><th>åºå·</th><th>产ååç§° / è§æ ¼åå·</th><th>åä½</th><th>æ°é</th><th>å«ç¨åä»·</th><th>å«ç¨æ»ä»·</th><th>ç¨ç</th><th>å票类å</th></tr></thead><tbody> |
| | | ${productRows(products)} |
| | | </tbody></table> |
| | | <div class="summary"> |
| | | <div>ä¸å«ç¨å计ï¼<strong>${fmtMoney(totalNoTax)}</strong></div> |
| | | <div>å«ç¨å计ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(total)}</span></div> |
| | | <div>å«ç¨å计ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(total)}</strong></div> |
| | | </div> |
| | | ${printScript} |
| | | </body></html>` |
| | | } |
| | | |
| | | function buildPurchaseHtml(companyName, ledger, products, forPrint) { |
| | | const { total, totalNoTax } = calcTotals(products) |
| | | const contractAmount = ledger.contractAmount != null ? Number(ledger.contractAmount) : total |
| | | const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">æå° / å¦å为 PDF</button></div>' : '' |
| | | const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : '' |
| | | const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : '' |
| | | |
| | | return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éè´å - ${esc(ledger.purchaseContractNumber)} - ${esc(companyName)}</title> |
| | | <style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body> |
| | | ${toolbar} |
| | | <div class="title">${esc(companyName)}</div> |
| | | <div class="info-top"> |
| | | <div class="left"><div><strong>è´æ¹åç§°ï¼</strong>${esc(companyName)}</div></div> |
| | | <div class="right"><div><strong>ååç¼å·ï¼</strong>${esc(ledger.purchaseContractNumber)}</div><div><strong>ç¾è®¢æ¥æï¼</strong>${fmtDate(ledger.executionDate)}</div></div> |
| | | </div> |
| | | <div class="info-grid"> |
| | | <div><span class="l">ä¾åºååç§°ï¼</span><span class="v">${esc(ledger.supplierName)}</span></div> |
| | | <div><span class="l">项ç®åç§°ï¼</span><span class="v">${esc(ledger.projectName)}</span></div> |
| | | <div><span class="l">å½å
¥äººï¼</span><span class="v">${esc(ledger.recorderName)}</span></div> |
| | | <div><span class="l">仿¬¾æ¹å¼ï¼</span><span class="v">${esc(ledger.paymentMethod)}</span></div> |
| | | <div><span class="l">å½å
¥æ¥æï¼</span><span class="v">${fmtDate(ledger.entryDate)}</span></div> |
| | | ${ledger.salesContractNo ? `<div><span class="l">å
³èéå®ååå·ï¼</span><span class="v">${esc(ledger.salesContractNo)}</span></div>` : ''} |
| | | ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">夿³¨ï¼</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''} |
| | | </div> |
| | | <table><thead><tr><th>åºå·</th><th>产ååç§° / è§æ ¼åå·</th><th>åä½</th><th>æ°é</th><th>å«ç¨åä»·</th><th>å«ç¨æ»ä»·</th><th>ç¨ç</th><th>å票类å</th></tr></thead><tbody> |
| | | ${productRows(products)} |
| | | </tbody></table> |
| | | <div class="summary"> |
| | | <div>ä¸å«ç¨å计ï¼<strong>${fmtMoney(totalNoTax)}</strong></div> |
| | | <div>å«ç¨å计ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(total)}</span></div> |
| | | <div>å«ç¨å计ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(total)}</strong></div> |
| | | <div>ååéé¢ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(contractAmount)}</span></div> |
| | | <div>ååéé¢ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(contractAmount)}</strong></div> |
| | | </div> |
| | | ${printScript} |
| | | </body></html>` |
| | | } |
| | | |
| | | export function buildSalesDocument(companyName, ledger) { |
| | | return buildSalesHtml(companyName, ledger, false) |
| | | } |
| | | |
| | | export function buildPurchaseDocument(companyName, ledger, products) { |
| | | return buildPurchaseHtml(companyName, ledger, products, false) |
| | | } |
| | | |
| | | function buildSalesPrintHtml(companyName, ledger) { |
| | | return buildSalesHtml(companyName, ledger, true) |
| | | } |
| | | |
| | | function buildPurchasePrintHtml(companyName, ledger, products) { |
| | | return buildPurchaseHtml(companyName, ledger, products, true) |
| | | } |
| | | |
| | | export function openPrintWindow(html) { |
| | | const w = window.open('', '_blank', 'width=900,height=700') |
| | | if (!w) return |
| | | w.document.write(html) |
| | | w.document.close() |
| | | } |
| | | |
| | | export async function downloadAsJpg(html, filename) { |
| | | const iframe = document.createElement('iframe') |
| | | iframe.style.position = 'fixed' |
| | | iframe.style.left = '0' |
| | | iframe.style.top = '0' |
| | | iframe.style.width = '900px' |
| | | iframe.style.height = '600px' |
| | | iframe.style.opacity = '0' |
| | | iframe.style.pointerEvents = 'none' |
| | | iframe.style.zIndex = '99999' |
| | | document.body.appendChild(iframe) |
| | | |
| | | return new Promise((resolve, reject) => { |
| | | const iframeDoc = iframe.contentDocument || iframe.contentWindow.document |
| | | iframeDoc.open() |
| | | iframeDoc.write(html) |
| | | iframeDoc.close() |
| | | |
| | | const doCapture = async () => { |
| | | try { |
| | | const body = iframeDoc.body |
| | | iframe.style.height = Math.max(body.scrollHeight, 600) + 'px' |
| | | await new Promise(r => setTimeout(r, 100)) |
| | | const canvas = await html2canvas(body, { |
| | | scale: 2, |
| | | useCORS: true, |
| | | backgroundColor: '#ffffff', |
| | | windowWidth: 900 |
| | | }) |
| | | document.body.removeChild(iframe) |
| | | canvas.toBlob(blob => { |
| | | const url = URL.createObjectURL(blob) |
| | | const a = document.createElement('a') |
| | | a.href = url |
| | | a.download = filename |
| | | a.click() |
| | | URL.revokeObjectURL(url) |
| | | resolve() |
| | | }, 'image/jpeg', 0.95) |
| | | } catch (e) { |
| | | document.body.removeChild(iframe) |
| | | reject(e) |
| | | } |
| | | } |
| | | |
| | | iframe.onload = () => setTimeout(doCapture, 300) |
| | | setTimeout(doCapture, 1000) |
| | | }) |
| | | } |
| | | |
| | | export function downloadSalesJpg(companyName, ledger, filename) { |
| | | const html = buildSalesHtml(companyName, ledger, false) |
| | | return downloadAsJpg(html, filename) |
| | | } |
| | | |
| | | export function downloadPurchaseJpg(companyName, ledger, products, filename) { |
| | | const html = buildPurchaseHtml(companyName, ledger, products, false) |
| | | return downloadAsJpg(html, filename) |
| | | } |
| | | |
| | | export function printSales(companyName, ledger) { |
| | | const html = buildSalesPrintHtml(companyName, ledger) |
| | | openPrintWindow(html) |
| | | } |
| | | |
| | | export function printPurchase(companyName, ledger, products) { |
| | | const html = buildPurchasePrintHtml(companyName, ledger, products) |
| | | openPrintWindow(html) |
| | | } |
| | | |
| | | export function printSalesMulti(companyName, orders) { |
| | | let bodies = '' |
| | | orders.forEach((ledger, i) => { |
| | | const html = buildSalesHtml(companyName, ledger, true) |
| | | const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/) |
| | | if (bodyMatch) { |
| | | const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '') |
| | | bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>' |
| | | } |
| | | }) |
| | | const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éå®å - ${esc(companyName)}</title> |
| | | <style> |
| | | *{margin:0;padding:0;box-sizing:border-box} |
| | | body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff} |
| | | .toolbar{text-align:center;margin-bottom:20px} |
| | | .toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px} |
| | | .toolbar button:hover{background:#40a9ff} |
| | | ${coreCss()} |
| | | .print-page{page-break-inside:avoid} |
| | | .page-break{page-break-after:always} |
| | | @media print{body{padding:15px 24px}.toolbar{display:none}} |
| | | </style></head><body> |
| | | <div class="toolbar"><button onclick="window.print()">æå°å
¨é¨ (å
±${orders.length}份)</button></div> |
| | | ${bodies} |
| | | <script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script> |
| | | </body></html>` |
| | | openPrintWindow(combined) |
| | | } |
| | | |
| | | export function printPurchaseMulti(companyName, orders) { |
| | | let bodies = '' |
| | | orders.forEach((item, i) => { |
| | | const html = buildPurchaseHtml(companyName, item.ledger, item.products, true) |
| | | const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/) |
| | | if (bodyMatch) { |
| | | const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '') |
| | | bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>' |
| | | } |
| | | }) |
| | | const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éè´å - ${esc(companyName)}</title> |
| | | <style> |
| | | *{margin:0;padding:0;box-sizing:border-box} |
| | | body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff} |
| | | .toolbar{text-align:center;margin-bottom:20px} |
| | | .toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px} |
| | | .toolbar button:hover{background:#40a9ff} |
| | | ${coreCss()} |
| | | .print-page{page-break-inside:avoid} |
| | | .page-break{page-break-after:always} |
| | | @media print{body{padding:15px 24px}.toolbar{display:none}} |
| | | </style></head><body> |
| | | <div class="toolbar"><button onclick="window.print()">æå°å
¨é¨ (å
±${orders.length}份)</button></div> |
| | | ${bodies} |
| | | <script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script> |
| | | </body></html>` |
| | | openPrintWindow(combined) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** éé¢ç±»å段é»è®¤æå¤§å°æ°ä½æ° */ |
| | | export const AMOUNT_DECIMAL_SCALE = 6 |
| | | |
| | | /** |
| | | * æ ¼å¼åæ°åï¼æå¤ä¿ç scale ä½å°æ°ï¼å¹¶å»é¤æ«å°¾å¤ä½ç 0 |
| | | */ |
| | | export function formatDecimal(value, scale = AMOUNT_DECIMAL_SCALE, fallback = '0') { |
| | | if (value === null || value === undefined || value === '') { |
| | | return fallback |
| | | } |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) { |
| | | return fallback |
| | | } |
| | | return parseFloat(num.toFixed(scale)).toString() |
| | | } |
| | | |
| | | /** 带货å¸ç¬¦å·çéé¢å±ç¤º */ |
| | | export function formatCurrency(value, scale = AMOUNT_DECIMAL_SCALE) { |
| | | return `Â¥${formatDecimal(value, scale)}` |
| | | } |
| | | |
| | | /** el-table-column formatter */ |
| | | export function tableAmountFormatter(row, column, cellValue) { |
| | | return formatDecimal(cellValue) |
| | | } |
| | | |
| | | /** 计ç®åä¿çæå®ä½å°æ° */ |
| | | export function roundAmount(value, scale = AMOUNT_DECIMAL_SCALE) { |
| | | if (value === null || value === undefined || value === '') { |
| | | return 0 |
| | | } |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) { |
| | | return 0 |
| | | } |
| | | return parseFloat(num.toFixed(scale)) |
| | | } |
| | | |
| | | /** æå»º summarizeTable éé¢åæ ¼å¼é
ç½® */ |
| | | export function buildAmountSummaryFormat(fields, extraFormat = {}) { |
| | | const format = { ...extraFormat } |
| | | fields.forEach(field => { |
| | | format[field] = { decimalPlaces: AMOUNT_DECIMAL_SCALE } |
| | | }) |
| | | return format |
| | | } |
| | |
| | | import { formatDecimal, AMOUNT_DECIMAL_SCALE } from './numberFormat' |
| | | |
| | | /** |
| | | * éç¨çè¡¨æ ¼åè®¡æ¹æ³ |
| | | * @param {Object} param - å
å«è¡¨æ ¼åé
ç½®åæ°æ®æºç对象 |
| | |
| | | // 妿æå®äºä¸éè¦ä¿çå°æ°ï¼åç´æ¥è½¬æ¢ä¸ºæ´æ° |
| | | sums[index] = Math.round(sum).toString(); |
| | | } else { |
| | | // é»è®¤ä¿ç两ä½å°æ° |
| | | sums[index] = parseFloat(sum).toFixed( |
| | | sums[index] = formatDecimal( |
| | | sum, |
| | | specialFormat[prop]?.decimalPlaces ?? 2 |
| | | ); |
| | | } |
| | | } else { |
| | | sums[index] = ""; |
| | | sums[index] = ''; |
| | | } |
| | | } else { |
| | | sums[index] = ""; |
| | | sums[index] = ''; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | const calculateTaxExclusiveTotalPrice = (taxInclusiveTotalPrice, taxRate) => { |
| | | const taxRateDecimal = taxRate / 100; |
| | | return (taxInclusiveTotalPrice / (1 + taxRateDecimal)).toFixed(2); |
| | | return formatDecimal( |
| | | Number(taxInclusiveTotalPrice) / (1 + taxRateDecimal), |
| | | AMOUNT_DECIMAL_SCALE |
| | | ); |
| | | }; |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | const calculateTaxIncludeTotalPrice = (taxInclusiveUnitPrice, quantity) => { |
| | | return (taxInclusiveUnitPrice * quantity).toFixed(2); |
| | | return formatDecimal( |
| | | Number(taxInclusiveUnitPrice) * Number(quantity), |
| | | AMOUNT_DECIMAL_SCALE |
| | | ); |
| | | }; |
| | | // 导åºå½æ°ä¾å
¶ä»æä»¶ä½¿ç¨ |
| | | export { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div class="search_form" |
| | | style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | |
| | | @change="handleQuery"> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | <el-option label="ç»éå客æ·" |
| | | value="ç»éå客æ·" /> |
| | | </el-select> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | |
| | | clearable> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | <el-option label="ç»éå客æ·" |
| | | value="ç»éå客æ·" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitReminderForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitReminderForm">确认</el-button> |
| | | <el-button @click="closeReminderDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitNegotiationForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitNegotiationForm">确认</el-button> |
| | | <el-button @click="closeNegotiationDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | <span class="info-value">{{ detailForm.companyAddress }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡åºæ¬æ·ï¼</span> |
| | | <span class="info-value">{{ detailForm.basicBankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-col> --> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <!-- <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡è´¦å·ï¼</span> |
| | |
| | | <span class="info-value">{{ detailForm.bankCode }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-row> --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | |
| | | </div> |
| | | </div> |
| | | <!-- æ´½è°è¿åº¦è®°å½ --> |
| | | <div class="detail-section"> |
| | | <!-- <div class="detail-section"> |
| | | <div class="section-header"> |
| | | <h3 class="section-title">æ´½è°è¿åº¦è®°å½</h3> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openNegotiationDialog(detailForm)"> |
| | | æ·»å è¿åº¦ |
| | | </el-button> |
| | | </div> |
| | | <el-table :data="negotiationRecords" |
| | | border |
| | |
| | | <Paperclip /> |
| | | </el-icon> |
| | | éä»¶ |
| | | <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : 'ä¸ä¼ ' }} --> |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | class="no-records"> |
| | | ææ æ´½è°è¿åº¦è®°å½ |
| | | </div> |
| | | </div> |
| | | </div> --> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDetailDialog">å
³é</el-button> |
| | |
| | | addReturnVisit, |
| | | getReturnVisit, |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import {listCustomer, getCustomer, addCustomer, updateCustomer, delCustomer, backCustomer} from "@/api/basicData/customer.js"; |
| | | import { |
| | | listCustomer, |
| | | getCustomer, |
| | | addCustomer, |
| | | updateCustomer, |
| | | delCustomer, |
| | | backCustomer, |
| | | } from "@/api/basicData/customer.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | const negotiationFormRef = ref(); |
| | | const negotiationForm = reactive({ |
| | | customerName: "", |
| | | customerId: "", |
| | | customerId: "", |
| | | followUpMethod: "", |
| | | followUpLevel: "", |
| | | followUpTime: "", |
| | |
| | | prop: "contactPhone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "è·è¿è¿åº¦", |
| | | prop: "followUpLevel", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "è·è¿æ¶é´", |
| | | prop: "followUpTime", |
| | | width: 120, |
| | | }, |
| | | // { |
| | | // label: "è·è¿è¿åº¦", |
| | | // prop: "followUpLevel", |
| | | // width: 120, |
| | | // }, |
| | | // { |
| | | // label: "è·è¿æ¶é´", |
| | | // prop: "followUpTime", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "é¶è¡åºæ¬æ·", |
| | | prop: "basicBankAccount", |
| | |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainer", |
| | | }, |
| | | { |
| | | /*{ |
| | | label: "å®¢æ·æ¥æº", |
| | | prop: "type", |
| | | dataType: "tag", |
| | |
| | | } |
| | | return "success"; |
| | | }, |
| | | }, |
| | | },*/ |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | showHide: row => !isCustomerEditLocked(row), |
| | | clickFun: row => { |
| | | if (isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("已被é宿¨¡åå¼ç¨çç§æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "æ·»å æ´½è°è¿åº¦", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openNegotiationDialog(row); |
| | | }, |
| | | }, |
| | | // { |
| | | // name: "æ·»å æ´½è°è¿åº¦", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openNegotiationDialog(row); |
| | | // }, |
| | | // }, |
| | | // { |
| | | // name: "å访æé", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openReminderDialog(row); |
| | | // }, |
| | | // }, |
| | | { |
| | | name: "å访æé", |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openReminderDialog(row); |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 0 |
| | | type: 0, |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | customerType: "", |
| | | type: 0 |
| | | type: 0, |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 0 |
| | | type: 0, |
| | | }, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | if (type === "edit" && isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("已被é宿¨¡åå¼ç¨çç§æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainer = userStore.nickName; |
| | |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | const isCustomerEditLocked = row => { |
| | | const salesReferenced = Number(row?.salesReferenceFlag || 0) === 1; |
| | | const customerType = Number(row?.type); |
| | | const usageStatus = Number(row?.usageStatus || 0); |
| | | return salesReferenced && (customerType === 0 || (customerType === 1 && usageStatus === 1)); |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {type: 0}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", { type: 0 }, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | if (reminderForm.id) { |
| | | submitvalue.value = { |
| | | id: reminderForm.id, |
| | | customerId: currentCustomerId.value, |
| | | customerId: currentCustomerId.value, |
| | | isEnabled: reminderForm.reminderSwitch ? 1 : 0, |
| | | content: reminderForm.reminderContent, |
| | | reminderTime: reminderForm.reminderTime, |
| | |
| | | // å°å½åè®°å½æ°æ®å¡«å
å°è¡¨å |
| | | Object.assign(negotiationForm, { |
| | | customerName: row.customerName, |
| | | customerId: row.customerId, |
| | | customerId: row.customerId, |
| | | followUpMethod: row.followUpMethod, |
| | | followUpLevel: row.followUpLevel, |
| | | followUpTime: row.followUpTime, |
| | |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div class="search_form" |
| | | style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">客æ·åç§°ï¼</span> |
| | | <el-input v-model="searchForm.customerName" |
| | |
| | | @change="handleQuery"> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | <el-option label="ç»éå客æ·" |
| | | value="ç»éå客æ·" /> |
| | | </el-select> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <!-- <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡åºæ¬æ·ï¼" |
| | | prop="basicBankAccount"> |
| | |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-row> --> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="弿·è¡å·ï¼" |
| | | prop="bankCode"> |
| | | <el-input v-model="form.bankCode" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·åç±»ï¼" |
| | | prop="customerType"> |
| | |
| | | clearable> |
| | | <el-option label="é¶å®å®¢æ·" |
| | | value="é¶å®å®¢æ·" /> |
| | | <el-option label="è¿éå客æ·" |
| | | value="è¿éå客æ·" /> |
| | | <el-option label="ç»éå客æ·" |
| | | value="ç»éå客æ·" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="assignDialogVisible" |
| | | title="åé
客æ·" |
| | | width="500px" |
| | | @close="closeAssignDialog"> |
| | | <el-form :model="assignForm" |
| | | :rules="assignRules" |
| | | ref="assignFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input v-model="assignForm.customerName" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="åé
人å" |
| | | prop="boundId"> |
| | | <el-select v-model="assignForm.boundId" |
| | | placeholder="è¯·éæ©åé
人å" |
| | | style="width: 100%" |
| | | filterable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId || item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitAssignForm">确认</el-button> |
| | | <el-button @click="closeAssignDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="shareDialogVisible" |
| | | title="å
±äº«å®¢æ·" |
| | | width="500px" |
| | | @close="closeShareDialog"> |
| | | <el-form :model="shareForm" |
| | | :rules="shareRules" |
| | | ref="shareFormRef" |
| | | label-width="100px"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input v-model="shareForm.customerName" |
| | | disabled /> |
| | | </el-form-item> |
| | | <el-form-item label="å
±äº«äººå" |
| | | prop="boundIds"> |
| | | <el-select v-model="shareForm.boundIds" |
| | | placeholder="è¯·éæ©å
±äº«äººå" |
| | | style="width: 100%" |
| | | filterable |
| | | multiple |
| | | collapse-tags |
| | | collapse-tags-tooltip> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId || item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitShareForm">确认</el-button> |
| | | <el-button @click="closeShareDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitReminderForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitReminderForm">确认</el-button> |
| | | <el-button @click="closeReminderDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitNegotiationForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitNegotiationForm">确认</el-button> |
| | | <el-button @click="closeNegotiationDialog">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | <span class="info-value">{{ detailForm.companyAddress }}</span> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡åºæ¬æ·ï¼</span> |
| | | <span class="info-value">{{ detailForm.basicBankAccount }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-col> --> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <!-- <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | | <span class="info-label">é¶è¡è´¦å·ï¼</span> |
| | |
| | | <span class="info-value">{{ detailForm.bankCode }}</span> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </el-row> --> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="info-item"> |
| | |
| | | </div> |
| | | </div> |
| | | <!-- æ´½è°è¿åº¦è®°å½ --> |
| | | <div class="detail-section"> |
| | | <!-- <div class="detail-section"> |
| | | <div class="section-header"> |
| | | <h3 class="section-title">æ´½è°è¿åº¦è®°å½</h3> |
| | | <el-button type="primary" |
| | | size="small" |
| | | @click="openNegotiationDialog(detailForm)"> |
| | | æ·»å è¿åº¦ |
| | | </el-button> |
| | | </div> |
| | | <el-table :data="negotiationRecords" |
| | | border |
| | |
| | | <Paperclip /> |
| | | </el-icon> |
| | | éä»¶ |
| | | <!-- {{ row.fileList && row.fileList.length > 0 ? row.fileList.length : 'ä¸ä¼ ' }} --> |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | class="no-records"> |
| | | ææ æ´½è°è¿åº¦è®°å½ |
| | | </div> |
| | | </div> |
| | | </div> --> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDetailDialog">å
³é</el-button> |
| | |
| | | } from "@/api/basicData/customerFile.js"; |
| | | import { |
| | | listCustomer, |
| | | addCustomer, |
| | | delCustomer, |
| | | updateCustomer, |
| | | getCustomer, |
| | | assignCustomer, |
| | | recycleCustomer, |
| | | shareCustomer, |
| | | addCustomer, |
| | | updateCustomer, |
| | | delCustomer, |
| | | } from "@/api/basicData/customer.js"; |
| | | |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const assignDialogVisible = ref(false); |
| | | const assignFormRef = ref(); |
| | | const assignForm = reactive({ |
| | | id: undefined, |
| | | customerName: "", |
| | | boundId: undefined, |
| | | }); |
| | | const assignRules = { |
| | | boundId: [{ required: true, message: "è¯·éæ©åé
人å", trigger: "change" }], |
| | | }; |
| | | const shareDialogVisible = ref(false); |
| | | const shareFormRef = ref(); |
| | | const shareForm = reactive({ |
| | | id: undefined, |
| | | customerName: "", |
| | | boundIds: [], |
| | | }); |
| | | const shareRules = { |
| | | boundIds: [{ required: true, message: "è¯·éæ©å
±äº«äººå", trigger: "change" }], |
| | | }; |
| | | |
| | | // å访æéç¸å
³ |
| | | const reminderDialogVisible = ref(false); |
| | |
| | | }, |
| | | { |
| | | label: "å°ååèç³»æ¹å¼", |
| | | prop: "addressPhone", |
| | | prop: "companyAddress", |
| | | width: 250, |
| | | }, |
| | | { |
| | |
| | | // prop: "followUpTime", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "é¶è¡åºæ¬æ·", |
| | | prop: "basicBankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "é¶è¡è´¦å·", |
| | | prop: "bankAccount", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "弿·è¡å·", |
| | | prop: "bankCode", |
| | | width: 220, |
| | | }, |
| | | // { |
| | | // label: "é¶è¡åºæ¬æ·", |
| | | // prop: "basicBankAccount", |
| | | // width: 220, |
| | | // }, |
| | | // { |
| | | // label: "é¶è¡è´¦å·", |
| | | // prop: "bankAccount", |
| | | // width: 220, |
| | | // }, |
| | | // { |
| | | // label: "弿·è¡å·", |
| | | // prop: "bankCode", |
| | | // width: 220, |
| | | // }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainer", |
| | | }, |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "é¢ç¨äºº", |
| | | prop: "usageUserName", |
| | | width: 120, |
| | | fixed: "right", |
| | | }, |
| | | { |
| | | label: "é¢ç¨ç¶æ", |
| | | prop: "usageStatus", |
| | | dataType: "tag", |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintenanceTime", |
| | | width: 100, |
| | | fixed: "right", |
| | | formatData: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "å·²é¢ç¨"; |
| | | } |
| | | return "æªé¢ç¨"; |
| | | }, |
| | | formatType: value => { |
| | | if (value === 1 || value === "1") { |
| | | return "success"; |
| | | } |
| | | return "info"; |
| | | }, |
| | | }, |
| | | { |
| | | label: "å
±äº«äºº", |
| | | prop: "togetherUserNames", |
| | | width: 120, |
| | | fixed: "right", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 200, |
| | | width: 140, |
| | | operation: [ |
| | | { |
| | | name: "åé
", |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | showHide: row => row.usageStatus != 1, |
| | | clickFun: row => { |
| | | openAssignDialog(row); |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "åæ¶", |
| | | type: "text", |
| | | disabled: row => isCustomerRecoveryLocked(row), |
| | | showHide: row => row.usageStatus == 1, |
| | | clickFun: row => { |
| | | if (isCustomerRecoveryLocked(row)) { |
| | | proxy.$modal.msgWarning("å·²é¢ç¨å¹¶ä¸è¢«é宿¨¡åå¼ç¨çå
¬æµ·å®¢æ·ä¸è½åæ¶"); |
| | | return; |
| | | } |
| | | recycle(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "å
񄧮", |
| | | type: "text", |
| | | showHide: row => row.usageStatus == 1, |
| | | clickFun: row => { |
| | | openShareDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | disabled: row => isCustomerEditLocked(row), |
| | | clickFun: row => { |
| | | if (isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("å·²é¢ç¨å¹¶ä¸è¢«é宿¨¡åå¼ç¨çå
¬æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | // { |
| | | // name: "详æ
", |
| | | // name: "æ·»å æ´½è°è¿åº¦", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openDetailDialog(row); |
| | | // openNegotiationDialog(row); |
| | | // }, |
| | | // }, |
| | | // { |
| | | // name: "å访æé", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openReminderDialog(row); |
| | | // }, |
| | | // }, |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openDetailDialog(row); |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | searchForm: { |
| | | customerName: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | form: { |
| | | customerName: "", |
| | |
| | | bankAccount: "", |
| | | bankCode: "", |
| | | customerType: "", |
| | | type: 1 |
| | | }, |
| | | rules: { |
| | | customerName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNumber: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // taxpayerIdentificationNumber: [ |
| | | // { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | // ], |
| | | // companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPerson: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | // contactPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainer: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/basic/customer/importData", |
| | | data: { |
| | | type: 1 |
| | | }, |
| | | data: {}, |
| | | // æä»¶ä¸ä¼ åçåè° |
| | | beforeUpload: file => { |
| | | console.log("æä»¶å³å°ä¸ä¼ ", file); |
| | |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const { total, ...queryPage } = page; |
| | | listCustomer({ ...searchForm.value, ...queryPage }).then(res => { |
| | | listCustomer({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | if (type === "edit" && isCustomerEditLocked(row)) { |
| | | proxy.$modal.msgWarning("å·²é¢ç¨å¹¶ä¸è¢«é宿¨¡åå¼ç¨çå
¬æµ·å®¢æ·ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainer = userStore.nickName; |
| | |
| | | }, |
| | | ]; |
| | | form.value.maintenanceTime = getCurrentDate(); |
| | | form.value.type = 1; |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | const isCustomerSalesReferenced = row => Number(row?.salesReferenceFlag || 0) === 1; |
| | | const isCustomerRecoveryLocked = row => Number(row?.usageStatus || 0) === 1 && isCustomerSalesReferenced(row); |
| | | const isCustomerEditLocked = row => isCustomerSalesReferenced(row) && ( |
| | | Number(row?.type) === 0 || (Number(row?.type) === 1 && Number(row?.usageStatus || 0) === 1) |
| | | ); |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | const ensureUserList = () => { |
| | | if (userList.value.length) { |
| | | return Promise.resolve(); |
| | | } |
| | | return userListNoPage().then(res => { |
| | | userList.value = res.data || []; |
| | | }); |
| | | }; |
| | | const openAssignDialog = row => { |
| | | assignForm.id = row.id; |
| | | assignForm.customerName = row.customerName; |
| | | assignForm.boundId = undefined; |
| | | ensureUserList().then(() => { |
| | | assignDialogVisible.value = true; |
| | | }); |
| | | }; |
| | | const closeAssignDialog = () => { |
| | | proxy.resetForm("assignFormRef"); |
| | | assignForm.id = undefined; |
| | | assignForm.customerName = ""; |
| | | assignForm.boundId = undefined; |
| | | assignDialogVisible.value = false; |
| | | }; |
| | | const openShareDialog = row => { |
| | | shareForm.id = row.id; |
| | | shareForm.customerName = row.customerName; |
| | | shareForm.boundIds = row.userIds || []; |
| | | ensureUserList().then(() => { |
| | | shareDialogVisible.value = true; |
| | | }); |
| | | }; |
| | | const closeShareDialog = () => { |
| | | proxy.resetForm("shareFormRef"); |
| | | shareForm.id = undefined; |
| | | shareForm.customerName = ""; |
| | | shareForm.boundIds = []; |
| | | shareDialogVisible.value = false; |
| | | }; |
| | | const submitAssignForm = () => { |
| | | proxy.$refs.assignFormRef.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | assignCustomer({ |
| | | id: assignForm.id, |
| | | usageUser: assignForm.boundId, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("åé
æå"); |
| | | closeAssignDialog(); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | const submitShareForm = () => { |
| | | proxy.$refs.shareFormRef.validate(valid => { |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | shareCustomer({ |
| | | id: shareForm.id, |
| | | userIds: shareForm.boundIds, |
| | | }).then(() => { |
| | | proxy.$modal.msgSuccess("å
±äº«æå"); |
| | | closeShareDialog(); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | const recycle = row => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶å®¢æ·â" + row.customerName + "âåï¼", "åæ¶æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | return recycleCustomer({id: row.id}).then(() => { |
| | | proxy.$modal.msgSuccess("åæ¶æå"); |
| | | getList(); |
| | | }) |
| | | }) |
| | | .catch(error => { |
| | | if (error === "cancel" || error === "close") { |
| | | proxy.$modal.msg("已忶"); |
| | | } |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/basic/customer/export", {type: 1}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | proxy.download("/basic/customer/export", {}, "å®¢æ·æ¡£æ¡.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.maintainer !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delCustomer(ids) |
| | | .then(res => { |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | |
| | | }; |
| | | } |
| | | |
| | | console.log("æäº¤å访æéæ°æ®:", submitvalue.value); |
| | | |
| | | // è°ç¨æ¥å£ |
| | | addReturnVisit(submitvalue.value) |
| | | .then(res => { |
| | |
| | | negotiationForm.followUpTime = ""; |
| | | negotiationForm.followerUserName = userStore.nickName; // é»è®¤å½åç»å½äºº |
| | | negotiationForm.content = ""; |
| | | // { |
| | | // "customerId": 152, |
| | | // "followUpMethod": "çµè¯æ²é", |
| | | // "followUpLevel": "没ææå", |
| | | // "followUpTime": "2026-03-04T15:30:00", |
| | | // "followerUserName": "管çåè´¦å·", |
| | | // "content": "111" |
| | | // } |
| | | negotiationDialogVisible.value = true; |
| | | }; |
| | | |
| | |
| | | |
| | | if (isEdit) { |
| | | // ä¿®æ¹æä½ |
| | | console.log("ä¿®æ¹æ´½è°è¿åº¦æ°æ®:", negotiationForm); |
| | | // è¿éå¯ä»¥è°ç¨æ´æ°æ¥å£ |
| | | // å®é
项ç®ä¸éè¦æ ¹æ®å端æ¥å£è¿è¡è°æ´ |
| | | // 示ä¾ï¼updateCustomerFollow(negotiationForm).then(res => { |
| | | // // æ´æ°æ¬å°æ°æ® |
| | | // const index = negotiationForm.editIndex; |
| | | // negotiationRecords.value[index] = { |
| | | // followUpTime: negotiationForm.followUpTime, |
| | | // followUpMethod: negotiationForm.followUpMethod, |
| | | // followUpLevel: negotiationForm.followUpLevel, |
| | | // followerUserName: negotiationForm.followerUserName, |
| | | // content: negotiationForm.content, |
| | | // id: negotiationForm.id, |
| | | // }; |
| | | // proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | // closeNegotiationDialog(); |
| | | // }); |
| | | updateCustomerFollow(negotiationForm).then(res => { |
| | | // æ´æ°æ¬å°æ°æ® |
| | | getCustomer(negotiationForm.customerId).then(res => { |
| | |
| | | |
| | | // æå¼è¯¦æ
å¼¹çª |
| | | const openDetailDialog = row => { |
| | | // è°ç¨getCustomeræ¥å£è·å客æ·è¯¦æ
|
| | | getCustomer(row.id).then(res => { |
| | | // å¡«å
客æ·åºæ¬ä¿¡æ¯ |
| | | Object.assign(detailForm, res.data); |
| | |
| | | const downloadAttachment = row => { |
| | | if (row.url) { |
| | | // proxy.download(row.url, {}, row.name); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | } else { |
| | | proxy.$modal.msgError("ä¸è½½é¾æ¥ä¸åå¨"); |
| | | } |
| | |
| | | prop="paramCode"> |
| | | <el-input v-model="formData.paramCode" |
| | | disabled |
| | | placeholder="ä¿ååèªå¨çæ" /> |
| | | placeholder="èªå¨çæ" /> |
| | | </el-form-item> |
| | | <el-form-item label="åæ°åç§°" |
| | | prop="paramName"> |
| | |
| | | value="2" /> |
| | | </el-select> |
| | | </el-form-item> --> |
| | | <el-form-item label="å建æ¶é´" |
| | | prop="createTime"> |
| | | <el-date-picker v-model="formCreateTimeDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="åä½" |
| | | prop="unit"> |
| | | <el-input v-model="formData.unit" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref, reactive, computed } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { onMounted, ref, reactive } from "vue"; |
| | | import { |
| | | parameterListPage, |
| | | addParameter, |
| | |
| | | import { deptTreeSelect } from "@/api/system/user.js"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | |
| | | { |
| | | label: "å建æ¶é´", |
| | | prop: "createTime", |
| | | formatData: val => { |
| | | if (!val) return ""; |
| | | return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}"); |
| | | }, |
| | | }, |
| | | { |
| | | label: "æä½", |
| | |
| | | remark: "", |
| | | isRequired: 0, |
| | | paramFormat: "", |
| | | createTime: "", |
| | | }); |
| | | const rules = reactive({ |
| | | paramName: [{ required: true, message: "请è¾å
¥åæ°åç§°", trigger: "blur" }], |
| | |
| | | }, |
| | | }, |
| | | ], |
| | | }); |
| | | const formCreateTimeDate = computed({ |
| | | get: () => (formData.createTime ? String(formData.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | formData.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | // const productTypes = ref([]); |
| | | const isEdit = ref(false); |
| | |
| | | formData.unit = ""; |
| | | formData.remark = ""; |
| | | formData.isRequired = 0; |
| | | formData.createTime = dayjs().format("YYYY-MM-DD HH:mm:ss"); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | |
| | | formData.remark = row.remark || ""; |
| | | formData.paramFormat = row.paramFormat || ""; |
| | | formData.isRequired = row.isRequired || 0; |
| | | formData.createTime = row.createTime || ""; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | |
| | | <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false"> |
| | | <el-form :inline="true" :model="query" class="mb-2"> |
| | | <el-form-item label="产ååç§°"> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§ååç§°" clearable @keyup.enter="onSearch" /> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§ååç§°" clearable @keyup.enter="onSearch" style="width: 200px" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="产ååå·"> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥äº§ååå·" clearable @keyup.enter="onSearch" /> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥äº§ååå·" clearable @keyup.enter="onSearch" style="width: 200px" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | |
| | | @selection-change="handleSelectionChange" @select="handleSelect"> |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column type="index" label="åºå·" width="60" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="160" /> |
| | | <el-table-column prop="model" label="产ååå·" min-width="200" /> |
| | | <el-table-column prop="unit" label="åä½" min-width="160" /> |
| | | <el-table-column prop="topProductName" label="产å大类" min-width="120" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="140" /> |
| | | <el-table-column prop="model" label="产ååå·" min-width="180" /> |
| | | <el-table-column prop="unit" label="åä½" min-width="100" /> |
| | | </el-table> |
| | | |
| | | <div class="mt-3 flex justify-end"> |
| | |
| | | <el-form-item label="产ååç§°ï¼" |
| | | prop="productName"> |
| | | <el-input v-model="form.productName" |
| | | type="textarea" |
| | | resize="vertical" |
| | | placeholder="请è¾å
¥äº§ååç§°" |
| | | maxlength="20" |
| | | maxlength="50" |
| | | show-word-limit |
| | | clearable |
| | | @keydown.enter.prevent /> |
| | |
| | | :rules="modelRules" |
| | | ref="modelFormRef"> |
| | | <el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产åç¼å·ï¼" |
| | | prop="productCode"> |
| | | <el-input v-model="modelForm.productCode" |
| | | placeholder="请è¾å
¥äº§åç¼å·" |
| | | clearable |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产åç¼å·ï¼" |
| | | prop="productCode"> |
| | | <el-input v-model="modelForm.productCode" |
| | | placeholder="请è¾å
¥äº§åç¼å·" |
| | | clearable |
| | | @keydown.enter.prevent /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="24"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" |
| | | prop="model"> |
| | |
| | | rules: { |
| | | productName: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | { max: 20, message: "产ååç§°ä¸è½è¶
è¿20个å符", trigger: "blur" }, |
| | | { max: 50, message: "产ååç§°ä¸è½è¶
è¿50个å符", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | modelForm: { |
| | |
| | | <div class="search_form"> |
| | | <div> |
| | | <span class="search_title">ä¾åºåæ¡£æ¡ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥ä¾åºååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | <el-input v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥ä¾åºååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"></PIMTable> |
| | | </div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ä¾åºåä¿¡æ¯' : 'ç¼è¾ä¾åºåä¿¡æ¯'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form |
| | | :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ä¾åºåä¿¡æ¯' : 'ç¼è¾ä¾åºåä¿¡æ¯'" |
| | | width="70%" |
| | | @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="supplierName"> |
| | | <el-input |
| | | v-model="form.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | <el-form-item label="ä¾åºååç§°ï¼" |
| | | prop="supplierName"> |
| | | <el-input v-model="form.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="纳ç¨äººè¯å«å·ï¼" |
| | | prop="taxpayerIdentificationNum" |
| | | > |
| | | <el-input |
| | | v-model="form.taxpayerIdentificationNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | <el-form-item label="纳ç¨äººè¯å«å·ï¼" |
| | | prop="taxpayerIdentificationNum"> |
| | | <el-input v-model="form.taxpayerIdentificationNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
¬å¸å°åï¼" |
| | | prop="companyAddress"> |
| | | <el-input v-model="form.companyAddress" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸å°åï¼" prop="companyAddress"> |
| | | <el-input |
| | | v-model="form.companyAddress" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸çµè¯ï¼" prop="companyPhone"> |
| | | <el-input |
| | | v-model="form.companyPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡ï¼" prop="bankAccountName"> |
| | | <el-input |
| | | v-model="form.bankAccountName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´¦å·ï¼" prop="bankAccountNum"> |
| | | <el-input |
| | | v-model="form.bankAccountNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactUserName"> |
| | | <el-input |
| | | v-model="form.contactUserName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactUserPhone"> |
| | | <el-input |
| | | v-model="form.contactUserPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤äººï¼" prop="maintainUserId"> |
| | | <el-select |
| | | v-model="form.maintainUserId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | disabled |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | <el-form-item label="ç»´æ¤äººï¼" |
| | | prop="maintainUserId"> |
| | | <el-select v-model="form.maintainUserId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | disabled> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤æ¶é´ï¼" prop="maintainTime"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.maintainTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | <el-form-item label="ç»´æ¤æ¶é´ï¼" |
| | | prop="maintainTime"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.maintainTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" prop="supplierType"> |
| | | <el-select v-model="form.supplierType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="ç²" value="ç²" /> |
| | | <el-option label="ä¹" value="ä¹" /> |
| | | <el-option label="ä¸" value="ä¸" /> |
| | | <el-option label="ä¸" value="ä¸" /> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" |
| | | prop="supplierType"> |
| | | <el-select v-model="form.supplierType" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="ç²" |
| | | value="ç²" /> |
| | | <el-option label="ä¹" |
| | | value="ä¹" /> |
| | | <el-option label="ä¸" |
| | | value="ä¸" /> |
| | | <el-option label="ä¸" |
| | | value="ä¸" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" prop="isWhite"> |
| | | <el-select v-model="form.isWhite" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯" :value="0" /> |
| | | <el-option label="å¦" :value="1" /> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" |
| | | prop="isWhite"> |
| | | <el-select v-model="form.isWhite" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="æ¯" |
| | | :value="0" /> |
| | | <el-option label="å¦" |
| | | :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </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> |
| | | </el-dialog> |
| | | |
| | | <!-- ä¾åºå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :on-error="handleFileError" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-dialog :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body> |
| | | <el-upload ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :on-error="handleFileError" |
| | | :auto-upload="false" |
| | | drag> |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <el-link |
| | | type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate" |
| | | >ä¸è½½æ¨¡æ¿</el-link |
| | | > |
| | | <el-link type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button type="primary" |
| | | @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { delSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | addSupplier, |
| | | getSupplier, |
| | | listSupplier, |
| | | updateSupplier, |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import { onMounted, ref } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { delSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | addSupplier, |
| | | getSupplier, |
| | | listSupplier, |
| | | updateSupplier, |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 250, |
| | | }, |
| | | { |
| | | label: "ä¾åºåç±»å", |
| | | prop: "supplierType", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "èç³»æ¹å¼", |
| | | prop: "companyPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "弿·è¡", |
| | | prop: "bankAccountName", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "è´¦å·", |
| | | prop: "bankAccountNum", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "è系人", |
| | | prop: "contactUserName", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "contactUserPhone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | | }, |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | // width: 250, |
| | | }, |
| | | // { |
| | | // label: "ä¾åºåç±»å", |
| | | // prop: "supplierType", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | // width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | // width: 300, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | | }, |
| | | |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintainTime", |
| | | width:100 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintainTime", |
| | | // width: 100, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | //èµè´¨éä»¶ |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFileDialog(row) |
| | | // { |
| | | // //èµè´¨éä»¶ |
| | | // name: "èµè´¨æä»¶", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openFileDialog(row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierName: "", |
| | | taxpayerIdentificationNum: "", |
| | | companyAddress: "", |
| | | maintainUserId: "", |
| | | maintainTime: "", |
| | | supplierType: "", |
| | | isWhite: "", |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNum: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | // supplierType: [ |
| | | // { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | // ], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | upload.isUploading = true; |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼ä¾åºå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼ä¾åºå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "ä¾åºå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download( |
| | | "/system/supplier/downloadTemplate", |
| | | {}, |
| | | "ä¾åºå导å
¥æ¨¡æ¿.xlsx" |
| | | ); |
| | | } |
| | | |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.isUploading = false; |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | upload.open = false; |
| | | proxy.$refs["uploadRef"].clearFiles(); |
| | | getList(); |
| | | } else if (response.code === 500) { |
| | | proxy.$modal.msgError(response.msg); |
| | | } else { |
| | | proxy.$modal.msgWarning(response.msg); |
| | | } |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ 失败å¤ç */ |
| | | const handleFileError = (error, file, fileList) => { |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainUserId = userStore.id; |
| | | form.value.maintainTime = getCurrentDate(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getSupplier(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitEdit(); |
| | | } else { |
| | | submitAdd(); |
| | | } |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierName: "", |
| | | taxpayerIdentificationNum: "", |
| | | companyAddress: "", |
| | | companyPhone: "", |
| | | bankAccountName: "", |
| | | bankAccountNum: "", |
| | | contactUserName: "", |
| | | contactUserPhone: "", |
| | | maintainUserId: "", |
| | | maintainTime: "", |
| | | supplierType: "", |
| | | isWhite: "", |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNum: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankAccountName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankAccountNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contactUserName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | contactUserPhone: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | supplierType: [{ required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | upload.isUploading = true; |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listSupplier({ ...searchForm.value, ...page, isWhite: 1 }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼ä¾åºå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼ä¾åºå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "ä¾åºå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download("/system/supplier/downloadTemplate", {}, "ä¾åºå导å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.isUploading = false; |
| | | if(response.code === 200){ |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | upload.open = false; |
| | | proxy.$refs["uploadRef"].clearFiles(); |
| | | getList(); |
| | | }else if(response.code === 500){ |
| | | proxy.$modal.msgError(response.msg); |
| | | }else{ |
| | | proxy.$modal.msgWarning(response.msg); |
| | | } |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ 失败å¤ç */ |
| | | const handleFileError = (error, file, fileList) => { |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainUserId = userStore.id; |
| | | form.value.maintainTime = getCurrentDate(); |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getSupplier(row.id).then((res) => { |
| | | form.value = { ...res.data }; |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitEdit(); |
| | | } else { |
| | | submitAdd(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | addSupplier(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | updateSupplier(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | addSupplier(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | updateSupplier(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/system/supplier/export", { isWhite: 1 }, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | proxy.download( |
| | | "/system/supplier/export", |
| | | { isWhite: 1 }, |
| | | "ä¾åºåæ¡£æ¡.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.maintainUserName !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delSupplier(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | </script> |
| | | |
| | | |
| | |
| | | <div class="search_form"> |
| | | <div style="margin-bottom: 10px;"> |
| | | <span class="search_title">ä¾åºåæ¡£æ¡ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥ä¾åºååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | <el-input v-model="searchForm.supplierName" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥ä¾åºååç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div style="margin-bottom: 10px;"> |
| | | <el-button type="primary" @click="openForm('add')" |
| | | >æ°å¢ä¾åºå</el-button |
| | | > |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢ä¾åºå</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="info" plain icon="Upload" @click="handleImport" |
| | | >导å
¥</el-button |
| | | > |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="handleImport">导å
¥</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | ></PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination"></PIMTable> |
| | | </div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ä¾åºåä¿¡æ¯' : 'ç¼è¾ä¾åºåä¿¡æ¯'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form |
| | | :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | > |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ä¾åºåä¿¡æ¯' : 'ç¼è¾ä¾åºåä¿¡æ¯'" |
| | | width="70%" |
| | | @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="supplierName"> |
| | | <el-input |
| | | v-model="form.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | <el-form-item label="ä¾åºååç§°ï¼" |
| | | prop="supplierName"> |
| | | <el-input v-model="form.supplierName" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item |
| | | label="纳ç¨äººè¯å«å·ï¼" |
| | | prop="taxpayerIdentificationNum" |
| | | > |
| | | <el-input |
| | | v-model="form.taxpayerIdentificationNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | <el-form-item label="纳ç¨äººè¯å«å·ï¼" |
| | | prop="taxpayerIdentificationNum"> |
| | | <el-input v-model="form.taxpayerIdentificationNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
¬å¸å°åï¼" |
| | | prop="companyAddress"> |
| | | <el-input v-model="form.companyAddress" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸å°åï¼" prop="companyAddress"> |
| | | <el-input |
| | | v-model="form.companyAddress" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
¬å¸çµè¯ï¼" prop="companyPhone"> |
| | | <el-input |
| | | v-model="form.companyPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡ï¼" prop="bankAccountName"> |
| | | <el-input |
| | | v-model="form.bankAccountName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´¦å·ï¼" prop="bankAccountNum"> |
| | | <el-input |
| | | v-model="form.bankAccountNum" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è系人ï¼" prop="contactUserName"> |
| | | <el-input |
| | | v-model="form.contactUserName" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èç³»çµè¯ï¼" prop="contactUserPhone"> |
| | | <el-input |
| | | v-model="form.contactUserPhone" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤äººï¼" prop="maintainUserId"> |
| | | <el-select |
| | | v-model="form.maintainUserId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | disabled |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | <el-form-item label="ç»´æ¤äººï¼" |
| | | prop="maintainUserId"> |
| | | <el-select v-model="form.maintainUserId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | disabled> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»´æ¤æ¶é´ï¼" prop="maintainTime"> |
| | | <el-date-picker |
| | | style="width: 100%" |
| | | v-model="form.maintainTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | /> |
| | | <el-form-item label="ç»´æ¤æ¶é´ï¼" |
| | | prop="maintainTime"> |
| | | <el-date-picker style="width: 100%" |
| | | v-model="form.maintainTime" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" prop="supplierType"> |
| | | <el-select v-model="form.supplierType" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="ç²" value="ç²" /> |
| | | <el-option label="ä¹" value="ä¹" /> |
| | | <el-option label="ä¸" value="ä¸" /> |
| | | <el-option label="ä¸" value="ä¸" /> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" |
| | | prop="supplierType"> |
| | | <el-select v-model="form.supplierType" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="ç²" |
| | | value="ç²" /> |
| | | <el-option label="ä¹" |
| | | value="ä¹" /> |
| | | <el-option label="ä¸" |
| | | value="ä¸" /> |
| | | <el-option label="ä¸" |
| | | value="ä¸" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" prop="isWhite"> |
| | | <el-select v-model="form.isWhite" placeholder="è¯·éæ©" clearable> |
| | | <el-option label="æ¯" :value="0" /> |
| | | <el-option label="å¦" :value="1" /> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" |
| | | prop="isWhite"> |
| | | <el-select v-model="form.isWhite" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option label="æ¯" |
| | | :value="0" /> |
| | | <el-option label="å¦" |
| | | :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </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> |
| | | </el-dialog> |
| | | |
| | | <!-- ä¾åºå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :on-error="handleFileError" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-dialog :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body> |
| | | <el-upload ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :on-error="handleFileError" |
| | | :auto-upload="false" |
| | | drag> |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | | <div class="el-upload__tip text-center"> |
| | | <span>ä»
å
许导å
¥xlsãxlsxæ ¼å¼æä»¶ã</span> |
| | | <el-link |
| | | type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate" |
| | | >ä¸è½½æ¨¡æ¿</el-link |
| | | > |
| | | <el-link type="primary" |
| | | :underline="false" |
| | | style="font-size: 12px; vertical-align: baseline" |
| | | @click="importTemplate">ä¸è½½æ¨¡æ¿</el-link> |
| | | </div> |
| | | </template> |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button type="primary" |
| | | @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { delSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | addSupplier, |
| | | getSupplier, |
| | | listSupplier, |
| | | updateSupplier, |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import { onMounted, ref } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { delSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { |
| | | addSupplier, |
| | | getSupplier, |
| | | listSupplier, |
| | | updateSupplier, |
| | | } from "@/api/basicData/supplierManageFile.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 250, |
| | | }, |
| | | { |
| | | label: "ä¾åºåç±»å", |
| | | prop: "supplierType", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "èç³»æ¹å¼", |
| | | prop: "companyPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "弿·è¡", |
| | | prop: "bankAccountName", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "è´¦å·", |
| | | prop: "bankAccountNum", |
| | | width: 220, |
| | | }, |
| | | { |
| | | label: "è系人", |
| | | prop: "contactUserName", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "contactUserPhone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | | }, |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | // width: 250, |
| | | }, |
| | | // { |
| | | // label: "ä¾åºåç±»å", |
| | | // prop: "supplierType", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | // width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | // width: 220, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | | // width: 220, |
| | | }, |
| | | |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintainTime", |
| | | width:100 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintainTime", |
| | | width: 100, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 150, |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | }, |
| | | { |
| | | //èµè´¨éä»¶ |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFileDialog(row) |
| | | // { |
| | | // //èµè´¨éä»¶ |
| | | // name: "èµè´¨æä»¶", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openFileDialog(row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierName: "", |
| | | taxpayerIdentificationNum: "", |
| | | companyAddress: "", |
| | | maintainUserId: "", |
| | | maintainTime: "", |
| | | supplierType: "", |
| | | isWhite: "", |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNum: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | // supplierType: [ |
| | | // { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | // ], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | upload.isUploading = true; |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼ä¾åºå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼ä¾åºå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "ä¾åºå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download( |
| | | "/system/supplier/downloadTemplate", |
| | | {}, |
| | | "ä¾åºå导å
¥æ¨¡æ¿.xlsx" |
| | | ); |
| | | } |
| | | |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.isUploading = false; |
| | | if (response.code === 200) { |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | upload.open = false; |
| | | proxy.$refs["uploadRef"].clearFiles(); |
| | | getList(); |
| | | } else if (response.code === 500) { |
| | | proxy.$modal.msgError(response.msg); |
| | | } else { |
| | | proxy.$modal.msgWarning(response.msg); |
| | | } |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ 失败å¤ç */ |
| | | const handleFileError = (error, file, fileList) => { |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainUserId = userStore.id; |
| | | form.value.maintainTime = getCurrentDate(); |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getSupplier(row.id).then(res => { |
| | | form.value = { ...res.data }; |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitEdit(); |
| | | } else { |
| | | submitAdd(); |
| | | } |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const userList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const fileListDialogVisible = ref(false); |
| | | const recordId = ref(); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | | const dialogFormVisible = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | form: { |
| | | supplierName: "", |
| | | taxpayerIdentificationNum: "", |
| | | companyAddress: "", |
| | | companyPhone: "", |
| | | bankAccountName: "", |
| | | bankAccountNum: "", |
| | | contactUserName: "", |
| | | contactUserPhone: "", |
| | | maintainUserId: "", |
| | | maintainTime: "", |
| | | supplierType: "", |
| | | isWhite: "", |
| | | }, |
| | | rules: { |
| | | supplierName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxpayerIdentificationNum: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | companyPhone: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankAccountName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | bankAccountNum: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | contactUserName: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | contactUserPhone: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | supplierType: [{ required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | upload.isUploading = true; |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | listSupplier({ ...searchForm.value, ...page, isWhite: 0 }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼ä¾åºå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼ä¾åºå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/system/supplier/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "ä¾åºå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** ä¸è½½æ¨¡æ¿ */ |
| | | function importTemplate() { |
| | | proxy.download("/system/supplier/downloadTemplate", {}, "ä¾åºå导å
¥æ¨¡æ¿.xlsx"); |
| | | } |
| | | |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.isUploading = false; |
| | | if(response.code === 200){ |
| | | proxy.$modal.msgSuccess("æä»¶ä¸ä¼ æå"); |
| | | upload.open = false; |
| | | proxy.$refs["uploadRef"].clearFiles(); |
| | | getList(); |
| | | }else if(response.code === 500){ |
| | | proxy.$modal.msgError(response.msg); |
| | | }else{ |
| | | proxy.$modal.msgWarning(response.msg); |
| | | } |
| | | }; |
| | | |
| | | /** æä»¶ä¸ä¼ 失败å¤ç */ |
| | | const handleFileError = (error, file, fileList) => { |
| | | upload.isUploading = false; |
| | | proxy.$modal.msgError("æä»¶ä¸ä¼ 失败"); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | operationType.value = type; |
| | | form.value = {}; |
| | | form.value.maintainUserId = userStore.id; |
| | | form.value.maintainTime = getCurrentDate(); |
| | | userListNoPage().then((res) => { |
| | | userList.value = res.data; |
| | | }); |
| | | if (type === "edit") { |
| | | getSupplier(row.id).then((res) => { |
| | | form.value = { ...res.data }; |
| | | }); |
| | | } |
| | | dialogFormVisible.value = true; |
| | | }; |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate((valid) => { |
| | | if (valid) { |
| | | if (operationType.value === "edit") { |
| | | submitEdit(); |
| | | } else { |
| | | submitAdd(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | addSupplier(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | updateSupplier(form.value).then((res) => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | }; |
| | | // æäº¤æ°å¢ |
| | | const submitAdd = () => { |
| | | addSupplier(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // æäº¤ä¿®æ¹ |
| | | const submitEdit = () => { |
| | | updateSupplier(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | getList(); |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/system/supplier/export", { isWhite: 0 }, "ä¾åºåæ¡£æ¡.xlsx"); |
| | | proxy.download( |
| | | "/system/supplier/export", |
| | | { isWhite: 0 }, |
| | | "ä¾åºåæ¡£æ¡.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter(item => item.maintainUserName !== userStore.nickName); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | }; |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | // æ£æ¥æ¯å¦æä»äººç»´æ¤çæ°æ® |
| | | const unauthorizedData = selectedRows.value.filter( |
| | | item => item.maintainUserName !== userStore.nickName |
| | | ); |
| | | if (unauthorizedData.length > 0) { |
| | | proxy.$modal.msgWarning("ä¸å¯å é¤ä»äººç»´æ¤çæ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | tableLoading.value = true; |
| | | delSupplier(ids) |
| | | .then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | .then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | | const year = today.getFullYear(); |
| | | const month = String(today.getMonth() + 1).padStart(2, "0"); // æä»½ä»0å¼å§ |
| | | const day = String(today.getDate()).padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | } |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFileDialog = async row => { |
| | | recordId.value = row.id; |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | defineExpose({ |
| | | getList, |
| | | }); |
| | | </script> |
| | | |
| | |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç³è¯·é¨é¨ï¼"> |
| | | <el-select disabled |
| | | v-model="form.approveDeptId" |
| | | placeholder="éæ©é¨é¨"> |
| | | <el-option v-for="user in productOptions" |
| | | :key="user.deptId" |
| | | :label="user.deptName" |
| | | :value="user.deptId" /> |
| | | </el-select> |
| | | <el-input v-model="form.approveDeptName" |
| | | placeholder="" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | clearable |
| | | type="textarea" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="Number(props.approveType) === 4"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¥ééé¢ï¼"> |
| | | <el-input-number v-model="form.price" |
| | | disabled |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | {{ formatCurrency(currentQuotation.totalAmount) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | label="åä½" /> |
| | | <el-table-column prop="unitPrice" |
| | | label="åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.unitPrice) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | |
| | | <el-descriptions-item label="ååéé¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | {{ formatCurrency(currentPurchase.contractAmount) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | label="æ°é" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" |
| | | label="å«ç¨åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveUnitPrice) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" |
| | | label="å«ç¨æ»ä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveTotalPrice) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | |
| | | v-if="operationType === 'approval'"> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | :loading="submitLoading" |
| | | @click="submitForm(2)">ä¸éè¿</el-button> |
| | | <el-button type="primary" |
| | | :loading="submitLoading" |
| | | @click="submitForm(1)">éè¿</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import ImagePreview from "@/components/AttachmentPreview/image/index.vue"; |
| | | import { formatCurrency } from "@/utils/numberFormat"; |
| | | const emit = defineEmits(["close"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | |
| | | const deliveryLoading = ref(false); |
| | | const currentDelivery = ref({}); |
| | | const deliveryProductList = ref([]); |
| | | const submitLoading = ref(false); |
| | | const isQuotationApproval = computed(() => Number(props.approveType) === 6); |
| | | const isPurchaseApproval = computed(() => Number(props.approveType) === 5); |
| | | const isDeliveryApproval = computed(() => Number(props.approveType) === 7); |
| | |
| | | // 夿æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = |
| | | activities.value.findIndex(a => a.isShen) === activities.value.length - 1; |
| | | updateApproveNode({ ...currentActivity, isLast }).then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | submitLoading.value = true; |
| | | updateApproveNode({ ...currentActivity, isLast }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | |
| | | currentQuotation.value = {}; |
| | | purchaseLoading.value = false; |
| | | currentPurchase.value = {}; |
| | | submitLoading.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | |
| | | </div> |
| | | </div> |
| | | </FormDialog> |
| | | |
| | | <!-- æä»¶ç®¡çå¼¹çª --> |
| | | <FormDialog |
| | | v-model="filesDialogVisible" |
| | | title="æä»¶ç®¡ç" |
| | | :width="'900px'" |
| | | @close="closeFilesDialog" |
| | | @confirm="closeFilesDialog" |
| | | @cancel="closeFilesDialog" |
| | | > |
| | | <div class="file-manager"> |
| | | <!-- æä»¶ä¸ä¼ --> |
| | | <div class="upload-section"> |
| | | <el-upload |
| | | :action="uploadUrl" |
| | | :headers="uploadHeaders" |
| | | :on-success="handleUploadSuccess" |
| | | :on-error="handleUploadError" |
| | | :before-upload="beforeUpload" |
| | | name="files" |
| | | multiple |
| | | :show-file-list="false" |
| | | accept=".txt,.md,.docx,.xlsx,.xls,.pdf" |
| | | > |
| | | <el-button type="primary">ä¸ä¼ æä»¶</el-button> |
| | | </el-upload> |
| | | <el-button |
| | | type="success" |
| | | @click="saveFiles" |
| | | :disabled="uploadedBlobIds.length === 0" |
| | | :loading="savingFiles" |
| | | style="margin-left: 10px" |
| | | > |
| | | ä¿åæä»¶å
³è |
| | | </el-button> |
| | | <el-button |
| | | v-if="uploadedBlobIds.length > 0" |
| | | type="text" |
| | | @click="clearUploadedFiles" |
| | | style="margin-left: 10px" |
| | | > |
| | | æ¸
空å¾
ä¿åå表 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <!-- å¾
ä¿åçæä»¶å表 --> |
| | | <div v-if="uploadedBlobIds.length > 0" class="uploaded-list"> |
| | | <div class="uploaded-tip"> |
| | | <el-icon style="color: #409eff"><InfoFilled /></el-icon> |
| | | <span>å·²ä¸ä¼ {{ uploadedBlobIds.length }} 个æä»¶,请ç¹å»"ä¿åæä»¶å
³è"æé®è§¦ååéåå¤ç</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- æä»¶å表ä¸åéåç¶æ --> |
| | | <el-table :data="fileList" style="margin-top: 20px" border> |
| | | <el-table-column prop="fileName" label="æä»¶å" show-overflow-tooltip /> |
| | | <el-table-column prop="fileType" label="æä»¶ç±»å" width="100" /> |
| | | <el-table-column label="åéåç¶æ" width="120"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.vectorStatus)"> |
| | | {{ getStatusText(row.vectorStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="chunkCount" label="åçæ°" width="100" align="center" /> |
| | | <el-table-column label="é误信æ¯" width="200" show-overflow-tooltip> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.vectorError" style="color: #f56c6c">{{ row.vectorError }}</span> |
| | | <span v-else style="color: #909399">-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createTime" label="ä¸ä¼ æ¶é´" width="180" /> |
| | | <el-table-column label="æä½" width="150" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-button |
| | | v-if="row.vectorStatus === 3" |
| | | type="text" |
| | | @click="reprocessFile(row)" |
| | | > |
| | | éæ°å¤ç |
| | | </el-button> |
| | | <el-button type="text" @click="deleteFile(row)" style="color: #f56c6c"> |
| | | å é¤ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </FormDialog> |
| | | |
| | | <!-- ç¥è¯åºé®çå¼¹çª --> |
| | | <FormDialog |
| | | v-model="chatDialogVisible" |
| | | title="ç¥è¯åºé®ç" |
| | | :width="'800px'" |
| | | @close="closeChatDialog" |
| | | @confirm="closeChatDialog" |
| | | @cancel="closeChatDialog" |
| | | > |
| | | <div class="knowledge-chat"> |
| | | <div class="chat-header"> |
| | | <el-tag type="success">å½åç¥è¯åº: {{ currentKnowledgeBase?.title }}</el-tag> |
| | | </div> |
| | | |
| | | <!-- 对è¯åºå --> |
| | | <div class="chat-messages" ref="chatMessagesRef"> |
| | | <div |
| | | v-for="(msg, index) in messages" |
| | | :key="index" |
| | | :class="['message', msg.role]" |
| | | > |
| | | <div class="message-role">{{ msg.role === 'user' ? 'æ' : 'AI婿' }}</div> |
| | | <div class="message-content">{{ msg.content }}</div> |
| | | </div> |
| | | <div v-if="chatLoading" class="message assistant"> |
| | | <div class="message-role">AI婿</div> |
| | | <div class="message-content typing">æ£å¨æèä¸...</div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- è¾å
¥æ¡ --> |
| | | <div class="chat-input"> |
| | | <el-input |
| | | v-model="inputQuestion" |
| | | placeholder="请è¾å
¥é®é¢,æå车åé(Ctrl+Enterå¿«æ·åé)" |
| | | @keyup.enter="sendMessage" |
| | | :disabled="chatLoading" |
| | | > |
| | | <template #append> |
| | | <el-button @click="sendMessage" :loading="chatLoading">åé</el-button> |
| | | </template> |
| | | </el-input> |
| | | <div class="chat-actions"> |
| | | <el-button type="text" size="small" @click="clearMessages">æ¸
空对è¯</el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search, InfoFilled } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch, nextTick } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance, computed, watch } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue'; |
| | | import { |
| | | listKnowledgeBase, |
| | | delKnowledgeBase, |
| | | addKnowledgeBase, |
| | | updateKnowledgeBase, |
| | | getVectorStatus, |
| | | reprocessVector, |
| | | saveKnowledgeBaseFiles, |
| | | deleteKnowledgeBaseFile, |
| | | knowledgeChat |
| | | } from "@/api/collaborativeApproval/knowledgeBase.js"; |
| | | import { listKnowledgeBase, delKnowledgeBase,addKnowledgeBase,updateKnowledgeBase } from "@/api/collaborativeApproval/knowledgeBase.js"; |
| | | import useUserStore from '@/store/modules/user'; |
| | | import { userListNoPageByTenantId } from '@/api/system/user.js'; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = { |
| | |
| | | dialogTitle: "", |
| | | dialogType: "add", |
| | | viewDialogVisible: false, |
| | | currentKnowledge: {}, |
| | | filesDialogVisible: false, |
| | | currentKnowledgeBase: null, |
| | | fileList: [], |
| | | uploadedBlobIds: [], |
| | | savingFiles: false, |
| | | vectorStatusTimer: null, // åéåç¶æè½®è¯¢å®æ¶å¨ |
| | | chatDialogVisible: false, |
| | | messages: [], |
| | | inputQuestion: "", |
| | | chatLoading: false, |
| | | memoryId: "" |
| | | currentKnowledge: {} |
| | | }); |
| | | |
| | | const { |
| | |
| | | dialogTitle, |
| | | dialogType, |
| | | viewDialogVisible, |
| | | currentKnowledge, |
| | | filesDialogVisible, |
| | | currentKnowledgeBase, |
| | | fileList, |
| | | uploadedBlobIds, |
| | | savingFiles, |
| | | vectorStatusTimer, |
| | | chatDialogVisible, |
| | | messages, |
| | | inputQuestion, |
| | | chatLoading, |
| | | memoryId |
| | | currentKnowledge |
| | | } = toRefs(data); |
| | | |
| | | // 表åå¼ç¨ |
| | |
| | | // ç¨æ·ç¸å
³ |
| | | const userStore = useUserStore(); |
| | | const userList = ref([]); |
| | | // èå¤©æ¶æ¯å®¹å¨å¼ç¨ |
| | | const chatMessagesRef = ref(); |
| | | |
| | | // æä»¶ä¸ä¼ ç¸å
³ |
| | | const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/common/upload"; |
| | | const uploadHeaders = { Authorization: "Bearer " + getToken() }; |
| | | |
| | | // è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | |
| | | } |
| | | }, |
| | | { |
| | | label: "æä»¶æ°é", |
| | | prop: "fileCount", |
| | | width: 100, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "åçæ°é", |
| | | prop: "totalChunkCount", |
| | | width: 100, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "ä½¿ç¨æ¬¡æ°", |
| | | prop: "usageCount", |
| | | width: 100, |
| | |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | } |
| | | }, |
| | | { |
| | | name: "æä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openFilesDialog(row); |
| | | } |
| | | }, |
| | | { |
| | | name: "é®ç", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openChatDialog(row); |
| | | } |
| | | }, |
| | | { |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | |
| | | // â
GET请æ±ä½¿ç¨paramsä¼ å |
| | | listKnowledgeBase({ |
| | | current: page.value.current, |
| | | size: page.value.size, |
| | | title: searchForm.value.title, |
| | | type: searchForm.value.type |
| | | }) |
| | | listKnowledgeBase({...page.value, ...searchForm.value}) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | page.value.total = res.data.total; |
| | | |
| | | // 妿å½å页æ°è¶
è¿æ»é¡µæ°,éç½®å°ç¬¬1页并鿰æ¥è¯¢ |
| | | // 妿å½å页æ°è¶
è¿æ»é¡µæ°ï¼éç½®å°ç¬¬1页并鿰æ¥è¯¢ |
| | | const maxPage = Math.ceil(res.data.total / page.value.size) || 1; |
| | | if (page.value.current > maxPage && maxPage > 0) { |
| | | page.value.current = 1; |
| | | // éæ°æ¥è¯¢ç¬¬1é¡µæ°æ® |
| | | return getList(); |
| | | } |
| | | |
| | | tableData.value = res.data.records; |
| | | }) |
| | | .catch(err => { |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | console.error("æ¥è¯¢ç¥è¯åºå表失败:", err); |
| | | }); |
| | | }) |
| | | }; |
| | | |
| | | // å页å¤ç |
| | |
| | | const submitForm = async () => { |
| | | try { |
| | | await formRef.value.validate(); |
| | | |
| | | // â
POST请æ±ä½¿ç¨dataä¼ å,æç¡®åæ°ç»æ |
| | | const formData = { |
| | | title: form.value.title, |
| | | type: form.value.type, |
| | | scenario: form.value.scenario || "", |
| | | efficiency: form.value.efficiency || "", |
| | | problem: form.value.problem, |
| | | solution: form.value.solution, |
| | | keyPoints: form.value.keyPoints || "", |
| | | creator: form.value.creator || "", |
| | | usageCount: form.value.usageCount || 0 |
| | | }; |
| | | |
| | | if (dialogType.value === "add") { |
| | | // æ°å¢ç¥è¯ |
| | | addKnowledgeBase(formData).then(res => { |
| | | addKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ·»å æå"); |
| | | closeKnowledgeDialog(); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | console.error("æ·»å ç¥è¯åºå¤±è´¥:", err); |
| | | ElMessage.error(err.msg || "æ·»å 失败"); |
| | | }); |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } else { |
| | | // æ´æ°ç¥è¯ - æ·»å idåæ° |
| | | updateKnowledgeBase({ |
| | | id: form.value.id, |
| | | ...formData |
| | | }).then(res => { |
| | | updateKnowledgeBase({...form.value}).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("æ´æ°æå"); |
| | | closeKnowledgeDialog(); |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | console.error("æ´æ°ç¥è¯åºå¤±è´¥:", err); |
| | | ElMessage.error(err.msg || "æ´æ°å¤±è´¥"); |
| | | }); |
| | | ElMessage.error(err.msg); |
| | | }) |
| | | } |
| | | } catch (error) { |
| | | console.error("表åéªè¯å¤±è´¥:", error); |
| | |
| | | return; |
| | | } |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤,æ¯å¦ç¡®è®¤å é¤?", "å é¤", { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | // â
DELETE请æ±ä½¿ç¨dataä¼ éIDæ°ç» |
| | | // console.log(selectedIds.value); |
| | | delKnowledgeBase(selectedIds.value).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success("å 餿å"); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | } |
| | | }).catch(err => { |
| | | console.error("å é¤ç¥è¯åºå¤±è´¥:", err); |
| | | ElMessage.error(err.msg || "å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | |
| | | const handleExport = () => { |
| | | proxy.download('/knowledgeBase/export', { ...searchForm.value }, 'ç¥è¯åº.xlsx') |
| | | } |
| | | |
| | | // ============ æä»¶ç®¡çç¸å
³ ============ |
| | | |
| | | // æå¼æä»¶ç®¡çå¼¹çª |
| | | const openFilesDialog = (row) => { |
| | | currentKnowledgeBase.value = row; |
| | | filesDialogVisible.value = true; |
| | | loadFileList(); |
| | | }; |
| | | |
| | | // å è½½æä»¶å表 |
| | | const loadFileList = async () => { |
| | | if (!currentKnowledgeBase.value?.id) return; |
| | | |
| | | try { |
| | | const res = await getVectorStatus(currentKnowledgeBase.value.id); |
| | | fileList.value = res.data || []; |
| | | |
| | | // æ£æ¥æ¯å¦æå¤çä¸çæä»¶,妿æåå¯å¨è½®è¯¢ |
| | | const hasProcessing = res.data.some(item => item.vectorStatus === 1); |
| | | if (hasProcessing && !vectorStatusTimer.value) { |
| | | startVectorStatusPolling(); |
| | | } else if (!hasProcessing && vectorStatusTimer.value) { |
| | | stopVectorStatusPolling(); |
| | | } |
| | | } catch (error) { |
| | | console.error("å è½½æä»¶å表失败:", error); |
| | | ElMessage.error("å è½½æä»¶å表失败"); |
| | | } |
| | | }; |
| | | |
| | | // å¼å§è½®è¯¢åéåç¶æ |
| | | const startVectorStatusPolling = () => { |
| | | vectorStatusTimer.value = setInterval(async () => { |
| | | try { |
| | | const res = await getVectorStatus(currentKnowledgeBase.value.id); |
| | | fileList.value = res.data || []; |
| | | |
| | | // æ£æ¥æ¯å¦è¿æå¤çä¸çæä»¶ |
| | | const hasProcessing = res.data.some(item => item.vectorStatus === 1); |
| | | if (!hasProcessing) { |
| | | stopVectorStatusPolling(); |
| | | ElMessage.success("æææä»¶åéåå¤ç宿"); |
| | | } |
| | | } catch (error) { |
| | | console.error("轮询åéåç¶æå¤±è´¥:", error); |
| | | stopVectorStatusPolling(); |
| | | } |
| | | }, 3000); // æ¯3ç§è½®è¯¢ä¸æ¬¡ |
| | | }; |
| | | |
| | | // åæ¢è½®è¯¢åéåç¶æ |
| | | const stopVectorStatusPolling = () => { |
| | | if (vectorStatusTimer.value) { |
| | | clearInterval(vectorStatusTimer.value); |
| | | vectorStatusTimer.value = null; |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ åæ ¡éª |
| | | const beforeUpload = (file) => { |
| | | const allowedTypes = ['.txt', '.md', '.docx', '.xlsx', '.xls', '.pdf']; |
| | | const fileName = file.name.toLowerCase(); |
| | | const isAllowed = allowedTypes.some(type => fileName.endsWith(type)); |
| | | |
| | | if (!isAllowed) { |
| | | ElMessage.error('åªæ¯æ txtãmdãdocxãxlsxãxlsãpdf æ ¼å¼çæä»¶'); |
| | | return false; |
| | | } |
| | | |
| | | const isLt50M = file.size / 1024 / 1024 < 50; |
| | | if (!isLt50M) { |
| | | ElMessage.error('æä»¶å¤§å°ä¸è½è¶
è¿ 50MB'); |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | }; |
| | | |
| | | // ä¸ä¼ æå |
| | | const handleUploadSuccess = (response, file) => { |
| | | console.log("ä¸ä¼ ååº:", response); // è°è¯æ¥å¿ |
| | | |
| | | if (response.code === 200) { |
| | | // â
å端è¿åçæ¯ List<StorageBlobVO>,æä»¥dataæ¯æ°ç» |
| | | if (Array.isArray(response.data) && response.data.length > 0) { |
| | | // åæ°ç»ç¬¬ä¸ä¸ªå
ç´ çid |
| | | const blobId = response.data[0].id; |
| | | if (blobId) { |
| | | uploadedBlobIds.value.push(blobId); |
| | | ElMessage.success(`æä»¶ ${file.name} ä¸ä¼ æå`); |
| | | } else { |
| | | console.error("ä¸ä¼ ååºä¸æªæ¾å°id:", response.data[0]); |
| | | ElMessage.error("ä¸ä¼ 失败: æªè·åå°æä»¶ID"); |
| | | } |
| | | } else { |
| | | console.error("ä¸ä¼ ååºæ ¼å¼é误:", response); |
| | | ElMessage.error("ä¸ä¼ 失败: ååºæ ¼å¼é误"); |
| | | } |
| | | } else { |
| | | ElMessage.error(response.msg || "ä¸ä¼ 失败"); |
| | | } |
| | | }; |
| | | |
| | | // ä¸ä¼ 失败 |
| | | const handleUploadError = (error, file) => { |
| | | ElMessage.error(`æä»¶ ${file.name} ä¸ä¼ 失败`); |
| | | }; |
| | | |
| | | // ä¿åæä»¶å
³è |
| | | const saveFiles = async () => { |
| | | // åæ°æ ¡éª |
| | | if (!currentKnowledgeBase.value?.id) { |
| | | ElMessage.error("ç¥è¯åºä¿¡æ¯å¼å¸¸"); |
| | | return; |
| | | } |
| | | |
| | | if (uploadedBlobIds.value.length === 0) { |
| | | ElMessage.warning("请å
ä¸ä¼ æä»¶"); |
| | | return; |
| | | } |
| | | |
| | | savingFiles.value = true; |
| | | |
| | | try { |
| | | // â
POST请æ±ä½¿ç¨dataä¼ å,æç¡®åæ°ç»æ |
| | | await saveKnowledgeBaseFiles({ |
| | | knowledgeBaseId: currentKnowledgeBase.value.id, // ç¥è¯åºID |
| | | storageBlobIds: uploadedBlobIds.value // æä»¶blob IDæ°ç» |
| | | }); |
| | | |
| | | ElMessage.success("æä»¶å
³èä¿åæå,æ£å¨åå°å¤çåéå"); |
| | | uploadedBlobIds.value = []; |
| | | |
| | | // å»¶è¿å·æ°æä»¶å表,ç»åå°å¤çæ¶é´ |
| | | setTimeout(() => { |
| | | loadFileList(); |
| | | }, 1000); |
| | | } catch (error) { |
| | | console.error("ä¿åæä»¶å
³è失败:", error); |
| | | ElMessage.error("ä¿åæä»¶å
³è失败"); |
| | | } finally { |
| | | savingFiles.value = false; |
| | | } |
| | | }; |
| | | |
| | | // éæ°å¤çåéåçæä»¶ |
| | | const reprocessFile = async (row) => { |
| | | try { |
| | | await reprocessVector(row.id); |
| | | ElMessage.success("已鿰æäº¤åéåä»»å¡"); |
| | | // å»¶è¿å·æ° |
| | | setTimeout(() => { |
| | | loadFileList(); |
| | | }, 1000); |
| | | } catch (error) { |
| | | console.error("éæ°å¤ç失败:", error); |
| | | ElMessage.error("éæ°å¤ç失败"); |
| | | } |
| | | }; |
| | | |
| | | // æ¸
空å¾
ä¿åçæä»¶å表 |
| | | const clearUploadedFiles = () => { |
| | | uploadedBlobIds.value = []; |
| | | ElMessage.success("å·²æ¸
空å¾
ä¿åæä»¶å表"); |
| | | }; |
| | | |
| | | // å 餿件 |
| | | const deleteFile = async (row) => { |
| | | try { |
| | | await ElMessageBox.confirm( |
| | | "ç¡®å®è¦å é¤è¯¥æä»¶å?å é¤åå°æ æ³æ¢å¤åéæ°æ®", |
| | | "å é¤ç¡®è®¤", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ); |
| | | |
| | | // â
DELETE请æ±ä½¿ç¨dataä¼ éIDæ°ç» |
| | | await deleteKnowledgeBaseFile([row.id]); // 注æ: row.idæ¯åéè®°å½ID,䏿¯storageBlobId |
| | | ElMessage.success("å 餿å"); |
| | | loadFileList(); |
| | | } catch (error) { |
| | | if (error !== 'cancel') { |
| | | console.error("å é¤æä»¶å¤±è´¥:", error); |
| | | ElMessage.error("å é¤æä»¶å¤±è´¥"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // ç¶æææ¬æ å° |
| | | const getStatusText = (status) => { |
| | | const map = { |
| | | 0: 'å¾
å¤ç', |
| | | 1: 'å¤çä¸', |
| | | 2: '已宿', |
| | | 3: '失败' |
| | | }; |
| | | return map[status] || 'æªç¥'; |
| | | }; |
| | | |
| | | // ç¶ææ ç¾ç±»åæ å° |
| | | const getStatusType = (status) => { |
| | | const map = { |
| | | 0: 'info', |
| | | 1: 'warning', |
| | | 2: 'success', |
| | | 3: 'danger' |
| | | }; |
| | | return map[status] || 'info'; |
| | | }; |
| | | |
| | | // å
³éæä»¶ç®¡çå¼¹çª |
| | | const closeFilesDialog = () => { |
| | | filesDialogVisible.value = false; |
| | | currentKnowledgeBase.value = null; |
| | | fileList.value = []; |
| | | uploadedBlobIds.value = []; |
| | | stopVectorStatusPolling(); // åæ¢è½®è¯¢ |
| | | getList(); // å·æ°ä¸»å表,æ´æ°æä»¶æ°é |
| | | }; |
| | | |
| | | // ============ ç¥è¯åºé®çç¸å
³ ============ |
| | | |
| | | // çæUUIDçfallbackæ¹æ¡ |
| | | const generateUUID = () => { |
| | | if (typeof crypto !== 'undefined' && crypto.randomUUID) { |
| | | return crypto.randomUUID(); |
| | | } |
| | | // fallback: å
¼å®¹ä¸æ¯æ crypto.randomUUID çç¯å¢ |
| | | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { |
| | | const r = Math.random() * 16 | 0; |
| | | const v = c === 'x' ? r : (r & 0x3 | 0x8); |
| | | return v.toString(16); |
| | | }); |
| | | }; |
| | | |
| | | // æå¼é®çå¼¹çª |
| | | const openChatDialog = (row) => { |
| | | currentKnowledgeBase.value = row; |
| | | chatDialogVisible.value = true; |
| | | memoryId.value = generateUUID(); |
| | | messages.value = []; |
| | | inputQuestion.value = ""; |
| | | }; |
| | | |
| | | // åéæ¶æ¯ |
| | | const sendMessage = async () => { |
| | | // åæ°æ ¡éª |
| | | if (!inputQuestion.value.trim()) { |
| | | ElMessage.warning("请è¾å
¥é®é¢"); |
| | | return; |
| | | } |
| | | |
| | | if (!currentKnowledgeBase.value?.id) { |
| | | ElMessage.error("ç¥è¯åºä¿¡æ¯å¼å¸¸"); |
| | | return; |
| | | } |
| | | |
| | | const question = inputQuestion.value.trim(); |
| | | |
| | | // æ·»å ç¨æ·æ¶æ¯ |
| | | messages.value.push({ |
| | | role: 'user', |
| | | content: question |
| | | }); |
| | | |
| | | inputQuestion.value = ""; |
| | | chatLoading.value = true; |
| | | |
| | | // æ»å¨å°åºé¨ |
| | | await nextTick(); |
| | | scrollToBottom(); |
| | | |
| | | try { |
| | | // â
æµå¼è¯·æ±ä½¿ç¨Fetch API |
| | | const response = await knowledgeChat({ |
| | | knowledgeBaseId: currentKnowledgeBase.value.id, // ç¥è¯åºID |
| | | memoryId: memoryId.value, // ä¼è¯ID |
| | | question: question // ç¨æ·é®é¢ |
| | | }); |
| | | |
| | | if (!response.ok) { |
| | | const errorText = await response.text(); |
| | | throw new Error(errorText || '请æ±å¤±è´¥'); |
| | | } |
| | | |
| | | // â
å端è¿å text/stream;charset=utf-8 |
| | | const reader = response.body.getReader(); |
| | | const decoder = new TextDecoder(); |
| | | let aiContent = ''; |
| | | |
| | | messages.value.push({ role: 'assistant', content: '' }); |
| | | |
| | | while (true) { |
| | | const { done, value } = await reader.read(); |
| | | if (done) break; |
| | | |
| | | const text = decoder.decode(value, { stream: true }); // â
æ·»å streamé项 |
| | | aiContent += text; |
| | | messages.value[messages.value.length - 1].content = aiContent; |
| | | |
| | | // æ»å¨å°åºé¨ |
| | | await nextTick(); |
| | | scrollToBottom(); |
| | | } |
| | | |
| | | // 妿AIè¿å空å
容,æ¾ç¤ºæç¤º |
| | | if (!aiContent.trim()) { |
| | | messages.value[messages.value.length - 1].content = 'æ±æ,ç¥è¯åºä¸æªæ¾å°ç¸å
³å
容,请å°è¯å
¶ä»é®é¢ã'; |
| | | } |
| | | } catch (error) { |
| | | console.error("é®ç请æ±å¤±è´¥:", error); |
| | | ElMessage.error("é®ç请æ±å¤±è´¥,请ç¨åéè¯"); |
| | | messages.value.push({ |
| | | role: 'assistant', |
| | | content: 'æ±æ,åçäºé误,请ç¨åéè¯' |
| | | }); |
| | | } finally { |
| | | chatLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | // æ¸
ç©ºå¯¹è¯ |
| | | const clearMessages = () => { |
| | | ElMessageBox.confirm( |
| | | "ç¡®å®è¦æ¸
空ææå¯¹è¯è®°å½å?", |
| | | "æ¸
空确认", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning" |
| | | } |
| | | ).then(() => { |
| | | messages.value = []; |
| | | memoryId.value = generateUUID(); // éæ°çæä¼è¯ID |
| | | ElMessage.success("对è¯å·²æ¸
空"); |
| | | }).catch(() => { |
| | | // ç¨æ·åæ¶ |
| | | }); |
| | | }; |
| | | |
| | | // æ»å¨å°åºé¨ |
| | | const scrollToBottom = () => { |
| | | if (chatMessagesRef.value) { |
| | | chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight; |
| | | } |
| | | }; |
| | | |
| | | // å
³éé®çå¼¹çª |
| | | const closeChatDialog = () => { |
| | | chatDialogVisible.value = false; |
| | | currentKnowledgeBase.value = null; |
| | | messages.value = []; |
| | | inputQuestion.value = ""; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | /* æä»¶ç®¡çæ ·å¼ */ |
| | | .file-manager { |
| | | padding: 20px 0; |
| | | } |
| | | |
| | | .upload-section { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .uploaded-list { |
| | | margin-top: 16px; |
| | | padding: 12px; |
| | | background: #f0f9ff; |
| | | border-radius: 6px; |
| | | border: 1px solid #b3d8ff; |
| | | } |
| | | |
| | | .uploaded-tip { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | color: #409eff; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | /* ç¥è¯åºé®çæ ·å¼ */ |
| | | .knowledge-chat { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 500px; |
| | | } |
| | | |
| | | .chat-header { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .chat-messages { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | padding: 16px; |
| | | background: #f5f7fa; |
| | | border-radius: 8px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .message { |
| | | margin-bottom: 16px; |
| | | max-width: 80%; |
| | | } |
| | | |
| | | .message.user { |
| | | margin-left: auto; |
| | | text-align: right; |
| | | } |
| | | |
| | | .message.assistant { |
| | | margin-right: auto; |
| | | } |
| | | |
| | | .message-role { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .message-content { |
| | | display: inline-block; |
| | | padding: 10px 14px; |
| | | border-radius: 8px; |
| | | line-height: 1.6; |
| | | word-wrap: break-word; |
| | | white-space: pre-wrap; |
| | | } |
| | | |
| | | .message.user .message-content { |
| | | background: #409eff; |
| | | color: white; |
| | | } |
| | | |
| | | .message.assistant .message-content { |
| | | background: white; |
| | | color: #303133; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .typing { |
| | | animation: typing 1.5s infinite; |
| | | } |
| | | |
| | | @keyframes typing { |
| | | 0%, 50%, 100% { |
| | | opacity: 1; |
| | | } |
| | | 25%, 75% { |
| | | opacity: 0.5; |
| | | } |
| | | } |
| | | |
| | | .chat-input { |
| | | margin-top: auto; |
| | | } |
| | | |
| | | .chat-actions { |
| | | margin-top: 8px; |
| | | text-align: right; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | |
| | | <!-- ç³è¯·ç±»åéæ© --> |
| | | <el-card class="type-card"> |
| | | <div class="type-selector"> |
| | | <div |
| | | v-for="type in applicationTypes" |
| | | :key="type.value" |
| | | class="type-item" |
| | | :class="{ active: currentType === type.value }" |
| | | @click="changeType(type.value)" |
| | | > |
| | | <div class="type-icon"> |
| | | <el-icon :size="24"><component :is="type.icon"/></el-icon> |
| | | </div> |
| | | <div class="type-info"> |
| | | <div class="type-name">{{ type.name }}</div> |
| | | <div class="type-desc">{{ type.desc }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®ç³è¯·è¡¨å --> |
| | | <el-card> |
| | | <div class="form-header"> |
| | | <h3>{{ getCurrentTypeName() }}ç³è¯·</h3> |
| | | </div> |
| | | |
| | | <el-form |
| | | ref="meetingFormRef" |
| | | :model="meetingForm" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®ä¸»é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®å®¤" prop="roomId"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="è¯·éæ©ä¼è®®å®¤" style="width: 100%"> |
| | | <el-option |
| | | v-for="room in meetingRooms" |
| | | :key="room.id" |
| | | :label="`${room.name} (${room.location})`" |
| | | :value="room.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="主æäºº" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¥æ" prop="meetingDate"> |
| | | <el-date-picker |
| | | v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¼è®®æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-time-picker |
| | | v-model="meetingForm.startTime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | :disabled-hours="disabledStartHours" |
| | | :disabled-minutes="disabledStartMinutes" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime"> |
| | | <el-time-picker |
| | | v-model="meetingForm.endTime" |
| | | placeholder="è¯·éæ©ç»ææ¶é´" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | | :disabled-hours="disabledEndHours" |
| | | :disabled-minutes="disabledEndMinutes" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="person in employees" |
| | | :key="person.id" |
| | | :label="`${person.staffName}${person.postName ? ` (${person.postName})` : ''}`" |
| | | :value="person.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¼è®®è¯´æ" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥ä¼è®®è¯´æ" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | <el-button type="primary" @click="submitForm">æäº¤</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | <div> |
| | | |
| | | <!-- ç³è¯·ç±»åéæ© --> |
| | | <el-card class="type-card"> |
| | | <div class="type-selector"> |
| | | <div |
| | | v-for="type in applicationTypes" |
| | | :key="type.value" |
| | | class="type-item" |
| | | :class="{ active: currentType === type.value }" |
| | | @click="changeType(type.value)" |
| | | > |
| | | <div class="type-icon"> |
| | | <el-icon :size="24"><component :is="type.icon"/></el-icon> |
| | | </div> |
| | | <div class="type-info"> |
| | | <div class="type-name">{{ type.name }}</div> |
| | | <div class="type-desc">{{ type.desc }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ä¼è®®ç³è¯·è¡¨å --> |
| | | <el-card> |
| | | <div class="form-header"> |
| | | <h3>{{ getCurrentTypeName() }}ç³è¯·</h3> |
| | | </div> |
| | | |
| | | <el-form |
| | | ref="meetingFormRef" |
| | | :model="meetingForm" |
| | | :rules="rules" |
| | | label-width="100px" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®ä¸»é¢" prop="title"> |
| | | <el-input v-model="meetingForm.title" placeholder="请è¾å
¥ä¼è®®ä¸»é¢"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®å®¤" prop="roomId"> |
| | | <el-select v-model="meetingForm.roomId" placeholder="è¯·éæ©ä¼è®®å®¤" style="width: 100%"> |
| | | <el-option |
| | | v-for="room in meetingRooms" |
| | | :key="room.id" |
| | | :label="`${room.name} (${room.location})`" |
| | | :value="room.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="主æäºº" prop="host"> |
| | | <el-input v-model="meetingForm.host" placeholder="请è¾å
¥ä¸»æäººå§å"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¼è®®æ¥æ" prop="meetingDate"> |
| | | <el-date-picker |
| | | v-model="meetingForm.meetingDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©ä¼è®®æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | :disabled-date="disabledDate" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <!-- 空åï¼ä¿æå¸å± --> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼å§æ¶é´" prop="startTime"> |
| | | <el-select |
| | | v-model="meetingForm.startTime" |
| | | placeholder="è¯·éæ©å¼å§æ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in startTimeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç»ææ¶é´" prop="endTime"> |
| | | <el-select |
| | | v-model="meetingForm.endTime" |
| | | placeholder="è¯·éæ©ç»ææ¶é´" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="time in endTimeOptions" |
| | | :key="time.value" |
| | | :label="time.label" |
| | | :value="time.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-form-item label="åä¼äººå" prop="participants"> |
| | | <el-select |
| | | v-model="meetingForm.participants" |
| | | multiple |
| | | filterable |
| | | placeholder="è¯·éæ©åä¼äººå" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="person in employees" |
| | | :key="person.id" |
| | | :label="`${person.staffName}${person.postName ? ` (${person.postName})` : ''}`" |
| | | :value="person.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¼è®®è¯´æ" prop="description"> |
| | | <el-input |
| | | v-model="meetingForm.description" |
| | | type="textarea" |
| | | :rows="4" |
| | | placeholder="请è¾å
¥ä¼è®®è¯´æ" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="form-footer"> |
| | | <el-button @click="resetForm">éç½®</el-button> |
| | | <el-button type="primary" @click="submitForm">æäº¤</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | |
| | | |
| | | // ç³è¯·ç±»åé项 |
| | | const applicationTypes = ref([ |
| | | { |
| | | value: 'approval', |
| | | name: 'å®¡æ¹æµç¨ä¼è®®', |
| | | desc: 'éè¦ç»è¿å¤çº§å®¡æ¹çä¼è®®ç³è¯·', |
| | | icon: Document |
| | | }, |
| | | { |
| | | value: 'department', |
| | | name: 'é¨é¨çº§ä¼è®®', |
| | | desc: 'é¨é¨å
é¨ä¼è®®ç³è¯·æµç¨', |
| | | icon: Promotion |
| | | }, |
| | | { |
| | | value: 'notification', |
| | | name: 'ä¼è®®éç¥', |
| | | desc: 'æ é审æ¹ç´æ¥åå¸çä¼è®®éç¥', |
| | | icon: Bell |
| | | } |
| | | { |
| | | value: 'approval', |
| | | name: 'å®¡æ¹æµç¨ä¼è®®', |
| | | desc: 'éè¦ç»è¿å¤çº§å®¡æ¹çä¼è®®ç³è¯·', |
| | | icon: Document |
| | | }, |
| | | { |
| | | value: 'department', |
| | | name: 'é¨é¨çº§ä¼è®®', |
| | | desc: 'é¨é¨å
é¨ä¼è®®ç³è¯·æµç¨', |
| | | icon: Promotion |
| | | }, |
| | | { |
| | | value: 'notification', |
| | | name: 'ä¼è®®éç¥', |
| | | desc: 'æ é审æ¹ç´æ¥åå¸çä¼è®®éç¥', |
| | | icon: Bell |
| | | } |
| | | ]) |
| | | |
| | | // è¡¨åæ°æ® |
| | | const meetingForm = reactive({ |
| | | title: '', |
| | | type: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: [], |
| | | description: '' |
| | | title: '', |
| | | type: '', |
| | | roomId: '', |
| | | host: '', |
| | | meetingDate: '', |
| | | startTime: '', |
| | | endTime: '', |
| | | participants: [], |
| | | description: '' |
| | | }) |
| | | |
| | | // 表åå¼ç¨ |
| | |
| | | // åå·¥å表 |
| | | const employees = ref([]) |
| | | |
| | | // æ¶é´é项ï¼ä»¥åå°æ¶ä¸ºé´éï¼ |
| | | const timeOptions = ref([]) |
| | | |
| | | const getTimeInMinutes = (time) => { |
| | | if (!time) return -1 |
| | | const [hour, minute] = time.split(':').map(Number) |
| | | return hour * 60 + minute |
| | | if (!time) return -1 |
| | | const [hour, minute] = time.split(':').map(Number) |
| | | return hour * 60 + minute |
| | | } |
| | | |
| | | const isToday = (dateText) => { |
| | | if (!dateText) return false |
| | | const [year, month, day] = dateText.split('-').map(Number) |
| | | const now = new Date() |
| | | return year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate() |
| | | if (!dateText) return false |
| | | const [year, month, day] = dateText.split('-').map(Number) |
| | | const now = new Date() |
| | | return year === now.getFullYear() && month === now.getMonth() + 1 && day === now.getDate() |
| | | } |
| | | |
| | | const validateStartTime = (_rule, value, callback) => { |
| | | if (!value) { |
| | | callback() |
| | | return |
| | | } |
| | | callback() |
| | | if (!value) { |
| | | callback() |
| | | return |
| | | } |
| | | |
| | | if (isToday(meetingForm.meetingDate)) { |
| | | const now = new Date() |
| | | const currentMinutes = now.getHours() * 60 + now.getMinutes() |
| | | if (getTimeInMinutes(value) > currentMinutes) { |
| | | callback(new Error('å½å¤©å¼å§æ¶é´ä¸è½æäºå½åæ¶é´')) |
| | | return |
| | | } |
| | | } |
| | | |
| | | callback() |
| | | } |
| | | |
| | | const validateEndTime = (_rule, value, callback) => { |
| | | if (!value || !meetingForm.startTime) { |
| | | callback() |
| | | return |
| | | } |
| | | if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) { |
| | | callback(new Error('ç»ææ¶é´å¿
须大äºå¼å§æ¶é´')) |
| | | return |
| | | } |
| | | callback() |
| | | if (!value || !meetingForm.startTime) { |
| | | callback() |
| | | return |
| | | } |
| | | |
| | | if (getTimeInMinutes(value) <= getTimeInMinutes(meetingForm.startTime)) { |
| | | callback(new Error('ç»ææ¶é´å¿
须大äºå¼å§æ¶é´')) |
| | | return |
| | | } |
| | | |
| | | callback() |
| | | } |
| | | |
| | | // è¡¨åæ ¡éªè§å |
| | | const rules = { |
| | | title: [{required: true, message: '请è¾å
¥ä¼è®®ä¸»é¢', trigger: 'blur'}], |
| | | roomId: [{required: true, message: 'è¯·éæ©ä¼è®®å®¤', trigger: 'change'}], |
| | | host: [{required: true, message: '请è¾å
¥ä¸»æäºº', trigger: 'blur'}], |
| | | meetingDate: [{required: true, message: 'è¯·éæ©ä¼è®®æ¥æ', trigger: 'change'}], |
| | | startTime: [ |
| | | {required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change'}, |
| | | {validator: validateStartTime, trigger: 'change'} |
| | | ], |
| | | endTime: [ |
| | | {required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change'}, |
| | | {validator: validateEndTime, trigger: 'change'} |
| | | ], |
| | | participants: [{required: true, message: 'è¯·éæ©åä¼äººå', trigger: 'change'}] |
| | | title: [{required: true, message: '请è¾å
¥ä¼è®®ä¸»é¢', trigger: 'blur'}], |
| | | roomId: [{required: true, message: 'è¯·éæ©ä¼è®®å®¤', trigger: 'change'}], |
| | | host: [{required: true, message: '请è¾å
¥ä¸»æäºº', trigger: 'blur'}], |
| | | meetingDate: [{required: true, message: 'è¯·éæ©ä¼è®®æ¥æ', trigger: 'change'}], |
| | | startTime: [ |
| | | {required: true, message: 'è¯·éæ©å¼å§æ¶é´', trigger: 'change'}, |
| | | {validator: validateStartTime, trigger: 'change'} |
| | | ], |
| | | endTime: [ |
| | | {required: true, message: 'è¯·éæ©ç»ææ¶é´', trigger: 'change'}, |
| | | {validator: validateEndTime, trigger: 'change'} |
| | | ], |
| | | participants: [{required: true, message: 'è¯·éæ©åä¼äººå', trigger: 'change'}] |
| | | } |
| | | |
| | | // æ¶é´éæ©å¨ç¦ç¨é»è¾ |
| | | const disabledStartHours = () => { |
| | | const hours = [] |
| | | for (let h = 0; h < 24; h++) { |
| | | if (h < 8 || h > 18) hours.push(h) |
| | | } |
| | | if (isToday(meetingForm.meetingDate)) { |
| | | const now = new Date() |
| | | for (let h = 8; h < now.getHours(); h++) { |
| | | if (!hours.includes(h)) hours.push(h) |
| | | } |
| | | } |
| | | return hours |
| | | const startTimeOptions = computed(() => { |
| | | if (!isToday(meetingForm.meetingDate)) { |
| | | return timeOptions.value |
| | | } |
| | | const now = new Date() |
| | | const currentMinutes = now.getHours() * 60 + now.getMinutes() |
| | | return timeOptions.value.filter(item => getTimeInMinutes(item.value) <= currentMinutes) |
| | | }) |
| | | |
| | | const endTimeOptions = computed(() => { |
| | | if (!meetingForm.startTime) { |
| | | return timeOptions.value |
| | | } |
| | | const startMinutes = getTimeInMinutes(meetingForm.startTime) |
| | | return timeOptions.value.filter(item => getTimeInMinutes(item.value) > startMinutes) |
| | | }) |
| | | |
| | | // åå§åæ¶é´é项 |
| | | const initTimeOptions = () => { |
| | | const options = [] |
| | | const now = new Date() |
| | | const currentHour = now.getHours() |
| | | const currentMinute = now.getMinutes() |
| | | // meetingDate æ¯ "yyyy-MM-dd" |
| | | const meetingDate = new Date(meetingForm.meetingDate) |
| | | |
| | | const isSameDay = |
| | | now.getFullYear() === meetingDate.getFullYear() && |
| | | now.getMonth() === meetingDate.getMonth() && |
| | | now.getDate() === meetingDate.getDate() |
| | | |
| | | console.log('æ¯å¦åä¸å¤©:', isSameDay) |
| | | for (let hour = 8; hour <= 18; hour++) { |
| | | // å¼å§æ¶é´å¿
é¡»æäºå½åæ¶é´ |
| | | if (hour < currentHour && isSameDay) { |
| | | continue |
| | | } |
| | | if (hour === currentHour && currentMinute > 30 && isSameDay) { |
| | | continue |
| | | } |
| | | // æ¯ä¸ªå°æ¶æ·»å 两个éé¡¹ï¼æ´ç¹ååç¹ |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:00`, |
| | | label: `${hour.toString().padStart(2, '0')}:00` |
| | | }) |
| | | |
| | | if (hour < 18) { // 18:00ä¹å没æåç¹é项 |
| | | options.push({ |
| | | value: `${hour.toString().padStart(2, '0')}:30`, |
| | | label: `${hour.toString().padStart(2, '0')}:30` |
| | | }) |
| | | } |
| | | } |
| | | timeOptions.value = options |
| | | } |
| | | |
| | | const disabledStartMinutes = (hour) => { |
| | | const minutes = [] |
| | | for (let m = 0; m < 60; m++) { |
| | | if (m !== 0 && m !== 30) minutes.push(m) |
| | | } |
| | | if (isToday(meetingForm.meetingDate)) { |
| | | const now = new Date() |
| | | if (hour === now.getHours()) { |
| | | if (now.getMinutes() >= 30) { |
| | | minutes.push(0) |
| | | } |
| | | } |
| | | } |
| | | return minutes |
| | | } |
| | | |
| | | const disabledEndHours = () => { |
| | | const hours = [] |
| | | for (let h = 0; h < 24; h++) { |
| | | if (h < 8 || h > 18) hours.push(h) |
| | | } |
| | | if (meetingForm.startTime) { |
| | | const startHour = parseInt(meetingForm.startTime.split(':')[0]) |
| | | for (let h = 8; h < startHour; h++) { |
| | | if (!hours.includes(h)) hours.push(h) |
| | | } |
| | | } |
| | | return hours |
| | | } |
| | | |
| | | const disabledEndMinutes = (hour) => { |
| | | const minutes = [] |
| | | for (let m = 0; m < 60; m++) { |
| | | if (m !== 0 && m !== 30) minutes.push(m) |
| | | } |
| | | if (meetingForm.startTime) { |
| | | const startHour = parseInt(meetingForm.startTime.split(':')[0]) |
| | | const startMinute = parseInt(meetingForm.startTime.split(':')[1]) |
| | | if (hour === startHour) { |
| | | if (startMinute >= 0) minutes.push(0) |
| | | if (startMinute >= 30) minutes.push(30) |
| | | // only keep minutes > startMinute |
| | | for (let m = 0; m <= startMinute; m++) { |
| | | if (!minutes.includes(m)) minutes.push(m) |
| | | } |
| | | } |
| | | } |
| | | return minutes |
| | | } |
| | | watch(() => meetingForm.meetingDate, () => { |
| | | if (meetingForm.startTime && !startTimeOptions.value.some(item => item.value === meetingForm.startTime)) { |
| | | meetingForm.startTime = '' |
| | | } |
| | | if (meetingForm.endTime && !endTimeOptions.value.some(item => item.value === meetingForm.endTime)) { |
| | | meetingForm.endTime = '' |
| | | } |
| | | if (meetingForm.startTime) { |
| | | meetingFormRef.value?.validateField('startTime') |
| | | } |
| | | if (meetingForm.endTime) { |
| | | meetingFormRef.value?.validateField('endTime') |
| | | } |
| | | initTimeOptions() |
| | | }) |
| | | |
| | | watch(() => meetingForm.startTime, () => { |
| | | if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) { |
| | | meetingForm.endTime = '' |
| | | } |
| | | if (meetingForm.endTime) { |
| | | meetingFormRef.value?.validateField('endTime') |
| | | } |
| | | |
| | | if (meetingForm.endTime && getTimeInMinutes(meetingForm.endTime) <= getTimeInMinutes(meetingForm.startTime)) { |
| | | meetingForm.endTime = '' |
| | | } |
| | | if (meetingForm.endTime) { |
| | | meetingFormRef.value?.validateField('endTime') |
| | | } |
| | | |
| | | }) |
| | | |
| | | // ç¦ç¨æ¥æï¼ç¦ç¨ä»å¤©ä¹åçæ¥æï¼ |
| | | const disabledDate = (time) => { |
| | | // ç¦ç¨ä»å¤©ä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 86400000 |
| | | // ç¦ç¨ä»å¤©ä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 86400000 |
| | | } |
| | | |
| | | // 忢ç³è¯·ç±»å |
| | | const changeType = (type) => { |
| | | currentType.value = type |
| | | currentType.value = type |
| | | } |
| | | |
| | | // è·åå½åç±»ååç§° |
| | | const getCurrentTypeName = () => { |
| | | const type = applicationTypes.value.find(t => t.value === currentType.value) |
| | | return type ? type.name : '' |
| | | const type = applicationTypes.value.find(t => t.value === currentType.value) |
| | | return type ? type.name : '' |
| | | } |
| | | |
| | | // é置表å |
| | | const resetForm = () => { |
| | | meetingFormRef.value?.resetFields() |
| | | meetingFormRef.value?.resetFields() |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | meetingFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | |
| | | let formData = {...meetingForm} |
| | | formData.applicationType = currentType.value |
| | | formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00` |
| | | formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00` |
| | | formData.participants = JSON.stringify(formData.participants) |
| | | console.log(formData) |
| | | saveMeetingApplication(formData).then(() => { |
| | | |
| | | // 模ææäº¤æä½ |
| | | ElMessage.success(`${getCurrentTypeName()}æäº¤æå`) |
| | | resetForm() |
| | | }) |
| | | |
| | | } |
| | | }) |
| | | meetingFormRef.value?.validate((valid) => { |
| | | if (valid) { |
| | | |
| | | let formData = {...meetingForm} |
| | | formData.applicationType = currentType.value |
| | | formData.startTime = `${meetingForm.meetingDate} ${meetingForm.startTime}:00` |
| | | formData.endTime = `${meetingForm.meetingDate} ${meetingForm.endTime}:00` |
| | | formData.participants = JSON.stringify(formData.participants) |
| | | console.log(formData) |
| | | saveMeetingApplication(formData).then(() => { |
| | | |
| | | // 模ææäº¤æä½ |
| | | ElMessage.success(`${getCurrentTypeName()}æäº¤æå`) |
| | | |
| | | // æ ¹æ®ä¸åç±»åæ§è¡ä¸åæä½ |
| | | switch (currentType.value) { |
| | | case 'approval': |
| | | ElMessage.info('ä¼è®®å·²æäº¤å®¡æ¹æµç¨') |
| | | break |
| | | case 'department': |
| | | ElMessage.info('é¨é¨çº§ä¼è®®ç³è¯·å·²æäº¤') |
| | | break |
| | | case 'notification': |
| | | ElMessage.info('ä¼è®®éç¥å·²åå¸') |
| | | break |
| | | } |
| | | resetForm() |
| | | }) |
| | | |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶åå§å |
| | | onMounted(() => { |
| | | getRoomEnum().then(res => { |
| | | meetingRooms.value = res.data |
| | | }) |
| | | staffOnJobListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | staffState: 1 |
| | | }).then(res => { |
| | | employees.value = res.data.records.sort((a, b) => (a.postName || '').localeCompare(b.postName || '')) |
| | | }) |
| | | initTimeOptions() |
| | | getRoomEnum().then(res => { |
| | | meetingRooms.value = res.data |
| | | }) |
| | | staffOnJobListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | staffState: 1 |
| | | }).then(res => { |
| | | employees.value = res.data.records.sort((a, b) => (a.postName || '').localeCompare(b.postName || '')) |
| | | }) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .page-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .page-header h2 { |
| | | margin: 0; |
| | | color: #303133; |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .type-card { |
| | | margin-bottom: 20px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .type-selector { |
| | | display: flex; |
| | | gap: 20px; |
| | | display: flex; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .type-item { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 8px; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | } |
| | | |
| | | .type-item:hover { |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | border-color: #409eff; |
| | | box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .type-item.active { |
| | | border-color: #409eff; |
| | | background-color: #ecf5ff; |
| | | border-color: #409eff; |
| | | background-color: #ecf5ff; |
| | | } |
| | | |
| | | .type-icon { |
| | | margin-right: 15px; |
| | | color: #409eff; |
| | | margin-right: 15px; |
| | | color: #409eff; |
| | | } |
| | | |
| | | .type-name { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .type-desc { |
| | | font-size: 14px; |
| | | color: #909399; |
| | | font-size: 14px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .form-header { |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 15px; |
| | | border-bottom: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .form-header h3 { |
| | | margin: 0; |
| | | color: #303133; |
| | | margin: 0; |
| | | color: #303133; |
| | | } |
| | | |
| | | .form-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 30px; |
| | | padding-top: 20px; |
| | | border-top: 1px solid #ebeef5; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 10px; |
| | | margin-top: 30px; |
| | | padding-top: 20px; |
| | | border-top: 1px solid #ebeef5; |
| | | } |
| | | </style> |
| | |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150"/> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.staffCount }}人 |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="审æ¹ç¶æ" align="center" width="120"> |
| | |
| | | let resp = await getExamineList({...searchForm, ...queryParams}) |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = room ? `${room.name}(${room.location})` : '' |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.length |
| | | it.staffCount = staffs.size |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => { |
| | | return { |
| | |
| | | if (endIdx === -1) { |
| | | endIdx = timeSlots.value.length |
| | | } |
| | | // å¾å延伏䏿 ¼ï¼è®©ä¼è®®æ ¼åè¦çå°ç»ææ¶é´åä¸ |
| | | const displayEndIdx = Math.min(endIdx + 1, timeSlots.value.length) |
| | | if (startIdx !== -1) { |
| | | console.log('endIdx111', endIdx) |
| | | if (startIdx !== -1 && endIdx !== -1) { |
| | | // æ 记被å ç¨çæ¶é´æ®µ |
| | | for (let i = startIdx; i < displayEndIdx; i++) { |
| | | for (let i = startIdx; i < endIdx; i++) { |
| | | occupiedSlots.add(timeSlots.value[i].value) |
| | | } |
| | | |
| | |
| | | cells.push({ |
| | | type: 'meeting', |
| | | meeting: meeting, |
| | | span: displayEndIdx - startIdx, |
| | | span: endIdx - startIdx, |
| | | startTime: meeting.startTime, |
| | | endTime: meeting.endTime |
| | | }) |
| | |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150"/> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.staffCount }}人 |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="åå¸ç¶æ" align="center" width="120"> |
| | |
| | | let resp = await getMeetingPublish({...searchForm, ...queryParams}) |
| | | approvalList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = room ? `${room.name}(${room.location})` : '' |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.length |
| | | it.staffCount = staffs.size |
| | | it.status = it.publishStatus |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id=>id === staff.id)).map(staff => { |
| | |
| | | <el-table-column prop="location" label="ä¼è®®å°ç¹" align="center" width="150" /> |
| | | <el-table-column prop="participants" label="åä¼äººæ°" align="center" width="100"> |
| | | <template #default="scope"> |
| | | {{ scope.row.staffCount }}人 |
| | | {{ scope.row.participants.length }}人 |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" align="center" width="200" fixed="right"> |
| | |
| | | let resp = await getMeetingPublish({ ...searchForm, ...queryParams }) |
| | | meetingList.value = resp.data.records.map(it => { |
| | | let room = roomEnum.value.find(room => it.roomId === room.id) |
| | | it.location = room ? `${room.name}(${room.location})` : '' |
| | | it.location = `${room.name}(${room.location})` |
| | | let staffs = JSON.parse(it.participants) |
| | | it.staffCount = staffs.length |
| | | it.staffCount = staffs.size |
| | | it.meetingTime = `${it.meetingDate} ${dayjs(it.startTime).format('HH:mm:ss')} ~ ${dayjs(it.endTime).format('HH:mm:ss')}` |
| | | it.participants = staffList.value.filter(staff => staffs.some(id => id === staff.id)).map(staff => { |
| | | return { |
| | |
| | | <p><strong>主æäººï¼</strong>${row.host}</p> |
| | | <p><strong>åä¼äººåï¼</strong></p> |
| | | <ol> |
| | | ${(row.participants || []).map(p => `<li>${p?.name || ''}</li>`).join('')} |
| | | ${row.participants.map(p => `<li>${p.name}</li>`).join('')} |
| | | </ol> |
| | | <p><strong>ä¼è®®å
容ï¼</strong></p> |
| | | <ol> |
| | |
| | | }, |
| | | rules: { |
| | | purchaseContractNumber: [ |
| | | { required: false, message: "请è¾å
¥", trigger: "blur" }, |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | productData.value = []; |
| | | fileList.value = []; |
| | | if (operationType.value == "add") { |
| | | form.value.purchaseContractNumber = ""; |
| | | createPurchaseNo().then(res => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | } |
| | | userListNoPage().then(res => { |
| | | userList.value = res.data; |
| | |
| | | } |
| | | // æäº¤è¡¨å |
| | | const submitForm = n => { |
| | | proxy.$refs["formRef"].validate(async valid => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (productData.value.length > 0) { |
| | | form.value.productData = proxy.HaveJson(productData.value); |
| | |
| | | form.value.tempFileIds = tempFileIds; |
| | | form.value.type = 2; |
| | | form.value.approvalStatus = n; |
| | | |
| | | // 妿éè´ååå·ä¸ºç©ºï¼åæ ¹æ®å½å
¥æ¥æèªå¨çæ |
| | | if (!form.value.purchaseContractNumber) { |
| | | try { |
| | | const purchaseNoRes = await createPurchaseNo(form.value.entryDate); |
| | | if (purchaseNoRes?.data) { |
| | | form.value.purchaseContractNumber = purchaseNoRes.data; |
| | | } |
| | | } catch (error) { |
| | | console.error("çæéè´ååå·å¤±è´¥:", error); |
| | | proxy.$modal.msgWarning("çæéè´ååå·å¤±è´¥"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | addOrEditPurchase(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | |
| | | formatData: (v) => (v === "active" ? "çæä¸" : "å·²åºæ¢"), |
| | | formatType: (v) => (v === "active" ? "success" : "info"), |
| | | }, |
| | | { label: "已读人æ°", prop: "readCount", width: 100 }, |
| | | // { label: "已读人æ°", prop: "readCount", width: 100 }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | <!-- ç¨å°ç³è¯·ç®¡ç --> |
| | | <div class="tab-content"> |
| | | <el-row :gutter="20" class="mb-20 "> |
| | |
| | | :isShowPagination="true" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | |
| | | <!-- ç¨å°ç³è¯·å¯¹è¯æ¡ --> |
| | |
| | | const sealFormRef = ref() |
| | | const userList = ref([]) |
| | | const sealForm = reactive({ |
| | | id: null, |
| | | applicationNum: '', |
| | | title: '', |
| | | sealType: '', |
| | |
| | | |
| | | // ç¨å°ç³è¯·è¡¨æ ¼åé
ç½®ï¼éå¨ getStatusText/getSealTypeText çä¹åå®ä¹ï¼ |
| | | const sealTableColumn = ref([ |
| | | { label: 'ç³è¯·ç¼å·', prop: 'applicationNum' }, |
| | | { label: 'ç³è¯·ç¼å·', prop: 'applicationNum',}, |
| | | { label: 'ç³è¯·æ é¢', prop: 'title', showOverflowTooltip: true }, |
| | | { label: 'ç³è¯·äºº', prop: 'createUserName' }, |
| | | { label: 'ç³è¯·äºº', prop: 'createUserName', }, |
| | | { label: 'æå±é¨é¨', prop: 'department', width: 150 }, |
| | | { |
| | | label: 'ç¨å°ç±»å', |
| | |
| | | formatData: (v) => getStatusText(v), |
| | | formatType: (v) => getStatusType(v) |
| | | }, |
| | | { label: '审æ¹äºº', prop: 'approveUserName', width: 100 }, |
| | | { |
| | | dataType: 'action', |
| | | label: 'æä½', |
| | | width: 250, |
| | | width: 200, |
| | | fixed: 'right', |
| | | align: 'center', |
| | | operation: [ |
| | | { |
| | | name: '审æ¹', |
| | | clickFun: (row) => approveSeal(row), |
| | | showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId |
| | | showHide: (row) => row.status === 'pending' |
| | | }, |
| | | { |
| | | name: 'æç»', |
| | | clickFun: (row) => rejectSeal(row), |
| | | showHide: (row) => row.status === 'pending' && Number(userStore.id) === row.approveUserId |
| | | showHide: (row) => row.status === 'pending' |
| | | }, |
| | | { |
| | | name: 'éæ°ç³è¯·', |
| | | clickFun: (row) => reapplySeal(row), |
| | | showHide: (row) => row.status === 'rejected' && Number(userStore.id) === row.createUser |
| | | }, |
| | | { name: '详æ
', clickFun: (row) => viewSealDetail(row) } |
| | | { name: '详æ
', clickFun: (row) => viewSealDetail(row) } |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | // æç´¢å°ç« ç³è¯· |
| | | const searchSealApplications = () => { |
| | | page.current = 1 |
| | | page.current=1 |
| | | getSealApplicationList() |
| | | |
| | | // ElMessage.success('æç´¢å®æ') |
| | | } |
| | | // éç½®å°ç« ç³è¯·æç´¢ |
| | | const resetSealSearch = () => { |
| | |
| | | sealSearchForm.applicationNum = '' |
| | | searchSealApplications() |
| | | } |
| | | |
| | | // éæ°ç³è¯·ç¨å° |
| | | const reapplySeal = (row) => { |
| | | // é¢å¡«è¡¨åæ°æ® |
| | | Object.assign(sealForm, { |
| | | id: row.id, |
| | | applicationNum: row.applicationNum, |
| | | title: row.title, |
| | | sealType: row.sealType, |
| | | reason: row.reason, |
| | | approveUserId: row.approveUserId, |
| | | urgency: row.urgency || 'normal', |
| | | status: 'pending', |
| | | storageBlobDTOs: row.storageBlobVOList || [] |
| | | }) |
| | | showSealApplyDialog.value = true |
| | | } |
| | | |
| | | // æäº¤ç¨å°ç³è¯· |
| | | const submitSealApplication = async () => { |
| | | try { |
| | | await sealFormRef.value.validate() |
| | | const request = sealForm.id ? updateSealApplication : addSealApplication |
| | | request(sealForm).then(res => { |
| | | if (res.code == 200) { |
| | | ElMessage.success(sealForm.id ? 'éæ°ç³è¯·æå' : 'ç³è¯·æäº¤æå') |
| | | addSealApplication(sealForm).then(res => { |
| | | if(res.code == 200){ |
| | | ElMessage.success('ç³è¯·æäº¤æå') |
| | | closeSealApplyDialog() |
| | | getSealApplicationList() |
| | | Object.assign(sealForm, { |
| | | id: null, |
| | | applicationNum: '', |
| | | title: '', |
| | | sealType: '', |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | applicationNum: '', |
| | | title: '', |
| | | sealType: '', |
| | | reason: '', |
| | | approveUserId: '', |
| | | urgency: 'normal', |
| | | status: 'pending', |
| | | storageBlobDTOs: [] |
| | | }) |
| | | } |
| | | }).catch(err => { |
| | | console.log(err.msg) |
| | | }) |
| | | |
| | | } catch (error) { |
| | | } |
| | | } |
| | | |
| | | // å
³éç¨å°ç³è¯·å¯¹è¯æ¡ |
| | | const closeSealApplyDialog = () => { |
| | | // æ¸
ç©ºè¡¨åæ°æ® |
| | | Object.assign(sealForm, { |
| | | id: null, |
| | | applicationNum: '', |
| | | title: '', |
| | | sealType: '', |
| | |
| | | }).then(() => { |
| | | row.status = 'approved' |
| | | updateSealApplication(row).then(res => { |
| | | if (res.code == 200) { |
| | | if(res.code == 200){ |
| | | ElMessage.success('审æ¹éè¿') |
| | | getSealApplicationList() |
| | | } |
| | |
| | | inputErrorMessage: 'æç»åå ä¸è½ä¸ºç©º' |
| | | }).then(({ value }) => { |
| | | row.status = 'rejected' |
| | | row.reason = value |
| | | updateSealApplication(row).then(res => { |
| | | if (res.code == 200) { |
| | | if(res.code == 200){ |
| | | ElMessage.success('å·²æç»ç³è¯·') |
| | | getSealApplicationList() |
| | | } |
| | |
| | | const getSealApplicationList = async () => { |
| | | tableLoading.value = true |
| | | listSealApplication(page, sealSearchForm) |
| | | .then(res => { |
| | | sealApplications.value = res.data.records |
| | | page.total = res.data.total |
| | | tableLoading.value = false |
| | | }).catch(err => { |
| | | tableLoading.value = false |
| | | }) |
| | | .then(res => { |
| | | sealApplications.value = res.data.records |
| | | page.total = res.data.total |
| | | tableLoading.value = false |
| | | }).catch(err => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | // å页ååå¤ç |
| | | const paginationChange = (obj) => { |
| | |
| | | .attachment-table { |
| | | border-radius: 4px; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è¯åï¼" prop="rating"> |
| | | <el-rate |
| | | v-model="form.rating" |
| | | :disabled="operationType === 'view'" |
| | | :max="5" |
| | | show-text |
| | | :texts="['é叏䏿»¡æ', '䏿»¡æ', 'ä¸è¬', '满æ', 'é常满æ']" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="è¯ä»·å
容æè¿°ï¼" prop="evaluation"> |
| | | <el-input |
| | | v-model="form.evaluation" |
| | | placeholder="请è¾å
¥è¯ä»·å
容æè¿°" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | type="textarea" |
| | | :rows="3" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | |
| | | disposeUserId: "", |
| | | disDate: "", |
| | | disRes: "", |
| | | rating: null, |
| | | evaluation: "", |
| | | }, |
| | | rules: { |
| | | feedbackDate: [{required: true, message: "è¯·éæ©", trigger: "change"}], |
| | |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <view-dia ref="viewDia"></view-dia> |
| | | <FileList v-if="fileDialogVisible" v-model:visible="fileDialogVisible" record-type="after_sales_service" :record-id="recordId" /> |
| | | </div> |
| | | </template> |
| | |
| | | <script setup> |
| | | import {onMounted, ref, reactive, toRefs, getCurrentInstance, nextTick, defineAsyncComponent} from "vue"; |
| | | import FormDia from "@/views/customerService/afterSalesHandling/components/formDia.vue"; |
| | | import ViewDia from "@/views/customerService/components/viewDia.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | afterSalesServiceListPage, |
| | |
| | | }, |
| | | align: "center" |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | prop: "productModelQuantities", |
| | | align: "center", |
| | | formatData: params => { |
| | | if (!params) return "0"; |
| | | return String(params).split(',').reduce((sum, val) => sum + (Number(val) || 0), 0); |
| | | } |
| | | }, |
| | | { |
| | | label: "é®é¢æè¿°", |
| | | prop: "proDesc", |
| | |
| | | } |
| | | }, |
| | | { |
| | | name: "æ¥ç", |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("view", row); |
| | | viewDia.value?.openDialog(row); |
| | | }, |
| | | }, |
| | | // TODO ä¸ºåæ¥åæ·»å ç |
| | |
| | | selectedRows.value = selection; |
| | | }; |
| | | const formDia = ref() |
| | | const viewDia = ref() |
| | | const fileListRef = ref(null) |
| | | const fileListDialogVisible = ref(false) |
| | | const currentFileRow = ref(null) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="å®åå详æ
" width="80%" @close="closeDia"> |
| | | <div v-loading="loading"> |
| | | <span class="descriptions">åºç¡èµæ</span> |
| | | <el-descriptions :column="4" border style="margin-top: 10px;"> |
| | | <el-descriptions-item label="客æ·åç§°">{{ detail.customerName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®åç±»å">{{ getDictLabel(classificationOptions, detail.serviceType) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
³èéå®åå·">{{ detail.salesContractNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç´§æ¥ç¨åº¦">{{ getDictLabel(degreeOfUrgencyOptions, detail.urgency) }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥åç¼å·">{{ detail.afterSalesServiceNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çç¶æ"> |
| | | <el-tag :type="detail.status === 1 ? 'danger' : 'success'" size="small"> |
| | | {{ detail.status === 1 ? 'å¾
å¤ç' : 'å·²å¤ç' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°äºº">{{ detail.checkNickName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å馿¥æ">{{ detail.feedbackDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="客æ·è¯æ±" :span="4">{{ detail.proDesc || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <span class="descriptions">å¤çä¿¡æ¯</span> |
| | | <el-descriptions :column="3" border style="margin-top: 10px;"> |
| | | <el-descriptions-item label="å¤ç人">{{ detail.disposeNickName || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çæ¥æ">{{ detail.disDate || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¤çç»æ" :span="3">{{ detail.disRes || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </div> |
| | | |
| | | <div style="margin-top: 20px;"> |
| | | <span class="descriptions">å
³è产å</span> |
| | | <el-table :data="tableData" border style="width: 100%; margin-top: 10px;"> |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="productCategory" label="产å大类" align="center" /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" align="center" /> |
| | | <el-table-column prop="unit" label="åä½" align="center" /> |
| | | <el-table-column prop="expressCompany" label="å¿«éå
¬å¸" align="center" /> |
| | | <el-table-column prop="expressNumber" label="å¿«éåå·" align="center" /> |
| | | <el-table-column prop="shippingCarNumber" label="å货车ç" align="center" /> |
| | | <el-table-column prop="shippingDate" label="åè´§æ¥æ" align="center" /> |
| | | <el-table-column prop="quantity" label="å®åæ°é" align="center" /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, getCurrentInstance } from 'vue'; |
| | | import { getAfterSalesServiceById } from '@/api/customerService/index.js'; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const dialogVisible = ref(false); |
| | | const loading = ref(false); |
| | | const detail = ref({}); |
| | | const tableData = ref([]); |
| | | |
| | | const { post_sale_waiting_list, degree_of_urgency } = proxy.useDict( |
| | | "post_sale_waiting_list", |
| | | "degree_of_urgency" |
| | | ); |
| | | const classificationOptions = computed(() => post_sale_waiting_list?.value || []); |
| | | const degreeOfUrgencyOptions = computed(() => degree_of_urgency?.value || []); |
| | | |
| | | const getDictLabel = (options, value) => { |
| | | if (!value) return '-'; |
| | | const item = options.find(i => String(i.value) === String(value)); |
| | | return item ? item.label : value; |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisible.value = true; |
| | | loading.value = true; |
| | | detail.value = {}; |
| | | tableData.value = []; |
| | | |
| | | getAfterSalesServiceById(row.id).then(res => { |
| | | loading.value = false; |
| | | if (res.code === 200) { |
| | | detail.value = res.data || {}; |
| | | let productData = res.data?.salesLedgerDto?.productData || []; |
| | | const selectedIds = res.data.productModelIds ? String(res.data.productModelIds).split(",") : []; |
| | | tableData.value = productData.filter(item => selectedIds.includes(String(item.id))); |
| | | } |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .descriptions { |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002fa7; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| | |
| | | {{ getShippingStatusText(row) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #quantity="{ row }"> |
| | | <el-input-number v-model="row.quantity" :min="0" size="small" /> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | |
| | | salesContractNo: "", |
| | | proDesc: "", |
| | | customerName: "", |
| | | productModelQuantities: "", |
| | | }, |
| | | rules: { |
| | | customerName: [ |
| | |
| | | align: "center", |
| | | }, |
| | | { label: "åè´§æ¥æ", prop: "shippingDate", minWidth: 100, align: "center" }, |
| | | { label: "æ°é", prop: "quantity", width: 100 }, |
| | | { label: "ç¨ç(%)", prop: "taxRate", width: 100 }, |
| | | { |
| | | label: "å«ç¨åä»·(å
)", |
| | | prop: "taxInclusiveUnitPrice", |
| | | width: 160, |
| | | formatData: formatCurrency, |
| | | }, |
| | | { |
| | | label: "å«ç¨æ»ä»·(å
)", |
| | | prop: "taxInclusiveTotalPrice", |
| | | width: 160, |
| | | formatData: formatCurrency, |
| | | }, |
| | | { |
| | | label: "ä¸å«ç¨æ»ä»·(å
)", |
| | | prop: "taxExclusiveTotalPrice", |
| | | width: 160, |
| | | formatData: formatCurrency, |
| | | label: "æ°é", |
| | | prop: "quantity", |
| | | width: 140, |
| | | dataType: "slot", |
| | | slot: "quantity", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | |
| | | form.value.customerId = null; |
| | | } |
| | | getSalesLedger({ |
| | | customerName: form.value.customerName, |
| | | customerId: form.value.customerId, |
| | | }).then(res => { |
| | | if (res.code === 200) { |
| | | associatedSalesOrderNumberOptions.value = res.data.records.map(item => ({ |
| | |
| | | form.value = { ...row }; |
| | | if (form.value.customerName) { |
| | | const res = await getSalesLedger({ |
| | | customerName: form.value.customerName, |
| | | customerId: form.value.customerId, |
| | | }); |
| | | if (res?.code === 200) { |
| | | console.log(res); |
| | |
| | | id: item.id, |
| | | }) |
| | | ); |
| | | const opt = associatedSalesOrderNumberOptions.value.find( |
| | | item => item.value === form.value.salesContractNo |
| | | ); |
| | | if (opt) { |
| | | let restoredData = (opt.productData || []).map(normalizeProductRow); |
| | | const selectedIds = form.value.productModelIds ? String(form.value.productModelIds).split(",") : []; |
| | | const quantities = form.value.productModelQuantities ? String(form.value.productModelQuantities).split(",") : []; |
| | | tableData.value = restoredData.filter(item => selectedIds.includes(String(item.id))).map(item => { |
| | | let qIndex = selectedIds.indexOf(String(item.id)); |
| | | if (qIndex !== -1 && qIndex < quantities.length && quantities[qIndex] !== "") { |
| | | item.quantity = Number(quantities[qIndex]); |
| | | } |
| | | return item; |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | console.log(form.value); |
| | |
| | | form.value.productModelIds = tableData.value |
| | | .map(item => item.id) |
| | | .join(","); |
| | | form.value.productModelQuantities = tableData.value |
| | | .map(item => item.quantity || 0) |
| | | .join(","); |
| | | if (operationType.value === "add") { |
| | | afterSalesServiceAdd(form.value).then(response => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | |
| | | </div> |
| | | <form-dia ref="formDia" |
| | | @close="handleQuery"></form-dia> |
| | | <view-dia ref="viewDia"></view-dia> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | nextTick, |
| | | } from "vue"; |
| | | import FormDia from "@/views/customerService/feedbackRegistration/components/formDia.vue"; |
| | | import ViewDia from "@/views/customerService/components/viewDia.vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | afterSalesServiceDelete, |
| | |
| | | { |
| | | icon: markRaw(FolderOpened), |
| | | count: 0, |
| | | label: "å·²å¤ç", |
| | | label: "å¾
å¤ç", |
| | | color: "#ff9a2e", |
| | | bgColor: "#fff5e6", |
| | | }, |
| | | { |
| | | icon: markRaw(UserFilled), |
| | | count: 0, |
| | | label: "已宿", |
| | | label: "å·²å¤ç", |
| | | color: "#00b42a", |
| | | bgColor: "#e6f7ed", |
| | | }, |
| | |
| | | align: "center", |
| | | }, |
| | | { |
| | | label: "æ°é", |
| | | prop: "productModelQuantities", |
| | | align: "center", |
| | | formatData: params => { |
| | | if (!params) return "0"; |
| | | return String(params).split(',').reduce((sum, val) => sum + (Number(val) || 0), 0); |
| | | } |
| | | }, |
| | | { |
| | | label: "客æ·è¯æ±", |
| | | prop: "proDesc", |
| | | width: 300, |
| | |
| | | dataType: "action", |
| | | label: "æä½", |
| | | fixed: "right", |
| | | width: 180, |
| | | align: "center", |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | viewDia.value?.openDialog(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | |
| | | selectedRows.value = selection; |
| | | }; |
| | | const formDia = ref(); |
| | | const viewDia = ref(); |
| | | |
| | | // åå
¸è·å |
| | | /* |
| | | /* |
| | | post_sale_waiting_list æ°å¢çå®ååç±» |
| | | degree_of_urgency æ°å¢çç´§æ¥ç¨åº¦ |
| | | work_order_status 主页çå·¥åç¶æ |
| | |
| | | if (res.code === 200) { |
| | | const statsData = Array.isArray(res.data) ? res.data : []; |
| | | statsList.value[0].count = getStatsCountByStatus(statsData, 3); |
| | | statsList.value[1].count = getStatsCountByStatus(statsData, 2); |
| | | statsList.value[2].count = getStatsCountByStatus(statsData, 1); |
| | | statsList.value[1].count = getStatsCountByStatus(statsData, 1); |
| | | statsList.value[2].count = getStatsCountByStatus(statsData, 2); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | form.value = {...row} |
| | | form.value.inspector = form.value.inspectorIds.split(',').map(Number) |
| | | |
| | | // å¤çWEEKLYé¢ççåæ¾ï¼å°frequencyDetailæå为weekåtime |
| | | if (form.value.frequencyType === 'WEEKLY' && form.value.frequencyDetail) { |
| | | const parts = form.value.frequencyDetail.split(',') |
| | | form.value.week = parts[0] || '' |
| | | form.value.time = parts[1] || '' |
| | | form.value.frequencyDetail = '' |
| | | } |
| | | |
| | | // 妿æè®¾å¤IDï¼èªå¨è®¾ç½®è®¾å¤ä¿¡æ¯ |
| | | if (form.value.taskId) { |
| | | setDeviceModel(form.value.taskId); |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisible" |
| | | title="ä¸ä¼ å·¡æ£è®°å½" |
| | | width="980px" |
| | | @close="handleClose" |
| | | @cancel="handleClose" |
| | | > |
| | | <FormDialog v-model="dialogVisible" |
| | | :title="operationType === 'view' ? 'å·¡æ£è®°å½è¯¦æ
' : 'ä¸ä¼ å·¡æ£è®°å½'" |
| | | width="980px" |
| | | :operation-type="operationType" |
| | | @close="handleClose" |
| | | @cancel="handleClose"> |
| | | <main class="upload-content"> |
| | | <el-card v-if="taskInfo" class="section-card"> |
| | | <el-descriptions :column="1" border> |
| | | <el-card v-if="taskInfo" |
| | | class="section-card"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="å·¡æ£ä»»å¡åç§°"> |
| | | {{ taskInfo.taskName || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å·¡æ£é¡¹ç®"> |
| | | {{ taskInfo.inspectionProject || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ§è¡å·¡æ£äºº"> |
| | | <template v-if="formattedInspector && formattedInspector.length"> |
| | | <el-tag v-for="tag in formattedInspector" |
| | | :key="tag" |
| | | size="small" |
| | | style="margin-right: 4px"> |
| | | {{ tag }} |
| | | </el-tag> |
| | | </template> |
| | | <span v-else>--</span> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="颿¬¡"> |
| | | {{ formatFrequencyType(taskInfo.frequencyType) || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å¼å§æ¥æä¸æ¶é´"> |
| | | {{ formatFrequencyDetail(taskInfo.frequencyDetail) || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°äºº"> |
| | | {{ taskInfo.registrant || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°æ¥æ"> |
| | | {{ formatDateTime(taskInfo.createTime) || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨"> |
| | | {{ taskInfo.remarks || "-" }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-card> |
| | | |
| | | <el-card class="section-card"> |
| | | <h3>å·¡æ£ç¶æ</h3> |
| | | <el-radio-group v-model="hasException"> |
| | | <el-radio-group v-model="hasException" |
| | | :disabled="operationType === 'view'"> |
| | | <el-radio-button :value="false">æ£å¸¸</el-radio-button> |
| | | <el-radio-button :value="true">åå¨å¼å¸¸</el-radio-button> |
| | | </el-radio-group> |
| | | </el-card> |
| | | |
| | | <el-card v-if="hasException === true" class="section-card"> |
| | | <el-card v-if="hasException === true" |
| | | class="section-card"> |
| | | <h3>å¼å¸¸æè¿°</h3> |
| | | <el-input |
| | | v-model="abnormalDescription" |
| | | type="textarea" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :rows="4" |
| | | placeholder="请æè¿°å¼å¸¸æ
åµ..." |
| | | /> |
| | | <el-input v-model="abnormalDescription" |
| | | type="textarea" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :rows="4" |
| | | :disabled="operationType === 'view'" |
| | | placeholder="请æè¿°å¼å¸¸æ
åµ..." /> |
| | | </el-card> |
| | | |
| | | <el-card v-if="hasException === true" class="section-card"> |
| | | <el-tabs v-model="currentUploadType"> |
| | | <el-tab-pane label="ç产å" name="before" /> |
| | | <el-tab-pane label="ç产ä¸" name="after" /> |
| | | <el-tab-pane label="ç产å" name="issue" /> |
| | | </el-tabs> |
| | | |
| | | <div class="upload-buttons"> |
| | | <el-upload |
| | | :show-file-list="false" |
| | | :http-request="uploadFile" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading" |
| | | accept="image/*" |
| | | > |
| | | <el-button type="primary" :loading="uploading"> |
| | | <el-icon><Camera /></el-icon> |
| | | <el-card v-if="hasException === true" |
| | | class="section-card"> |
| | | <div class="upload-buttons" |
| | | v-if="operationType !== 'view'"> |
| | | <el-upload :show-file-list="false" |
| | | :http-request="uploadFile" |
| | | :disabled="beforeModelValue.length >= uploadConfig.limit || uploading" |
| | | accept="image/*"> |
| | | <el-button type="primary" |
| | | :loading="uploading"> |
| | | <el-icon> |
| | | <Camera /> |
| | | </el-icon> |
| | | éæ©å¾ç |
| | | </el-button> |
| | | </el-upload> |
| | | |
| | | <el-upload |
| | | :show-file-list="false" |
| | | :http-request="uploadFile" |
| | | :disabled="getCurrentFiles().length >= uploadConfig.limit || uploading" |
| | | accept="video/*" |
| | | > |
| | | <el-button type="success" :loading="uploading"> |
| | | <el-icon><VideoCamera /></el-icon> |
| | | <el-upload :show-file-list="false" |
| | | :http-request="uploadFile" |
| | | :disabled="beforeModelValue.length >= uploadConfig.limit || uploading" |
| | | accept="video/*"> |
| | | <el-button type="success" |
| | | :loading="uploading"> |
| | | <el-icon> |
| | | <VideoCamera /> |
| | | </el-icon> |
| | | éæ©è§é¢ |
| | | </el-button> |
| | | </el-upload> |
| | | </div> |
| | | |
| | | <el-progress |
| | | v-if="uploading" |
| | | :percentage="uploadProgress" |
| | | class="upload-progress" |
| | | /> |
| | | |
| | | <div v-if="getCurrentFiles().length" class="file-list"> |
| | | <div |
| | | v-for="(file, index) in getCurrentFiles()" |
| | | :key="file.uid || file.id || index" |
| | | class="file-item" |
| | | > |
| | | <el-progress v-if="uploading" |
| | | :percentage="uploadProgress" |
| | | class="upload-progress" /> |
| | | <div v-if="beforeModelValue.length" |
| | | class="file-list"> |
| | | <div v-for="(file, index) in beforeModelValue" |
| | | :key="file.uid || file.id || index" |
| | | class="file-item"> |
| | | <div class="file-preview-container"> |
| | | <el-image |
| | | v-if="file.type === 'image' || !file.type" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | fit="cover" |
| | | class="file-preview" |
| | | :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]" |
| | | preview-teleported |
| | | /> |
| | | |
| | | <div v-else class="video-preview" @click="previewVideo(file)"> |
| | | <el-icon><VideoCamera /></el-icon> |
| | | <el-image v-if="file.type === 'image' || !file.type" |
| | | :src="file.url || file.tempFilePath || file.path || file.downloadUrl" |
| | | fit="cover" |
| | | class="file-preview" |
| | | :preview-src-list="[file.url || file.tempFilePath || file.path || file.downloadUrl]" |
| | | preview-teleported /> |
| | | <div v-else |
| | | class="video-preview" |
| | | @click="previewVideo(file)"> |
| | | <el-icon> |
| | | <VideoCamera /> |
| | | </el-icon> |
| | | <span>è§é¢</span> |
| | | </div> |
| | | |
| | | <el-button |
| | | class="delete-btn" |
| | | type="danger" |
| | | circle |
| | | size="small" |
| | | @click="removeFile(index)" |
| | | > |
| | | <el-icon><Close /></el-icon> |
| | | <el-button class="delete-btn" |
| | | type="danger" |
| | | circle |
| | | size="small" |
| | | v-if="operationType !== 'view'" |
| | | @click="removeFile(index)"> |
| | | <el-icon> |
| | | <Close /> |
| | | </el-icon> |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div class="file-info"> |
| | | <div class="file-name"> |
| | | {{ file.bucketFilename || file.name || (file.type === "image" ? "å¾ç" : "è§é¢") }} |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-empty |
| | | v-else |
| | | :description="`è¯·éæ©è¦ä¸ä¼ ç${getUploadTypeText()}å¾çæè§é¢`" |
| | | /> |
| | | |
| | | <el-alert |
| | | class="upload-summary" |
| | | type="info" |
| | | :closable="false" |
| | | :title="`ç产åï¼${beforeModelValue.length}个æä»¶ | ç产ä¸ï¼${afterModelValue.length}个æä»¶ | ç产åï¼${issueModelValue.length}个æä»¶`" |
| | | /> |
| | | <el-empty v-else |
| | | :description="operationType === 'view' ? 'ææ å¼å¸¸ç
§çæè§é¢' : 'è¯·éæ©è¦ä¸ä¼ çå·¡æ£å¾çæè§é¢'" /> |
| | | </el-card> |
| | | |
| | | <el-result |
| | | v-if="hasException === false" |
| | | icon="success" |
| | | title="设å¤è¿è¡æ£å¸¸" |
| | | sub-title="æ éä¸ä¼ ç
§ç" |
| | | /> |
| | | <el-result v-if="hasException === false" |
| | | icon="success" |
| | | title="设å¤è¿è¡æ£å¸¸" |
| | | :sub-title="operationType === 'view' ? '' : 'æ éä¸ä¼ ç
§ç'" /> |
| | | </main> |
| | | |
| | | <template #footer> |
| | | <footer class="footer-buttons"> |
| | | <el-button type="primary" @click="submitUpload">æäº¤</el-button> |
| | | <el-button v-if="hasException === true" type="warning" @click="goToRepair"> |
| | | <el-button type="primary" |
| | | v-if="operationType !== 'view'" |
| | | @click="submitUpload">æäº¤</el-button> |
| | | <el-button v-if="hasException === true && operationType !== 'view'" |
| | | type="warning" |
| | | @click="goToRepair"> |
| | | æ°å¢æ¥ä¿® |
| | | </el-button> |
| | | <el-button @click="handleClose">åæ¶</el-button> |
| | | <el-button @click="handleClose">{{ operationType === 'view' ? 'å
³é' : 'åæ¶' }}</el-button> |
| | | </footer> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <el-dialog |
| | | v-model="showVideoDialog" |
| | | :title="currentVideoFile?.originalFilename || currentVideoFile?.name || 'è§é¢é¢è§'" |
| | | width="720px" |
| | | > |
| | | <video |
| | | v-if="currentVideoFile" |
| | | :src="currentVideoFile.url || currentVideoFile.downloadUrl" |
| | | class="video-player" |
| | | controls |
| | | autoplay |
| | | /> |
| | | <el-dialog v-model="showVideoDialog" |
| | | :title="currentVideoFile?.originalFilename || currentVideoFile?.name || 'è§é¢é¢è§'" |
| | | width="720px"> |
| | | <video v-if="currentVideoFile" |
| | | :src="currentVideoFile.url || currentVideoFile.downloadUrl" |
| | | class="video-player" |
| | | controls |
| | | autoplay /> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, ref } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Camera, Close, VideoCamera } from "@element-plus/icons-vue"; |
| | | import axios from "axios"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { uploadInspectionTask } from "@/api/inspectionManagement/index.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | import { computed, ref } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { ElLoading, ElMessage, ElMessageBox } from "element-plus"; |
| | | import { Camera, Close, VideoCamera } from "@element-plus/icons-vue"; |
| | | import axios from "axios"; |
| | | import dayjs from "dayjs"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { uploadInspectionTask } from "@/api/inspectionManagement/index.js"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | const emit = defineEmits(["closeDia", "success"]); |
| | | const router = useRouter(); |
| | | const emit = defineEmits(["closeDia", "success"]); |
| | | const router = useRouter(); |
| | | |
| | | const dialogVisible = ref(false); |
| | | const taskInfo = ref(null); |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | const dialogVisible = ref(false); |
| | | const taskInfo = ref(null); |
| | | const uploading = ref(false); |
| | | const uploadProgress = ref(0); |
| | | const operationType = ref("add"); // add, view |
| | | |
| | | const beforeModelValue = ref([]); |
| | | const afterModelValue = ref([]); |
| | | const issueModelValue = ref([]); |
| | | const beforeModelValue = ref([]); |
| | | |
| | | const currentUploadType = ref("before"); |
| | | const hasException = ref(null); |
| | | const abnormalDescription = ref(""); |
| | | |
| | | const showVideoDialog = ref(false); |
| | | const currentVideoFile = ref(null); |
| | | |
| | | const uploadConfig = { |
| | | action: "/common/upload", |
| | | limit: 10, |
| | | fileSize: 50, |
| | | fileType: ["jpg", "jpeg", "png", "mp4", "mov"], |
| | | }; |
| | | |
| | | const uploadFileUrl = computed( |
| | | () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}` |
| | | ); |
| | | |
| | | const processFileUrl = fileUrl => { |
| | | if (!fileUrl) return ""; |
| | | |
| | | let currentUrl = String(fileUrl); |
| | | if (currentUrl.includes("\\")) { |
| | | const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; |
| | | } else { |
| | | const fileName = currentUrl.split("\\").pop(); |
| | | currentUrl = `/uploads/${fileName}`; |
| | | const formattedInspector = computed(() => { |
| | | if (!taskInfo.value?.inspector) return []; |
| | | if (Array.isArray(taskInfo.value.inspector)) return taskInfo.value.inspector; |
| | | if (typeof taskInfo.value.inspector === "string") { |
| | | return taskInfo.value.inspector |
| | | .split(",") |
| | | .map(s => s.trim()) |
| | | .filter(s => s); |
| | | } |
| | | } |
| | | |
| | | if (currentUrl && !currentUrl.startsWith("http")) { |
| | | if (!currentUrl.startsWith("/")) { |
| | | currentUrl = `/${currentUrl}`; |
| | | } |
| | | currentUrl = __BASE_API__ + currentUrl; |
| | | } |
| | | |
| | | return currentUrl; |
| | | }; |
| | | |
| | | const normalizeList = (list, fileType) => { |
| | | if (!Array.isArray(list)) return []; |
| | | |
| | | return list.filter(Boolean).map(item => { |
| | | let currentType = item.type; |
| | | if (!currentType && item.contentType) { |
| | | currentType = item.contentType.startsWith("video") ? "video" : "image"; |
| | | } else if (!currentType) { |
| | | currentType = fileType || "image"; |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | url: processFileUrl(item.url || item.previewURL || item.downloadUrl || item.path || ""), |
| | | downloadUrl: processFileUrl( |
| | | item.downloadUrl || item.url || item.previewURL || item.path || "" |
| | | ), |
| | | name: item.name || item.originalFilename || item.bucketFilename, |
| | | tempId: item.tempId || item.id || item.tempFileId, |
| | | tempFileId: item.tempFileId || item.tempId || item.id, |
| | | size: item.size || item.byteSize || 0, |
| | | type: currentType, |
| | | status: "success", |
| | | uid: item.uid || `${Date.now()}-${Math.random()}`, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const resetState = () => { |
| | | taskInfo.value = null; |
| | | beforeModelValue.value = []; |
| | | afterModelValue.value = []; |
| | | issueModelValue.value = []; |
| | | currentUploadType.value = "before"; |
| | | hasException.value = null; |
| | | abnormalDescription.value = ""; |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | showVideoDialog.value = false; |
| | | currentVideoFile.value = null; |
| | | }; |
| | | |
| | | const openDialog = row => { |
| | | const raw = JSON.parse(JSON.stringify(row?.__raw || row || {})); |
| | | taskInfo.value = raw; |
| | | |
| | | beforeModelValue.value = normalizeList( |
| | | raw.commonFileListBeforeVO || raw.commonFileListBefore || [], |
| | | "image" |
| | | ); |
| | | afterModelValue.value = normalizeList( |
| | | raw.commonFileListVO || raw.commonFileList || [], |
| | | "image" |
| | | ); |
| | | issueModelValue.value = normalizeList( |
| | | raw.commonFileListAfterVO || raw.commonFileListAfter || [], |
| | | "image" |
| | | ); |
| | | |
| | | abnormalDescription.value = raw.abnormalDescription || ""; |
| | | |
| | | if (raw.hasException !== undefined && raw.hasException !== null) { |
| | | hasException.value = raw.hasException; |
| | | } else if (raw.inspectionResult !== undefined && raw.inspectionResult !== null) { |
| | | hasException.value = String(raw.inspectionResult) === "0"; |
| | | } else { |
| | | hasException.value = null; |
| | | } |
| | | |
| | | if ( |
| | | hasException.value !== true && |
| | | (beforeModelValue.value.length || afterModelValue.value.length || issueModelValue.value.length) |
| | | ) { |
| | | hasException.value = true; |
| | | } |
| | | |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | dialogVisible.value = false; |
| | | resetState(); |
| | | emit("closeDia"); |
| | | }; |
| | | |
| | | const getCurrentFiles = () => { |
| | | if (currentUploadType.value === "before") return beforeModelValue.value; |
| | | if (currentUploadType.value === "after") return afterModelValue.value; |
| | | if (currentUploadType.value === "issue") return issueModelValue.value; |
| | | return []; |
| | | }; |
| | | |
| | | const getUploadTypeText = () => { |
| | | if (currentUploadType.value === "before") return "ç产å"; |
| | | if (currentUploadType.value === "after") return "ç产ä¸"; |
| | | if (currentUploadType.value === "issue") return "ç产å"; |
| | | return ""; |
| | | }; |
| | | |
| | | const getTabType = () => { |
| | | if (currentUploadType.value === "before") return 10; |
| | | if (currentUploadType.value === "after") return 11; |
| | | if (currentUploadType.value === "issue") return 12; |
| | | return 10; |
| | | }; |
| | | |
| | | const previewVideo = file => { |
| | | currentVideoFile.value = file; |
| | | showVideoDialog.value = true; |
| | | }; |
| | | |
| | | const uploadFile = async uploadRequest => { |
| | | const rawFile = uploadRequest.file; |
| | | |
| | | if (getCurrentFiles().length >= uploadConfig.limit) { |
| | | ElMessage.warning(`æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`); |
| | | return; |
| | | } |
| | | |
| | | const ext = rawFile.name.split(".").pop()?.toLowerCase(); |
| | | if (!uploadConfig.fileType.includes(ext)) { |
| | | ElMessage.warning(`æä»¶æ ¼å¼ä¸æ¯æï¼è¯·ä¸ä¼ ${uploadConfig.fileType.join("/")} æ ¼å¼`); |
| | | return; |
| | | } |
| | | |
| | | if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) { |
| | | ElMessage.warning(`æä»¶å¤§å°ä¸è½è¶
è¿ ${uploadConfig.fileSize}MB`); |
| | | return; |
| | | } |
| | | |
| | | const token = getToken(); |
| | | if (!token) { |
| | | ElMessage.warning("ç¨æ·æªç»å½"); |
| | | return; |
| | | } |
| | | |
| | | const formData = new FormData(); |
| | | formData.append("files", rawFile); |
| | | formData.append("type", getTabType()); |
| | | |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | |
| | | try { |
| | | const { data } = await axios.post(uploadFileUrl.value, formData, { |
| | | headers: { |
| | | Authorization: `Bearer ${token}`, |
| | | "Content-Type": "multipart/form-data", |
| | | }, |
| | | onUploadProgress: event => { |
| | | if (event.total) { |
| | | uploadProgress.value = Math.round((event.loaded / event.total) * 100); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | if (data.code !== 200) { |
| | | ElMessage.error(data.msg || "ä¸ä¼ 失败"); |
| | | return; |
| | | } |
| | | |
| | | const resultData = Array.isArray(data.data) ? data.data[0] : data.data; |
| | | const finalUrl = processFileUrl( |
| | | resultData.url || resultData.previewURL || resultData.downloadUrl || "" |
| | | ); |
| | | const finalName = resultData.name || resultData.originalFilename || resultData.bucketFilename; |
| | | const finalId = resultData.tempId || resultData.id || resultData.tempFileId; |
| | | |
| | | const uploadedFile = { |
| | | ...resultData, |
| | | url: finalUrl, |
| | | downloadUrl: finalUrl, |
| | | name: finalName, |
| | | tempId: finalId, |
| | | tempFileId: resultData.tempFileId || finalId, |
| | | size: rawFile.size || resultData.size || resultData.byteSize || 0, |
| | | type: rawFile.type?.startsWith("video") ? "video" : "image", |
| | | status: "success", |
| | | uid: `${Date.now()}-${Math.random()}`, |
| | | }; |
| | | |
| | | getCurrentFiles().push(uploadedFile); |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "ä¸ä¼ 失败"); |
| | | } finally { |
| | | uploading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const buildFileItem = item => ({ |
| | | id: item?.id, |
| | | tempId: item?.tempId, |
| | | tempFileId: item?.tempFileId, |
| | | url: item?.downloadUrl || item?.url || "", |
| | | downloadUrl: item?.downloadUrl || item?.url || "", |
| | | name: item?.name, |
| | | bucketFilename: item?.bucketFilename || item?.name, |
| | | originalFilename: item?.originalFilename || item?.name, |
| | | size: item?.size || 0, |
| | | byteSize: item?.byteSize || item?.size || 0, |
| | | contentType: item?.contentType || "", |
| | | type: item?.type, |
| | | }); |
| | | |
| | | const submitUpload = async () => { |
| | | if (hasException.value === null) { |
| | | ElMessage.warning("è¯·éæ©å·¡æ£ç¶æ"); |
| | | return; |
| | | } |
| | | |
| | | if (hasException.value === true) { |
| | | const totalFiles = |
| | | beforeModelValue.value.length + |
| | | afterModelValue.value.length + |
| | | issueModelValue.value.length; |
| | | |
| | | if (!totalFiles) { |
| | | ElMessage.warning("请ä¸ä¼ å¼å¸¸ç
§çæè§é¢"); |
| | | return; |
| | | } |
| | | |
| | | if (!abnormalDescription.value.trim()) { |
| | | ElMessage.warning("请填åå¼å¸¸æè¿°"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | const loading = ElLoading.service({ |
| | | text: "æäº¤ä¸...", |
| | | background: "rgba(0, 0, 0, 0.3)", |
| | | return [taskInfo.value.inspector]; |
| | | }); |
| | | |
| | | try { |
| | | const allFiles = [ |
| | | ...beforeModelValue.value, |
| | | ...afterModelValue.value, |
| | | ...issueModelValue.value, |
| | | ]; |
| | | |
| | | const tempFileIds = allFiles |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(Boolean); |
| | | |
| | | const { |
| | | createTime, |
| | | updateTime, |
| | | storageBlobDTO, |
| | | commonFileListAfterVO, |
| | | commonFileListVO, |
| | | commonFileListBeforeVO, |
| | | commonFileListAfter, |
| | | commonFileList, |
| | | commonFileListBefore, |
| | | __raw, |
| | | ...baseTaskInfo |
| | | } = taskInfo.value || {}; |
| | | |
| | | const submitData = { |
| | | ...baseTaskInfo, |
| | | commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), |
| | | commonFileListDTO: afterModelValue.value.map(buildFileItem), |
| | | commonFileListAfterDTO: issueModelValue.value.map(buildFileItem), |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, |
| | | abnormalDescription: abnormalDescription.value, |
| | | tempFileIds, |
| | | const formatFrequencyType = type => { |
| | | const mapping = { |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦", |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(submitData); |
| | | |
| | | if (result && (result.code === 200 || result.success)) { |
| | | ElMessage.success("æäº¤æå"); |
| | | dialogVisible.value = false; |
| | | resetState(); |
| | | emit("success"); |
| | | emit("closeDia"); |
| | | } else { |
| | | ElMessage.error(result?.msg || result?.message || "æäº¤å¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "æäº¤å¤±è´¥"); |
| | | } finally { |
| | | loading.close(); |
| | | } |
| | | }; |
| | | |
| | | const removeFile = async index => { |
| | | try { |
| | | await ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼", "确认å é¤", { |
| | | type: "warning", |
| | | }); |
| | | getCurrentFiles().splice(index, 1); |
| | | } catch {} |
| | | }; |
| | | |
| | | const goToRepair = () => { |
| | | 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, |
| | | commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), |
| | | commonFileListDTO: afterModelValue.value.map(buildFileItem), |
| | | commonFileListAfterDTO: issueModelValue.value.map(buildFileItem), |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: afterModelValue.value, |
| | | issue: issueModelValue.value, |
| | | }, |
| | | return mapping[type] || type; |
| | | }; |
| | | |
| | | sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData)); |
| | | router.push("/equipmentManagement/repair/add"); |
| | | }; |
| | | const formatFrequencyDetail = detail => { |
| | | if (typeof detail !== "string") return detail; |
| | | const replacements = { |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | return detail.replace( |
| | | /MON|TUE|WED|THU|FRI|SAT|SUN/g, |
| | | match => replacements[match] |
| | | ); |
| | | }; |
| | | |
| | | const formatFileSize = size => { |
| | | if (!size) return "0 B"; |
| | | const formatDateTime = date => { |
| | | if (!date) return "-"; |
| | | return dayjs(date).format("YYYY-MM-DD"); |
| | | }; |
| | | |
| | | const units = ["B", "KB", "MB", "GB"]; |
| | | let index = 0; |
| | | let fileSize = size; |
| | | const hasException = ref(null); |
| | | const abnormalDescription = ref(""); |
| | | |
| | | while (fileSize >= 1024 && index < units.length - 1) { |
| | | fileSize /= 1024; |
| | | index += 1; |
| | | } |
| | | const showVideoDialog = ref(false); |
| | | const currentVideoFile = ref(null); |
| | | |
| | | return `${fileSize.toFixed(2)} ${units[index]}`; |
| | | }; |
| | | const uploadConfig = { |
| | | action: "/common/upload", |
| | | limit: 10, |
| | | fileSize: 50, |
| | | fileType: ["jpg", "jpeg", "png", "mp4", "mov"], |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | const uploadFileUrl = computed( |
| | | () => `${import.meta.env.VITE_APP_BASE_API}${uploadConfig.action}` |
| | | ); |
| | | |
| | | const processFileUrl = fileUrl => { |
| | | if (!fileUrl) return ""; |
| | | |
| | | let currentUrl = String(fileUrl); |
| | | if (currentUrl.includes("\\")) { |
| | | const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; |
| | | } else { |
| | | const fileName = currentUrl.split("\\").pop(); |
| | | currentUrl = `/uploads/${fileName}`; |
| | | } |
| | | } |
| | | |
| | | if (currentUrl && !currentUrl.startsWith("http")) { |
| | | if (!currentUrl.startsWith("/")) { |
| | | currentUrl = `/${currentUrl}`; |
| | | } |
| | | currentUrl = __BASE_API__ + currentUrl; |
| | | } |
| | | |
| | | return currentUrl; |
| | | }; |
| | | |
| | | const normalizeList = (list, fileType) => { |
| | | if (!Array.isArray(list)) return []; |
| | | |
| | | return list.filter(Boolean).map(item => { |
| | | let currentType = item.type; |
| | | if (!currentType && item.contentType) { |
| | | currentType = item.contentType.startsWith("video") ? "video" : "image"; |
| | | } else if (!currentType) { |
| | | currentType = fileType || "image"; |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | url: processFileUrl( |
| | | item.url || item.previewURL || item.downloadUrl || item.path || "" |
| | | ), |
| | | downloadUrl: processFileUrl( |
| | | item.downloadUrl || item.url || item.previewURL || item.path || "" |
| | | ), |
| | | name: item.name || item.originalFilename || item.bucketFilename, |
| | | tempId: item.tempId || item.id || item.tempFileId, |
| | | tempFileId: item.tempFileId || item.tempId || item.id, |
| | | size: item.size || item.byteSize || 0, |
| | | type: currentType, |
| | | status: "success", |
| | | uid: item.uid || `${Date.now()}-${Math.random()}`, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const resetState = () => { |
| | | taskInfo.value = null; |
| | | beforeModelValue.value = []; |
| | | hasException.value = null; |
| | | abnormalDescription.value = ""; |
| | | uploading.value = false; |
| | | uploadProgress.value = 0; |
| | | showVideoDialog.value = false; |
| | | currentVideoFile.value = null; |
| | | operationType.value = "add"; |
| | | }; |
| | | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type || "add"; |
| | | const raw = JSON.parse(JSON.stringify(row?.__raw || row || {})); |
| | | taskInfo.value = raw; |
| | | |
| | | beforeModelValue.value = normalizeList( |
| | | raw.commonFileListBeforeVO || raw.commonFileListBefore || [], |
| | | "image" |
| | | ); |
| | | |
| | | abnormalDescription.value = raw.abnormalDescription || ""; |
| | | |
| | | if (raw.hasException !== undefined && raw.hasException !== null) { |
| | | hasException.value = raw.hasException; |
| | | } else if ( |
| | | raw.inspectionResult !== undefined && |
| | | raw.inspectionResult !== null |
| | | ) { |
| | | hasException.value = String(raw.inspectionResult) === "0"; |
| | | } else { |
| | | hasException.value = null; |
| | | } |
| | | |
| | | if (hasException.value !== true && beforeModelValue.value.length) { |
| | | hasException.value = true; |
| | | } |
| | | |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleClose = () => { |
| | | dialogVisible.value = false; |
| | | resetState(); |
| | | emit("closeDia"); |
| | | }; |
| | | |
| | | const previewVideo = file => { |
| | | currentVideoFile.value = file; |
| | | showVideoDialog.value = true; |
| | | }; |
| | | |
| | | const uploadFile = async uploadRequest => { |
| | | const rawFile = uploadRequest.file; |
| | | |
| | | if (beforeModelValue.value.length >= uploadConfig.limit) { |
| | | ElMessage.warning(`æå¤åªè½éæ©${uploadConfig.limit}个æä»¶`); |
| | | return; |
| | | } |
| | | |
| | | const ext = rawFile.name.split(".").pop()?.toLowerCase(); |
| | | if (!uploadConfig.fileType.includes(ext)) { |
| | | ElMessage.warning( |
| | | `æä»¶æ ¼å¼ä¸æ¯æï¼è¯·ä¸ä¼ ${uploadConfig.fileType.join("/")} æ ¼å¼` |
| | | ); |
| | | return; |
| | | } |
| | | |
| | | if (rawFile.size > uploadConfig.fileSize * 1024 * 1024) { |
| | | ElMessage.warning(`æä»¶å¤§å°ä¸è½è¶
è¿ ${uploadConfig.fileSize}MB`); |
| | | return; |
| | | } |
| | | |
| | | const token = getToken(); |
| | | if (!token) { |
| | | ElMessage.warning("ç¨æ·æªç»å½"); |
| | | return; |
| | | } |
| | | |
| | | const formData = new FormData(); |
| | | formData.append("files", rawFile); |
| | | formData.append("type", 10); // ç产ååºå®ä¸º10 |
| | | |
| | | uploading.value = true; |
| | | uploadProgress.value = 0; |
| | | |
| | | try { |
| | | const { data } = await axios.post(uploadFileUrl.value, formData, { |
| | | headers: { |
| | | Authorization: `Bearer ${token}`, |
| | | "Content-Type": "multipart/form-data", |
| | | }, |
| | | onUploadProgress: event => { |
| | | if (event.total) { |
| | | uploadProgress.value = Math.round((event.loaded / event.total) * 100); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | if (data.code !== 200) { |
| | | ElMessage.error(data.msg || "ä¸ä¼ 失败"); |
| | | return; |
| | | } |
| | | |
| | | const resultData = Array.isArray(data.data) ? data.data[0] : data.data; |
| | | const finalUrl = processFileUrl( |
| | | resultData.url || resultData.previewURL || resultData.downloadUrl || "" |
| | | ); |
| | | const finalName = |
| | | resultData.name || |
| | | resultData.originalFilename || |
| | | resultData.bucketFilename; |
| | | const finalId = resultData.tempId || resultData.id || resultData.tempFileId; |
| | | |
| | | const uploadedFile = { |
| | | ...resultData, |
| | | url: finalUrl, |
| | | downloadUrl: finalUrl, |
| | | name: finalName, |
| | | tempId: finalId, |
| | | tempFileId: resultData.tempFileId || finalId, |
| | | size: rawFile.size || resultData.size || resultData.byteSize || 0, |
| | | type: rawFile.type?.startsWith("video") ? "video" : "image", |
| | | status: "success", |
| | | uid: `${Date.now()}-${Math.random()}`, |
| | | }; |
| | | |
| | | beforeModelValue.value.push(uploadedFile); |
| | | ElMessage.success("ä¸ä¼ æå"); |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "ä¸ä¼ 失败"); |
| | | } finally { |
| | | uploading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const buildFileItem = item => ({ |
| | | id: item?.id, |
| | | tempId: item?.tempId, |
| | | tempFileId: item?.tempFileId, |
| | | url: item?.downloadUrl || item?.url || "", |
| | | downloadUrl: item?.downloadUrl || item?.url || "", |
| | | name: item?.name, |
| | | bucketFilename: item?.bucketFilename || item?.name, |
| | | originalFilename: item?.originalFilename || item?.name, |
| | | size: item?.size || 0, |
| | | byteSize: item?.byteSize || item?.size || 0, |
| | | contentType: item?.contentType || "", |
| | | type: item?.type, |
| | | }); |
| | | |
| | | const submitUpload = async () => { |
| | | if (hasException.value === null) { |
| | | ElMessage.warning("è¯·éæ©å·¡æ£ç¶æ"); |
| | | return; |
| | | } |
| | | |
| | | if (hasException.value === true) { |
| | | const totalFiles = beforeModelValue.value.length; |
| | | |
| | | if (!totalFiles) { |
| | | ElMessage.warning("请ä¸ä¼ å¼å¸¸ç
§çæè§é¢"); |
| | | return; |
| | | } |
| | | |
| | | if (!abnormalDescription.value.trim()) { |
| | | ElMessage.warning("请填åå¼å¸¸æè¿°"); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | const loading = ElLoading.service({ |
| | | text: "æäº¤ä¸...", |
| | | background: "rgba(0, 0, 0, 0.3)", |
| | | }); |
| | | |
| | | try { |
| | | const allFiles = [...beforeModelValue.value]; |
| | | |
| | | const tempFileIds = allFiles |
| | | .map(item => item?.tempId ?? item?.tempFileId ?? item?.id) |
| | | .filter(Boolean); |
| | | |
| | | const { |
| | | createTime, |
| | | updateTime, |
| | | storageBlobDTO, |
| | | commonFileListAfterVO, |
| | | commonFileListVO, |
| | | commonFileListBeforeVO, |
| | | commonFileListAfter, |
| | | commonFileList, |
| | | commonFileListBefore, |
| | | __raw, |
| | | ...baseTaskInfo |
| | | } = taskInfo.value || {}; |
| | | |
| | | const submitData = { |
| | | ...baseTaskInfo, |
| | | commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), |
| | | commonFileListDTO: [], |
| | | commonFileListAfterDTO: [], |
| | | hasException: hasException.value, |
| | | inspectionResult: hasException.value ? 0 : 1, |
| | | abnormalDescription: abnormalDescription.value, |
| | | tempFileIds, |
| | | }; |
| | | |
| | | const result = await uploadInspectionTask(submitData); |
| | | |
| | | if (result && (result.code === 200 || result.success)) { |
| | | ElMessage.success("æäº¤æå"); |
| | | dialogVisible.value = false; |
| | | resetState(); |
| | | emit("success"); |
| | | emit("closeDia"); |
| | | } else { |
| | | ElMessage.error(result?.msg || result?.message || "æäº¤å¤±è´¥"); |
| | | } |
| | | } catch (error) { |
| | | ElMessage.error(error?.message || "æäº¤å¤±è´¥"); |
| | | } finally { |
| | | loading.close(); |
| | | } |
| | | }; |
| | | |
| | | const removeFile = async index => { |
| | | try { |
| | | await ElMessageBox.confirm("ç¡®å®è¦å é¤è¿ä¸ªæä»¶åï¼", "确认å é¤", { |
| | | type: "warning", |
| | | }); |
| | | beforeModelValue.value.splice(index, 1); |
| | | } catch {} |
| | | }; |
| | | |
| | | const goToRepair = () => { |
| | | 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, |
| | | commonFileListBeforeDTO: beforeModelValue.value.map(buildFileItem), |
| | | commonFileListDTO: [], |
| | | commonFileListAfterDTO: [], |
| | | uploadedFiles: { |
| | | before: beforeModelValue.value, |
| | | after: [], |
| | | issue: [], |
| | | }, |
| | | }; |
| | | |
| | | sessionStorage.setItem("repairTaskInfo", JSON.stringify(taskData)); |
| | | router.push("/equipmentManagement/repair/add"); |
| | | }; |
| | | |
| | | 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 += 1; |
| | | } |
| | | |
| | | return `${fileSize.toFixed(2)} ${units[index]}`; |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .inspection-upload-page { |
| | | min-height: 70vh; |
| | | background: #f5f7fa; |
| | | padding: 20px 20px 90px; |
| | | box-sizing: border-box; |
| | | } |
| | | .inspection-upload-page { |
| | | min-height: 70vh; |
| | | background: #f5f7fa; |
| | | padding: 20px 20px 90px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .upload-content { |
| | | max-width: 960px; |
| | | margin: 20px auto 0; |
| | | } |
| | | .upload-content { |
| | | max-width: 960px; |
| | | margin: 20px auto 0; |
| | | } |
| | | |
| | | .section-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .section-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .section-card h3 { |
| | | margin: 0 0 16px; |
| | | font-size: 16px; |
| | | } |
| | | .section-card h3 { |
| | | margin: 0 0 16px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin: 16px 0; |
| | | } |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin: 16px 0; |
| | | } |
| | | |
| | | .upload-progress { |
| | | margin-bottom: 16px; |
| | | } |
| | | .upload-progress { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .file-list { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | .file-list { |
| | | display: grid; |
| | | grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); |
| | | gap: 12px; |
| | | } |
| | | |
| | | .file-preview-container { |
| | | position: relative; |
| | | aspect-ratio: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f2f3f5; |
| | | } |
| | | .file-preview-container { |
| | | position: relative; |
| | | aspect-ratio: 1; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | background: #f2f3f5; |
| | | } |
| | | |
| | | .file-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | .file-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .video-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | background: #303133; |
| | | color: #fff; |
| | | display: flex; |
| | | gap: 6px; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | } |
| | | .video-preview { |
| | | width: 100%; |
| | | height: 100%; |
| | | background: #303133; |
| | | color: #fff; |
| | | display: flex; |
| | | gap: 6px; |
| | | align-items: center; |
| | | justify-content: center; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 6px; |
| | | right: 6px; |
| | | } |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 6px; |
| | | right: 6px; |
| | | } |
| | | |
| | | .file-info { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | } |
| | | .file-info { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .file-name { |
| | | color: #606266; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | .file-name { |
| | | color: #606266; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .file-size { |
| | | color: #909399; |
| | | margin-top: 2px; |
| | | } |
| | | .file-size { |
| | | color: #909399; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .upload-summary { |
| | | margin-top: 16px; |
| | | } |
| | | .upload-summary { |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | .footer-buttons { |
| | | position: sticky; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | padding: 14px 20px 0; |
| | | background: #f5f7fa; |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | } |
| | | .footer-buttons { |
| | | position: sticky; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | padding: 14px 20px 0; |
| | | background: #f5f7fa; |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .video-player { |
| | | width: 100%; |
| | | max-height: 70vh; |
| | | background: #000; |
| | | } |
| | | .video-player { |
| | | width: 100%; |
| | | max-height: 70vh; |
| | | background: #000; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="æ¥çéä»¶" v-model="dialogVisitable" width="800px" @close="cancel"> |
| | | <el-dialog title="æ¥çéä»¶" |
| | | v-model="dialogVisitable" |
| | | width="800px" |
| | | @close="cancel"> |
| | | <div class="upload-container"> |
| | | <div class="form-container"> |
| | | <div class="title">ç产å</div> |
| | | |
| | | <div class="title">å¾çå表</div> |
| | | <div class="media-list"> |
| | | <img |
| | | v-for="(item, index) in beforeProductionImgs" |
| | | :key="`before-img-${index}`" |
| | | :src="item" |
| | | alt="" |
| | | class="media-image" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | /> |
| | | <img v-for="(item, index) in beforeProductionImgs" |
| | | :key="`before-img-${index}`" |
| | | :src="item" |
| | | alt="" |
| | | class="media-image" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" /> |
| | | </div> |
| | | |
| | | <div class="media-list"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="`before-video-${index}`" |
| | | class="video-item" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | > |
| | | <div v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="`before-video-${index}`" |
| | | class="video-item" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')"> |
| | | <div class="video-thumb"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" class="video-icon" /> |
| | | <img src="@/assets/images/video.png" |
| | | alt="ææ¾" |
| | | class="video-icon" /> |
| | | </div> |
| | | <div class="video-text">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="form-container"> |
| | | <!-- <div class="form-container"> |
| | | <div class="title">ç产ä¸</div> |
| | | |
| | | <div class="media-list"> |
| | |
| | | <div class="video-text">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> --> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | /> |
| | | |
| | | <div v-else-if="mediaType === 'video'" class="video-player-wrap"> |
| | | <video :src="mediaList[currentMediaIndex]" autoplay controls class="video-player" /> |
| | | <div v-if="isMediaViewerVisible" |
| | | class="media-viewer-overlay" |
| | | @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" |
| | | @click.stop> |
| | | <vue-easy-lightbox v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" /> |
| | | <div v-else-if="mediaType === 'video'" |
| | | class="video-player-wrap"> |
| | | <video :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | class="video-player" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | import { ref } from "vue"; |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const dialogVisitable = ref(false); |
| | | |
| | | const beforeProductionImgs = ref([]); |
| | | const afterProductionImgs = ref([]); |
| | | const productionIssuesImgs = ref([]); |
| | | const beforeProductionImgs = ref([]); |
| | | const afterProductionImgs = ref([]); |
| | | const productionIssuesImgs = ref([]); |
| | | |
| | | const beforeProductionVideos = ref([]); |
| | | const afterProductionVideos = ref([]); |
| | | const productionIssuesVideos = ref([]); |
| | | const beforeProductionVideos = ref([]); |
| | | const afterProductionVideos = ref([]); |
| | | const productionIssuesVideos = ref([]); |
| | | |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); |
| | | const mediaType = ref("image"); |
| | | const isMediaViewerVisible = ref(false); |
| | | const currentMediaIndex = ref(0); |
| | | const mediaList = ref([]); |
| | | const mediaType = ref("image"); |
| | | |
| | | const processFileUrl = fileUrl => { |
| | | if (!fileUrl) return ""; |
| | | const processFileUrl = fileUrl => { |
| | | if (!fileUrl) return ""; |
| | | |
| | | let currentUrl = String(fileUrl); |
| | | if (currentUrl.includes("\\")) { |
| | | const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; |
| | | } else { |
| | | const fileName = currentUrl.split("\\").pop(); |
| | | currentUrl = `/uploads/${fileName}`; |
| | | let currentUrl = String(fileUrl); |
| | | if (currentUrl.includes("\\")) { |
| | | const uploadsIndex = currentUrl.toLowerCase().indexOf("uploads"); |
| | | if (uploadsIndex > -1) { |
| | | currentUrl = `/${currentUrl.substring(uploadsIndex).replace(/\\/g, "/")}`; |
| | | } else { |
| | | const fileName = currentUrl.split("\\").pop(); |
| | | currentUrl = `/uploads/${fileName}`; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (currentUrl && !currentUrl.startsWith("http")) { |
| | | if (!currentUrl.startsWith("/")) { |
| | | currentUrl = `/${currentUrl}`; |
| | | if (currentUrl && !currentUrl.startsWith("http")) { |
| | | if (!currentUrl.startsWith("/")) { |
| | | currentUrl = `/${currentUrl}`; |
| | | } |
| | | currentUrl = __BASE_API__ + currentUrl; |
| | | } |
| | | currentUrl = __BASE_API__ + currentUrl; |
| | | } |
| | | |
| | | return currentUrl; |
| | | }; |
| | | return currentUrl; |
| | | }; |
| | | |
| | | const processItems = items => { |
| | | const images = []; |
| | | const videos = []; |
| | | const processItems = items => { |
| | | const images = []; |
| | | const videos = []; |
| | | |
| | | if (!Array.isArray(items)) { |
| | | if (!Array.isArray(items)) { |
| | | return { images, videos }; |
| | | } |
| | | |
| | | items.forEach(item => { |
| | | if (!item) return; |
| | | |
| | | const fileUrl = processFileUrl( |
| | | item.previewURL || item.url || item.downloadUrl || item.path || "" |
| | | ); |
| | | const contentType = String(item.contentType || "").toLowerCase(); |
| | | |
| | | if (!fileUrl) return; |
| | | |
| | | if (contentType.startsWith("video/")) { |
| | | videos.push(fileUrl); |
| | | return; |
| | | } |
| | | |
| | | images.push(fileUrl); |
| | | }); |
| | | |
| | | return { images, videos }; |
| | | } |
| | | }; |
| | | |
| | | items.forEach(item => { |
| | | if (!item) return; |
| | | |
| | | const fileUrl = processFileUrl( |
| | | item.previewURL || item.url || item.downloadUrl || item.path || "" |
| | | const openDialog = row => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems( |
| | | row.commonFileListBeforeVO || [] |
| | | ); |
| | | const contentType = String(item.contentType || "").toLowerCase(); |
| | | const { images: afterImgs, videos: afterVids } = processItems( |
| | | row.commonFileListVO || [] |
| | | ); |
| | | const { images: issueImgs, videos: issueVids } = processItems( |
| | | row.commonFileListAfterVO || [] |
| | | ); |
| | | |
| | | if (!fileUrl) return; |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | afterProductionImgs.value = afterImgs; |
| | | afterProductionVideos.value = afterVids; |
| | | productionIssuesImgs.value = issueImgs; |
| | | productionIssuesVideos.value = issueVids; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | |
| | | if (contentType.startsWith("video/")) { |
| | | videos.push(fileUrl); |
| | | return; |
| | | } |
| | | const showMedia = (items, index, type) => { |
| | | mediaList.value = items; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | }; |
| | | |
| | | images.push(fileUrl); |
| | | }); |
| | | const closeMediaViewer = () => { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = "image"; |
| | | }; |
| | | |
| | | return { images, videos }; |
| | | }; |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | const openDialog = row => { |
| | | const { images: beforeImgs, videos: beforeVids } = processItems( |
| | | row.commonFileListBeforeVO || [] |
| | | ); |
| | | const { images: afterImgs, videos: afterVids } = processItems( |
| | | row.commonFileListVO || [] |
| | | ); |
| | | const { images: issueImgs, videos: issueVids } = processItems( |
| | | row.commonFileListAfterVO || [] |
| | | ); |
| | | |
| | | beforeProductionImgs.value = beforeImgs; |
| | | beforeProductionVideos.value = beforeVids; |
| | | afterProductionImgs.value = afterImgs; |
| | | afterProductionVideos.value = afterVids; |
| | | productionIssuesImgs.value = issueImgs; |
| | | productionIssuesVideos.value = issueVids; |
| | | dialogVisitable.value = true; |
| | | }; |
| | | |
| | | const showMedia = (items, index, type) => { |
| | | mediaList.value = items; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | }; |
| | | |
| | | const closeMediaViewer = () => { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = "image"; |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | .upload-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | .title { |
| | | font-size: 14px; |
| | | color: #165dff; |
| | | line-height: 20px; |
| | | font-weight: 600; |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 3px; |
| | | width: 4px; |
| | | height: 14px; |
| | | background-color: #165dff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .media-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | .media-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .media-image { |
| | | max-width: 100px; |
| | | height: 100px; |
| | | margin: 5px; |
| | | cursor: pointer; |
| | | } |
| | | .media-image { |
| | | max-width: 100px; |
| | | height: 100px; |
| | | margin: 5px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .video-item { |
| | | position: relative; |
| | | margin: 10px; |
| | | cursor: pointer; |
| | | } |
| | | .video-item { |
| | | position: relative; |
| | | margin: 10px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .video-thumb { |
| | | width: 160px; |
| | | height: 90px; |
| | | background-color: #333; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .video-thumb { |
| | | width: 160px; |
| | | height: 90px; |
| | | background-color: #333; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .video-icon { |
| | | width: 30px; |
| | | height: 30px; |
| | | opacity: 0.8; |
| | | } |
| | | .video-icon { |
| | | width: 30px; |
| | | height: 30px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .video-text { |
| | | text-align: center; |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | .video-text { |
| | | text-align: center; |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | inset: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | inset: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | .media-viewer-content { |
| | | position: relative; |
| | | max-width: 90vw; |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .video-player-wrap { |
| | | position: relative; |
| | | } |
| | | .video-player-wrap { |
| | | position: relative; |
| | | } |
| | | |
| | | .video-player { |
| | | max-width: 90vw; |
| | | max-height: 80vh; |
| | | } |
| | | .video-player { |
| | | max-width: 90vw; |
| | | max-height: 80vh; |
| | | } |
| | | </style> |
| | |
| | | formatData: cell => { |
| | | if (!cell) return "-"; |
| | | try { |
| | | return dayjs(cell).format("YYYY-MM-DD HH:mm:ss"); |
| | | return dayjs(cell).format("YYYY-MM-DD"); |
| | | } catch { |
| | | return cell; |
| | | } |
| | |
| | | operation: operations |
| | | .map(op => { |
| | | switch (op) { |
| | | case "view": |
| | | return { |
| | | name: "详æ
", |
| | | clickFun: openViewDialog, |
| | | color: "#409EFF", |
| | | }; |
| | | case "edit": |
| | | return { |
| | | name: "ç¼è¾", |
| | |
| | | ]; |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["upload", "viewFile"]); |
| | | const operationColumn = getOperationColumn(["view", "upload", "viewFile"]); |
| | | // å·¡æ£è®°å½ä¸å±ç¤º"æ¯å¦å¯ç¨"å |
| | | const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); |
| | | tableColumns.value = [ |
| | | ...taskColumns, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["upload", "viewFile"]; |
| | | operationsArr.value = ["view", "upload", "viewFile"]; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | |
| | | |
| | | const openUploadDialog = row => { |
| | | nextTick(() => { |
| | | uploadFiles.value?.openDialog(row); |
| | | uploadFiles.value?.openDialog("add", row); |
| | | }); |
| | | }; |
| | | |
| | | const openViewDialog = row => { |
| | | nextTick(() => { |
| | | uploadFiles.value?.openDialog("view", row); |
| | | }); |
| | | }; |
| | | |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¥ä¿®æ¥ä¿®é¡¹ç®"> |
| | | <el-form-item label="æ¥ä¿®é¡¹ç®"> |
| | | <el-input v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥æ¥ä¿®æ¥ä¿®é¡¹ç®" |
| | | placeholder="请è¾å
¥æ¥ä¿®é¡¹ç®" |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | :tableLoading="loading" |
| | | :page="pagination" |
| | | :isShowPagination="true" |
| | | :row-class-name="rowClassName" |
| | | @pagination="handleSizeChange" |
| | | > |
| | | <template #status="{ row }"> |
| | |
| | | </el-dialog> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="å¤ä»¶é¢ç¨è®°å½" name="usage"> |
| | | <el-tab-pane label="å¤ä»¶ä½¿ç¨è®°å½" name="usage"> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="usageQuery" class="search-form"> |
| | | <el-form-item label="å¤ä»¶åç§°"> |
| | |
| | | onMounted(() => { |
| | | fetchListData(); |
| | | }); |
| | | |
| | | // æ ¹æ®æ°éè¿è¡å¤æèæ¯é¢è² |
| | | const rowClassName = ({ row }) => { |
| | | const num = Number(row.quantity); |
| | | if (num <= 1) return 'row-qty-low'; |
| | | if (num >= 8) return 'row-qty-high'; |
| | | return 'row-qty-mid'; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | <style> |
| | | .spare-part-category { |
| | | padding: 20px; |
| | | } |
| | |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | .el-table .row-qty-low td.el-table__cell, |
| | | .el-table .row-qty-low:hover > td.el-table__cell { |
| | | background-color: #fde8e8 !important; |
| | | } |
| | | .el-table .row-qty-mid td.el-table__cell, |
| | | .el-table .row-qty-mid:hover > td.el-table__cell { |
| | | background-color: #f0f4f8 !important; |
| | | } |
| | | .el-table .row-qty-high td.el-table__cell, |
| | | .el-table .row-qty-high:hover > td.el-table__cell { |
| | | background-color: #e8f5e9 !important; |
| | | } |
| | | |
| | | /* æé®ç»æ ·å¼ */ |
| | |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <el-form-item label="ä¿å
»é¡¹ç®"> |
| | | <el-input |
| | | v-model="form.machineryCategory" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | /> |
| | | </el-form-item> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisitable" |
| | | title="ä¿å
»è®°å½è¯¦æ
" |
| | | width="800px" |
| | | operation-type="detail" |
| | | @close="cancel" |
| | | > |
| | | <el-descriptions border :column="2"> |
| | | <el-descriptions-item label="设å¤åç§°"> |
| | | {{ detailData.deviceName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·"> |
| | | {{ detailData.deviceModel || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="计åä¿å
»æ¥æ"> |
| | | {{ formatDate(detailData.maintenancePlanTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥äºº"> |
| | | {{ detailData.createUserName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»é¡¹ç®" :span="2"> |
| | | {{ detailData.machineryCategory || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å®é
ä¿å
»äºº"> |
| | | {{ detailData.maintenanceActuallyName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å®é
ä¿å
»æ¥æ"> |
| | | {{ formatDate(detailData.maintenanceActuallyTime) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
ȍȾ" :span="2"> |
| | | {{ detailData.maintenanceResult || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ" :span="2"> |
| | | <el-tag v-if="detailData.status === 2" type="danger">失败</el-tag> |
| | | <el-tag v-else-if="detailData.status === 1" type="success">å®ç»</el-tag> |
| | | <el-tag v-else type="warning">å¾
ä¿å
»</el-tag> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div v-if="fileList && fileList.length > 0" class="image-section"> |
| | | <div class="image-title">ä¿å
»éä»¶(å¾ç)ï¼</div> |
| | | <AttachmentUploadImage |
| | | v-model:fileList="fileList" |
| | | :disabled="true" |
| | | /> |
| | | </div> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { attachmentList } from "@/api/basicData/storageAttachment.js"; |
| | | import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue'; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const detailData = ref({}); |
| | | const fileList = ref([]); |
| | | |
| | | const formatDate = (date) => { |
| | | return date ? dayjs(date).format("YYYY-MM-DD") : "--"; |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisitable.value = true; |
| | | detailData.value = { ...row }; |
| | | fileList.value = []; |
| | | |
| | | if (row.id) { |
| | | attachmentList({ |
| | | recordType: 'device_maintenance', |
| | | recordId: row.id, |
| | | }).then(res => { |
| | | if (res && res.data) { |
| | | fileList.value = res.data || []; |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .image-section { |
| | | margin-top: 20px; |
| | | } |
| | | .image-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #606266; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisitable" |
| | | title="ä¿å
»ä»»å¡è¯¦æ
" |
| | | width="800px" |
| | | operation-type="detail" |
| | | @close="cancel" |
| | | > |
| | | <el-descriptions border :column="2"> |
| | | <el-descriptions-item label="设å¤åç§°"> |
| | | {{ detailData.taskName || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="è§æ ¼åå·"> |
| | | {{ detailData.deviceModel || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å½å
¥äºº"> |
| | | {{ detailData.registrant || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ç»è®°æ¶é´"> |
| | | {{ detailData.registrationDate || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»é¡¹ç®" :span="2"> |
| | | {{ detailData.machineryCategory || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä¿å
»äºº"> |
| | | {{ detailData.maintenancePerson || '--' }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä»»å¡é¢ç"> |
| | | {{ formatFrequencyType(detailData.frequencyType) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ§è¡æ¶é´"> |
| | | {{ formatFrequencyDetail(detailData.frequencyDetail) }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="宿¶ä»»å¡"> |
| | | <el-tag :type="detailData.isActive === 1 ? 'success' : 'info'"> |
| | | {{ detailData.isActive === 1 ? 'å¼å¯' : 'å
³é' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="2"> |
| | | {{ detailData.remarks || '--' }} |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div v-if="fileList && fileList.length > 0" class="image-section"> |
| | | <div class="image-title">设å¤å¾çï¼</div> |
| | | <AttachmentUploadImage |
| | | v-model:fileList="fileList" |
| | | :disabled="true" |
| | | /> |
| | | </div> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref } from "vue"; |
| | | import { getLedgerById } from "@/api/equipmentManagement/ledger"; |
| | | import AttachmentUploadImage from '@/components/AttachmentUpload/image/index.vue'; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | const detailData = ref({}); |
| | | const fileList = ref([]); |
| | | |
| | | const formatFrequencyType = (type) => { |
| | | const map = { |
| | | DAILY: "æ¯æ¥", |
| | | WEEKLY: "æ¯å¨", |
| | | MONTHLY: "æ¯æ", |
| | | QUARTERLY: "å£åº¦", |
| | | }; |
| | | return map[type] || "--"; |
| | | }; |
| | | |
| | | const formatFrequencyDetail = (detail) => { |
| | | if (!detail) return "--"; |
| | | const replacements = { |
| | | MON: "å¨ä¸", |
| | | TUE: "å¨äº", |
| | | WED: "å¨ä¸", |
| | | THU: "å¨å", |
| | | FRI: "å¨äº", |
| | | SAT: "å¨å
", |
| | | SUN: "卿¥", |
| | | }; |
| | | return detail.replace(/MON|TUE|WED|THU|FRI|SAT|SUN/g, match => replacements[match]); |
| | | }; |
| | | |
| | | const openDialog = (row) => { |
| | | dialogVisitable.value = true; |
| | | detailData.value = { ...row }; |
| | | fileList.value = []; |
| | | |
| | | if (row.taskId) { |
| | | getLedgerById(row.taskId).then(res => { |
| | | if (res.code === 200 && res.data) { |
| | | fileList.value = res.data.storageBlobVOs || []; |
| | | } |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .image-section { |
| | | margin-top: 20px; |
| | | } |
| | | .image-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | color: #606266; |
| | | } |
| | | </style> |
| | |
| | | <el-form-item label="ä¿å
»é¡¹ç®" prop="machineryCategory"> |
| | | <el-input |
| | | v-model.trim="form.machineryCategory" |
| | | type="textarea" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | maxlength="100" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | |
| | | <el-col :span="12" v-if="form.frequencyType === 'DAILY' && form.frequencyType"> |
| | | <el-form-item label="æ¥æ" prop="frequencyDetail"> |
| | | <el-time-picker v-model="form.frequencyDetail" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" /> |
| | | value-format="HH:mm" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'WEEKLY' && form.frequencyType"> |
| | |
| | | <el-option label="卿¥" value="SUN"/> |
| | | </el-select> |
| | | <el-time-picker v-model="form.time" placeholder="éæ©æ¶é´" format="HH:mm" |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | value-format="HH:mm" style="width: 50%"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.frequencyType === 'MONTHLY' && form.frequencyType"> |
| | |
| | | format="MM,DD,HH:mm" |
| | | value-format="MM,DD,HH:mm" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="宿¶ä»»å¡"> |
| | | <el-switch |
| | | v-model="form.isActive" |
| | | :active-value="1" |
| | | :inactive-value="0" |
| | | active-text="å¼å¯" |
| | | inactive-text="å
³é" |
| | | inline-prompt |
| | | /> |
| | | <span style="margin-left: 12px; color: #909399; font-size: 12px;"> |
| | | {{ form.isActive === 1 ? 'ä»»å¡å°æè®¡åæ§è¡' : '任塿忧è¡' }} |
| | | </span> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | time: '', |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | registrationDate: '', |
| | | maintenancePerson: '' // ä¿å
»äºº |
| | | maintenancePerson: '', // ä¿å
»äºº |
| | | isActive: 0 // 宿¶ä»»å¡å¼å
³ï¼0=å
³éï¼1=å¼å¯ |
| | | }, |
| | | rules: { |
| | | taskId: [{ required: true, message: "è¯·éæ©è®¾å¤", trigger: "change" },], |
| | |
| | | time: '', |
| | | deviceModel: undefined, |
| | | registrationDate: '', |
| | | maintenancePerson: '' |
| | | maintenancePerson: '', |
| | | isActive: 0 |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | delete payload.inspector |
| | | delete payload.inspectorIds |
| | | |
| | | delete payload.active |
| | | |
| | | if (payload.frequencyType === 'WEEKLY') { |
| | | let frequencyDetail = '' |
| | | frequencyDetail = payload.week + ',' + payload.time |
| | | payload.frequencyDetail = frequencyDetail |
| | | } |
| | | |
| | | |
| | | // å½å
¥æ¥æï¼ç´æ¥ä½¿ç¨è¡¨åéç registrationDate åæ®µ |
| | | // ä¸äºé»è®¤ç¶æå段 |
| | | if (payload.status === undefined || payload.status === null || payload.status === '') { |
| | | payload.status = '0' // é»è®¤ç¶æï¼å¯æå®é
æä¸¾è°æ´ |
| | | } |
| | | payload.active = true |
| | | payload.deleted = 0 |
| | | |
| | | |
| | | if (operationType.value === 'edit') { |
| | | await deviceMaintenanceTaskEdit(payload) |
| | | } else { |
| | |
| | | }" |
| | | @selection-change="handleScheduledSelectionChange" |
| | | @pagination="changeScheduledPage"> |
| | | <template #isActiveRef="{ row }"> |
| | | <el-tag v-if="row.isActive === 1" |
| | | type="success">å¼å¯</el-tag> |
| | | <el-tag v-else |
| | | type="info">å
³é</el-tag> |
| | | </template> |
| | | <template #statusRef="{ row }"> |
| | | <el-tag v-if="row.status === 1" |
| | | type="success">å¯ç¨</el-tag> |
| | |
| | | type="danger">åç¨</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="handleDetail(row)"> |
| | | 详æ
|
| | | </el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="editScheduledTask(row)"> |
| | |
| | | type="warning">å¾
ä¿å
»</el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="handleRecordDetail(row)"> |
| | | 详æ
|
| | | </el-button> |
| | | <!-- è¿ä¸ªåè½è·æ°å¢ä¿å
»åè½ä¸æ¨¡ä¸æ ·ï¼æå¥æä¹ï¼ --> |
| | | <!-- <el-button |
| | | type="primary" |
| | |
| | | @ok="getTableData" /> |
| | | <FormDia ref="formDiaRef" |
| | | @closeDia="getScheduledTableData" /> |
| | | <DetailDia ref="detailDiaRef" /> |
| | | <RecordDetailDia ref="recordDetailDiaRef" /> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'device_maintenance'" |
| | |
| | | import PlanModal from "./Form/PlanModal.vue"; |
| | | import MaintenanceModal from "./Form/MaintenanceModal.vue"; |
| | | import FormDia from "./Form/formDia.vue"; |
| | | import DetailDia from "./Form/detailDia.vue"; |
| | | import RecordDetailDia from "./Form/RecordDetailDia.vue"; |
| | | import { |
| | | getUpkeepPage, |
| | | delUpkeep, |
| | |
| | | const maintainModalRef = ref(); |
| | | // 宿¶ä»»å¡å¼¹çªæ§å¶å¨ |
| | | const formDiaRef = ref(); |
| | | // 宿¶ä»»å¡è¯¦æ
å¼¹çªæ§å¶å¨ |
| | | const detailDiaRef = ref(); |
| | | // ä¿å
»è®°å½è¯¦æ
å¼¹çªæ§å¶å¨ |
| | | const recordDetailDiaRef = ref(); |
| | | // éä»¶å¼¹çª |
| | | const fileListDialogRef = ref(null); |
| | | const fileDialogVisible = ref(false); |
| | |
| | | { |
| | | prop: "frequencyType", |
| | | label: "颿¬¡", |
| | | minWidth: 150, |
| | | minWidth: 80, |
| | | // PIMTable 使ç¨çæ¯ formatDataï¼è䏿¯ Element-Plus ç formatter |
| | | formatData: cell => |
| | | ({ |
| | |
| | | { |
| | | prop: "frequencyDetail", |
| | | label: "å¼å§æ¥æä¸æ¶é´", |
| | | minWidth: 150, |
| | | minWidth: 130, |
| | | // åæ ·æ¹ç¨ formatDataï¼PIMTable å
é¨ä¼æåå
æ ¼å¼ä¼ è¿æ¥ |
| | | formatData: cell => { |
| | | if (typeof cell !== "string") return ""; |
| | |
| | | ); |
| | | }, |
| | | }, |
| | | { |
| | | prop: "isActive", |
| | | label: "宿¶ä»»å¡", |
| | | dataType: "slot", |
| | | slot: "isActiveRef", |
| | | align: "center", |
| | | minWidth: 100, |
| | | }, |
| | | { prop: "maintenancePerson", label: "ä¿å
»äºº", minWidth: 100 }, |
| | | { prop: "registrant", label: "ç»è®°äºº", minWidth: 100 }, |
| | | { |
| | |
| | | label: "ç»è®°æ¥æ", |
| | | minWidth: 100, |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | cell ? dayjs(cell).format("YYYY-MM-DD") : "-", |
| | | }, |
| | | { |
| | | fixed: "right", |
| | |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "200px", |
| | | width: "160px", |
| | | }, |
| | | ]); |
| | | |
| | |
| | | align: "center", |
| | | prop: "maintenanceActuallyTime", |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | cell ? dayjs(cell).format("YYYY-MM-DD") : "-", |
| | | }, |
| | | { |
| | | label: "ä¿å
ȍȾ", |
| | |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | align: "center", |
| | | width: "350px", |
| | | width: "250px", |
| | | }, |
| | | ]); |
| | | |
| | |
| | | if (row) { |
| | | nextTick(() => { |
| | | formDiaRef.value?.openDialog("edit", row); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const handleDetail = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | detailDiaRef.value?.openDialog(row); |
| | | }); |
| | | } |
| | | }; |
| | |
| | | maintainModalRef.value.open(row.id, row); |
| | | }; |
| | | |
| | | const handleRecordDetail = row => { |
| | | if (row) { |
| | | nextTick(() => { |
| | | recordDetailDiaRef.value?.openDialog(row); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const addPlan = () => { |
| | | planModalRef.value.openModal(); |
| | | }; |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç¼å·" prop="assetCode"> |
| | | <el-input v-model="form.assetCode" placeholder="ä¿ååèªå¨çæ" disabled /> |
| | | <el-input v-model="form.assetCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" prop="createTime"> |
| | | <el-date-picker v-model="createTimeDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | |
| | | keeper: "", |
| | | status: "in_use", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | ...createDefaultForm(), |
| | | }); |
| | | const createTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const buildAssetCode = () => `GD${Date.now().toString().slice(-10)}`; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "æ°å¢åºå®èµäº§"; |
| | | Object.assign(form, createDefaultForm(), { |
| | | assetCode: buildAssetCode(), |
| | | purchaseDate: new Date().toISOString().split('T')[0], |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button v-if="row.status !== 'amortized'" type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="èµäº§ç¼å·" prop="assetCode"> |
| | | <el-input v-model="form.assetCode" placeholder="ä¿ååèªå¨çæ" disabled /> |
| | | <el-input v-model="form.assetCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ" style="width: 100%;"> |
| | | <el-option label="å¨ç¨" value="in_use" /> |
| | | <el-option label="é²ç½®" value="idle" /> |
| | | <el-option v-if="isView" label="å·²æé宿¯" value="amortized" /> |
| | | <el-option label="å·²æé宿¯" value="amortized" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" prop="createTime"> |
| | | <el-date-picker v-model="createTimeDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | |
| | | ); |
| | | |
| | | const createDefaultForm = () => ({ |
| | | id: null, |
| | | assetCode: "", |
| | | assetName: "", |
| | | category: "", |
| | |
| | | status: "in_use", |
| | | description: "", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | ...createDefaultForm(), |
| | | }); |
| | | const createTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const buildAssetCode = () => `WX${Date.now().toString().slice(-10)}`; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "æ°å¢æ å½¢èµäº§"; |
| | | Object.assign(form, createDefaultForm(), { |
| | | assetCode: buildAssetCode(), |
| | | acquisitionDate: new Date().toISOString().split('T')[0], |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | }; |
| | | |
| | | const summaryProps = ["amount", "taxAmount", "totalAmount"]; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (summaryProps.includes(col.property)) { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur[col.property]); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0) |
| | | ); |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "amount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.amount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = value => { |
| | | const formatMoney = value => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value) |
| | | .toFixed(2) |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·åå·" prop="invoiceApplicationNo"> |
| | | <el-input v-model="form.invoiceApplicationNo" placeholder="ä¿ååèªå¨çæ" disabled /> |
| | | <el-input v-model="form.invoiceApplicationNo" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="formCreateTimeDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number |
| | | v-model="form.paymentAmount" |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾åå·" prop="paymentNumber"> |
| | | <el-input v-model="paymentForm.paymentNumber" placeholder="ä¿ååèªå¨çæ" disabled /> |
| | | <el-input v-model="paymentForm.paymentNumber" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="paymentFormCreateTimeDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | |
| | | const paymentRules = { |
| | |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | status: 0, |
| | | createTime: "", |
| | | }); |
| | | const formCreateTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | const paymentFormCreateTimeDate = computed({ |
| | | get: () => (paymentForm.createTime ? String(paymentForm.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | paymentForm.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | paymentAmount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | applyDate: [{ required: true, message: "è¯·éæ©ç³è¯·æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "amount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.amount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | |
| | | stockInRecordIds, |
| | | inboundBatches: formatInboundBatches(row.inboundBatches), |
| | | status: normalizeStatus(row.status), |
| | | createTime: row.createTime ?? "", |
| | | }); |
| | | }; |
| | | |
| | |
| | | remark: row.remark ?? "", |
| | | status: statusOverride !== undefined ? statusOverride : normalizeStatus(row.status), |
| | | paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0), |
| | | createTime: row.createTime, |
| | | }); |
| | | |
| | | const buildSubmitPayload = (forUpdate = false) => { |
| | |
| | | remark: form.remark || "", |
| | | status: 0, |
| | | paymentAmount: form.paymentAmount, |
| | | createTime: form.createTime, |
| | | }; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | status: 0, |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | |
| | | bankAccount: row.bankAccountNum ?? row.bankAccount ?? "", |
| | | bankName: row.bankAccountName ?? row.bankName ?? "", |
| | | remark: "", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | paymentDialogVisible.value = true; |
| | | nextTick(() => { |
| | |
| | | paymentAmount: paymentForm.paymentAmount, |
| | | paymentNumber: paymentForm.paymentNumber || "", |
| | | remark: paymentForm.remark || "", |
| | | createTime: paymentForm.createTime, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | slot: "inboundDate", |
| | | }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "è§æ ¼åå·", prop: "specificationModel", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { |
| | | label: "éé¢", |
| | | prop: "inboundAmount", |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "inboundAmount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.inboundAmount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseInbound", |
| | | buildFilterParams(), |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | { label: "éè´§åå·", prop: "returnNo", minWidth: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | { label: "å
³èå
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | { |
| | | label: "åè´§ç±»å", |
| | | prop: "shippingType", |
| | | minWidth: "110", |
| | | formatData: (val) => ({ 1: "货车", 2: "å¿«é" }[String(val)] || "--"), |
| | | }, |
| | | { |
| | | label: "å货车çå·", |
| | | prop: "truckPlateNo", |
| | | minWidth: "140", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "1" ? row?.truckPlateNo || "--" : "--"), |
| | | }, |
| | | { |
| | | label: "å¿«éå
¬å¸", |
| | | prop: "expressCompany", |
| | | minWidth: "140", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressCompany || "--" : "--"), |
| | | }, |
| | | { |
| | | label: "å¿«éåå·", |
| | | prop: "expressNo", |
| | | minWidth: "150", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressNo || "--" : "--"), |
| | | }, |
| | | { label: "éè´§æ¥æ", prop: "preparedAt", minWidth: "170" }, |
| | | { |
| | | label: "鿬¾æ»é¢", |
| | |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "totalAmount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.totalAmount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | closingBalance: generateForm.closingBalance, |
| | | accountStatementDetails: selectedPurchases.value.map(buildDetailSubmitItem), |
| | | }); |
| | | |
| | | const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"]; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (summaryProps.includes(col.property)) { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur[col.property]); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | v-loading="tableLoading" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç³è¯·åå·" prop="applyCode"> |
| | | <el-input v-model="form.applyCode" placeholder="ä¿ååèªå¨çæ" disabled /> |
| | | <el-input v-model="form.applyCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" prop="createTime"> |
| | | <el-date-picker |
| | | v-model="formCreateTimeDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" :disabled="isView" /> |
| | | </el-form-item> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance, defineAsyncComponent } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | |
| | | applyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | const formCreateTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | |
| | | const rules = { |
| | |
| | | proxy.download("/accountInvoiceApplication/exportAccountInvoiceApplication", params, filename); |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "amount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.amount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | |
| | | status: normalizeStatus(row.status ?? row.auditStatus), |
| | | outboundBatchNos, |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | createTime: row.createTime ?? "", |
| | | }); |
| | | }; |
| | | |
| | |
| | | isView.value = false; |
| | | dialogTitle.value = "æ°å¢å¼ç¥¨ç³è¯·"; |
| | | Object.assign(form, { |
| | | applyCode: "", |
| | | applyCode: "KP" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | outboundBatchNos: [], |
| | | outboundBatches: "", |
| | |
| | | applyDate: new Date().toISOString().split("T")[0], |
| | | content: "", |
| | | remark: "", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | |
| | | invoiceAmount: form.amount, |
| | | taxRate: form.taxRate, |
| | | status: 0, |
| | | createTime: form.createTime, |
| | | }; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | |
| | | dialogTitle.value = "ç¼è¾å¼ç¥¨ç³è¯·"; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | loadOutboundBatches(form.customerId, true); |
| | | }; |
| | | |
| | | const view = (row) => { |
| | |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> |
| | | <!-- <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> --> |
| | | <el-button type="success" @click="handleExport" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | |
| | | v-loading="tableLoading" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog" |
| | | > |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="100px"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row v-if="isView" :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¶æ"> |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select @change="checkCustomer" v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isView"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isView"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
³èå¼ç¥¨ç³è¯·" prop="accountInvoiceApplicationId"> |
| | | <el-select v-model="form.accountInvoiceApplicationId" |
| | | placeholder="请å
鿩客æ·" |
| | | readonly |
| | | @change="selectionApply" |
| | | :disabled="!form.customerId || isView" |
| | | class="outbound-batch-select"> |
| | | <el-option v-for="(item,index) in applyList" :key="index" :label="item.invoiceApplicationNo" :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker |
| | |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类å" prop="invoiceType"> |
| | | <el-select |
| | | v-model="form.invoiceType" |
| | | placeholder="è¯·éæ©å票类å" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | v-model="form.invoiceType" |
| | | placeholder="è¯·éæ©å票类å" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="handleInvoiceTypeChange" |
| | | > |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="å¢å¼ç¨ä¸ç¨å票" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="å¢å¼ç¨æ®éå票" /> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select |
| | |
| | | placeholder="è¯·éæ©ç¨ç" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="handleTaxRateChange" |
| | | @change="calculateTax" |
| | | > |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="10"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="handleTaxRateChange" |
| | | @change="calculateTax" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="7"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨é¢"> |
| | | <el-input v-model="form.taxAmount" style="width: 100%;" disabled /> |
| | | <el-input v-model="form.taxAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="7"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ä»·ç¨å计"> |
| | | <el-input v-model="form.totalAmount" style="width: 100%;" disabled /> |
| | | <el-input v-model="form.totalAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | cancelAccountSalesInvoice, |
| | | deleteAccountSalesInvoice, |
| | | } from "@/api/financialManagement/accountSalesInvoice.js"; |
| | | import {getAccountInvoiceApplicationList} from "@/api/financialManagement/invoiceApply.js"; |
| | | |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | |
| | | const fileDialogVisible = ref(false); |
| | | const currentRecordId = ref(0); |
| | | |
| | | const applySelectVisible = ref(false); |
| | | const applyList = ref([]); |
| | | |
| | | const openFileDialog = (row) => { |
| | | if (!row.accountInvoiceApplicationId) { |
| | | ElMessage.warning("æªå
³èå¼ç¥¨ç³è¯·ï¼æ æ³æ¥çéä»¶"); |
| | |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | deptId: undefined, |
| | | accountInvoiceApplicationId: undefined, |
| | | storageAttachmentId: undefined, |
| | | }); |
| | | |
| | | const rules = { |
| | | invoiceNo: [{ required: true, message: "请è¾å
¥å票å·ç ", trigger: "blur" }], |
| | | accountInvoiceApplicationId: [{ required: true, message: "è¯·éæ©å¼ç¥¨ç³è¯·", trigger: "change" }], |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | invoiceDate: [{ required: true, message: "è¯·éæ©å¼ç¥¨æ¥æ", trigger: "change" }], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | }; |
| | | |
| | | // éæ©å®¢æ· |
| | | const checkCustomer = (val) =>{ |
| | | loadApplyList(val); |
| | | } |
| | | |
| | | //æ¥è¯¢å¼ç¥¨ç³è¯·å表 |
| | | const loadApplyList = (customerId) => { |
| | | if (!customerId) { |
| | | applyList.value = []; |
| | | return Promise.resolve(); |
| | | } |
| | | return getAccountInvoiceApplicationList({ customerId }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | const list = res.data?.records ?? res.data ?? []; |
| | | applyList.value = Array.isArray(list) ? list : []; |
| | | } else { |
| | | applyList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | applyList.value = []; |
| | | }) |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const selectionApply = (val)=>{ |
| | | const applyObj = applyList.value.find(item=>item.id === val); |
| | | if(!applyObj){ |
| | | ElMessage.warning("è¯·éæ©å
³èçå¼ç¥¨ç³è¯·"); |
| | | return; |
| | | } |
| | | form.remark = applyObj.remark; |
| | | form.content = applyObj.invoiceContent; |
| | | form.invoiceDate = applyObj.applyDate; |
| | | form.invoiceType = applyObj.invoiceType; |
| | | form.deptId = applyObj.deptId; |
| | | form.taxRate = applyObj.taxRate; |
| | | form.totalAmount = applyObj.invoiceAmount |
| | | handleTaxRateChange(); |
| | | } |
| | | |
| | | /** ä»·ç¨åè®¡åæ´ï¼æç¨çåç®ä¸å«ç¨éé¢ãç¨é¢ */ |
| | | const calculateTaxFromInclusive = (inclusiveTotal) => { |
| | | const total = Number(inclusiveTotal ?? form.totalAmount ?? 0); |
| | | if (total <= 0) { |
| | | form.amount = 0; |
| | | form.taxAmount = 0; |
| | | form.totalAmount = 0; |
| | | return; |
| | | } |
| | | const rate = Number(form.taxRate) / 100; |
| | | form.totalAmount = Number(total.toFixed(2)); |
| | | form.amount = Number((form.totalAmount / (1 + rate)).toFixed(2)); |
| | | form.taxAmount = Number((form.totalAmount - form.amount).toFixed(2)); |
| | | }; |
| | | |
| | | const calculateTaxFromExclusive = () => { |
| | | const calculateTax = () => { |
| | | form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2)); |
| | | form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2)); |
| | | }; |
| | | |
| | | const handleTaxRateChange = () => { |
| | | if (form.totalAmount > 0) { |
| | | calculateTaxFromInclusive(form.totalAmount); |
| | | } else { |
| | | calculateTaxFromExclusive(); |
| | | } |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "amount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.amount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | const handleInvoiceTypeChange = () => { |
| | | calculateTax(); |
| | | }; |
| | | |
| | | const normalizeTableRow = (row) => ({ |
| | |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | applySelectVisible.value = false; |
| | | isView.value = false; |
| | | isView.value = false; |
| | | }; |
| | | |
| | |
| | | <el-form-item label="æ¶æ¬¾åå·" |
| | | prop="receiptCode"> |
| | | <el-input v-model="form.receiptCode" |
| | | placeholder="ä¿ååèªå¨çæ" |
| | | placeholder="ç³»ç»èªå¨çæ" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | prop="amount"> |
| | | <el-input-number v-model="form.amount" |
| | | :min="0" |
| | | :max="collectionAmountInputMax" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" /> |
| | | :disabled="isView" |
| | | placeholder="æ ¹æ®å
³èåæ®èªå¨æ±æ»ï¼å¯ä¿®æ¹" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å建æ¶é´" |
| | | prop="createTime"> |
| | | <el-date-picker v-model="formCreateTimeDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(row.outboundAmount) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="amountReceived" |
| | | label="å·²æ¶æ¬¾éé¢" |
| | | width="110" |
| | | align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(row.amountReceived) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxRate" |
| | | label="ç¨ç" |
| | | width="80" |
| | |
| | | nextTick, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | import dayjs from "dayjs"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const originalReceiptAmount = ref(0); |
| | | const submitLoading = ref(false); |
| | | |
| | | const customerList = ref([]); |
| | |
| | | stockOutRecordIds: [], |
| | | outboundBatches: "", |
| | | remark: "", |
| | | createTime: "", |
| | | }); |
| | | const formCreateTimeDate = computed({ |
| | | get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""), |
| | | set: (value) => { |
| | | form.createTime = value ? `${value} ${dayjs().format("HH:mm:ss")}` : ""; |
| | | }, |
| | | }); |
| | | |
| | | const maxCollectionAmount = computed(() => { |
| | | const selected = form.stockOutRecordIds || []; |
| | | const editAmount = isEdit.value ? Number(originalReceiptAmount.value) || 0 : 0; |
| | | if (!selected.length) return isEdit.value ? editAmount : undefined; |
| | | const selectedValueSet = new Set(selected.map(id => String(id))); |
| | | const selectedOptions = outboundBatchOptions.value.filter( |
| | | opt => opt.amountLimitAvailable && selectedValueSet.has(String(opt.value)) |
| | | ); |
| | | if (selectedOptions.length !== selectedValueSet.size) { |
| | | return isEdit.value ? Number(editAmount.toFixed(2)) : undefined; |
| | | } |
| | | const sum = selectedOptions.reduce( |
| | | (acc, opt) => acc + (Number(opt.outboundAmount) || 0), |
| | | 0 |
| | | ); |
| | | return Number((sum + editAmount).toFixed(2)); |
| | | }); |
| | | |
| | | const collectionAmountInputMax = computed( |
| | | () => maxCollectionAmount.value ?? Number.MAX_SAFE_INTEGER |
| | | ); |
| | | |
| | | const validateCollectionAmount = (rule, value, callback) => { |
| | | if (value === undefined || value === null || value === "") { |
| | | callback(); |
| | | return; |
| | | } |
| | | const amount = Number(value); |
| | | if (Number.isNaN(amount)) { |
| | | callback(new Error("请è¾å
¥æ¶æ¬¾éé¢")); |
| | | return; |
| | | } |
| | | const max = maxCollectionAmount.value; |
| | | if (max !== undefined && amount - max > 0.000001) { |
| | | callback(new Error(`æ¶æ¬¾éé¢ä¸è½è¶
è¿${max.toFixed(2)}`)); |
| | | return; |
| | | } |
| | | callback(); |
| | | }; |
| | | |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | |
| | | receiptDate: [ |
| | | { required: true, message: "è¯·éæ©æ¶æ¬¾æ¥æ", trigger: "change" }, |
| | | ], |
| | | amount: [ |
| | | { required: true, message: "请è¾å
¥æ¶æ¬¾éé¢", trigger: "blur" }, |
| | | { validator: validateCollectionAmount, trigger: ["blur", "change"] }, |
| | | ], |
| | | amount: [{ required: true, message: "请è¾å
¥æ¶æ¬¾éé¢", trigger: "blur" }], |
| | | receiptMethod: [ |
| | | { required: true, message: "è¯·éæ©æ¶æ¬¾æ¹å¼", trigger: "change" }, |
| | | ], |
| | |
| | | return list.map((item, index) => { |
| | | if (typeof item === "string" || typeof item === "number") { |
| | | const text = String(item); |
| | | return { |
| | | label: text, |
| | | value: text, |
| | | outboundAmount: 0, |
| | | amountLimitAvailable: false, |
| | | }; |
| | | return { label: text, value: text, outboundAmount: 0 }; |
| | | } |
| | | const label = |
| | | item.outboundBatches ?? |
| | |
| | | item.label ?? |
| | | `åºåºå${index + 1}`; |
| | | const value = item.id ?? item.stockOutRecordId ?? label; |
| | | const outboundAmount = Number(item.outboundAmount) || 0; |
| | | const amountReceived = Number(item.amountReceived) || 0; |
| | | const availableAmount = outboundAmount - amountReceived; |
| | | return { |
| | | label: String(label), |
| | | value, |
| | | outboundAmount: |
| | | availableAmount > 0 ? Number(availableAmount.toFixed(2)) : 0, |
| | | amountLimitAvailable: true, |
| | | outboundAmount: Number(item.outboundAmount) || 0, |
| | | }; |
| | | }); |
| | | }; |
| | |
| | | label: String(id), |
| | | value: id, |
| | | outboundAmount: 0, |
| | | amountLimitAvailable: false, |
| | | }); |
| | | }); |
| | | }; |
| | |
| | | outboundSelectVisible.value = false; |
| | | syncCollectionAmount(); |
| | | formRef.value?.validateField("stockOutRecordIds"); |
| | | formRef.value?.validateField("amount"); |
| | | }; |
| | | |
| | | const handleOutboundDialogClosed = () => { |
| | |
| | | collectionNumber: form.receiptCode || "", |
| | | remark: form.remark || "", |
| | | stockOutRecordIds: (form.stockOutRecordIds || []).join(","), |
| | | createTime: form.createTime, |
| | | }; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | |
| | | stockOutRecordIds, |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | remark: row.remark ?? "", |
| | | createTime: row.createTime ?? "", |
| | | }); |
| | | }; |
| | | |
| | |
| | | outboundSelectVisible.value = false; |
| | | isView.value = false; |
| | | isEdit.value = false; |
| | | originalReceiptAmount.value = 0; |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "æ°å¢æ¶æ¬¾"; |
| | | originalReceiptAmount.value = 0; |
| | | Object.assign(form, { |
| | | receiptCode: "", |
| | | receiptCode: "SK" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | receiptDate: new Date().toISOString().split("T")[0], |
| | | amount: 0, |
| | |
| | | stockOutRecordIds: [], |
| | | outboundBatches: "", |
| | | remark: "", |
| | | createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"), |
| | | }); |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾æ¶æ¬¾"; |
| | | fillFormFromRow(row); |
| | | originalReceiptAmount.value = Number(form.amount || 0); |
| | | dialogVisible.value = true; |
| | | loadOutboundBatches(form.customerId, true); |
| | | }; |
| | | |
| | | const view = row => { |
| | | isView.value = true; |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ¥çæ¶æ¬¾"; |
| | | originalReceiptAmount.value = 0; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | closingBalance: generateForm.closingBalance, |
| | | accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem), |
| | | }); |
| | | |
| | | const summaryProps = ["openingBalance", "currentPlan", "currentActually", "closingBalance"]; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (summaryProps.includes(col.property)) { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur[col.property]); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = Number(total.toFixed(2)).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | <template> |
| | | <!-- éå®åºåº --> |
| | | <!-- éå®åºåº --> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" |
| | | :inline="true"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="åºåºåå·:"> |
| | | <el-input v-model="filters.outboundBatches" |
| | | placeholder="请è¾å
¥åºåºåå·" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | <el-input v-model="filters.outboundBatches" placeholder="请è¾å
¥åºåºåå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·åç§°:"> |
| | | <el-input v-model="filters.customerName" |
| | | placeholder="请è¾å
¥å®¢æ·åç§°" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | <el-input v-model="filters.customerName" placeholder="请è¾å
¥å®¢æ·åç§°" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="åºåºæ¥æ:"> |
| | | <el-date-picker v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable /> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="onSearch">æç´¢</el-button> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <div class="actions"> |
| | | <div></div> |
| | | <div> |
| | | <el-button @click="handleOut" |
| | | icon="Download">导åº</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" /> |
| | | @pagination="changePage" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountSales } from "@/api/financialManagement/accountSales"; |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountSales } from "@/api/financialManagement/accountSales"; |
| | | |
| | | defineOptions({ |
| | | name: "éå®åºåº", |
| | | }); |
| | | defineOptions({ |
| | | name: "éå®åºåº", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | outboundBatches: "", |
| | | customerName: "", |
| | | dateRange: [], |
| | | }); |
| | | const filters = reactive({ |
| | | outboundBatches: "", |
| | | customerName: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "åºåºåå·", prop: "outboundBatches", minWidth: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", minWidth: "180" }, |
| | | { label: "åºåºæ¥æ", prop: "shippingDate", width: "170" }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "è§æ ¼åå·", prop: "specificationModel", minWidth: "140" }, |
| | | { |
| | | label: "éé¢", |
| | | prop: "outboundAmount", |
| | | minWidth: "120", |
| | | formatData: val => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | | : Number(val).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }), |
| | | }, |
| | | { label: "åè´§ç¼å·", prop: "shippingNo", minWidth: "140" }, |
| | | { label: "éå®è®¢åå·", prop: "salesContractNo", minWidth: "150" }, |
| | | ]; |
| | | const columns = [ |
| | | { label: "åºåºåå·", prop: "outboundBatches", minWidth: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", minWidth: "180" }, |
| | | { label: "åºåºæ¥æ", prop: "shippingDate", width: "170" }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { |
| | | label: "éé¢", |
| | | prop: "outboundAmount", |
| | | minWidth: "120", |
| | | align: "right", |
| | | formatData: (val) => (val === null || val === undefined || val === "" ? "" : Number(val).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })), |
| | | }, |
| | | { label: "åè´§ç¼å·", prop: "shippingNo", minWidth: "140" }, |
| | | { label: "éå®è®¢åå·", prop: "salesContractNo", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | outboundBatches: filters.outboundBatches || undefined, |
| | | customerName: filters.customerName || undefined, |
| | | }; |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | function buildFilterParams() { |
| | | const params = { |
| | | outboundBatches: filters.outboundBatches || undefined, |
| | | customerName: filters.customerName || undefined, |
| | | }; |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | } |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSales({ |
| | | ...buildFilterParams(), |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then(res => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.outboundBatches = ""; |
| | | filters.customerName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "outboundAmount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.outboundAmount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSales({ |
| | | ...buildFilterParams(), |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }) |
| | | .then((res) => { |
| | | const ok = res.code === 200 || res.code === 0; |
| | | if (ok && res.data) { |
| | | pagination.total = res.data.total ?? 0; |
| | | dataList.value = res.data.records ?? []; |
| | | } else { |
| | | sums[index] = ""; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | return sums; |
| | | }; |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountSales/exportAccountSalesOutbound", |
| | | buildFilterParams(), |
| | | `éå®åºåº_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | const resetFilters = () => { |
| | | filters.outboundBatches = ""; |
| | | filters.customerName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountSales/exportAccountSalesOutbound", |
| | | buildFilterParams(), |
| | | `éå®åºåº_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| | |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | isShowSummary |
| | | :summaryMethod="getSummaries" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | { label: "éè´§åå·", prop: "returnNo", minWidth: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", minWidth: "180" }, |
| | | { label: "å
³èåè´§åå·", prop: "shippingNo", minWidth: "150" }, |
| | | { |
| | | label: "åè´§ç±»å", |
| | | prop: "shippingType", |
| | | minWidth: "110", |
| | | formatData: (val) => ({ 1: "货车", 2: "å¿«é" }[String(val)] || "--"), |
| | | }, |
| | | { |
| | | label: "å货车çå·", |
| | | prop: "truckPlateNo", |
| | | minWidth: "140", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "1" ? row?.truckPlateNo || "--" : "--"), |
| | | }, |
| | | { |
| | | label: "å¿«éå
¬å¸", |
| | | prop: "expressCompany", |
| | | minWidth: "140", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressCompany || "--" : "--"), |
| | | }, |
| | | { |
| | | label: "å¿«éåå·", |
| | | prop: "expressNo", |
| | | minWidth: "150", |
| | | formatData: (_val, row) => (String(row?.shippingType) === "2" ? row?.expressNo || "--" : "--"), |
| | | }, |
| | | { label: "éè´§æ¥æ", prop: "makeTime", minWidth: "170" }, |
| | | { |
| | | label: "鿬¾æ»é¢", |
| | | prop: "refundAmount", |
| | | minWidth: "120", |
| | | align: "right", |
| | | formatData: (val) => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getSummaries = ({ columns, data }) => { |
| | | const sums = []; |
| | | columns.forEach((col, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "å计"; |
| | | } else if (col.property === "refundAmount") { |
| | | const total = data.reduce((prev, cur) => { |
| | | const v = Number(cur.refundAmount); |
| | | return prev + (isNaN(v) ? 0 : v); |
| | | }, 0); |
| | | sums[index] = total.toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | return map[key] || ""; |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | // èè°çº¦å®ï¼åé¡µåæ°ä½¿ç¨ current/sizeï¼æ¥æèå´æå为 startDate/endDate |
| | | const getTableData = async () => { |
| | | try { |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | |
| | | <el-radio-button :value="'waiting'">å¾
å¼å§({{ orderProgressMeta.waitingCount }})</el-radio-button> |
| | | <el-radio-button :value="'inProgress'">è¿è¡ä¸({{ orderProgressMeta.inProgressCount }})</el-radio-button> |
| | | <el-radio-button :value="'completed'">已宿({{ orderProgressMeta.completedCount }})</el-radio-button> |
| | | <el-radio-button :value="'end'">å·²ç»æ({{ orderProgressMeta.endCount }})</el-radio-button> |
| | | <el-radio-button :value="'paused'">å·²æå({{ orderProgressMeta.pausedCount }})</el-radio-button> |
| | | </el-radio-group> |
| | | </div> |
| | | <div class="order-table-wrap"> |
| | | <el-table :data="filteredOrders" stripe max-height="440"> |
| | | <el-table-column prop="orderNo" label="订åç¼å·" min-width="150" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="120" /> |
| | | <el-table-column prop="planQty" label="è®¡åæ°é" min-width="90" /> |
| | | <el-table-column prop="completedQty" label="已宿" min-width="90" /> |
| | | <el-table-column label="宿ç" min-width="180"> |
| | | <template #default="{ row }"> |
| | | <div class="table-progress"> |
| | | <el-progress |
| | | :stroke-width="8" |
| | | :percentage="row.completionRate" |
| | | :show-text="false" |
| | | status="success" |
| | | /> |
| | | <span>{{ row.completionRate }}%</span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="deliveryDate" label="交æ" min-width="110" /> |
| | | <el-table-column label="ç¶æ" min-width="90"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getOrderStatusType(row.status)" effect="light"> |
| | | {{ row.statusLabel || getOrderStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <el-table :data="filteredOrders" stripe> |
| | | <el-table-column prop="orderNo" label="订åç¼å·" min-width="150" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="120" /> |
| | | <el-table-column prop="planQty" label="è®¡åæ°é" min-width="90" /> |
| | | <el-table-column prop="completedQty" label="已宿" min-width="90" /> |
| | | <el-table-column label="宿ç" min-width="180"> |
| | | <template #default="{ row }"> |
| | | <div class="table-progress"> |
| | | <el-progress |
| | | :stroke-width="8" |
| | | :percentage="row.completionRate" |
| | | :show-text="false" |
| | | status="success" |
| | | /> |
| | | <span>{{ row.completionRate }}%</span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="deliveryDate" label="交æ" min-width="110" /> |
| | | <el-table-column label="ç¶æ" min-width="90"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getOrderStatusType(row.status)" effect="light"> |
| | | {{ row.statusLabel || getOrderStatusText(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <div v-if="visiblePanels.contract" class="cockpit-panel contract-panel"> |
| | |
| | | <div v-if="visiblePanels.todo" class="cockpit-panel todo-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">å¾
åäºé¡¹</div> |
| | | <span class="panel-more" @click="openTodoDialog">æ´å¤</span> |
| | | <span class="panel-more" @click="goToApprovalProcess">æ´å¤</span> |
| | | </div> |
| | | <ul class="todo-list" v-if="todoList.length > 0"> |
| | | <li v-for="item in todoList" :key="item.id" class="todo-item"> |
| | |
| | | <div v-if="visiblePanels.realtime" class="cockpit-panel realtime-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">çäº§å®æ¶çæ¿</div> |
| | | <span class="panel-more" @click="goToProductionOrder">æ´å¤</span> |
| | | </div> |
| | | <div class="realtime-grid"> |
| | | <div class="realtime-item" v-for="item in realtimeBoard" :key="item.key"> |
| | |
| | | <div v-if="visiblePanels.plan" class="cockpit-panel plan-panel"> |
| | | <div class="panel-title-row"> |
| | | <div class="panel-title">仿¥ç产计å</div> |
| | | <span class="panel-more">{{ todayPlanTotal }}项</span> |
| | | <span class="panel-more" @click="goToProductionPlan">{{ todayPlanTotal }}项</span> |
| | | </div> |
| | | <ul class="plan-list"> |
| | | <li v-for="item in todayPlanList" :key="item.orderNo" class="plan-item"> |
| | |
| | | <el-button type="primary" @click="handleProcessDialogConfirm">确认</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="todoDialogVisible" title="å
¨é¨å¾
åäºé¡¹" width="900px" append-to-body destroy-on-close> |
| | | <div v-loading="todoDialogLoading" class="todo-dialog-body"> |
| | | <el-table :data="todoDialogList" stripe max-height="520"> |
| | | <el-table-column prop="approveId" label="å¾
åç¼å·" min-width="160" /> |
| | | <el-table-column prop="approveDeptName" label="é¨é¨/模æ¿" min-width="160" /> |
| | | <el-table-column prop="approveReason" label="äºç±" min-width="240" show-overflow-tooltip /> |
| | | <el-table-column prop="approveUserName" label="å起人" min-width="120" /> |
| | | <el-table-column prop="approveTime" label="æ¶é´" min-width="170" /> |
| | | </el-table> |
| | | </div> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | }); |
| | | |
| | | const realtimeBoardData = ref({ |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }); |
| | |
| | | waitingCount: 0, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | endCount: 0, |
| | | pausedCount: 0, |
| | | }); |
| | | |
| | | const todayPlanList = ref([]); |
| | | const todayPlanTotal = ref(0); |
| | | const todoDialogVisible = ref(false); |
| | | const todoDialogLoading = ref(false); |
| | | const todoDialogList = ref([]); |
| | | |
| | | const sum = ref(0); |
| | | const yny = ref(0); |
| | |
| | | |
| | | const productionOrders = ref([]); |
| | | |
| | | const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "end"]; |
| | | const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "paused"]; |
| | | const orderFilterAliasMap = { |
| | | 1: "waiting", |
| | | 2: "inProgress", |
| | | 3: "completed", |
| | | 5: "end", |
| | | 4: "paused", |
| | | }; |
| | | const orderFilter = ref("all"); |
| | | const filteredOrders = computed(() => productionOrders.value); |
| | |
| | | }; |
| | | |
| | | const realtimeBoard = computed(() => { |
| | | const oee = Number(realtimeBoardData.value.deviceOee?.value || 0); |
| | | const orderAchievement = Number(realtimeBoardData.value.orderAchievementRate?.value || 0); |
| | | const defectRate = Number(realtimeBoardData.value.defectRate?.value || 0); |
| | | const oeeCompare = Number(realtimeBoardData.value.deviceOee?.compareYesterday || 0); |
| | | const orderCompare = Number(realtimeBoardData.value.orderAchievementRate?.compareYesterday || 0); |
| | | const defectCompare = Number(realtimeBoardData.value.defectRate?.compareYesterday || 0); |
| | | return [ |
| | | { |
| | | key: "oee", |
| | | label: "è®¾å¤ OEE", |
| | | percent: clampPercent(oee), |
| | | display: `${oee.toFixed(2)}%`, |
| | | delta: getCompareText(oeeCompare), |
| | | trend: getCompareTrend(oeeCompare), |
| | | color: "#2d8cff", |
| | | }, |
| | | { |
| | | key: "order", |
| | | label: "订åè¾¾æç", |
| | |
| | | display: `${orderAchievement.toFixed(2)}%`, |
| | | delta: getCompareText(orderCompare), |
| | | trend: getCompareTrend(orderCompare), |
| | | color: "#31d2ff", |
| | | color: "#2d8cff", |
| | | }, |
| | | { |
| | | key: "defect", |
| | |
| | | pathPrefixes: ["/inventorymanagement"], |
| | | }, |
| | | production: { |
| | | titles: ["ç产管æ§"], |
| | | pathPrefixes: ["/productionManagement", "/productionPlan"], |
| | | titles: ["ç产管ç", "主ç产计å", "ç产订å", "ç产æ¥å·¥"], |
| | | pathPrefixes: ["/productionmanagement", "/productionplan"], |
| | | }, |
| | | quality: { |
| | | titles: ["è´¨é管ç"], |
| | |
| | | realtime: visibleModules.value.production, |
| | | quick: quickEntries.value.length > 0, |
| | | plan: visibleModules.value.production, |
| | | receipt: visibleModules.value.finance, |
| | | receipt: visibleModules.value.sales || visibleModules.value.finance, |
| | | })); |
| | | |
| | | const hasLeftPanels = computed( |
| | |
| | | 1: "å¾
å¼å§", |
| | | 2: "è¿è¡ä¸", |
| | | 3: "已宿", |
| | | 5: "å·²ç»æ", |
| | | 4: "å·²æå", |
| | | }; |
| | | return mapping[status] || "æªç¥"; |
| | | }; |
| | |
| | | const res = await productionRealtimeBoard(); |
| | | const data = res?.data || {}; |
| | | realtimeBoardData.value = { |
| | | deviceOee: { |
| | | value: Number(data.deviceOee?.value || 0), |
| | | compareYesterday: Number(data.deviceOee?.compareYesterday || 0), |
| | | }, |
| | | orderAchievementRate: { |
| | | value: Number(data.orderAchievementRate?.value || 0), |
| | | compareYesterday: Number(data.orderAchievementRate?.compareYesterday || 0), |
| | |
| | | }; |
| | | } catch { |
| | | realtimeBoardData.value = { |
| | | deviceOee: { value: 0, compareYesterday: 0 }, |
| | | orderAchievementRate: { value: 0, compareYesterday: 0 }, |
| | | defectRate: { value: 0, compareYesterday: 0 }, |
| | | }; |
| | |
| | | const res = await productionOrderProgress({ |
| | | status: orderFilter.value, |
| | | tab: orderFilter.value, |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const data = res?.data || {}; |
| | | const statusValue = normalizeOrderFilter(data.status, orderFilter.value); |
| | |
| | | waitingCount: resolveProgressCount(data.waitingCount, statusValue, "waiting", total), |
| | | inProgressCount: resolveProgressCount(data.inProgressCount, statusValue, "inProgress", total), |
| | | completedCount: resolveProgressCount(data.completedCount, statusValue, "completed", total), |
| | | endCount: resolveProgressCount(data.endCount, statusValue, "end", total), |
| | | pausedCount: resolveProgressCount(data.pausedCount, statusValue, "paused", total), |
| | | }; |
| | | productionOrders.value = (data.records || []).map(mapOrderProgressRecord); |
| | | } catch { |
| | |
| | | waitingCount: 0, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | endCount: 0, |
| | | pausedCount: 0, |
| | | }; |
| | | productionOrders.value = []; |
| | | } |
| | |
| | | const refreshTodayProductionPlan = async () => { |
| | | try { |
| | | const res = await todayProductionPlan({ |
| | | limit: 1000, |
| | | limit: 4, |
| | | planDate: nowDate.value, |
| | | }); |
| | | const data = res?.data || {}; |
| | |
| | | const todoInfoS = async () => { |
| | | const res = await homeTodos(); |
| | | todoList.value = res.data || []; |
| | | }; |
| | | |
| | | const openTodoDialog = async () => { |
| | | todoDialogVisible.value = true; |
| | | todoDialogLoading.value = true; |
| | | try { |
| | | const res = await homeTodos(); |
| | | todoDialogList.value = res.data || []; |
| | | } catch { |
| | | todoDialogList.value = []; |
| | | } finally { |
| | | todoDialogLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const statisticsReceivable = async () => { |
| | |
| | | const goToQuick = (path) => { |
| | | if (!path) return; |
| | | router.push(path).catch(() => {}); |
| | | }; |
| | | |
| | | const goToProductionOrder = () => { |
| | | router.push('/productionManagement/productionOrder').catch(() => {}); |
| | | }; |
| | | |
| | | const goToApprovalProcess = () => { |
| | | router.push('/collaborativeApproval/approvalProcess').catch(() => {}); |
| | | }; |
| | | |
| | | const goToProductionPlan = () => { |
| | | router.push('/productionManagement/productionPlan').catch(() => {}); |
| | | }; |
| | | |
| | | watch(orderFilter, () => { |
| | |
| | | } |
| | | |
| | | .stats-grid { |
| | | display: flex; |
| | | display: grid; |
| | | grid-template-columns: repeat(4, minmax(0, 1fr)); |
| | | gap: 14px; |
| | | } |
| | | |
| | | .stats-grid .stat-card { |
| | | flex: 1; |
| | | } |
| | | |
| | | .stat-card { |
| | |
| | | font-size: 14px; |
| | | color: #2563eb; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .todo-dialog-body { |
| | | min-height: 220px; |
| | | } |
| | | |
| | | .plan-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .plan-list { |
| | | margin: 10px 0 0; |
| | | padding: 0 4px 0 0; |
| | | list-style: none; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | max-height: 300px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .plan-list::-webkit-scrollbar { |
| | | width: 6px; |
| | | } |
| | | |
| | | .plan-list::-webkit-scrollbar-thumb { |
| | | border-radius: 999px; |
| | | background: rgba(37, 99, 235, 0.28); |
| | | } |
| | | |
| | | .todo-list { |
| | |
| | | .realtime-grid { |
| | | margin-top: 10px; |
| | | display: grid; |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | gap: 10px; |
| | | } |
| | | |
| | |
| | | border: 1px solid rgba(148, 163, 184, 0.2); |
| | | border-radius: 12px; |
| | | background: #f8fbff; |
| | | padding: 10px 8px; |
| | | padding: 16px 12px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .realtime-value { |
| | | font-size: 26px; |
| | | font-size: 32px; |
| | | color: #0f172a; |
| | | font-weight: 700; |
| | | } |
| | |
| | | |
| | | .order-panel { |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .order-table-wrap { |
| | | flex: 1; |
| | | min-height: 0; |
| | | } |
| | | |
| | | .quick-panel { |
| | |
| | | transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; |
| | | } |
| | | |
| | | .plan-panel { |
| | | min-height: 236px; |
| | | } |
| | | |
| | | .quality-panel { |
| | | min-height: 0; |
| | | } |
| | | |
| | | .receipt-panel { |
| | | min-height: 340px; |
| | | } |
| | | |
| | | .plan-list { |
| | | margin: 10px 0 0; |
| | | padding: 0; |
| | | list-style: none; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .plan-item { |
| | |
| | | .main-grid { |
| | | grid-template-columns: minmax(0, 1fr) 340px; |
| | | } |
| | | |
| | | .stats-grid { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | @media (max-width: 1200px) { |
| | |
| | | } |
| | | |
| | | .realtime-grid { |
| | | grid-template-columns: repeat(3, minmax(0, 1fr)); |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | |
| | | .quality-cards { |
| | |
| | | justify-content: flex-start; |
| | | } |
| | | |
| | | .stats-grid { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | |
| | | .right-column { |
| | | grid-template-columns: 1fr; |
| | | } |
| | |
| | | <template> |
| | | <div> |
| | | <div class="search_form" |
| | | style="margin-bottom: 10px"> |
| | | <el-form ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline"> |
| | | <div class="search_form" style="margin-bottom: 10px"> |
| | | <el-form |
| | | ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="åºåºæ¥æ" |
| | | prop="timeStr"> |
| | | <el-form-item label="åºåºæ¥æ" prop="timeStr"> |
| | | <el-date-picker v-model="searchForm.timeStr" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="产å大类" |
| | | prop="productName"> |
| | | <el-form-item label="产å大类" prop="productName"> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="4"> |
| | | <el-form-item label="è§æ ¼åå·" |
| | | prop="model"> |
| | | <el-form-item label="产åè§æ ¼" prop="model"> |
| | | <el-input v-model="searchForm.model" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="æ¹å·" |
| | | prop="batchNo"> |
| | | <el-form-item label="æ¹å·" prop="batchNo"> |
| | | <el-input v-model="searchForm.batchNo" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="æ¥æº" |
| | | prop="recordType"> |
| | | <el-select v-model="searchForm.recordType" |
| | | style="width: 240px" |
| | | placeholder="è¯·éæ©" |
| | | clearable> |
| | | <el-option v-for="item in stockRecordTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | <el-form-item label="æ¥æº" prop="recordType"> |
| | | <el-select |
| | | v-model="searchForm.recordType" |
| | | style="width: 240px" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | > |
| | | <el-option |
| | | v-for="item in stockRecordTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- æé® --> |
| | | <el-col :span="4"> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="getList"> |
| | | <el-button type="primary" @click="getList"> |
| | | æç´¢ |
| | | </el-button> |
| | | |
| | | <el-button @click="resetSearch"> |
| | | éç½® |
| | | </el-button> |
| | |
| | | </el-form> |
| | | </div> |
| | | <div class="actions"> |
| | | <el-button type="primary" |
| | | @click="handleBatchApprove">审æ¹</el-button> |
| | | <el-button :disabled="!canReverseApprove" |
| | | @click="handleReverseApprove">å审</el-button> |
| | | <!-- <el-button type="primary" @click="handleBatchApprove">审æ¹</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | | @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" |
| | | plain |
| | | @click="handlePrint">æå°</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | <el-button type="primary" plain @click="handlePrint">æå°</el-button> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="åºåºæ¹æ¬¡" |
| | | prop="outboundBatches" |
| | | min-width="100" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åºåºæ¥æ" |
| | | prop="createTime" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="产å大类" |
| | | prop="productName" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æ¹å·" |
| | | prop="batchNo" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åºåºæ°é" |
| | | prop="stockOutNum" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åºåºäºº" |
| | | prop="createBy" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æ¥æº" |
| | | prop="recordType" |
| | | show-overflow-tooltip> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row) => row.id" |
| | | style="width: 100%" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="åºåºæ¹æ¬¡" |
| | | prop="outboundBatches" |
| | | min-width="100" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="åºåºæ¥æ" |
| | | prop="createTime" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="æ¹å·" prop="batchNo" width="150" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" width="80" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="åºåºæ°é" |
| | | prop="stockOutNum" |
| | | width="80" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="åºåºäºº" prop="createBy" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="æ¥æº" prop="recordType" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ getRecordType(scope.row.recordType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | show-overflow-tooltip> |
| | | <!-- <el-table-column |
| | | label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="scope"> |
| | | <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" |
| | | size="small"> |
| | | <el-tag |
| | | :type="getApprovalStatusTagType(scope.row.approvalStatus)" |
| | | size="small" |
| | | > |
| | | {{ getApprovalStatusLabel(scope.row.approvalStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import { |
| | | getStockOutPage, |
| | | delPendingStockOut, |
| | | batchApproveStockOutRecords, |
| | | batchUnapproveStockOutRecords, |
| | | } from "@/api/inventoryManagement/stockOut.js"; |
| | | import { |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import { |
| | | getStockOutPage, |
| | | delPendingStockOut, |
| | | batchApproveStockOutRecords, |
| | | } from "@/api/inventoryManagement/stockOut.js"; |
| | | import { |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | // æ¥æºç±»åé项 |
| | | const stockRecordTypeOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | // æ¥æºç±»åé项 |
| | | const stockRecordTypeOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | | const props = defineProps({ |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: "0", |
| | | }, |
| | | topParentProductId: { |
| | | type: [String, Number], |
| | | default: undefined, |
| | | }, |
| | | }); |
| | | const props = defineProps({ |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: "0", |
| | | }, |
| | | topParentProductId: { |
| | | type: [String, Number], |
| | | default: undefined, |
| | | }, |
| | | }); |
| | | |
| | | // æå°ç¸å
³ |
| | | const printPreviewVisible = ref(false); |
| | | const printData = ref([]); |
| | | // æå°ç¸å
³ |
| | | const printPreviewVisible = ref(false); |
| | | const printData = ref([]); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | timeStr: "", |
| | | recordType: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | timeStr: "", |
| | | recordType: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const searchFormRef = ref(null); |
| | | const searchFormRef = ref(null); |
| | | |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockOutPage({ |
| | | ...searchForm.value, |
| | | ...page, |
| | | topParentProductId: props.topParentProductId, |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockOutPage({ |
| | | ...searchForm.value, |
| | | ...page, |
| | | topParentProductId: props.topParentProductId, |
| | | }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | tableData.value.map((item) => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | tableData.value.map(item => { |
| | | item.children = []; |
| | | }); |
| | | total.value = res.data.total; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const getRecordType = recordType => { |
| | | return ( |
| | | stockRecordTypeOptions.value.find(item => item.value === recordType) |
| | | ?.label || "" |
| | | ); |
| | | }; |
| | | const getRecordType = (recordType) => { |
| | | return ( |
| | | stockRecordTypeOptions.value.find((item) => item.value === recordType) |
| | | ?.label || "" |
| | | ); |
| | | }; |
| | | |
| | | const approvalStatusLabelMap = { |
| | | 0: "å¾
审æ¹", |
| | | 1: "éè¿", |
| | | 2: "驳å", |
| | | 3: "å¾
确认", |
| | | pending: "å¾
审æ¹", |
| | | approved: "éè¿", |
| | | rejected: "驳å", |
| | | PENDING: "å¾
审æ¹", |
| | | APPROVED: "éè¿", |
| | | REJECTED: "驳å", |
| | | }; |
| | | const approvalStatusLabelMap = { |
| | | 0: "å¾
审æ¹", |
| | | 1: "éè¿", |
| | | 2: "驳å", |
| | | 3: "å¾
确认", |
| | | pending: "å¾
审æ¹", |
| | | approved: "éè¿", |
| | | rejected: "驳å", |
| | | PENDING: "å¾
审æ¹", |
| | | APPROVED: "éè¿", |
| | | REJECTED: "驳å", |
| | | }; |
| | | |
| | | const getApprovalStatusLabel = status => { |
| | | if (status === null || status === undefined || status === "") { |
| | | return "å¾
审æ¹"; |
| | | } |
| | | return approvalStatusLabelMap[status] || "å¾
审æ¹"; |
| | | }; |
| | | const getApprovalStatusLabel = (status) => { |
| | | if (status === null || status === undefined || status === "") { |
| | | return "å¾
审æ¹"; |
| | | } |
| | | return approvalStatusLabelMap[status] || "å¾
审æ¹"; |
| | | }; |
| | | |
| | | // éè¿/驳ååºå®è²ï¼å
¶ä½ï¼å«å¾
审æ¹ã空å¼ãæªæ å°ä½ææ¡ä¸ºå¾
审æ¹ï¼ç»ä¸ç¨ warning é¢è¦è² |
| | | const getApprovalStatusTagType = status => { |
| | | if ( |
| | | status === 1 || |
| | | status === "1" || |
| | | status === "approved" || |
| | | status === "APPROVED" |
| | | ) |
| | | return "success"; |
| | | if ( |
| | | status === 2 || |
| | | status === "2" || |
| | | status === "rejected" || |
| | | status === "REJECTED" |
| | | ) |
| | | return "danger"; |
| | | return "warning"; |
| | | }; |
| | | // éè¿/驳ååºå®è²ï¼å
¶ä½ï¼å«å¾
审æ¹ã空å¼ãæªæ å°ä½ææ¡ä¸ºå¾
审æ¹ï¼ç»ä¸ç¨ warning é¢è¦è² |
| | | const getApprovalStatusTagType = (status) => { |
| | | if ( |
| | | status === 1 || |
| | | status === "1" || |
| | | status === "approved" || |
| | | status === "APPROVED" |
| | | ) |
| | | return "success"; |
| | | if ( |
| | | status === 2 || |
| | | status === "2" || |
| | | status === "rejected" || |
| | | status === "REJECTED" |
| | | ) |
| | | return "danger"; |
| | | return "warning"; |
| | | }; |
| | | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === "0") { |
| | | findAllQualifiedStockOutRecordTypeOptions().then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }); |
| | | return; |
| | | } |
| | | findAllUnQualifiedStockOutRecordTypeOptions().then(res => { |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === "0") { |
| | | findAllQualifiedStockOutRecordTypeOptions().then((res) => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }); |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | |
| | | const isRejectedApproval = status => { |
| | | return status === 2 || status === "2" || status === "rejected" || status === "REJECTED"; |
| | | }; |
| | | |
| | | const canReverseApprove = computed(() => { |
| | | return ( |
| | | selectedRows.value.length > 0 && |
| | | selectedRows.value.every(row => isRejectedApproval(row.approvalStatus)) |
| | | ); |
| | | return; |
| | | } |
| | | findAllUnQualifiedStockOutRecordTypeOptions().then((res) => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }); |
| | | }; |
| | | |
| | | const handleReverseApprove = () => { |
| | | if (!canReverseApprove.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©é©³åç¶æçæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("å审åè®°å½å°æ¢å¤ä¸ºå¾
审æ¹ç¶æï¼æ¯å¦ç¡®è®¤å审ï¼", "å审", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchUnapproveStockOutRecords({ ids }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å审æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å审失败"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | const handleBatchApprove = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ¹ç»æ", "审æ¹", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | type: "warning", |
| | | distinguishCancelAndClose: true, |
| | | }) |
| | | .then(() => { |
| | | batchApproveStockOutRecords({ ids, approvalStatus: 1 }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹éè¿æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹éè¿å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(action => { |
| | | if (action === "cancel") { |
| | | batchApproveStockOutRecords({ ids, approvalStatus: 2 }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹é©³åæå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹é©³å失败"); |
| | | }); |
| | | return; |
| | | } |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download( |
| | | "/stockOutRecord/exportStockOutRecord", |
| | | { type: props.type }, |
| | | props.type === "0" ? "åæ ¼åºåºå°è´¦.xlsx" : "ä¸åæ ¼åºåºå°è´¦.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPendingStockOut(ids).then(res => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | const handleBatchApprove = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map((item) => item.id); |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ¹ç»æ", "审æ¹", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | type: "warning", |
| | | distinguishCancelAndClose: true, |
| | | }) |
| | | .then(() => { |
| | | batchApproveStockOutRecords({ ids, approvalStatus: 1 }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹éè¿æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹éè¿å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }) |
| | | .catch((action) => { |
| | | if (action === "cancel") { |
| | | batchApproveStockOutRecords({ ids, approvalStatus: 2 }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹é©³åæå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹é©³å失败"); |
| | | }); |
| | | return; |
| | | } |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download( |
| | | "/stockOutRecord/exportStockOutRecord", |
| | | { type: props.type }, |
| | | props.type === "0" ? "åæ ¼åºåºå°è´¦.xlsx" : "ä¸åæ ¼åºåºå°è´¦.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | delPendingStockOut(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }; |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | // æå°åè½ |
| | | const handlePrint = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | printData.value = [...selectedRows.value]; |
| | | console.log("æå°æ°æ®:", printData.value); |
| | | printPreviewVisible.value = true; |
| | | }; |
| | | // æå°åè½ |
| | | const handlePrint = () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | printData.value = [...selectedRows.value]; |
| | | console.log("æå°æ°æ®:", printData.value); |
| | | printPreviewVisible.value = true; |
| | | }; |
| | | |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log("å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:", printData.value.length); |
| | | console.log("æå°æ°æ®:", printData.value); |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log("å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:", printData.value.length); |
| | | console.log("æå°æ°æ®:", printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open("", "_blank", "width=800,height=600"); |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open("", "_blank", "width=800,height=600"); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>æå°é¢è§</title> |
| | | <style> |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>æå°é¢è§</title> |
| | | <style> |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | font-family: "SimSun", serif; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 200mm; |
| | | height: 75mm; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | background: white; |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | page-break-inside: avoid; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | color: #000; |
| | | } |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-row { |
| | | line-height: 20px; |
| | | } |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 12px; |
| | | } |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .table-section { |
| | | margin-bottom: 40px; |
| | | // flex: 0.6; |
| | | } |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | } |
| | | .product-table th, .product-table td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | .product-table th { |
| | | font-weight: bold; |
| | | } |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | .footer-section { |
| | | margin-top: auto; |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 22px; |
| | | justify-content: space-between; |
| | | } |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | } |
| | | .footer-item .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .footer-item .value { |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .address-item .address-value { |
| | | min-width: 200px; |
| | | } |
| | | @media print { |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | font-family: "SimSun", serif; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 200mm; |
| | | height: 75mm; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | background: white; |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | page-break-inside: avoid; |
| | | } |
| | | .print-page { |
| | | margin: 0; |
| | | padding: 10mm; |
| | | /* padding-left: 20mm; */ |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | color: #000; |
| | | } |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-row { |
| | | line-height: 20px; |
| | | } |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 12px; |
| | | } |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .table-section { |
| | | margin-bottom: 40px; |
| | | // flex: 0.6; |
| | | } |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | } |
| | | .product-table th, .product-table td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | .product-table th { |
| | | font-weight: bold; |
| | | } |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | .footer-section { |
| | | margin-top: auto; |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 22px; |
| | | justify-content: space-between; |
| | | } |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | } |
| | | .footer-item .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .footer-item .value { |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .address-item .address-value { |
| | | min-width: 200px; |
| | | } |
| | | @media print { |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | .print-page { |
| | | margin: 0; |
| | | padding: 10mm; |
| | | /* padding-left: 20mm; */ |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | `; |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | `; |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">${formatDate(item.createTime)}</span> |
| | | </div> |
| | | <div> |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">${item.supplierName}</span> |
| | | </div> |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">${formatDate(item.createTime)}</span> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">${item.code || ""}</span> |
| | | <div> |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">${item.supplierName}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>${item.productName || "ç ç°ç "}</td> |
| | | <td>${item.model || "æ å"}</td> |
| | | <td>${item.unit || "å"}</td> |
| | | <td>${item.taxInclusiveUnitPrice || "0"}</td> |
| | | <td>${item.inboundNum || "2000"}</td> |
| | | <td>${item.taxInclusiveTotalPrice || "0"}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">${item.inboundNum || "2000"}</td> |
| | | <td class="total-value">${ |
| | | item.taxInclusiveTotalPrice || "0" |
| | | }</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">${item.code || ""}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr> |
| | | <td>${item.productName || "ç ç°ç "}</td> |
| | | <td>${item.model || "æ å"}</td> |
| | | <td>${item.unit || "å"}</td> |
| | | <td>${item.taxInclusiveUnitPrice || "0"}</td> |
| | | <td>${item.inboundNum || "2000"}</td> |
| | | <td>${item.taxInclusiveTotalPrice || "0"}</td> |
| | | </tr> |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">${item.inboundNum || "2000"}</td> |
| | | <td class="total-value">${ |
| | | item.taxInclusiveTotalPrice || "0" |
| | | }</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${userStore.nickName || "æå¼å"}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">${formatDateTime(new Date())}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${userStore.nickName || "æå¼å"}</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">${formatDateTime(new Date())}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | `; |
| | | }); |
| | | |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | </div> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = dateString => { |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | 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 formatDateTime = date => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | const hours = String(date.getHours()).padStart(2, "0"); |
| | | const minutes = String(date.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | fetchStockRecordTypeOptions(); |
| | | }); |
| | | |
| | | watch( |
| | | () => props.topParentProductId, |
| | | () => { |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | ); |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = (dateString) => { |
| | | if (!dateString) return getCurrentDate(); |
| | | const date = new Date(dateString); |
| | | 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 formatDateTime = (date) => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | const day = String(date.getDate()).padStart(2, "0"); |
| | | const hours = String(date.getHours()).padStart(2, "0"); |
| | | const minutes = String(date.getMinutes()).padStart(2, "0"); |
| | | const seconds = String(date.getSeconds()).padStart(2, "0"); |
| | | return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | fetchStockRecordTypeOptions(); |
| | | }); |
| | | |
| | | watch( |
| | | () => props.topParentProductId, |
| | | () => { |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | .print-preview-dialog { |
| | | .el-dialog__body { |
| | | padding: 0; |
| | | max-height: 80vh; |
| | | overflow-y: auto; |
| | | } |
| | | } |
| | | |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | .print-preview-container { |
| | | .print-preview-header { |
| | | padding: 15px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | .el-button { |
| | | margin: 0 10px; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | .print-preview-content { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 400px; |
| | | } |
| | | } |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | .print-page { |
| | | width: 220mm; |
| | | height: 90mm; |
| | | padding: 10mm; |
| | | margin: 0 auto; |
| | | background: white; |
| | | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); |
| | | margin-bottom: 10px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-family: "SimSun", serif; |
| | | font-size: 10px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | |
| | | .info-row { |
| | | line-height: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, |
| | | td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .table-section { |
| | | margin-bottom: 4px; |
| | | flex: 1; |
| | | |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | |
| | | th, |
| | | td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 14px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | th { |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-label { |
| | | text-align: right; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .footer-section { |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 20px; |
| | | justify-content: space-between; |
| | | |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | |
| | | .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .value { |
| | | min-width: 80px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | &.address-item { |
| | | .address-value { |
| | | min-width: 200px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | @media print { |
| | | .app-container { |
| | | display: none; |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 10px; |
| | | .print-page { |
| | | box-shadow: none; |
| | | margin: 0; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | |
| | | .actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <div class="search_form" |
| | | style="margin-bottom: 10px;"> |
| | | <el-form ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline"> |
| | | <div class="search_form" style="margin-bottom: 10px;"> |
| | | <el-form |
| | | ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å
¥åºæ¥æ" |
| | | prop="timeStr"> |
| | | <el-form-item label="å
¥åºæ¥æ" prop="timeStr"> |
| | | <el-date-picker v-model="searchForm.timeStr" |
| | | type="date" |
| | | placeholder="è¯·éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="产å大类" |
| | | prop="productName"> |
| | | <el-form-item label="产å大类" prop="productName"> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="4"> |
| | | <el-form-item label="è§æ ¼åå·" |
| | | prop="model"> |
| | | <el-form-item label="产åè§æ ¼" prop="model"> |
| | | <el-input v-model="searchForm.model" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="æ¹å·" |
| | | prop="batchNo"> |
| | | <el-form-item label="æ¹å·" prop="batchNo"> |
| | | <el-input v-model="searchForm.batchNo" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="æ¥æº" |
| | | prop="recordType"> |
| | | <el-form-item label="æ¥æº" prop="recordType"> |
| | | <el-select v-model="searchForm.recordType" |
| | | style="width: 240px" |
| | | placeholder="è¯·éæ©" |
| | |
| | | <el-option v-for="item in stockRecordTypeOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | :value="item.value"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- æé® --> |
| | | <el-col :span="4"> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="getList"> |
| | | <el-button type="primary" @click="getList"> |
| | | æç´¢ |
| | | </el-button> |
| | | |
| | | <el-button @click="resetSearch"> |
| | | éç½® |
| | | </el-button> |
| | |
| | | </el-form> |
| | | </div> |
| | | <div class="actions"> |
| | | <el-button type="primary" |
| | | <!-- <el-button type="primary" |
| | | :disabled="!canBatchApprove" |
| | | @click="handleBatchApprove">审æ¹</el-button> |
| | | <el-button :disabled="!canReverseApprove" |
| | | @click="handleReverseApprove">å审</el-button> |
| | | @click="handleReverseApprove">å审</el-button> --> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" |
| | | plain |
| | |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | :selectable="isRowSelectable" |
| | | width="55" /> |
| | | width="55"/> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | width="60"/> |
| | | <el-table-column label="å
¥åºæ¹æ¬¡" |
| | | prop="inboundBatches" |
| | | width="200" |
| | | show-overflow-tooltip /> |
| | | width="150" |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="å
¥åºæ¶é´" |
| | | prop="createTime" |
| | | show-overflow-tooltip /> |
| | | width="120" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产å大类" |
| | | prop="productName" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="æ¹å·" |
| | | prop="batchNo" |
| | | show-overflow-tooltip /> |
| | | width="180" |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="å
¥åºæ°é" |
| | | prop="stockInNum" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="å
¥åºäºº" |
| | | prop="createBy" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="æ¥æº" |
| | | prop="recordType" |
| | | show-overflow-tooltip> |
| | |
| | | {{ getRecordType(scope.row.recordType) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column v-if="showSourceOrderNoColumn" |
| | | label="æºåå·" |
| | | width="150" |
| | | prop="sourceOrderNo" |
| | | show-overflow-tooltip> |
| | | <el-table-column |
| | | v-if="showSourceOrderNoColumn" |
| | | label="æºåå·" |
| | | width="150" |
| | | prop="sourceOrderNo" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ formatSourceOrderNo(scope.row?.sourceOrderNo) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="审æ¹ç¶æ" |
| | | <!-- <el-table-column label="审æ¹ç¶æ" |
| | | prop="approvalStatus" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" |
| | | size="small"> |
| | | <el-tag :type="getApprovalStatusTagType(scope.row.approvalStatus)" size="small"> |
| | | {{ getApprovalStatusLabel(scope.row.approvalStatus) }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table-column> --> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="pageProductChange" /> |
| | | @pagination="pageProductChange"/> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | onMounted, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { |
| | | getStockInRecordListPage, |
| | | batchDeletePendingStockInRecords, |
| | | batchApproveStockInRecords, |
| | | batchUnapproveStockInRecords, |
| | | } from "@/api/inventoryManagement/stockInRecord.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | // findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | onMounted, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | import { |
| | | getStockInRecordListPage, |
| | | batchDeletePendingStockInRecords, |
| | | batchApproveStockInRecords, |
| | | batchUnapproveStockInRecords, |
| | | } from "@/api/inventoryManagement/stockInRecord.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | // findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const {proxy} = getCurrentInstance(); |
| | | |
| | | const props = defineProps({ |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: "0", |
| | | }, |
| | | topParentProductId: { |
| | | type: [String, Number], |
| | | default: undefined, |
| | | }, |
| | | }); |
| | | const props = defineProps({ |
| | | type: { |
| | | type: String, |
| | | required: true, |
| | | default: '0' |
| | | }, |
| | | topParentProductId: { |
| | | type: [String, Number], |
| | | default: undefined |
| | | } |
| | | }) |
| | | |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | // æ¥æºç±»åé项 |
| | | const stockRecordTypeOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | const total = ref(0); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | // æ¥æºç±»åé项 |
| | | const stockRecordTypeOptions = ref([]); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }); |
| | | const total = ref(0); |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: "", |
| | | batchNo: "", |
| | | model: "", |
| | | timeStr: "", |
| | | recordType: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const searchFormRef = ref(null); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: "", |
| | | batchNo: "", |
| | | model: "", |
| | | timeStr: "", |
| | | recordType: "", |
| | | }, |
| | | }); |
| | | const {searchForm} = toRefs(data); |
| | | const searchFormRef = ref(null); |
| | | |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | |
| | | const getRecordType = recordType => { |
| | | return ( |
| | | stockRecordTypeOptions.value.find(item => item.value === recordType) |
| | | ?.label || "" |
| | | ); |
| | | }; |
| | | const getRecordType = (recordType) => { |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | const approvalStatusLabelMap = { |
| | | 0: "å¾
审æ¹", |
| | | 1: "éè¿", |
| | | 2: "驳å", |
| | | pending: "å¾
审æ¹", |
| | | approved: "éè¿", |
| | | rejected: "驳å", |
| | | PENDING: "å¾
审æ¹", |
| | | APPROVED: "éè¿", |
| | | REJECTED: "驳å", |
| | | }; |
| | | approvalStatusLabelMap[3] = "å¾
确认"; |
| | | const approvalStatusLabelMap = { |
| | | 0: "å¾
审æ¹", |
| | | 1: "éè¿", |
| | | 2: "驳å", |
| | | pending: "å¾
审æ¹", |
| | | approved: "éè¿", |
| | | rejected: "驳å", |
| | | PENDING: "å¾
审æ¹", |
| | | APPROVED: "éè¿", |
| | | REJECTED: "驳å", |
| | | }; |
| | | approvalStatusLabelMap[3] = "å¾
确认"; |
| | | |
| | | const getApprovalStatusLabel = status => { |
| | | if (status === null || status === undefined || status === "") { |
| | | return "å¾
审æ¹"; |
| | | } |
| | | return approvalStatusLabelMap[status] || "å¾
审æ¹"; |
| | | }; |
| | | const getApprovalStatusLabel = (status) => { |
| | | if (status === null || status === undefined || status === "") { |
| | | return "å¾
审æ¹"; |
| | | } |
| | | return approvalStatusLabelMap[status] || "å¾
审æ¹"; |
| | | }; |
| | | |
| | | // éè¿/驳ååºå®è²ï¼å
¶ä½ï¼å«å¾
审æ¹ã空å¼ãæªæ å°ä½ææ¡ä¸ºå¾
审æ¹ï¼ç»ä¸ç¨ warning é¢è¦è² |
| | | const getApprovalStatusTagType = status => { |
| | | if ( |
| | | status === 1 || |
| | | status === "1" || |
| | | status === "approved" || |
| | | status === "APPROVED" |
| | | ) |
| | | return "success"; |
| | | if ( |
| | | status === 2 || |
| | | status === "2" || |
| | | status === "rejected" || |
| | | status === "REJECTED" |
| | | ) |
| | | return "danger"; |
| | | return "warning"; |
| | | }; |
| | | // éè¿/驳ååºå®è²ï¼å
¶ä½ï¼å«å¾
审æ¹ã空å¼ãæªæ å°ä½ææ¡ä¸ºå¾
审æ¹ï¼ç»ä¸ç¨ warning é¢è¦è² |
| | | const getApprovalStatusTagType = (status) => { |
| | | if (status === 1 || status === "1" || status === "approved" || status === "APPROVED") return "success"; |
| | | if (status === 2 || status === "2" || status === "rejected" || status === "REJECTED") return "danger"; |
| | | return "warning"; |
| | | }; |
| | | |
| | | const isPendingApproval = status => { |
| | | return ( |
| | | status === 0 || |
| | | status === "0" || |
| | | status === "pending" || |
| | | status === "PENDING" || |
| | | status === null || |
| | | status === undefined || |
| | | status === "" |
| | | ); |
| | | }; |
| | | const isPendingApproval = status => { |
| | | return status === 0 || status === "0" || status === "pending" || status === "PENDING" || status === null || status === undefined || status === ""; |
| | | }; |
| | | |
| | | const isRejectedApproval = status => { |
| | | return ( |
| | | status === 2 || |
| | | status === "2" || |
| | | status === "rejected" || |
| | | status === "REJECTED" |
| | | ); |
| | | }; |
| | | const isRejectedApproval = status => { |
| | | return status === 2 || status === "2" || status === "rejected" || status === "REJECTED"; |
| | | }; |
| | | |
| | | const isRowSelectable = row => { |
| | | return ( |
| | | isPendingApproval(row?.approvalStatus) || |
| | | isRejectedApproval(row?.approvalStatus) |
| | | ); |
| | | }; |
| | | const isRowSelectable = row => { |
| | | return isPendingApproval(row?.approvalStatus) || isRejectedApproval(row?.approvalStatus); |
| | | }; |
| | | |
| | | const canBatchApprove = computed(() => { |
| | | return ( |
| | | selectedRows.value.length > 0 && |
| | | selectedRows.value.every(row => isPendingApproval(row.approvalStatus)) |
| | | ); |
| | | }); |
| | | const canBatchApprove = computed(() => { |
| | | return selectedRows.value.length > 0 |
| | | && selectedRows.value.every(row => isPendingApproval(row.approvalStatus)); |
| | | }); |
| | | |
| | | const canReverseApprove = computed(() => { |
| | | return ( |
| | | selectedRows.value.length > 0 && |
| | | selectedRows.value.every(row => isRejectedApproval(row.approvalStatus)) |
| | | ); |
| | | }); |
| | | const canReverseApprove = computed(() => { |
| | | return selectedRows.value.length > 0 |
| | | && selectedRows.value.every(row => isRejectedApproval(row.approvalStatus)); |
| | | }); |
| | | |
| | | const canDelete = computed(() => canBatchApprove.value); |
| | | const showSourceOrderNoColumn = computed(() => { |
| | | const topParentProductId = Number(props.topParentProductId); |
| | | return topParentProductId === 276 || topParentProductId === 278; |
| | | }); |
| | | const canDelete = computed(() => canBatchApprove.value); |
| | | const showSourceOrderNoColumn = computed(() => { |
| | | const topParentProductId = Number(props.topParentProductId); |
| | | return topParentProductId === 1 || topParentProductId === 3; |
| | | }); |
| | | |
| | | const formatSourceOrderNo = value => { |
| | | const text = String(value ?? "").trim(); |
| | | return text || "--"; |
| | | }; |
| | | const formatSourceOrderNo = (value) => { |
| | | const text = String(value ?? "").trim(); |
| | | return text || "--"; |
| | | }; |
| | | |
| | | const pageProductChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const pageProductChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockInRecordListPage( |
| | | Object.assign( |
| | | {}, |
| | | { |
| | | ...searchForm.value, |
| | | ...page, |
| | | topParentProductId: props.topParentProductId, |
| | | } |
| | | ) |
| | | ) |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockInRecordListPage(Object.assign({}, {...searchForm.value, ...page, topParentProductId: props.topParentProductId})) |
| | | .then(res => { |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total || 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === "0") { |
| | | findAllQualifiedStockInRecordTypeOptions().then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }); |
| | | return; |
| | | } |
| | | // findAllUnQualifiedStockInRecordTypeOptions() |
| | | // .then(res => { |
| | | // stockRecordTypeOptions.value = res.data; |
| | | // }) |
| | | }; |
| | | // è·åæ¥æºç±»åé项 |
| | | const fetchStockRecordTypeOptions = () => { |
| | | if (props.type === '0') { |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | | return |
| | | } |
| | | // findAllUnQualifiedStockInRecordTypeOptions() |
| | | // .then(res => { |
| | | // stockRecordTypeOptions.value = res.data; |
| | | // }) |
| | | } |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection.filter( |
| | | item => item.id && isRowSelectable(item) |
| | | ); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection.filter(item => item.id && isRowSelectable(item)); |
| | | }; |
| | | |
| | | const expandedRowKeys = ref([]); |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | const handleReverseApprove = () => { |
| | | if (!canReverseApprove.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å·²é©³åçæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("å审åè®°å½å°æ¢å¤ä¸ºå¾
审æ¹ç¶æï¼æ¯å¦ç¡®è®¤å审ï¼", "å审", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | const handleReverseApprove = () => { |
| | | if (!canReverseApprove.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å·²é©³åçæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("å审åè®°å½å°æ¢å¤ä¸ºå¾
审æ¹ç¶æï¼æ¯å¦ç¡®è®¤å审ï¼", "å审", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchUnapproveStockInRecords({ ids }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å审æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å审失败"); |
| | | }); |
| | | batchUnapproveStockInRecords({ids}) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å审æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å审失败"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | const handleBatchApprove = () => { |
| | | if (!canBatchApprove.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å¾
审æ¹çæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ¹ç»æ", "审æ¹", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | type: "warning", |
| | | distinguishCancelAndClose: true, |
| | | }) |
| | | const handleBatchApprove = () => { |
| | | if (!canBatchApprove.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å¾
审æ¹çæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ¹ç»æ", "审æ¹", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | type: "warning", |
| | | distinguishCancelAndClose: true, |
| | | }) |
| | | .then(() => { |
| | | batchApproveStockInRecords({ ids, approvalStatus: 1 }) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹éè¿æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹éè¿å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(action => { |
| | | if (action === "cancel") { |
| | | batchApproveStockInRecords({ ids, approvalStatus: 2 }) |
| | | batchApproveStockInRecords({ids, approvalStatus: 1}) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹é©³åæå"); |
| | | proxy.$modal.msgSuccess("审æ¹éè¿æå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹é©³å失败"); |
| | | proxy.$modal.msgError("审æ¹éè¿å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch((action) => { |
| | | if (action === "cancel") { |
| | | batchApproveStockInRecords({ids, approvalStatus: 2}) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("审æ¹é©³åæå"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("审æ¹é©³å失败"); |
| | | }); |
| | | return; |
| | | } |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // æ ¹æ®ä¸åç tab ç±»åè°ç¨ä¸åçå¯¼åºæ¥å£ |
| | | proxy.download( |
| | | "/stockInRecord/exportStockInRecord", |
| | | { type: props.type }, |
| | | props.type === "0" ? "åæ ¼å
¥åº.xlsx" : "ä¸åæ ¼å
¥åº.xlsx" |
| | | ); |
| | | proxy.download("/stockInRecord/exportStockInRecord", {type: props.type}, props.type === '0' ? "åæ ¼å
¥åº.xlsx" : "ä¸åæ ¼å
¥åº.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | if (!canDelete.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å¾
审æ¹çæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | if (!canDelete.value) { |
| | | proxy.$modal.msgWarning("è¯·éæ©å¾
审æ¹çæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = selectedRows.value.map(item => item.id); |
| | | |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchDeletePendingStockInRecords(ids) |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | .then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msgError("å é¤å¤±è´¥"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | onMounted(() => { |
| | | getList(); |
| | | fetchStockRecordTypeOptions(); |
| | | }); |
| | | |
| | | watch( |
| | | () => props.topParentProductId, |
| | | () => { |
| | | page.current = 1; |
| | | getList(); |
| | | fetchStockRecordTypeOptions(); |
| | | }); |
| | | |
| | | watch( |
| | | () => props.topParentProductId, |
| | | () => { |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | ); |
| | | } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 10px; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 10px; |
| | | } |
| | | </style> |
| | |
| | | prop="qualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | <!-- <el-table-column |
| | | label="ä¸åæ ¼åºåæ°é" |
| | | prop="unQualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | /> --> |
| | | <!-- <el-table-column |
| | | label="åæ ¼å»ç»æ°é" |
| | | prop="qualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | |
| | | label="ä¸åæ ¼å»ç»æ°é" |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | /> --> |
| | | <el-table-column |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | |
| | | prop="updateTime" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column fixed="right" label="æä½" min-width="180" align="center"> |
| | | <el-table-column fixed="right" label="æä½" min-width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | | link |
| | |
| | | " |
| | | >é¢ç¨</el-button |
| | | > |
| | | <el-button |
| | | <!-- <el-button |
| | | link |
| | | type="primary" |
| | | v-if=" |
| | |
| | | " |
| | | @click="handleFrozen(scope.row)" |
| | | >å»ç»</el-button |
| | | > |
| | | > --> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | |
| | | placeholder="è¯·éæ©åºåç±»å"> |
| | | <el-option label="åæ ¼åºå" |
| | | value="qualified" /> |
| | | <el-option label="ä¸åæ ¼åºå" |
| | | value="unqualified" /> |
| | | <!-- <el-option label="ä¸åæ ¼åºå" |
| | | value="unqualified" /> --> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="åºåæ°é" |
| | | prop="qualitity"> |
| | | <el-input-number v-model="formState.qualitity" |
| | | :step="0.01" |
| | | :min="0.01" |
| | | :precision="2" |
| | | :step="1" |
| | | :min="1" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="æ¹å·" |
| | |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum"> |
| | | <el-input-number v-model="formState.warnNum" |
| | | :step="0.01" |
| | | :step="1" |
| | | :min="0" |
| | | :max="formState.qualitity" |
| | | :precision="2" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" |
| | |
| | | <template> |
| | | <div> |
| | | <div class="search_form mb10"> |
| | | <el-form ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline"> |
| | | <el-form |
| | | ref="searchFormRef" |
| | | :model="searchForm" |
| | | class="demo-form-inline" |
| | | > |
| | | <el-row :gutter="20"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="产å大类" |
| | | prop="productName"> |
| | | <el-form-item label="产å大类" prop="productName"> |
| | | <el-input v-model="searchForm.productName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | |
| | | <el-col :span="4"> |
| | | <el-form-item label="è§æ ¼åå·" |
| | | prop="model"> |
| | | <el-form-item label="产åè§æ ¼" prop="model"> |
| | | <el-input v-model="searchForm.model" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="æ¹å·" |
| | | prop="batchNo"> |
| | | <el-form-item label="æ¹å·" prop="batchNo"> |
| | | <el-input v-model="searchForm.batchNo" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | clearable/> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- æé® --> |
| | | <el-col :span="4"> |
| | | <el-col :span="24" :sm="24" :md="12" :lg="12"> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="getList"> |
| | | æç´¢ |
| | | </el-button> |
| | | <el-button @click="resetSearch"> |
| | | éç½® |
| | | </el-button> |
| | | <div style="display: flex; align-items: center; gap: 4px; width: 100%; flex-wrap: wrap;"> |
| | | <el-button type="primary" @click="getList"> |
| | | æç´¢ |
| | | </el-button> |
| | | |
| | | <el-button @click="resetSearch"> |
| | | éç½® |
| | | </el-button> |
| | | <div style="display: flex; gap: 4px; margin-left: auto;"> |
| | | <el-button type="primary" @click="isShowNewModal = true"> |
| | | æ°å¢åºå |
| | | </el-button> |
| | | <el-button |
| | | type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="isShowImportModal = true" |
| | | > |
| | | 导å
¥åºå |
| | | </el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <div style="display: flex; gap: 10px; flex-wrap: nowrap;"> |
| | | <el-button type="primary" |
| | | @click="isShowNewModal = true">æ°å¢åºå</el-button> |
| | | <el-button type="info" |
| | | plain |
| | | icon="Upload" |
| | | @click="isShowImportModal = true"> |
| | | 导å
¥åºå |
| | | </el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row, index) => index" |
| | | style="width: 100%" |
| | | :row-class-name="tableRowClassName" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | | width="55" /> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="产ååç§°" |
| | | prop="productName" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="è§æ ¼åå·" |
| | | prop="model" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åä½" |
| | | prop="unit" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æ¹å·" |
| | | prop="batchNo" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åæ ¼åºåæ°é" |
| | | prop="qualifiedQuantity" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ä¸åæ ¼åºåæ°é" |
| | | prop="unQualifiedQuantity" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åæ ¼å»ç»æ°é" |
| | | prop="qualifiedLockedQuantity" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="ä¸åæ ¼å»ç»æ°é" |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" |
| | | prop="remark" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="80" |
| | | align="center"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" |
| | | :row-key="(row, index) => index" |
| | | style="width: 100%" |
| | | :row-class-name="tableRowClassName" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" label="åºå·" type="index" width="60" /> |
| | | <el-table-column |
| | | label="产ååç§°" |
| | | prop="productName" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="è§æ ¼åå·" prop="model" show-overflow-tooltip /> |
| | | <el-table-column label="åä½" prop="unit" show-overflow-tooltip /> |
| | | <!-- <el-table-column label="æ¹å·" prop="batchNo" show-overflow-tooltip /> --> |
| | | <el-table-column |
| | | label="åæ ¼åºåæ°é" |
| | | prop="qualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <!-- <el-table-column |
| | | label="ä¸åæ ¼åºåæ°é" |
| | | prop="unQualifiedQuantity" |
| | | show-overflow-tooltip |
| | | /> --> |
| | | <!-- <el-table-column |
| | | label="åæ ¼å»ç»æ°é" |
| | | prop="qualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="ä¸åæ ¼å»ç»æ°é" |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> --> |
| | | <el-table-column |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | fixed="right" |
| | | label="æä½" |
| | | min-width="80" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | @click="showDetailModal(scope.row)">详æ
</el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | @click="showDetailModal(scope.row)" |
| | | >详æ
</el-button |
| | | > |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" /> |
| | | <pagination |
| | | v-show="total > 0" |
| | | :total="total" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | <batch-no-qty-detail v-if="isShowDetailModal" |
| | | v-model:visible="isShowDetailModal" |
| | | :record="record" |
| | | @subtract="handleDetailSubtract" |
| | | @frozen="handleDetailFrozen" |
| | | @thaw="handleDetailThaw" /> |
| | | <new-stock-inventory v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | :top-product-parent-id="props.productId" |
| | | @completed="handleQuery" /> |
| | | <subtract-stock-inventory v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" /> |
| | | <batch-no-qty-detail |
| | | v-if="isShowDetailModal" |
| | | v-model:visible="isShowDetailModal" |
| | | :record="record" |
| | | @subtract="handleDetailSubtract" |
| | | @frozen="handleDetailFrozen" |
| | | @thaw="handleDetailThaw" |
| | | /> |
| | | <new-stock-inventory |
| | | v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | :top-product-parent-id="props.productId" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <subtract-stock-inventory |
| | | v-if="isShowSubtractModal" |
| | | v-model:visible="isShowSubtractModal" |
| | | :record="record" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" |
| | | /> |
| | | <!-- 导å
¥åºå--> |
| | | <import-stock-inventory v-if="isShowImportModal" |
| | | v-model:visible="isShowImportModal" |
| | | type="qualified" |
| | | @uploadSuccess="handleQuery" /> |
| | | <import-stock-inventory |
| | | v-if="isShowImportModal" |
| | | v-model:visible="isShowImportModal" |
| | | type="qualified" |
| | | @uploadSuccess="handleQuery" |
| | | /> |
| | | <!-- å»ç»/è§£å»åºå--> |
| | | <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" /> |
| | | <frozen-and-thaw-stock-inventory |
| | | v-if="isShowFrozenAndThawModal" |
| | | v-model:visible="isShowFrozenAndThawModal" |
| | | :record="record" |
| | | :operation-type="operationType" |
| | | :type="record.stockType" |
| | | @completed="handleQuery" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js"; |
| | | const props = defineProps({ |
| | | productId: { |
| | | type: Number, |
| | | required: true, |
| | | default: 0, |
| | | }, |
| | | }); |
| | | import pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js"; |
| | | const props = defineProps({ |
| | | productId: { |
| | | type: Number, |
| | | required: true, |
| | | default: 0, |
| | | }, |
| | | }); |
| | | |
| | | const NewStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/New.vue") |
| | | ); |
| | | const SubtractStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Subtract.vue") |
| | | ); |
| | | const ImportStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Import.vue") |
| | | ); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue") |
| | | ); |
| | | const BatchNoQtyDetail = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const record = ref({}); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | // æ¯å¦æ¾ç¤ºæ°å¢å¼¹æ¡ |
| | | const isShowNewModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ |
| | | const isShowSubtractModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºå»ç»/è§£å»å¼¹æ¡ |
| | | const isShowFrozenAndThawModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºè¯¦æ
å¼¹æ¡ |
| | | const isShowDetailModal = ref(false); |
| | | // æä½ç±»å |
| | | const operationType = ref("frozen"); |
| | | // æ¯å¦æ¾ç¤ºå¯¼å
¥å¼¹æ¡ |
| | | const isShowImportModal = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: "", |
| | | model: "", |
| | | batchNo: "", |
| | | topParentProductId: props.productId, |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const searchFormRef = ref(null); |
| | | const NewStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/New.vue") |
| | | ); |
| | | const SubtractStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Subtract.vue") |
| | | ); |
| | | const ImportStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/Import.vue") |
| | | ); |
| | | const FrozenAndThawStockInventory = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue") |
| | | ); |
| | | const BatchNoQtyDetail = defineAsyncComponent(() => |
| | | import("@/views/inventoryManagement/stockManagement/BatchNoQtyDetail.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const record = ref({}); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | // æ¯å¦æ¾ç¤ºæ°å¢å¼¹æ¡ |
| | | const isShowNewModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºé¢ç¨å¼¹æ¡ |
| | | const isShowSubtractModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºå»ç»/è§£å»å¼¹æ¡ |
| | | const isShowFrozenAndThawModal = ref(false); |
| | | // æ¯å¦æ¾ç¤ºè¯¦æ
å¼¹æ¡ |
| | | const isShowDetailModal = ref(false); |
| | | // æä½ç±»å |
| | | const operationType = ref("frozen"); |
| | | // æ¯å¦æ¾ç¤ºå¯¼å
¥å¼¹æ¡ |
| | | const isShowImportModal = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | productName: "", |
| | | model: "", |
| | | batchNo: "", |
| | | topParentProductId: props.productId, |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const searchFormRef = ref(null); |
| | | |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const resetSearch = () => { |
| | | searchFormRef.value?.resetFields(); |
| | | page.current = 1; |
| | | getList(); |
| | | } |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockInventoryListPageCombined({ ...searchForm.value, ...page }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | // æ°æ®å è½½å®æåæ£æ¥åºå |
| | | // checkStockAndCreatePurchase(); |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleFileSuccess = response => { |
| | | const { code, msg } = response; |
| | | if (code == 200) { |
| | | ElMessage({ message: "导å
¥æå", type: "success" }); |
| | | upload.open = false; |
| | | emits("uploadSuccess"); |
| | | } else { |
| | | ElMessage({ message: msg, type: "error" }); |
| | | } |
| | | }; |
| | | |
| | | // ç¹å»é¢ç¨ |
| | | const showSubtractModal = row => { |
| | | record.value = row; |
| | | isShowSubtractModal.value = true; |
| | | }; |
| | | |
| | | // ç¹å»è¯¦æ
|
| | | const showDetailModal = row => { |
| | | if (!row?.productId || !row?.productModelId) { |
| | | proxy.$modal.msgError("å½åæ°æ®ç¼ºå°äº§åIDæè§æ ¼åå·ID"); |
| | | return; |
| | | } |
| | | record.value = row; |
| | | isShowDetailModal.value = true; |
| | | }; |
| | | |
| | | const handleDetailSubtract = row => { |
| | | isShowDetailModal.value = false; |
| | | showSubtractModal(row); |
| | | }; |
| | | |
| | | const handleDetailFrozen = row => { |
| | | isShowDetailModal.value = false; |
| | | showFrozenModal(row); |
| | | }; |
| | | |
| | | const handleDetailThaw = row => { |
| | | isShowDetailModal.value = false; |
| | | showThawModal(row); |
| | | }; |
| | | |
| | | // ç¹å»å»ç» |
| | | const showFrozenModal = row => { |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "frozen"; |
| | | }; |
| | | |
| | | // ç¹å»è§£å» |
| | | const showThawModal = row => { |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "thaw"; |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | // è¡¨æ ¼è¡ç±»å |
| | | const tableRowClassName = ({ row }) => { |
| | | const stock = Number(row?.qualifiedUnLockedQuantity ?? 0); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | return ""; |
| | | } |
| | | return stock < warn ? "row-low-stock" : ""; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getStockInventoryListPageCombined({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | // æ°æ®å è½½å®æåæ£æ¥åºå |
| | | // checkStockAndCreatePurchase(); |
| | | }) |
| | | .then(() => { |
| | | proxy.download( |
| | | "/stockInventory/exportStockInventory", |
| | | { topParentProductId: props.productId }, |
| | | "åºåä¿¡æ¯.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | const handleFileSuccess = (response) => { |
| | | const { code, msg } = response; |
| | | if (code == 200) { |
| | | ElMessage({ message: "导å
¥æå", type: "success" }); |
| | | upload.open = false; |
| | | emits("uploadSuccess"); |
| | | } else { |
| | | ElMessage({ message: msg, type: "error" }); |
| | | } |
| | | }; |
| | | |
| | | // ç¹å»é¢ç¨ |
| | | const showSubtractModal = (row) => { |
| | | record.value = row; |
| | | isShowSubtractModal.value = true; |
| | | }; |
| | | |
| | | // ç¹å»è¯¦æ
|
| | | const showDetailModal = (row) => { |
| | | if (!row?.productId || !row?.productModelId) { |
| | | proxy.$modal.msgError("å½åæ°æ®ç¼ºå°äº§åIDæè§æ ¼åå·ID"); |
| | | return; |
| | | } |
| | | record.value = row; |
| | | isShowDetailModal.value = true; |
| | | }; |
| | | |
| | | const handleDetailSubtract = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showSubtractModal(row); |
| | | }; |
| | | |
| | | const handleDetailFrozen = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showFrozenModal(row); |
| | | }; |
| | | |
| | | const handleDetailThaw = (row) => { |
| | | isShowDetailModal.value = false; |
| | | showThawModal(row); |
| | | }; |
| | | |
| | | // ç¹å»å»ç» |
| | | const showFrozenModal = (row) => { |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "frozen"; |
| | | }; |
| | | |
| | | // ç¹å»è§£å» |
| | | const showThawModal = (row) => { |
| | | record.value = row; |
| | | isShowFrozenAndThawModal.value = true; |
| | | operationType.value = "thaw"; |
| | | }; |
| | | |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | // è¿æ»¤æåæ°æ® |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | console.log("selection", selectedRows.value); |
| | | }; |
| | | const expandedRowKeys = ref([]); |
| | | |
| | | // è¡¨æ ¼è¡ç±»å |
| | | const tableRowClassName = ({ row }) => { |
| | | const stock = Number(row?.qualifiedUnLockedQuantity ?? 0); |
| | | const warn = Number(row?.warnNum ?? 0); |
| | | if (!Number.isFinite(stock) || !Number.isFinite(warn)) { |
| | | return ""; |
| | | } |
| | | return stock < warn ? "row-low-stock" : ""; |
| | | }; |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download( |
| | | "/stockInventory/exportStockInventory", |
| | | { topParentProductId: props.productId }, |
| | | "åºåä¿¡æ¯.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | :deep(.row-low-stock td) { |
| | | background-color: #fde2e2; |
| | | color: #c45656; |
| | | } |
| | | :deep(.row-low-stock td) { |
| | | background-color: #fde2e2; |
| | | color: #c45656; |
| | | } |
| | | |
| | | :deep(.row-low-stock:hover > td) { |
| | | background-color: #fcd4d4; |
| | | } |
| | | :deep(.row-low-stock:hover > td) { |
| | | background-color: #fcd4d4; |
| | | } |
| | | </style> |
| | |
| | | <div class="login-page"> |
| | | <main class="page"> |
| | | <section class="factory"> |
| | | <!-- <div class="brand hero-brand">--> |
| | | <!-- <div class="logo hero-logo">--> |
| | | <!-- <img--> |
| | | <!-- :src="brandLogoUrl"--> |
| | | <!-- :alt="`${companyName} logo`"--> |
| | | <!-- class="logo-image hero-logo-image"--> |
| | | <!-- @error="handleLogoError"--> |
| | | <!-- />--> |
| | | <!-- </div>--> |
| | | <!-- </div>--> |
| | | <div class="brand hero-brand"> |
| | | <div class="logo hero-logo"> |
| | | <img |
| | | :src="brandLogoUrl" |
| | | :alt="`${companyName} logo`" |
| | | class="logo-image hero-logo-image" |
| | | @error="handleLogoError" |
| | | /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="hero"> |
| | | <div class="chip">æ°åå·¥å · æºè½æäº§ · 设å¤äºè · è´¨é追溯</div> |
| | |
| | | </el-table-column>
|
| | | <el-table-column label="æä½æ¥æ" align="center" prop="operTime" width="180" sortable="custom" :sort-orders="['descending', 'ascending']">
|
| | | <template #default="scope">
|
| | | <span>{{ parseTime(scope.row.operTime) }}</span>
|
| | | <span>{{ parseTime(scope.row.operTime, '{y}-{m}-{d}') }}</span>
|
| | | </template>
|
| | | </el-table-column>
|
| | | <el-table-column label="æ¶èæ¶é´" align="center" prop="costTime" width="110" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']">
|
| | |
| | | <el-col :span="24">
|
| | | <el-form-item label="æä½æ¹æ³ï¼">{{ form.method }}</el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="24">
|
| | | <el-col :span="24" v-if="form.status === 1">
|
| | | <el-form-item label="请æ±åæ°ï¼">{{ form.operParam }}</el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="24">
|
| | | <el-col :span="24" v-if="form.status === 1">
|
| | | <el-form-item label="è¿ååæ°ï¼">{{ form.jsonResult }}</el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="8">
|
| | |
| | | <el-form-item label="æ¶èæ¶é´ï¼">{{ form.costTime }}毫ç§</el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="8">
|
| | | <el-form-item label="æä½æ¶é´ï¼">{{ parseTime(form.operTime) }}</el-form-item>
|
| | | <el-form-item label="æä½æ¶é´ï¼">{{ parseTime(form.operTime, '{y}-{m}-{d}') }}</el-form-item>
|
| | | </el-col>
|
| | | <el-col :span="24">
|
| | | <el-form-item label="å¼å¸¸ä¿¡æ¯ï¼" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
|
| | |
| | | <el-option v-for="item in shifts_list" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | :disabled=" |
| | | operationType === 'add' && |
| | | (String(item.label || '').includes('å') || |
| | | String(item.label || '').includes('ä¼')) |
| | | " /> |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æå¡èå´(m)" |
| | | prop="radius" |
| | | v-show="!hideClockFields"> |
| | | prop="radius"> |
| | | <el-input-number v-model="form.radius" |
| | | :min="10" |
| | | :max="1000" |
| | |
| | | </el-form-item> |
| | | <!-- é«å¾·å°å¾éæ© --> |
| | | <el-form-item label="æå¡ä½ç½®" |
| | | prop="longitude" |
| | | v-show="!hideClockFields"> |
| | | prop="longitude"> |
| | | <div class="map-container"> |
| | | <div class="map-header" |
| | | style="margin-bottom: 10px"> |
| | |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="å°ç¹åç§°" |
| | | prop="locationName" |
| | | v-show="!hideClockFields"> |
| | | prop="locationName"> |
| | | <el-input v-model="form.locationName" |
| | | :disabled="operationType === 'view'" |
| | | placeholder="请è¾å
¥å°ç¹åç§°" /> |
| | | </el-form-item> |
| | | <!-- ä¸ä¸çæ¶é´ --> |
| | | <el-form-item label="ä¸çæ¶é´" |
| | | prop="startAt" |
| | | v-show="!hideClockFields"> |
| | | prop="startAt"> |
| | | <el-time-picker v-model="form.startAt" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | |
| | | :disabled="operationType === 'view'" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¸çæ¶é´" |
| | | prop="endAt" |
| | | v-show="!hideClockFields"> |
| | | prop="endAt"> |
| | | <el-time-picker v-model="form.endAt" |
| | | format="HH:mm" |
| | | value-format="HH:mm" |
| | |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitForm" |
| | | v-if="operationType !== 'view'">ç¡®å®</el-button> |
| | | <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </span> |
| | | </template> |
| | |
| | | // è·åçæ¬¡åå
¸å¼ |
| | | const { shifts_list } = useDict("shifts_list"); |
| | | |
| | | const selectedShiftLabel = computed(() => { |
| | | const item = shifts_list.value?.find( |
| | | s => String(s.value) === String(form.shift) |
| | | ); |
| | | return item?.label || ""; |
| | | }); |
| | | |
| | | const hideClockFields = computed(() => { |
| | | if (props.operationType === "view") return false; |
| | | return ( |
| | | selectedShiftLabel.value.includes("å") || |
| | | selectedShiftLabel.value.includes("ä¼") |
| | | ); |
| | | }); |
| | | |
| | | // è¡¨åæ°æ® |
| | | const formRef = ref(); |
| | | const form = reactive({ |
| | |
| | | }); |
| | | |
| | | // 表åéªè¯è§å |
| | | const rules = computed(() => { |
| | | const base = { |
| | | sysDeptId: [{ required: true, message: "è¯·éæ©é¨é¨", trigger: "change" }], |
| | | shift: [{ required: true, message: "è¯·éæ©çæ¬¡", trigger: "change" }], |
| | | }; |
| | | |
| | | if (hideClockFields.value) { |
| | | return base; |
| | | } |
| | | |
| | | return { |
| | | ...base, |
| | | locationName: [ |
| | | { required: true, message: "请è¾å
¥å°ç¹åç§°", trigger: "blur" }, |
| | | ], |
| | | longitude: [{ required: true, message: "è¯·éæ©æå¡ä½ç½®", trigger: "blur" }], |
| | | latitude: [{ required: true, message: "è¯·éæ©æå¡ä½ç½®", trigger: "blur" }], |
| | | radius: [{ required: true, message: "请è¾å
¥æå¡èå´", trigger: "blur" }], |
| | | startAt: [{ required: true, message: "è¯·éæ©ä¸çæ¶é´", trigger: "change" }], |
| | | endAt: [ |
| | | { required: true, message: "è¯·éæ©ä¸çæ¶é´", trigger: "change" }, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (form.shift && ["å¤ç", "å¤"].includes(form.shift)) { |
| | | callback(); |
| | | return; |
| | | } |
| | | if (form.startAt && value) { |
| | | const startParts = form.startAt.split(":"); |
| | | const endParts = value.split(":"); |
| | | const startTime = |
| | | parseInt(startParts[0]) * 60 + parseInt(startParts[1]); |
| | | const endTime = parseInt(endParts[0]) * 60 + parseInt(endParts[1]); |
| | | if (endTime <= startTime) { |
| | | callback(new Error("ä¸çæ¶é´ä¸è½æ©äºä¸çæ¶é´")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | const rules = { |
| | | sysDeptId: [{ required: true, message: "è¯·éæ©é¨é¨", trigger: "change" }], |
| | | locationName: [ |
| | | { required: true, message: "请è¾å
¥å°ç¹åç§°", trigger: "blur" }, |
| | | ], |
| | | longitude: [{ required: true, message: "è¯·éæ©æå¡ä½ç½®", trigger: "blur" }], |
| | | latitude: [{ required: true, message: "è¯·éæ©æå¡ä½ç½®", trigger: "blur" }], |
| | | shift: [{ required: true, message: "è¯·éæ©çæ¬¡", trigger: "change" }], |
| | | radius: [{ required: true, message: "请è¾å
¥æå¡èå´", trigger: "blur" }], |
| | | startAt: [{ required: true, message: "è¯·éæ©ä¸çæ¶é´", trigger: "change" }], |
| | | endAt: [ |
| | | { required: true, message: "è¯·éæ©ä¸çæ¶é´", trigger: "change" }, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (form.startAt && value) { |
| | | const startParts = form.startAt.split(":"); |
| | | const endParts = value.split(":"); |
| | | const startTime = |
| | | parseInt(startParts[0]) * 60 + parseInt(startParts[1]); |
| | | const endTime = parseInt(endParts[0]) * 60 + parseInt(endParts[1]); |
| | | if (endTime <= startTime) { |
| | | callback(new Error("ä¸çæ¶é´ä¸è½æ©äºä¸çæ¶é´")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | trigger: "change", |
| | | } else { |
| | | callback(); |
| | | } |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | // é¨é¨é项 |
| | | const deptOptions = ref([]); |
| | |
| | | watch( |
| | | () => form.radius, |
| | | newValue => { |
| | | if (circle && newValue != null && newValue !== "") { |
| | | circle.setRadius(Number(newValue)); |
| | | if (circle) { |
| | | circle.setRadius(newValue); |
| | | } |
| | | } |
| | | ); |
| | |
| | | () => { |
| | | if (formRef.value && form.endAt) { |
| | | formRef.value.validateField("endAt"); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => hideClockFields.value, |
| | | val => { |
| | | if (!val) return; |
| | | form.radius = null; |
| | | form.longitude = ""; |
| | | form.latitude = ""; |
| | | form.locationName = ""; |
| | | form.startAt = ""; |
| | | form.endAt = ""; |
| | | if (formRef.value) { |
| | | formRef.value.clearValidate([ |
| | | "radius", |
| | | "longitude", |
| | | "latitude", |
| | | "locationName", |
| | | "startAt", |
| | | "endAt", |
| | | ]); |
| | | } |
| | | } |
| | | ); |
| | |
| | | |
| | | // åå§åå°å¾ |
| | | setTimeout(() => { |
| | | if (!hideClockFields.value) { |
| | | initMap(); |
| | | } |
| | | initMap(); |
| | | }, 100); |
| | | } |
| | | } |
| | |
| | | .mt8 { |
| | | margin-top: 8px; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | <el-form :model="searchForm" |
| | | <!-- æ¥è¯¢æ¡ä»¶ --> |
| | | <!-- <el-form :model="searchForm" |
| | | :inline="true" |
| | | class="search-form mb16"> |
| | | <el-form-item label="é¨é¨ï¼" |
| | | prop="sysDeptId"> |
| | | <el-tree-select v-model="searchForm.sysDeptId" |
| | | prop="countId"> |
| | | <el-tree-select v-model="searchForm.countId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | clearable |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item label="çæ¬¡ï¼" |
| | | prop="shift"> |
| | | <el-select v-model="searchForm.shift" |
| | | placeholder="è¯·éæ©çæ¬¡" |
| | | clearable |
| | | style="width: 200px"> |
| | | <el-option |
| | | v-for="item in shifts_list" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | <el-form-item label="å°ç¹ï¼" |
| | | prop="locationName"> |
| | | <el-input v-model="searchForm.locationName" |
| | | placeholder="请è¾å
¥å°ç¹åç§°" |
| | | clearable |
| | | style="width: 200px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleSearch"> |
| | | @click="fetchData"> |
| | | <el-icon> |
| | | <Search /> |
| | | </el-icon> |
| | |
| | | éç½® |
| | | </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | </el-form> --> |
| | | <!-- çæ¬¡å表 --> |
| | | <el-card shadow="never" |
| | | class="mb16"> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="locationName" |
| | | label="å°ç¹åç§°"> |
| | | <template #default="scope"> |
| | | {{ scope.row.locationName || "-" }} |
| | | </template> |
| | | </el-table-column> |
| | | label="å°ç¹åç§°" /> |
| | | <el-table-column prop="longitude" |
| | | label="ç»åº¦"> |
| | | <template #default="scope"> |
| | | {{ scope.row.longitude ?? "-" }} |
| | | </template> |
| | | </el-table-column> |
| | | label="ç»åº¦" /> |
| | | <el-table-column prop="latitude" |
| | | label="纬度"> |
| | | <template #default="scope"> |
| | | {{ scope.row.latitude ?? "-" }} |
| | | </template> |
| | | </el-table-column> |
| | | label="纬度" /> |
| | | <el-table-column prop="radius" |
| | | label="æå¡èå´(m)"> |
| | | <template #default="scope"> |
| | | {{ scope.row.radius ?? "-" }} |
| | | </template> |
| | | </el-table-column> |
| | | label="æå¡èå´(m)" /> |
| | | <el-table-column prop="startAt" |
| | | label="ä¸çæ¶é´"> |
| | | <template #default="scope"> |
| | | {{ scope.row.startAt || "-" }} |
| | | {{ scope.row.startAt }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="endAt" |
| | | label="ä¸çæ¶é´"> |
| | | <template #default="scope"> |
| | | {{ scope.row.endAt || "-" }} |
| | | {{ scope.row.endAt }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | |
| | | <template #default="scope"> |
| | | <el-button type="primary" |
| | | link |
| | | :disabled="isRuleLocked(scope.row)" |
| | | @click="openForm('edit', scope.row)">ç¼è¾</el-button> |
| | | <el-button type="danger" |
| | | link |
| | | :disabled="isRuleLocked(scope.row)" |
| | | @click="handleDelete(scope.row.id)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { |
| | | Plus, |
| | |
| | | |
| | | // æ¥è¯¢è¡¨å |
| | | const searchForm = reactive({ |
| | | sysDeptId: "", |
| | | shift: "", |
| | | countId: "", |
| | | locationName: "", |
| | | }); |
| | | |
| | | // é¨é¨é项 |
| | |
| | | if (!value) return ""; |
| | | const shift = shifts_list.value.find(item => item.value === value); |
| | | return shift ? shift.label : value; |
| | | }; |
| | | |
| | | const isRuleLocked = row => { |
| | | return Boolean(row?.usageLocked || row?.canEdit === false || row?.canDelete === false); |
| | | }; |
| | | |
| | | // è·åé¨é¨å表 |
| | |
| | | tableLoading.value = true; |
| | | getAttendanceRules({ ...page, ...searchForm }) |
| | | .then(res => { |
| | | const records = Array.isArray(res?.data?.records) ? res.data.records : []; |
| | | tableData.value = records.filter(r => String(r?.id) !== "999"); |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleSearch = () => { |
| | | page.current = 1; |
| | | fetchData(); |
| | | }; |
| | | |
| | | // å页忴 |
| | |
| | | |
| | | // éç½®æç´¢ |
| | | const resetSearch = () => { |
| | | searchForm.sysDeptId = ""; |
| | | searchForm.shift = ""; |
| | | page.current = 1; |
| | | searchForm.countId = ""; |
| | | searchForm.locationName = ""; |
| | | fetchData(); |
| | | }; |
| | | |
| | | // æå¼è¡¨å |
| | | const openForm = (type, row = {}) => { |
| | | if (type === "edit" && isRuleLocked(row)) { |
| | | ElMessage.warning("è¯¥çæ¬¡é
置已被æç并产çæå¡è®°å½ï¼ä¸è½ç¼è¾"); |
| | | return; |
| | | } |
| | | operationType.value = type; |
| | | currentRow.value = row; |
| | | dialogVisible.value = true; |
| | |
| | | |
| | | // å é¤ç次 |
| | | const handleDelete = id => { |
| | | const row = tableData.value.find(item => String(item.id) === String(id)); |
| | | if (isRuleLocked(row)) { |
| | | ElMessage.warning("åå¨å·²æçä¸å·²ææå¡è®°å½ççæ¬¡é
ç½®ï¼ä¸è½å é¤"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("ç¡®å®è¦å é¤è¿æ¡ç次åï¼", "å é¤ç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | |
| | | :key="'d' + i"> |
| | | <el-dropdown trigger="click" |
| | | placement="bottom" |
| | | :disabled="isCellBlockedByLeaveDate(item, m)" |
| | | @command="(e) => handleCommand(e, item, m)" |
| | | @command="(e) => handleCommand(e, m)" |
| | | class="shift-dropdown"> |
| | | <div class="shift-box" |
| | | :class="{ |
| | | 'shift-box-early': m.shift === 'æ©ç', |
| | | 'shift-box-mid': m.shift === 'ä¸ç', |
| | | 'shift-box-night': m.shift === 'å¤ç', |
| | | 'shift-box-rest': !m.shift || m.shift === '伿¯', |
| | | 'shift-box-rest': m.shift === '伿¯', |
| | | 'shift-box-leave': m.shift === '请å', |
| | | 'shift-box-other': m.shift === 'å¤11', |
| | | 'shift-box-business': m.shift === 'å¤12', |
| | | }"> |
| | | <span class="shift-text">{{ getShiftNameByValue(m.shift) || '伿¯' }}</span> |
| | | <span class="shift-text">{{ getShiftNameByValue(m.shift) || 'â' }}</span> |
| | | </div> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | |
| | | </div> --> |
| | | <div class="user-total"> |
| | | <span class="total-label">å计åºå¤:</span> |
| | | <span class="total-value">{{ item.year.totalYearAttendance }}天</span> |
| | | <span class="total-value">{{ item.work_time }}天</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <div class="search_thing"> |
| | | <div class="search_label" |
| | | style="width: 90px"> |
| | | <span style="color: red; margin-right: 4px">*</span>é¨é¨ï¼ |
| | | </div> |
| | | <div class="search_input"> |
| | | <el-tree-select v-model="schedulingQuery.deptId" |
| | | :data="deptOptions" |
| | | :props="{ value: 'id', label: 'label', children: 'children' }" |
| | | value-key="id" |
| | | placeholder="è¯·éæ©é¨é¨" |
| | | check-strictly |
| | | clearable |
| | | @change="onSchedulingDeptChange" |
| | | style="width: 100%" /> |
| | | </div> |
| | | </div> |
| | | <div class="search_thing"> |
| | | <div class="search_label" |
| | | style="width: 90px"> |
| | | <span style="color: red; margin-right: 4px">*</span>人ååç§°ï¼ |
| | | </div> |
| | | <div class="search_input"> |
| | |
| | | style="width: 100%" |
| | | multiple |
| | | clearable |
| | | collapse-tags |
| | | :disabled="!schedulingQuery.deptId"> |
| | | collapse-tags> |
| | | <el-option v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | |
| | | <div class="search_input"> |
| | | <el-select v-model="schedulingQuery.shift" |
| | | placeholder="è¯·éæ©" |
| | | style="width: 100%" |
| | | :disabled="!schedulingQuery.deptId"> |
| | | <el-option v-for="item in schedulingClassType" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in classType" |
| | | :key="item.id" |
| | | :label="getShiftNameByValue(item.shift)" |
| | | :value="item.id"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | ref, |
| | | reactive, |
| | | computed, |
| | | watch, |
| | | onMounted, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import { |
| | | page, |
| | |
| | | update, |
| | | staffOnJobListPage, |
| | | } from "@/api/personnelManagement/class"; |
| | | import { findStaffLeaveListPage } from "@/api/personnelManagement/staffLeave.js"; |
| | | import { deptTreeSelect } from "@/api/system/user.js"; |
| | | import { getAttendanceRules } from "@/api/personnelManagement/attendanceRules.js"; |
| | | import { useDict } from "@/utils/dict"; |
| | |
| | | |
| | | // 人åå表 |
| | | const personList = ref([]); |
| | | const personListAll = ref([]); |
| | | const leaveDateMap = ref({}); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | |
| | | // æçæ¥è¯¢æ¡ä»¶ |
| | | const schedulingQuery = reactive({ |
| | | week: "", |
| | | deptId: "", |
| | | userId: null, |
| | | shift: "", |
| | | }); |
| | | |
| | | const schedulingClassType = computed(() => { |
| | | if (!schedulingQuery.deptId) return classType.value || []; |
| | | return (classType.value || []).filter(item => { |
| | | if (["998", "999"].includes(String(item?.id))) return true; |
| | | const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id; |
| | | return String(itemDeptId) === String(schedulingQuery.deptId); |
| | | }); |
| | | }); |
| | | |
| | | // åè¡¨æ°æ® |
| | |
| | | proxy.$modal.msgError("è¯·éæ©å¨æ¬¡"); |
| | | return; |
| | | } |
| | | if (!schedulingQuery.deptId) { |
| | | proxy.$modal.msgError("è¯·éæ©é¨é¨"); |
| | | return; |
| | | } |
| | | let time = schedulingQuery.week.getTime(); |
| | | |
| | | // æ ¼å¼åæ¥æä¸º YYYY-MM-DD æ ¼å¼ |
| | |
| | | proxy.$modal.msgError("è¯·éæ©çæ¬¡"); |
| | | return; |
| | | } |
| | | const blockedEmployees = (personList.value || []).filter(item => { |
| | | const leaveDate = getEmployeeLeaveDate(item?.id); |
| | | const weekEndDate = getSchedulingWeekEndDate(); |
| | | return leaveDate && weekEndDate && weekEndDate.getTime() > leaveDate.getTime(); |
| | | }); |
| | | if (blockedEmployees.length) { |
| | | proxy.$modal.msgError("åå¨å·²è¶
è¿ç¦»èæ¥æç人åï¼ä¸è½ç»§ç»æç"); |
| | | return; |
| | | } |
| | | loading.value = true; |
| | | add({ |
| | | startWeek, |
| | |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | | schedulingVisible.value = false; |
| | | schedulingQuery.week = ""; |
| | | schedulingQuery.deptId = ""; |
| | | schedulingQuery.userId = null; |
| | | schedulingQuery.shift = ""; |
| | | personList.value = []; |
| | | refresh(); |
| | | }) |
| | | .catch(err => { |
| | |
| | | } |
| | | proxy.$download.saveAs(blob, fileName + ".xlsx"); |
| | | }) |
| | | .catch(err => {}) |
| | | .catch(err => { |
| | | }) |
| | | .finally(() => { |
| | | downLoading.value = false; |
| | | }); |
| | | }; |
| | | // å¤çå½ä»¤ |
| | | const handleCommand = (e, row, cell) => { |
| | | if (isCellBlockedByLeaveDate(row, cell)) { |
| | | proxy.$modal.msgError("该å工已è¶
åºç¦»èæ¥æï¼ä¸è½ç»§ç»æç"); |
| | | return; |
| | | } |
| | | const handleCommand = (e, m) => { |
| | | // if (e != m.shift) { |
| | | update({ |
| | | id: cell.id, |
| | | id: m.id, |
| | | personalAttendanceLocationConfigId: e, |
| | | }).then(res => { |
| | | proxy.$modal.msgSuccess("æä½æå"); |
| | |
| | | // æ¥è¯¢è§åå表 |
| | | const fetchData = () => { |
| | | getAttendanceRules({ current: -1, size: -1 }).then(res => { |
| | | const records = Array.isArray(res?.data?.records) ? res.data.records : []; |
| | | const appendList = [ |
| | | { id: 998, shift: "伿¯" }, |
| | | { id: 999, shift: "请å" }, |
| | | ]; |
| | | const exists = new Set(records.map(r => String(r?.id))); |
| | | appendList.forEach(item => { |
| | | if (!exists.has(String(item.id))) { |
| | | records.push(item); |
| | | } |
| | | }); |
| | | classType.value = records; |
| | | classType.value = res.data.records; |
| | | }); |
| | | }; |
| | | // è·åç¨æ· |
| | |
| | | staffState: 1, |
| | | }).then(res => { |
| | | let arr = res.data.records; |
| | | personListAll.value = arr; |
| | | updatePersonListByDept(); |
| | | personList.value = arr; |
| | | }); |
| | | }; |
| | | |
| | | const getLeaveDates = () => { |
| | | findStaffLeaveListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | }).then(res => { |
| | | const records = Array.isArray(res?.data?.records) ? res.data.records : []; |
| | | const map = {}; |
| | | records.forEach(item => { |
| | | const staffId = item?.staffOnJobId ?? item?.staff_on_job_id; |
| | | const leaveDate = item?.leaveDate ?? item?.leave_date; |
| | | if (!staffId || !leaveDate) return; |
| | | const key = String(staffId); |
| | | const prev = map[key]; |
| | | if (!prev || String(leaveDate) > String(prev)) { |
| | | map[key] = leaveDate; |
| | | } |
| | | }); |
| | | leaveDateMap.value = map; |
| | | updatePersonListByDept(); |
| | | syncPersonListSelection(); |
| | | }); |
| | | }; |
| | | |
| | | const findDeptNodeById = (deptId, deptList = deptOptions.value) => { |
| | | const list = Array.isArray(deptList) ? deptList : []; |
| | | for (const node of list) { |
| | | if (String(node.id) === String(deptId)) return node; |
| | | if (node.children && node.children.length) { |
| | | const found = findDeptNodeById(deptId, node.children); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const collectDeptIds = node => { |
| | | const ids = []; |
| | | if (!node) return ids; |
| | | ids.push(String(node.id)); |
| | | if (node.children && node.children.length) { |
| | | node.children.forEach(child => { |
| | | ids.push(...collectDeptIds(child)); |
| | | }); |
| | | } |
| | | return ids; |
| | | }; |
| | | |
| | | const updatePersonListByDept = () => { |
| | | if (!schedulingQuery.deptId) { |
| | | personList.value = []; |
| | | return; |
| | | } |
| | | const node = findDeptNodeById(schedulingQuery.deptId); |
| | | const deptIds = new Set(collectDeptIds(node)); |
| | | personList.value = (personListAll.value || []).filter(item => { |
| | | const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id; |
| | | return deptIds.has(String(itemDeptId)) && !isEmployeeBlockedByLeaveDate(item); |
| | | }); |
| | | }; |
| | | |
| | | const parseDateOnly = value => { |
| | | if (!value) return null; |
| | | if (value instanceof Date) { |
| | | const d = new Date(value); |
| | | d.setHours(0, 0, 0, 0); |
| | | return d; |
| | | } |
| | | const normalized = String(value).trim().slice(0, 10); |
| | | if (!normalized) return null; |
| | | const d = new Date(normalized.replace(/-/g, "/")); |
| | | if (Number.isNaN(d.getTime())) return null; |
| | | d.setHours(0, 0, 0, 0); |
| | | return d; |
| | | }; |
| | | |
| | | const getSchedulingWeekEndDate = () => { |
| | | if (!schedulingQuery.week) return null; |
| | | const start = new Date(schedulingQuery.week); |
| | | start.setHours(0, 0, 0, 0); |
| | | return new Date(start.getTime() + 24 * 60 * 60 * 1000 * 5); |
| | | }; |
| | | |
| | | const getCellDate = cell => parseDateOnly(cell?.time); |
| | | |
| | | const getEmployeeLeaveDate = staffId => { |
| | | const leaveDate = leaveDateMap.value[String(staffId)]; |
| | | return parseDateOnly(leaveDate); |
| | | }; |
| | | |
| | | const isEmployeeBlockedByLeaveDate = item => { |
| | | const leaveDate = getEmployeeLeaveDate(item?.id); |
| | | if (!leaveDate) return false; |
| | | const weekEndDate = getSchedulingWeekEndDate(); |
| | | if (!weekEndDate) return false; |
| | | return weekEndDate.getTime() > leaveDate.getTime(); |
| | | }; |
| | | |
| | | const isCellBlockedByLeaveDate = (row, cell) => { |
| | | const staffId = row?.userId ?? row?.user_id ?? row?.id; |
| | | const leaveDate = getEmployeeLeaveDate(staffId); |
| | | const cellDate = getCellDate(cell); |
| | | if (!leaveDate || !cellDate) return false; |
| | | return cellDate.getTime() > leaveDate.getTime(); |
| | | }; |
| | | |
| | | const syncPersonListSelection = () => { |
| | | if (!Array.isArray(schedulingQuery.userId) || !schedulingQuery.userId.length) return; |
| | | const validIds = new Set((personList.value || []).map(item => String(item.id))); |
| | | const nextIds = schedulingQuery.userId.filter(id => validIds.has(String(id))); |
| | | if (nextIds.length !== schedulingQuery.userId.length) { |
| | | schedulingQuery.userId = nextIds; |
| | | proxy.$modal.msgWarning("å·²èªå¨åé¤è¶
åºç¦»èæ¥æç人å"); |
| | | } |
| | | }; |
| | | |
| | | const setDefaultShiftByDept = () => { |
| | | if (!schedulingQuery.deptId) { |
| | | schedulingQuery.shift = ""; |
| | | return; |
| | | } |
| | | const first = schedulingClassType.value?.[0]; |
| | | schedulingQuery.shift = first ? first.id : ""; |
| | | }; |
| | | |
| | | const onSchedulingDeptChange = () => { |
| | | schedulingQuery.userId = null; |
| | | updatePersonListByDept(); |
| | | setDefaultShiftByDept(); |
| | | }; |
| | | |
| | | // æ ¹æ®åå
¸è·åæ¥æ |
| | |
| | | onMounted(() => { |
| | | fetchData(); |
| | | getUsers(); |
| | | getLeaveDates(); |
| | | fetchDeptOptions(); |
| | | if (query.month) { |
| | | init(); |
| | |
| | | } |
| | | monthList.value.reverse(); |
| | | }); |
| | | |
| | | watch( |
| | | () => classType.value, |
| | | () => { |
| | | if (schedulingVisible.value && schedulingQuery.deptId) { |
| | | setDefaultShiftByDept(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => schedulingQuery.week, |
| | | () => { |
| | | if (!schedulingVisible.value) return; |
| | | updatePersonListByDept(); |
| | | syncPersonListSelection(); |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => schedulingQuery.deptId, |
| | | () => { |
| | | if (!schedulingVisible.value) return; |
| | | updatePersonListByDept(); |
| | | syncPersonListSelection(); |
| | | } |
| | | ); |
| | | |
| | | watch( |
| | | () => schedulingVisible.value, |
| | | val => { |
| | | if (!val) return; |
| | | schedulingQuery.week = ""; |
| | | schedulingQuery.deptId = ""; |
| | | schedulingQuery.userId = null; |
| | | schedulingQuery.shift = ""; |
| | | personList.value = []; |
| | | } |
| | | ); |
| | | </script> |
| | | |
| | | <style scoped> |
| | |
| | | .user-stats { |
| | | /* display: flex; */ |
| | | /* flex-wrap: wrap; |
| | | gap: 10px; */ |
| | | gap: 10px; */ |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | title="详æ
" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600"></PIMTable> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | title="详æ
" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="600" |
| | | ></PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | <FileList v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | :record-type="'staff_contract'" |
| | | :record-id="recordId" /> |
| | | <Files ref="filesDia"></Files> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, defineAsyncComponent, getCurrentInstance } from "vue"; |
| | | import { findStaffContractListPage } from "@/api/personnelManagement/staffContract.js"; |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | const fileDialogVisible = ref(false); |
| | | const recordId = ref(0); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "éä»¶", |
| | | type: "text", |
| | | clickFun: row => { |
| | | recordId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }, |
| | | import {ref} from "vue"; |
| | | import {findStaffContractListPage} from "@/api/personnelManagement/staffContract.js"; |
| | | const Files = defineAsyncComponent(() => import( "@/views/personnelManagement/contractManagement/filesDia.vue")); |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | const filesDia = ref() |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ååå¹´é", |
| | | prop: "contractTerm", |
| | | }, |
| | | { |
| | | label: "ååå¼å§æ¥æ", |
| | | prop: "contractStartTime", |
| | | }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractEndTime", |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 120, |
| | | operation: [ |
| | | { |
| | | name: "ä¸ä¼ éä»¶", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | filesDia.value.openDialog( row,'åå') |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === "edit") { |
| | | findStaffContractListPage({ staffOnJobId: row.id }).then(res => { |
| | | tableData.value = res.data.records; |
| | | }); |
| | | } |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | findStaffContractListPage({staffOnJobId: row.id}).then(res => { |
| | | tableData.value = res.data.records |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | const openUploadFile = (row) => { |
| | | filesDia.value.open = true |
| | | filesDia.value.row = row |
| | | } |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" |
| | | class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | type="daterange" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="changeDaterange" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-input v-model="searchForm.staffName" style="width: 240px" placeholder="请è¾å
¥å§åæç´¢" @change="handleQuery" |
| | | clearable :prefix-icon="Search" /> |
| | | <span style="margin-left: 10px" class="search_title">ååç»ææ¥æï¼</span> |
| | | <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange" |
| | | placeholder="è¯·éæ©" clearable @change="changeDaterange" /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <!-- <el-button type="primary" @click="openForm('add')">æ°å¢å
¥è</el-button>--> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</el-button>--> |
| | | <!-- <el-button type="info" @click="handleImport">导å
¥</el-button>--> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button>--> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | <PIMTable rowKey="id" :column="tableColumn" :tableData="tableData" :page="page" :isSelection="true" |
| | | @selection-change="handleSelectionChange" :tableLoading="tableLoading" @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" |
| | | @close="handleQuery"></form-dia> |
| | | <renew-contract v-if="isShowRenewContractModal" |
| | | v-model:visible="isShowRenewContractModal" |
| | | :id="id" |
| | | @completed="handleQuery" /> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | <renew-contract |
| | | v-if="isShowRenewContractModal" |
| | | v-model:visible="isShowRenewContractModal" |
| | | :id="id" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <!-- åå导å
¥å¯¹è¯æ¡ --> |
| | | <el-dialog :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body> |
| | | <el-upload ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag> |
| | | <el-dialog |
| | | :title="upload.title" |
| | | v-model="upload.open" |
| | | width="400px" |
| | | append-to-body |
| | | > |
| | | <el-upload |
| | | ref="uploadRef" |
| | | :limit="1" |
| | | accept=".xlsx, .xls" |
| | | :headers="upload.headers" |
| | | :action="upload.url + '?updateSupport=' + upload.updateSupport" |
| | | :disabled="upload.isUploading" |
| | | :on-progress="handleFileUploadProgress" |
| | | :on-success="handleFileSuccess" |
| | | :auto-upload="false" |
| | | drag |
| | | > |
| | | <el-icon class="el-icon--upload"><upload-filled /></el-icon> |
| | | <div class="el-upload__text">å°æä»¶æå°æ¤å¤ï¼æ<em>ç¹å»ä¸ä¼ </em></div> |
| | | <template #tip> |
| | |
| | | </el-upload> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button type="primary" @click="submitFileForm">ç¡® å®</el-button> |
| | | <el-button @click="upload.open = false">å æ¶</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, defineAsyncComponent } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | const RenewContract = defineAsyncComponent(() => |
| | | import( |
| | | "@/views/personnelManagement/employeeRecord/components/RenewContract.vue" |
| | | ) |
| | | ); |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref, defineAsyncComponent } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/contractManagement/components/formDia.vue"; |
| | | const RenewContract = defineAsyncComponent(() => import("@/views/personnelManagement/employeeRecord/components/RenewContract.vue")); |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import dayjs from "dayjs"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import FilesDia from "./filesDia.vue"; |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | entryDate: null, // å½å
¥æ¥æ |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined, |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: params => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postName", |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150, |
| | | }, |
| | | // { |
| | | // label: "ååå¹´é", |
| | | // prop: "contractTerm", |
| | | // }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 160, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "æ·ç±ä½å", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postJob", |
| | | }, |
| | | { |
| | | label: "ç°ä½å", |
| | | prop: "adress", |
| | | width: 200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width: 100 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width: 150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150 |
| | | }, |
| | | // { |
| | | // label: "ååå¹´é", |
| | | // prop: "contractTerm", |
| | | // }, |
| | | // { |
| | | // label: "ååå¼å§æ¥æ", |
| | | // prop: "contractStartTime", |
| | | // width: 120 |
| | | // }, |
| | | { |
| | | label: "ååç»ææ¥æ", |
| | | prop: "contractExpireTime", |
| | | width: 120 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | width: 160, |
| | | operation: [ |
| | | { |
| | | name: "详æ
", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | { |
| | | name: "ç»ç¾åå", |
| | | type: "text", |
| | | showHide: row => row.staffState === 1, |
| | | clickFun: row => { |
| | | isShowRenewContractModal.value = true; |
| | | id.value = row.id; |
| | | }, |
| | | }, |
| | | { |
| | | name: "ç»ç¾åå", |
| | | type: "text", |
| | | showHide: row => row.staffState === 1, |
| | | clickFun: (row) => { |
| | | isShowRenewContractModal.value = true; |
| | | id.value = row.id; |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref(); |
| | | const isShowRenewContractModal = ref(false); |
| | | const id = ref(0); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref(); |
| | | const { proxy } = getCurrentInstance(); |
| | | } |
| | | ], |
| | | }, |
| | | ]); |
| | | const filesDia = ref() |
| | | const isShowRenewContractModal = ref(false); |
| | | const id = ref(0); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | const changeDaterange = value => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = row => { |
| | | console.log(row); |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog(row, "åå"); |
| | | }); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined; |
| | | params.staffState = 1; |
| | | staffOnJobListPage(params) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const changeDaterange = (value) => { |
| | | searchForm.value.entryDateStart = undefined; |
| | | searchForm.value.entryDateEnd = undefined; |
| | | if (value) { |
| | | searchForm.value.entryDateStart = dayjs(value[0]).format("YYYY-MM-DD"); |
| | | searchForm.value.entryDateEnd = dayjs(value[1]).format("YYYY-MM-DD"); |
| | | } |
| | | getList(); |
| | | }; |
| | | // æå¼éä»¶å¼¹æ¡ |
| | | const openFilesFormDia = (row) => { |
| | | console.log(row) |
| | | nextTick(() => { |
| | | filesDia.value?.openDialog( row,'åå') |
| | | }) |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | const params = { ...searchForm.value, ...page }; |
| | | params.entryDate = undefined |
| | | params.staffState = 1 |
| | | staffOnJobListPage(params).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffOnJob/export", {staffState: 1}, "åå管ç.xlsx"); |
| | | }) |
| | | .then(() => { |
| | | proxy.download( |
| | | "/staff/staffOnJob/export", |
| | | { staffState: 1 }, |
| | | "åå管ç.xlsx" |
| | | ); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼åå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "åå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | console.log(upload.url + "?updateSupport=" + upload.updateSupport); |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false; |
| | | upload.isUploading = false; |
| | | proxy.$refs["uploadRef"].handleRemove(file); |
| | | getList(); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | const upload = reactive({ |
| | | // æ¯å¦æ¾ç¤ºå¼¹åºå±ï¼åå导å
¥ï¼ |
| | | open: false, |
| | | // å¼¹åºå±æ é¢ï¼åå导å
¥ï¼ |
| | | title: "", |
| | | // æ¯å¦ç¦ç¨ä¸ä¼ |
| | | isUploading: false, |
| | | // æ¯å¦æ´æ°å·²ç»åå¨çç¨æ·æ°æ® |
| | | updateSupport: 1, |
| | | // 设置ä¸ä¼ ç请æ±å¤´é¨ |
| | | headers: { Authorization: "Bearer " + getToken() }, |
| | | // ä¸ä¼ çå°å |
| | | url: import.meta.env.VITE_APP_BASE_API + "/staff/staffOnJob/import", |
| | | }); |
| | | /** 导å
¥æé®æä½ */ |
| | | function handleImport() { |
| | | upload.title = "åå导å
¥"; |
| | | upload.open = true; |
| | | } |
| | | /** æäº¤ä¸ä¼ æä»¶ */ |
| | | function submitFileForm() { |
| | | console.log(upload.url + '?updateSupport=' + upload.updateSupport) |
| | | proxy.$refs["uploadRef"].submit(); |
| | | } |
| | | /**æä»¶ä¸ä¼ ä¸å¤ç */ |
| | | const handleFileUploadProgress = (event, file, fileList) => { |
| | | upload.isUploading = true; |
| | | }; |
| | | /** æä»¶ä¸ä¼ æåå¤ç */ |
| | | const handleFileSuccess = (response, file, fileList) => { |
| | | upload.open = false; |
| | | upload.isUploading = false; |
| | | proxy.$refs["uploadRef"].handleRemove(file); |
| | | getList(); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ç¦»è' : 'ç¼è¾ç¦»è'" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢ç¦»è' : 'ç¼è¾ç¦»è'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <!-- å工信æ¯å±ç¤ºåºå --> |
| | | <div class="info-section"> |
| | | <div class="info-title">å工信æ¯</div> |
| | | <el-form :model="form" |
| | | label-width="200px" |
| | | label-position="left" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | style="margin-top: 20px"> |
| | | <el-form :model="form" label-width="200px" label-position="left" :rules="rules" ref="formRef" style="margin-top: 20px"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å§åï¼" |
| | | prop="staffOnJobId"> |
| | | <el-form-item label="å§åï¼" prop="staffOnJobId"> |
| | | <el-select v-model="form.staffOnJobId" |
| | | placeholder="è¯·éæ©äººå" |
| | | style="width: 100%" |
| | | :disabled="operationType === 'edit'" |
| | | @change="handleSelect"> |
| | | <el-option v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id" /> |
| | | <el-option |
| | | v-for="item in personList" |
| | | :key="item.id" |
| | | :label="item.staffName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | {{ currentStaffRecord.sex || '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ·ç±ä½åï¼"> |
| | | {{ currentStaffRecord.nativePlace || '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å²ä½ï¼"> |
| | | {{ currentStaffRecord.postName || '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç°ä½åï¼"> |
| | | {{ currentStaffRecord.adress || '-' }} |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¦»èæ¥æï¼" |
| | | prop="leaveDate"> |
| | | <el-date-picker v-model="form.leaveDate" |
| | | type="date" |
| | | :disabled="operationType === 'edit'" |
| | | :disabled-date="disabledFutureDate" |
| | | placeholder="è¯·éæ©ç¦»èæ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" /> |
| | | <el-form-item label="ç¦»èæ¥æï¼" prop="leaveDate"> |
| | | <el-date-picker |
| | | v-model="form.leaveDate" |
| | | type="date" |
| | | :disabled="operationType === 'edit'" |
| | | :disabled-date="disabledFutureDate" |
| | | placeholder="è¯·éæ©ç¦»èæ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="离èåå ï¼" |
| | | prop="reason"> |
| | | <el-select v-model="form.reason" |
| | | placeholder="è¯·éæ©ç¦»èåå " |
| | | style="width: 100%" |
| | | @change="handleSelectDimissionReason"> |
| | | <el-option v-for="(item, index) in dimissionReasonOptions" |
| | | :key="index" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | <el-form-item label="离èåå ï¼" prop="reason"> |
| | | <el-select v-model="form.reason" placeholder="è¯·éæ©ç¦»èåå " style="width: 100%" @change="handleSelectDimissionReason"> |
| | | <el-option |
| | | v-for="(item, index) in dimissionReasonOptions" |
| | | :key="index" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="夿³¨ï¼" |
| | | prop="remark" |
| | | v-if="form.reason === 'other'"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="夿³¨" |
| | | maxlength="500" |
| | | show-word-limit /> |
| | | <el-form-item label="夿³¨ï¼" prop="remark" v-if="form.reason === 'other'"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="夿³¨" |
| | | maxlength="500" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <!-- <el-row :gutter="30">--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <div class="info-item">--> |
| | | <!-- <span class="info-label">离èåå ï¼</span>--> |
| | | <!-- <el-select v-model="form.reason" placeholder="è¯·éæ©äººå" style="width: 100%" @change="handleSelect">--> |
| | | <!-- <el-option--> |
| | | <!-- v-for="(item, index) in dimissionReasonOptions"--> |
| | | <!-- :key="index"--> |
| | | <!-- :label="item.label"--> |
| | | <!-- :value="item.value"--> |
| | | <!-- />--> |
| | | <!-- </el-select>--> |
| | | <!-- </div>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <div class="info-item">--> |
| | | <!-- <span class="info-label">åå·¥ç¼å·ï¼</span>--> |
| | | <!-- <span class="info-value">{{ form.staffNo || '-' }}</span>--> |
| | | <!-- </div>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | |
| | | <!-- <el-row :gutter="30">--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <div class="info-item">--> |
| | | <!-- <span class="info-label">离èåå ï¼</span>--> |
| | | <!-- <el-select v-model="form.reason" placeholder="è¯·éæ©äººå" style="width: 100%" @change="handleSelect">--> |
| | | <!-- <el-option--> |
| | | <!-- v-for="(item, index) in dimissionReasonOptions"--> |
| | | <!-- :key="index"--> |
| | | <!-- :label="item.label"--> |
| | | <!-- :value="item.value"--> |
| | | <!-- />--> |
| | | <!-- </el-select>--> |
| | | <!-- </div>--> |
| | | <!-- </el-col>--> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <div class="info-item">--> |
| | | <!-- <span class="info-label">åå·¥ç¼å·ï¼</span>--> |
| | | <!-- <span class="info-value">{{ form.staffNo || '-' }}</span>--> |
| | | <!-- </div>--> |
| | | <!-- </el-col>--> |
| | | <!-- </el-row>--> |
| | | </div> |
| | | |
| | | <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, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/staffOnJob.js"; |
| | | import { |
| | | createStaffLeave, |
| | | updateStaffLeave, |
| | | } from "@/api/personnelManagement/staffLeave.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | import {ref, reactive, toRefs, getCurrentInstance} from "vue"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/staffOnJob.js"; |
| | | import {createStaffLeave, updateStaffLeave} from "@/api/personnelManagement/staffLeave.js"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const getTodayDate = () => { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = `${now.getMonth() + 1}`.padStart(2, "0"); |
| | | const day = `${now.getDate()}`.padStart(2, "0"); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const getTodayDate = () => { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); |
| | | const month = `${now.getMonth() + 1}`.padStart(2, '0'); |
| | | const day = `${now.getDate()}`.padStart(2, '0'); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | const disabledFutureDate = time => { |
| | | const todayEnd = new Date(); |
| | | todayEnd.setHours(23, 59, 59, 999); |
| | | return time.getTime() > todayEnd.getTime(); |
| | | }; |
| | | const data = reactive({ |
| | | form: { |
| | | staffOnJobId: undefined, |
| | | leaveDate: "", |
| | | reason: "", |
| | | remark: "", |
| | | }, |
| | | rules: { |
| | | staffName: [{ required: true, message: "è¯·éæ©äººå" }], |
| | | leaveDate: [ |
| | | { required: true, message: "è¯·éæ©ç¦»èæ¥æ", trigger: "change" }, |
| | | ], |
| | | reason: [{ required: true, message: "è¯·éæ©ç¦»èåå " }], |
| | | }, |
| | | dimissionReasonOptions: [ |
| | | { label: "èªèµå¾
é", value: "salary" }, |
| | | { label: "èä¸åå±", value: "career_development" }, |
| | | { label: "å·¥ä½ç¯å¢", value: "work_environment" }, |
| | | { label: "个人åå ", value: "personal_reason" }, |
| | | { label: "å
¶ä»", value: "other" }, |
| | | ], |
| | | currentStaffRecord: {}, |
| | | }); |
| | | const { form, rules, dimissionReasonOptions, currentStaffRecord } = |
| | | toRefs(data); |
| | | const disabledFutureDate = (time) => { |
| | | const todayEnd = new Date(); |
| | | todayEnd.setHours(23, 59, 59, 999); |
| | | return time.getTime() > todayEnd.getTime(); |
| | | }; |
| | | const data = reactive({ |
| | | form: { |
| | | staffOnJobId: undefined, |
| | | leaveDate: "", |
| | | reason: "", |
| | | remark: "", |
| | | }, |
| | | rules: { |
| | | staffName: [{ required: true, message: "è¯·éæ©äººå" }], |
| | | leaveDate: [{ required: true, message: "è¯·éæ©ç¦»èæ¥æ", trigger: "change" }], |
| | | reason: [{ required: true, message: "è¯·éæ©ç¦»èåå "}], |
| | | }, |
| | | dimissionReasonOptions: [ |
| | | {label: 'èªèµå¾
é', value: 'salary'}, |
| | | {label: 'èä¸åå±', value: 'career_development'}, |
| | | {label: 'å·¥ä½ç¯å¢', value: 'work_environment'}, |
| | | {label: '个人åå ', value: 'personal_reason'}, |
| | | {label: 'å
¶ä»', value: 'other'}, |
| | | ], |
| | | currentStaffRecord: {}, |
| | | }); |
| | | const { form, rules, dimissionReasonOptions, currentStaffRecord } = toRefs(data); |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === "edit") { |
| | | currentStaffRecord.value = row; |
| | | form.value.staffOnJobId = row.staffOnJobId; |
| | | form.value.leaveDate = row.leaveDate; |
| | | form.value.reason = row.reason; |
| | | form.value.remark = row.remark; |
| | | personList.value = [ |
| | | { |
| | | staffName: row.staffName, |
| | | id: row.staffOnJobId, |
| | | }, |
| | | ]; |
| | | } else { |
| | | form.value.leaveDate = getTodayDate(); |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | const handleSelectDimissionReason = val => { |
| | | if (val === "other") { |
| | | form.value.remark = ""; |
| | | } |
| | | }; |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | form.value.staffState = 0; |
| | | if (form.value.reason !== "other") { |
| | | form.value.remark = ""; |
| | | } |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | createStaffLeave(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | // æå¼å¼¹æ¡ |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | if (operationType.value === 'edit') { |
| | | currentStaffRecord.value = row |
| | | form.value.staffOnJobId = row.staffOnJobId |
| | | form.value.leaveDate = row.leaveDate |
| | | form.value.reason = row.reason |
| | | form.value.remark = row.remark |
| | | personList.value = [ |
| | | { |
| | | staffName: row.staffName, |
| | | id: row.staffOnJobId, |
| | | } |
| | | }); |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | // 表å已注éï¼æå¨éç½®è¡¨åæ°æ® |
| | | form.value = { |
| | | staffOnJobId: undefined, |
| | | leaveDate: "", |
| | | reason: "", |
| | | remark: "", |
| | | }; |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | ] |
| | | } else { |
| | | form.value.leaveDate = getTodayDate() |
| | | getList() |
| | | } |
| | | } |
| | | |
| | | const personList = ref([]); |
| | | |
| | | /** |
| | | * è·åå½åå¨è人åå表 |
| | | */ |
| | | const getList = () => { |
| | | staffOnJobListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | staffState: 1, |
| | | }).then(res => { |
| | | personList.value = res.data.records || []; |
| | | }); |
| | | }; |
| | | |
| | | const handleSelect = val => { |
| | | let obj = personList.value.find(item => item.id === val); |
| | | currentStaffRecord.value = {}; |
| | | if (obj) { |
| | | // ä¿çç¦»èæ¥æå离èåå ï¼åªæ´æ°åå·¥ä¿¡æ¯ |
| | | currentStaffRecord.value = obj; |
| | | const handleSelectDimissionReason = (val) => { |
| | | if (val === 'other') { |
| | | form.value.remark = '' |
| | | } |
| | | } |
| | | // æäº¤äº§å表å |
| | | const submitForm = () => { |
| | | form.value.staffState = 0 |
| | | if (form.value.reason !== 'other') { |
| | | form.value.remark = '' |
| | | } |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | if (operationType.value === "add") { |
| | | createStaffLeave(form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } else { |
| | | updateStaffLeave(currentStaffRecord.value.id, form.value).then(res => { |
| | | proxy.$modal.msgSuccess("æäº¤æå"); |
| | | closeDia(); |
| | | }) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | // 表å已注éï¼æå¨éç½®è¡¨åæ°æ® |
| | | form.value = { |
| | | staffOnJobId: undefined, |
| | | leaveDate: "", |
| | | reason: "", |
| | | remark: "", |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | |
| | | const personList = ref([]); |
| | | |
| | | /** |
| | | * è·åå½åå¨è人åå表 |
| | | */ |
| | | const getList = () => { |
| | | staffOnJobListPage({ |
| | | current: -1, |
| | | size: -1, |
| | | staffState: 1 |
| | | }).then(res => { |
| | | personList.value = res.data.records || [] |
| | | }) |
| | | }; |
| | | |
| | | const handleSelect = (val) => { |
| | | let obj = personList.value.find(item => item.id === val) |
| | | currentStaffRecord.value = {} |
| | | if (obj) { |
| | | // ä¿çç¦»èæ¥æå离èåå ï¼åªæ´æ°åå·¥ä¿¡æ¯ |
| | | currentStaffRecord.value = obj |
| | | } |
| | | } |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .info-section { |
| | | background: #f5f7fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | .info-section { |
| | | background: #f5f7fa; |
| | | padding: 20px; |
| | | border-radius: 8px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | } |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 20px; |
| | | padding-bottom: 10px; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | } |
| | | |
| | | .info-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | min-height: 32px; |
| | | } |
| | | .info-item { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | min-height: 32px; |
| | | } |
| | | |
| | | .info-label { |
| | | min-width: 140px; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | .info-label { |
| | | min-width: 140px; |
| | | color: #606266; |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .info-value { |
| | | flex: 1; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | .info-value { |
| | | flex: 1; |
| | | color: #303133; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">å§åï¼</span> |
| | | <el-input v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | <el-input |
| | | v-model="searchForm.staffName" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥å§åæç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | /> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px" |
| | | >æç´¢</el-button |
| | | > |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" |
| | | @click="openForm('add')">æ°å¢ç¦»è</el-button> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢ç¦»è</el-button> |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <!-- <el-button type="danger" plain @click="handleDelete">å é¤</el-button> --> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total"></PIMTable> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | :total="page.total" |
| | | ></PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" |
| | | @close="handleQuery"></form-dia> |
| | | <form-dia ref="formDia" @close="handleQuery"></form-dia> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { onMounted, ref } from "vue"; |
| | | import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue"; |
| | | import { |
| | | findStaffLeaveListPage, |
| | | batchDeleteStaffLeaves, |
| | | } from "@/api/personnelManagement/staffLeave.js"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import {onMounted, ref} from "vue"; |
| | | import FormDia from "@/views/personnelManagement/dimission/components/formDia.vue"; |
| | | import {findStaffLeaveListPage, batchDeleteStaffLeaves} from "@/api/personnelManagement/staffLeave.js"; |
| | | import {ElMessageBox} from "element-plus"; |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | const data = reactive({ |
| | | searchForm: { |
| | | staffName: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: (params) => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "ç¶æ", |
| | | prop: "staffState", |
| | | dataType: "tag", |
| | | formatData: params => { |
| | | if (params == 0) { |
| | | return "离è"; |
| | | } else if (params == 1) { |
| | | return "å¨è"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: params => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | formatType: (params) => { |
| | | if (params == 0) { |
| | | return "danger"; |
| | | } else if (params == 1) { |
| | | return "primary"; |
| | | } else { |
| | | return null; |
| | | } |
| | | }, |
| | | { |
| | | label: "ç¦»èæ¥æ", |
| | | prop: "leaveDate", |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "é¨é¨", |
| | | prop: "deptName", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postName", |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width: 100, |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | label: "ç¦»èæ¥æ", |
| | | prop: "leaveDate", |
| | | }, |
| | | { |
| | | label: "åå·¥ç¼å·", |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | | { |
| | | label: "æ§å«", |
| | | prop: "sex", |
| | | }, |
| | | { |
| | | label: "æ·ç±ä½å", |
| | | prop: "nativePlace", |
| | | }, |
| | | { |
| | | label: "é¨é¨", |
| | | prop: "deptName", |
| | | }, |
| | | { |
| | | label: "å²ä½", |
| | | prop: "postName", |
| | | }, |
| | | { |
| | | label: "ç°ä½å", |
| | | prop: "adress", |
| | | width:200 |
| | | }, |
| | | { |
| | | label: "第ä¸å¦å", |
| | | prop: "firstStudy", |
| | | }, |
| | | { |
| | | label: "ä¸ä¸", |
| | | prop: "profession", |
| | | width:100 |
| | | }, |
| | | { |
| | | label: "å¹´é¾", |
| | | prop: "age", |
| | | }, |
| | | { |
| | | label: "èç³»çµè¯", |
| | | prop: "phone", |
| | | width:150 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120 |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width:150 |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: 'right', |
| | | operation: [ |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | openForm("edit", row); |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref(); |
| | | const { proxy } = getCurrentInstance(); |
| | | }, |
| | | ], |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }); |
| | | const formDia = ref() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | findStaffLeaveListPage({ ...page, ...searchForm.value }) |
| | | .then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(err => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = selection => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row); |
| | | }); |
| | | }; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | findStaffLeaveListPage({...page, ...searchForm.value}).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total; |
| | | }).catch(err => { |
| | | tableLoading.value = false; |
| | | }) |
| | | }; |
| | | // è¡¨æ ¼éæ©æ°æ® |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map(item => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = (type, row) => { |
| | | nextTick(() => { |
| | | formDia.value?.openDialog(type, row) |
| | | }) |
| | | }; |
| | | |
| | | // å é¤ |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length > 0) { |
| | | ids = selectedRows.value.map((item) => item.id); |
| | | } else { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | batchDeleteStaffLeaves(ids).then(res => { |
| | | batchDeleteStaffLeaves(ids).then((res) => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | }; |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å¯¼åºï¼æ¯å¦ç¡®è®¤å¯¼åºï¼", "导åº", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | proxy.download("/staff/staffLeave/export", {}, "人å离è.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已忶"); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped></style> |
| | |
| | | prop: "staffNo", |
| | | }, |
| | | { |
| | | label: "é¨é¨", |
| | | prop: "deptName", |
| | | }, |
| | | { |
| | | label: "å§å", |
| | | prop: "staffName", |
| | | }, |
| | |
| | | <script setup> |
| | | import { ref, computed, reactive, toRefs, getCurrentInstance, watch } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import Cookies from "js-cookie"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { staffSalaryMainUpdate } from "@/api/personnelManagement/staffSalaryMain.js"; |
| | | |
| | |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | |
| | | return; |
| | | } |
| | | |
| | | const username = userStore.name || ""; |
| | | const currentUserId = Number(userStore.id); |
| | | const assignedAuditUserId = Number(row?.auditUserId); |
| | | |
| | | // æééªè¯ï¼åªææå®çå®¡æ ¸äººæè½è¿è¡å®¡æ ¸ |
| | | if (!currentUserId || currentUserId !== assignedAuditUserId) { |
| | | ElMessage.warning("æ¨ä¸æ¯æå®çå®¡æ ¸äººï¼æ æ³è¿è¡å®¡æ ¸æä½"); |
| | | return; |
| | | } |
| | | const username = Cookies.get("username") || ""; |
| | | const userIdRaw = Cookies.get("userId"); |
| | | const auditUserId = userIdRaw ? Number(userIdRaw) : undefined; |
| | | |
| | | // æå»ºå®¡æ ¸æ°æ® |
| | | const submitData = { |
| | | id: row.id, |
| | | status: Number(auditResult.value) === 2 ? 2 : 4, // 2=ä¸éè¿ 4=éè¿(å¾
åæ¾) |
| | | auditUserId: currentUserId, |
| | | auditUserId, |
| | | auditUserName: username, |
| | | }; |
| | | loading.value = true; |
| | |
| | | <template> |
| | | <FormDialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å»ºå·¥èµè¡¨' : 'ç¼è¾å·¥èµè¡¨'" |
| | | width="90%" |
| | | @close="closeDia"> |
| | | <FormDialog |
| | | v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å»ºå·¥èµè¡¨' : 'ç¼è¾å·¥èµè¡¨'" |
| | | width="90%" |
| | | @close="closeDia" |
| | | > |
| | | <template #footer> |
| | | <el-button type="info" |
| | | @click="saveDraft">ä¿åè稿</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认æäº¤</el-button> |
| | | <el-button type="info" @click="saveDraft">ä¿åè稿</el-button> |
| | | <el-button type="primary" @click="submitForm">确认æäº¤</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </template> |
| | | <div class="form-dia-body"> |
| | | <!-- åºç¡èµæ --> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title"><span class="card-title-line">|</span> åºç¡èµæ</span> |
| | | <el-icon class="card-collapse"> |
| | | <ArrowUp /> |
| | | </el-icon> |
| | | <el-icon class="card-collapse"><ArrowUp /></el-icon> |
| | | </template> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-position="top"> |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-position="top"> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="å·¥èµä¸»é¢" |
| | | prop="salaryTitle"> |
| | | <el-input v-model="form.salaryTitle" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | maxlength="20" |
| | | show-word-limit /> |
| | | <el-form-item label="å·¥èµä¸»é¢" prop="salaryTitle"> |
| | | <el-input |
| | | v-model="form.salaryTitle" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | maxlength="20" |
| | | show-word-limit |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="éæ©å·¥èµæä»½" |
| | | prop="salaryMonth"> |
| | | <el-date-picker v-model="form.salaryMonth" |
| | | type="month" |
| | | value-format="YYYY-MM" |
| | | format="YYYY-MM" |
| | | placeholder="è¯·éæ©å·¥èµæä»½" |
| | | style="width: 100%" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="éæ©é¨é¨" |
| | | prop="deptIds"> |
| | | <el-select v-model="form.deptIds" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | multiple |
| | | collapse-tags-tooltip |
| | | style="width: 100%"> |
| | | <el-option v-for="item in deptOptions" |
| | | :key="item.deptId" |
| | | :label="item.deptName" |
| | | :value="item.deptId" |
| | | :disabled="isDeptDisabled(item.deptId)" /> |
| | | <el-form-item label="éæ©é¨é¨" prop="deptIds"> |
| | | <el-select |
| | | v-model="form.deptIds" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | multiple |
| | | collapse-tags-tooltip |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in deptOptions" |
| | | :key="item.deptId" |
| | | :label="item.deptName" |
| | | :value="item.deptId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | placeholder="请è¾å
¥" |
| | | clearable /> |
| | | <el-form-item label="éæ©å·¥èµæä»½" prop="salaryMonth"> |
| | | <el-date-picker |
| | | v-model="form.salaryMonth" |
| | | type="month" |
| | | value-format="YYYY-MM" |
| | | format="YYYY-MM" |
| | | placeholder="è¯·éæ©å·¥èµæä»½" |
| | | style="width: 100%" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="24"> |
| | | <el-col :span="6"> |
| | | <el-form-item label="æ¯ä»é¶è¡" |
| | | prop="payBank"> |
| | | <el-select v-model="form.payBank" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="b in bankOptions" |
| | | :key="b" |
| | | :label="b" |
| | | :value="b" /> |
| | | <el-form-item label="æ¯ä»é¶è¡" prop="payBank"> |
| | | <el-select |
| | | v-model="form.payBank" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="b in bankOptions" |
| | | :key="b" |
| | | :label="b" |
| | | :value="b" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-form-item label="å®¡æ ¸äºº" |
| | | prop="auditUserId"> |
| | | <el-select v-model="form.auditUserId" |
| | | placeholder="è¯·éæ©å®¡æ ¸äºº" |
| | | clearable |
| | | filterable |
| | | style="width: 100%"> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" /> |
| | | <el-form-item label="å®¡æ ¸äºº" prop="auditUserId"> |
| | | <el-select |
| | | v-model="form.auditUserId" |
| | | placeholder="è¯·éæ©å®¡æ ¸äºº" |
| | | clearable |
| | | filterable |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in userList" |
| | | :key="item.userId" |
| | | :label="item.nickName" |
| | | :value="item.userId" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </el-card> |
| | | |
| | | <!-- æä½æé® --> |
| | | <div class="toolbar"> |
| | | <el-button type="primary" |
| | | @click="handleGenerate">çæå·¥èµè¡¨</el-button> |
| | | <el-button type="primary" @click="handleGenerate">çæå·¥èµè¡¨</el-button> |
| | | <el-button @click="handleClear">æ¸
空</el-button> |
| | | <el-button @click="handleBatchDelete">å é¤</el-button> |
| | | <el-button @click="handleTaxForm">个ç¨è¡¨</el-button> |
| | | </div> |
| | | |
| | | <!-- å工工èµè¯¦æ
è¡¨æ ¼ --> |
| | | <div class="employee-table-wrap"> |
| | | <el-table ref="employeeTableRef" |
| | | :data="employeeList" |
| | | border |
| | | max-height="400" |
| | | @selection-change="onEmployeeSelectionChange"> |
| | | <el-table-column type="selection" |
| | | width="55" |
| | | align="center" /> |
| | | <el-table-column label="åå·¥å§å" |
| | | prop="staffName" |
| | | minWidth="100" /> |
| | | <el-table-column label="é¨é¨" |
| | | prop="deptName" |
| | | minWidth="100" /> |
| | | <el-table-column label="åºæ¬å·¥èµ" |
| | | minWidth="110"> |
| | | <el-table |
| | | ref="employeeTableRef" |
| | | :data="employeeList" |
| | | border |
| | | max-height="400" |
| | | @selection-change="onEmployeeSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column label="åå·¥å§å" prop="staffName" minWidth="100" /> |
| | | <el-table-column label="é¨é¨" prop="deptName" minWidth="100" /> |
| | | <el-table-column label="åºæ¬å·¥èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.basicSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="handleSalaryInput(row)" /> |
| | | <el-input |
| | | v-model.number="row.basicSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.basicSalary = parseNum(row.basicSalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计件工èµ" |
| | | minWidth="110"> |
| | | <el-table-column label="计件工èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.pieceSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.pieceSalary = parseNum(row.pieceSalary)" /> |
| | | <el-input |
| | | v-model.number="row.pieceSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.pieceSalary = parseNum(row.pieceSalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="计æ¶å·¥èµ" |
| | | minWidth="110"> |
| | | <el-table-column label="计æ¶å·¥èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.hourlySalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.hourlySalary = parseNum(row.hourlySalary)" /> |
| | | <el-input |
| | | v-model.number="row.hourlySalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.hourlySalary = parseNum(row.hourlySalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¶ä»æ¶å
¥" |
| | | minWidth="110"> |
| | | <el-table-column label="å
¶ä»æ¶å
¥" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.otherIncome" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="handleSalaryInput(row)" /> |
| | | <el-input |
| | | v-model.number="row.otherIncome" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.otherIncome = parseNum(row.otherIncome)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="社ä¿ä¸ªäºº" |
| | | minWidth="110"> |
| | | <el-table-column label="社ä¿ä¸ªäºº" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.socialPersonal" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.socialPersonal = parseNum(row.socialPersonal)" /> |
| | | <el-input |
| | | v-model.number="row.socialPersonal" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.socialPersonal = parseNum(row.socialPersonal)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¬ç§¯é个人" |
| | | minWidth="120"> |
| | | <el-table-column label="å
¬ç§¯é个人" minWidth="120"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.fundPersonal" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.fundPersonal = parseNum(row.fundPersonal)" /> |
| | | <el-input |
| | | v-model.number="row.fundPersonal" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.fundPersonal = parseNum(row.fundPersonal)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å
¶ä»æ¯åº" |
| | | minWidth="110"> |
| | | <el-table-column label="å
¶ä»æ¯åº" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.otherDeduct" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.otherDeduct = parseNum(row.otherDeduct)" /> |
| | | <el-input |
| | | v-model.number="row.otherDeduct" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.otherDeduct = parseNum(row.otherDeduct)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="社ä¿è¡¥ç¼´" |
| | | minWidth="120"> |
| | | <el-table-column label="å·¥èµä¸ªç¨" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.socialSupplementAmount" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="handleSalaryInput(row)" /> |
| | | <el-input |
| | | v-model.number="row.salaryTax" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.salaryTax = parseNum(row.salaryTax)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å·¥èµä¸ªç¨" |
| | | minWidth="110"> |
| | | <el-table-column label="åºåå·¥èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.salaryTax" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.salaryTax = parseNum(row.salaryTax)" /> |
| | | <el-input |
| | | v-model.number="row.grossSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.grossSalary = parseNum(row.grossSalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åºåå·¥èµ" |
| | | minWidth="110"> |
| | | <el-table-column label="åºæ£å·¥èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.grossSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.grossSalary = parseNum(row.grossSalary)" /> |
| | | <el-input |
| | | v-model.number="row.deductSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.deductSalary = parseNum(row.deductSalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="åºæ£å·¥èµ" |
| | | minWidth="110"> |
| | | <el-table-column label="å®åå·¥èµ" minWidth="110"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.deductSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.deductSalary = parseNum(row.deductSalary)" /> |
| | | <el-input |
| | | v-model.number="row.netSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | @input="row.netSalary = parseNum(row.netSalary)" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å®åå·¥èµ" |
| | | minWidth="110"> |
| | | <el-table-column label="夿³¨" minWidth="120"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model.number="row.netSalary" |
| | | type="number" |
| | | placeholder="0" |
| | | size="small" |
| | | disabled |
| | | @input="row.netSalary = parseNum(row.netSalary)" /> |
| | | <el-input |
| | | v-model="row.remark" |
| | | placeholder="请è¾å
¥" |
| | | size="small" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" |
| | | minWidth="120"> |
| | | <el-table-column label="æä½" width="80" align="center" fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.remark" |
| | | placeholder="请è¾å
¥" |
| | | size="small" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" |
| | | width="80" |
| | | align="center" |
| | | fixed="right"> |
| | | <template #default="{ row }"> |
| | | <el-button type="primary" |
| | | link |
| | | @click="removeEmployee(row)">å é¤</el-button> |
| | | <el-button type="primary" link @click="removeEmployee(row)">å é¤</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div v-if="!employeeList.length" |
| | | class="table-empty">ææ æ°æ®</div> |
| | | <div v-else |
| | | class="salary-total"> |
| | | <div v-if="!employeeList.length" class="table-empty">ææ æ°æ®</div> |
| | | <div v-else class="salary-total"> |
| | | <span class="total-label">å·¥èµæ»é¢ï¼</span> |
| | | <span class="total-value">Â¥ {{ totalSalary.toFixed(2) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | |
| | | |
| | | <!-- æ°å¢äººåå¼¹çª --> |
| | | <el-dialog v-model="addPersonVisible" |
| | | title="æ°å¢äººå" |
| | | width="400px" |
| | | append-to-body |
| | | @close="addPersonClose"> |
| | | <el-dialog |
| | | v-model="addPersonVisible" |
| | | title="æ°å¢äººå" |
| | | width="400px" |
| | | append-to-body |
| | | @close="addPersonClose" |
| | | > |
| | | <div class="add-person-tree"> |
| | | <el-tree ref="personTreeRef" |
| | | :data="deptStaffTree" |
| | | show-checkbox |
| | | node-key="id" |
| | | :props="{ label: 'label', children: 'children' }" |
| | | default-expand-all /> |
| | | <el-tree |
| | | ref="personTreeRef" |
| | | :data="deptStaffTree" |
| | | show-checkbox |
| | | node-key="id" |
| | | :props="{ label: 'label', children: 'children' }" |
| | | default-expand-all |
| | | /> |
| | | </div> |
| | | <template #footer> |
| | | <el-button @click="addPersonVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" |
| | | @click="confirmAddPerson">ç¡®å®</el-button> |
| | | <el-button type="primary" @click="confirmAddPerson">ç¡®å®</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <!-- 个ç¨è¡¨å¼¹çª --> |
| | | <el-dialog v-model="taxDialogVisible" |
| | | title="个ç¨è¡¨" |
| | | width="700px" |
| | | append-to-body> |
| | | <el-dialog |
| | | v-model="taxDialogVisible" |
| | | title="个ç¨è¡¨" |
| | | width="700px" |
| | | append-to-body |
| | | > |
| | | <div class="tax-desc">个人æå¾ç¨å
å¾é¢ï¼5000å
</div> |
| | | <el-table :data="taxTableData" |
| | | border |
| | | style="width: 100%;margin-bottom: 20px;"> |
| | | <el-table-column prop="level" |
| | | label="级æ°" |
| | | width="80" |
| | | align="center" /> |
| | | <el-table-column prop="range" |
| | | label="å
¨å¹´åºçº³ç¨æå¾é¢/å
" |
| | | min-width="220" /> |
| | | <el-table-column prop="rate" |
| | | label="ç¨ç(%)" |
| | | width="100" |
| | | align="center" /> |
| | | <el-table-column prop="quickDeduction" |
| | | label="éç®æ£é¤æ°/å
" |
| | | width="160" |
| | | align="center" /> |
| | | <el-table :data="taxTableData" border style="width: 100%;margin-bottom: 20px;"> |
| | | <el-table-column prop="level" label="级æ°" width="80" align="center" /> |
| | | <el-table-column |
| | | prop="range" |
| | | label="å
¨å¹´åºçº³ç¨æå¾é¢/å
" |
| | | min-width="220" |
| | | /> |
| | | <el-table-column |
| | | prop="rate" |
| | | label="ç¨ç(%)" |
| | | width="100" |
| | | align="center" |
| | | /> |
| | | <el-table-column |
| | | prop="quickDeduction" |
| | | label="éç®æ£é¤æ°/å
" |
| | | width="160" |
| | | align="center" |
| | | /> |
| | | </el-table> |
| | | </el-dialog> |
| | | </FormDialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | watch, |
| | | } from "vue"; |
| | | import { ArrowUp } from "@element-plus/icons-vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listDept } from "@/api/system/dept.js"; |
| | | import { staffOnJobList } from "@/api/personnelManagement/monthlyStatistics.js"; |
| | | import { ref, reactive, toRefs, computed, getCurrentInstance, nextTick } from "vue"; |
| | | import { ArrowUp } from "@element-plus/icons-vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listDept } from "@/api/system/dept.js"; |
| | | import { staffOnJobList } from "@/api/personnelManagement/monthlyStatistics.js"; |
| | | import { bankList } from "@/api/personnelManagement/bank.js"; |
| | | import { |
| | | staffSalaryMainAdd, |
| | | staffSalaryMainUpdate, |
| | | staffSalaryMainCalculateSalary, |
| | | staffSalaryMainCalculateByEmployeeId, |
| | | staffSalaryMainAvailableDepts, |
| | | } from "@/api/personnelManagement/staffSalaryMain.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | |
| | | const emit = defineEmits(["update:modelValue", "close"]); |
| | | const props = defineProps({ |
| | | modelValue: { type: Boolean, default: false }, |
| | | operationType: { type: String, default: "add" }, |
| | | row: { type: Object, default: () => ({}) }, |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["update:modelValue", "close"]); |
| | | const props = defineProps({ |
| | | modelValue: { type: Boolean, default: false }, |
| | | operationType: { type: String, default: "add" }, |
| | | row: { type: Object, default: () => ({}) }, |
| | | }); |
| | | |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | | set: val => emit("update:modelValue", val), |
| | | }); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const formRef = ref(null); |
| | | const employeeTableRef = ref(null); |
| | | const personTreeRef = ref(null); |
| | | const addPersonVisible = ref(false); |
| | | const taxDialogVisible = ref(false); |
| | | const deptOptions = ref([]); |
| | | const availableDeptIds = ref(new Set()); |
| | | const deptStaffTree = ref([]); |
| | | const employeeList = ref([]); |
| | | const selectedEmployees = ref([]); |
| | | const bankOptions = ref([]); |
| | | const userList = ref([]); |
| | | const recalcTimers = new Map(); |
| | | const recalcVersions = new Map(); |
| | | const taxTableData = ref([ |
| | | { level: 1, range: "ä¸è¶
è¿36000å
", rate: 3, quickDeduction: 0 }, |
| | | { level: 2, range: "è¶
è¿36000-144000å
", rate: 10, quickDeduction: 2520 }, |
| | | { level: 3, range: "è¶
è¿144000-300000å
", rate: 20, quickDeduction: 16920 }, |
| | | { level: 4, range: "è¶
è¿300000-420000å
", rate: 25, quickDeduction: 31920 }, |
| | | { level: 5, range: "è¶
è¿420000-660000å
", rate: 30, quickDeduction: 52920 }, |
| | | { level: 6, range: "è¶
è¿660000-960000å
", rate: 35, quickDeduction: 85920 }, |
| | | { level: 7, range: "è¶
è¿960000å
", rate: 45, quickDeduction: 181920 }, |
| | | ]); |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | | set: (val) => emit("update:modelValue", val), |
| | | }); |
| | | |
| | | function parseNum(v) { |
| | | if (v === "" || v == null) return 0; |
| | | const n = Number(v); |
| | | return isNaN(n) ? 0 : n; |
| | | } |
| | | const formRef = ref(null); |
| | | const employeeTableRef = ref(null); |
| | | const personTreeRef = ref(null); |
| | | const addPersonVisible = ref(false); |
| | | const taxDialogVisible = ref(false); |
| | | const deptOptions = ref([]); |
| | | const deptStaffTree = ref([]); |
| | | const employeeList = ref([]); |
| | | const selectedEmployees = ref([]); |
| | | const bankOptions = ref([]); |
| | | const userList = ref([]); |
| | | const taxTableData = ref([ |
| | | { level: 1, range: "ä¸è¶
è¿36000å
", rate: 3, quickDeduction: 0 }, |
| | | { level: 2, range: "è¶
è¿36000-144000å
", rate: 10, quickDeduction: 2520 }, |
| | | { level: 3, range: "è¶
è¿144000-300000å
", rate: 20, quickDeduction: 16920 }, |
| | | { level: 4, range: "è¶
è¿300000-420000å
", rate: 25, quickDeduction: 31920 }, |
| | | { level: 5, range: "è¶
è¿420000-660000å
", rate: 30, quickDeduction: 52920 }, |
| | | { level: 6, range: "è¶
è¿660000-960000å
", rate: 35, quickDeduction: 85920 }, |
| | | { level: 7, range: "è¶
è¿960000å
", rate: 45, quickDeduction: 181920 }, |
| | | ]); |
| | | |
| | | function parseNullableNum(v) { |
| | | if (v === "" || v == null) return null; |
| | | const n = Number(v); |
| | | return isNaN(n) ? null : n; |
| | | } |
| | | function parseNum(v) { |
| | | if (v === "" || v == null) return 0; |
| | | const n = Number(v); |
| | | return isNaN(n) ? 0 : n; |
| | | } |
| | | |
| | | const createEmployeeRow = () => ({ |
| | | staffOnJobId: undefined, |
| | | // åºç¡èµæè¡¨å |
| | | const data = reactive({ |
| | | form: { |
| | | id: undefined, |
| | | staffName: "", |
| | | postName: "", |
| | | deptName: "", |
| | | basicSalary: 0, |
| | | pieceSalary: 0, |
| | | hourlySalary: 0, |
| | | otherIncome: 0, |
| | | socialPersonal: 0, |
| | | fundPersonal: 0, |
| | | otherDeduct: 0, |
| | | socialSupplementAmount: null, |
| | | salaryTax: 0, |
| | | grossSalary: 0, |
| | | deductSalary: 0, |
| | | netSalary: 0, |
| | | salaryTitle: "", |
| | | deptIds: [], |
| | | salaryMonth: "", |
| | | remark: "", |
| | | payBank: "", |
| | | auditUserId: undefined, |
| | | }, |
| | | rules: { |
| | | salaryTitle: [{ required: true, message: "请è¾å
¥å·¥èµä¸»é¢", trigger: "blur" }], |
| | | deptIds: [{ required: true, message: "è¯·éæ©é¨é¨", trigger: "change" }], |
| | | salaryMonth: [{ required: true, message: "è¯·éæ©å·¥èµæä»½", trigger: "change" }], |
| | | auditUserId: [{ required: true, message: "è¯·éæ©å®¡æ ¸äºº", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // 计ç®å·¥èµæ»é¢ï¼ææåå·¥å®åå·¥èµä¹åï¼ |
| | | const totalSalary = computed(() => { |
| | | return employeeList.value.reduce((sum, e) => sum + parseNum(e.netSalary), 0); |
| | | }); |
| | | |
| | | // æ ¹æ®å®¡æ ¸äººIDè·åå®¡æ ¸äººåç§° |
| | | const auditUserName = computed(() => { |
| | | if (!form.value.auditUserId) return ""; |
| | | const user = userList.value.find(u => u.userId === form.value.auditUserId); |
| | | return user ? user.nickName : ""; |
| | | }); |
| | | |
| | | const loadBankOptions = () => { |
| | | return bankList().then((res) => { |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | bankOptions.value = list |
| | | .map((b) => (b?.bankName == null ? "" : String(b.bankName).trim())) |
| | | .filter((v) => v !== ""); |
| | | }); |
| | | }; |
| | | |
| | | const normalizeEmployeeRow = (source, fallback = {}) => ({ |
| | | ...createEmployeeRow(), |
| | | ...fallback, |
| | | ...source, |
| | | staffOnJobId: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id, |
| | | id: source?.staffOnJobId ?? source?.staffId ?? source?.id ?? fallback.staffOnJobId ?? fallback.id, |
| | | staffName: source?.staffName ?? fallback.staffName ?? "", |
| | | postName: source?.postName ?? fallback.postName ?? "", |
| | | deptName: source?.deptName ?? fallback.deptName ?? "", |
| | | basicSalary: parseNum(source?.basicSalary ?? fallback.basicSalary), |
| | | pieceSalary: parseNum(source?.pieceSalary ?? fallback.pieceSalary), |
| | | hourlySalary: parseNum(source?.hourlySalary ?? fallback.hourlySalary), |
| | | otherIncome: parseNum(source?.otherIncome ?? fallback.otherIncome), |
| | | socialPersonal: parseNum(source?.socialPersonal ?? fallback.socialPersonal), |
| | | fundPersonal: parseNum(source?.fundPersonal ?? fallback.fundPersonal), |
| | | otherDeduct: parseNum(source?.otherDeduct ?? fallback.otherDeduct), |
| | | socialSupplementAmount: |
| | | source?.socialSupplementAmount ?? fallback.socialSupplementAmount ?? null, |
| | | salaryTax: parseNum(source?.salaryTax ?? fallback.salaryTax), |
| | | grossSalary: parseNum(source?.grossSalary ?? fallback.grossSalary), |
| | | deductSalary: parseNum(source?.deductSalary ?? fallback.deductSalary), |
| | | netSalary: parseNum(source?.netSalary ?? fallback.netSalary), |
| | | remark: source?.remark ?? fallback.remark ?? "", |
| | | const loadUserList = () => { |
| | | return userListNoPageByTenantId().then((res) => { |
| | | userList.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | const applyBackendRow = (targetRow, sourceRow) => { |
| | | Object.assign(targetRow, normalizeEmployeeRow(sourceRow, targetRow)); |
| | | }; |
| | | // æå¹³åé¨é¨æ ä¾ä¸æä½¿ç¨ |
| | | function flattenDept(tree, list = []) { |
| | | if (!tree?.length) return list; |
| | | tree.forEach((node) => { |
| | | list.push({ deptId: node.deptId, deptName: node.deptName }); |
| | | if (node.children?.length) flattenDept(node.children, list); |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | const clearRowRecalcState = row => { |
| | | const key = String(row?.staffOnJobId ?? row?.id ?? ""); |
| | | if (!key) return; |
| | | clearTimeout(recalcTimers.get(key)); |
| | | recalcTimers.delete(key); |
| | | recalcVersions.delete(key); |
| | | }; |
| | | const loadDeptOptions = () => { |
| | | listDept().then((res) => { |
| | | const tree = res.data ?? []; |
| | | deptOptions.value = flattenDept(tree); |
| | | }); |
| | | }; |
| | | |
| | | const resetRecalcState = () => { |
| | | recalcTimers.forEach(timer => clearTimeout(timer)); |
| | | recalcTimers.clear(); |
| | | recalcVersions.clear(); |
| | | }; |
| | | |
| | | const requestEmployeeRecalculate = row => { |
| | | const key = String(row?.staffOnJobId ?? row?.id ?? ""); |
| | | if (!key || !form.value.salaryMonth) return; |
| | | const version = (recalcVersions.get(key) || 0) + 1; |
| | | recalcVersions.set(key, version); |
| | | clearTimeout(recalcTimers.get(key)); |
| | | const payload = { |
| | | staffOnJobId: row.staffOnJobId ?? row.id, |
| | | salaryMonth: form.value.salaryMonth, |
| | | staffName: row.staffName ?? "", |
| | | postName: row.postName ?? "", |
| | | deptName: row.deptName ?? "", |
| | | basicSalary: parseNum(row.basicSalary), |
| | | pieceSalary: parseNum(row.pieceSalary), |
| | | hourlySalary: parseNum(row.hourlySalary), |
| | | otherIncome: parseNum(row.otherIncome), |
| | | otherDeduct: parseNum(row.otherDeduct), |
| | | socialSupplementAmount: parseNullableNum(row.socialSupplementAmount), |
| | | remark: row.remark ?? "", |
| | | }; |
| | | recalcTimers.set( |
| | | key, |
| | | setTimeout(() => { |
| | | staffSalaryMainCalculateByEmployeeId(payload).then(res => { |
| | | if (recalcVersions.get(key) !== version) return; |
| | | applyBackendRow(row, res?.data || {}); |
| | | // æå»º é¨é¨-äººå æ ï¼ç¨äºæ°å¢äººåå¼¹çªï¼ |
| | | const loadDeptStaffTree = () => { |
| | | Promise.all([listDept(), staffOnJobList()]).then(([deptRes, staffRes]) => { |
| | | const tree = deptRes.data ?? []; |
| | | const staffList = staffRes.data ?? []; |
| | | const deptMap = new Map(); |
| | | function walk(nodes) { |
| | | nodes.forEach((node) => { |
| | | deptMap.set(node.deptId, { |
| | | id: "dept_" + node.deptId, |
| | | deptId: node.deptId, |
| | | label: node.deptName, |
| | | type: "dept", |
| | | children: [], |
| | | }); |
| | | }, 250) |
| | | if (node.children?.length) walk(node.children); |
| | | }); |
| | | } |
| | | walk(tree); |
| | | staffList.forEach((s) => { |
| | | const deptId = s.deptId ?? s.dept_id; |
| | | const node = deptMap.get(deptId); |
| | | if (node) { |
| | | node.children.push({ |
| | | id: s.id ?? s.staffId, |
| | | staffId: s.id ?? s.staffId, |
| | | label: s.staffName ?? s.name, |
| | | type: "staff", |
| | | ...s, |
| | | }); |
| | | } |
| | | }); |
| | | deptStaffTree.value = Array.from(deptMap.values()).filter( |
| | | (n) => n.children && n.children.length > 0 |
| | | ); |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const handleSalaryInput = row => { |
| | | row.basicSalary = parseNum(row.basicSalary); |
| | | row.otherIncome = parseNum(row.otherIncome); |
| | | requestEmployeeRecalculate(row); |
| | | }; |
| | | |
| | | // åºç¡èµæè¡¨å |
| | | const data = reactive({ |
| | | form: { |
| | | const openDialog = (type, row) => { |
| | | nextTick(() => { |
| | | loadDeptOptions(); |
| | | loadBankOptions(); |
| | | loadUserList(); |
| | | employeeList.value = []; |
| | | Object.assign(form.value, { |
| | | id: undefined, |
| | | salaryTitle: "", |
| | | deptIds: [], |
| | |
| | | remark: "", |
| | | payBank: "", |
| | | auditUserId: undefined, |
| | | }, |
| | | rules: { |
| | | salaryTitle: [ |
| | | { required: true, message: "请è¾å
¥å·¥èµä¸»é¢", trigger: "blur" }, |
| | | ], |
| | | deptIds: [{ required: true, message: "è¯·éæ©é¨é¨", trigger: "change" }], |
| | | salaryMonth: [ |
| | | { required: true, message: "è¯·éæ©å·¥èµæä»½", trigger: "change" }, |
| | | ], |
| | | auditUserId: [ |
| | | { required: true, message: "è¯·éæ©å®¡æ ¸äºº", trigger: "change" }, |
| | | ], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | // 计ç®å·¥èµæ»é¢ï¼ææåå·¥å®åå·¥èµä¹åï¼ |
| | | const totalSalary = computed(() => { |
| | | return employeeList.value.reduce((sum, e) => sum + parseNum(e.netSalary), 0); |
| | | }); |
| | | |
| | | // æ ¹æ®å®¡æ ¸äººIDè·åå®¡æ ¸äººåç§° |
| | | const auditUserName = computed(() => { |
| | | if (!form.value.auditUserId) return ""; |
| | | const user = userList.value.find(u => u.userId === form.value.auditUserId); |
| | | return user ? user.nickName : ""; |
| | | }); |
| | | |
| | | const loadBankOptions = () => { |
| | | return bankList().then(res => { |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | bankOptions.value = list |
| | | .map(b => (b?.bankName == null ? "" : String(b.bankName).trim())) |
| | | .filter(v => v !== ""); |
| | | }); |
| | | }; |
| | | |
| | | const loadUserList = () => { |
| | | return userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data || []; |
| | | }); |
| | | }; |
| | | |
| | | // æå¹³åé¨é¨æ ä¾ä¸æä½¿ç¨ |
| | | function flattenDept(tree, list = []) { |
| | | if (!tree?.length) return list; |
| | | tree.forEach(node => { |
| | | list.push({ deptId: node.deptId, deptName: node.deptName }); |
| | | if (node.children?.length) flattenDept(node.children, list); |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | // å è½½ææé¨é¨ |
| | | const loadAllDeptOptions = () => { |
| | | listDept().then(res => { |
| | | const tree = res.data ?? []; |
| | | deptOptions.value = flattenDept(tree); |
| | | }); |
| | | }; |
| | | |
| | | // å è½½å¯ç¨é¨é¨å表ï¼ç¨äºå¤æé¨é¨æ¯å¦å¯éï¼ |
| | | const loadAvailableDepts = (salaryMonth) => { |
| | | if (!salaryMonth) { |
| | | availableDeptIds.value = new Set(); |
| | | return; |
| | | } |
| | | staffSalaryMainAvailableDepts(salaryMonth).then(res => { |
| | | const availableDepts = Array.isArray(res.data) ? res.data : []; |
| | | availableDeptIds.value = new Set(availableDepts.map(d => d.deptId)); |
| | | }); |
| | | }; |
| | | |
| | | // 夿é¨é¨æ¯å¦ç¦ç¨ |
| | | const isDeptDisabled = (deptId) => { |
| | | // å¦ææ²¡æéæ©æä»½ï¼ææé¨é¨é½å¯é |
| | | if (!form.value.salaryMonth) return false; |
| | | // 妿已鿩æä»½ï¼åªæå¨å¯ç¨é¨é¨å表ä¸çé¨é¨æå¯é |
| | | return !availableDeptIds.value.has(deptId); |
| | | }; |
| | | |
| | | // æå»º é¨é¨-äººå æ ï¼ç¨äºæ°å¢äººåå¼¹çªï¼ |
| | | const loadDeptStaffTree = () => { |
| | | Promise.all([listDept(), staffOnJobList()]).then(([deptRes, staffRes]) => { |
| | | const tree = deptRes.data ?? []; |
| | | const staffList = staffRes.data ?? []; |
| | | const deptMap = new Map(); |
| | | function walk(nodes) { |
| | | nodes.forEach(node => { |
| | | deptMap.set(node.deptId, { |
| | | id: "dept_" + node.deptId, |
| | | deptId: node.deptId, |
| | | label: node.deptName, |
| | | type: "dept", |
| | | children: [], |
| | | }); |
| | | if (node.children?.length) walk(node.children); |
| | | }); |
| | | // ç¼è¾ï¼å表页已è¿åä¸»è¡¨åæ®µï¼è¿éåªååæ¾ï¼æç»ç±âçæå·¥èµè¡¨/计ç®å·¥èµâå¾å°ï¼ |
| | | if (type === "edit" && row?.id) { |
| | | form.value.id = row.id; |
| | | form.value.salaryTitle = row.salaryTitle ?? ""; |
| | | // deptIds å端æ¯å符串ï¼å¤ä¸ªç¨éå·åéï¼ï¼å½å表å仿¯åé deptId |
| | | form.value.deptIds = row.deptIds |
| | | ? String(row.deptIds).split(",").map((id) => Number(id.trim())).filter(Boolean) |
| | | : []; |
| | | form.value.salaryMonth = row.salaryMonth ?? ""; |
| | | form.value.remark = row.remark ?? ""; |
| | | form.value.payBank = row.payBank ?? ""; |
| | | form.value.auditUserId = row.auditUserId ?? undefined; |
| | | |
| | | // 妿æåå·¥æç»æ°æ®ï¼ç´æ¥åæ¾ |
| | | if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) { |
| | | employeeList.value = row.staffSalaryDetailList.map((e) => ({ |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | id: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName ?? "", |
| | | postName: e.postName ?? "", |
| | | deptName: e.deptName ?? "", |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })); |
| | | } |
| | | walk(tree); |
| | | staffList.forEach(s => { |
| | | const deptId = s.deptId ?? s.dept_id; |
| | | const node = deptMap.get(deptId); |
| | | if (node) { |
| | | node.children.push({ |
| | | id: s.id ?? s.staffId, |
| | | staffId: s.id ?? s.staffId, |
| | | label: s.staffName ?? s.name, |
| | | type: "staff", |
| | | ...s, |
| | | }); |
| | | } |
| | | }); |
| | | deptStaffTree.value = Array.from(deptMap.values()).filter( |
| | | n => n.children && n.children.length > 0 |
| | | ); |
| | | }); |
| | | }; |
| | | |
| | | const openDialog = (type, row) => { |
| | | nextTick(() => { |
| | | resetRecalcState(); |
| | | loadBankOptions(); |
| | | loadUserList(); |
| | | loadAllDeptOptions(); |
| | | employeeList.value = []; |
| | | Object.assign(form.value, { |
| | | id: undefined, |
| | | salaryTitle: "", |
| | | deptIds: [], |
| | | salaryMonth: "", |
| | | remark: "", |
| | | payBank: "", |
| | | auditUserId: undefined, |
| | | }); |
| | | // ç¼è¾ï¼å表页已è¿åä¸»è¡¨åæ®µï¼è¿éåªååæ¾ï¼æç»ç±âçæå·¥èµè¡¨/计ç®å·¥èµâå¾å°ï¼ |
| | | if (type === "edit" && row?.id) { |
| | | form.value.id = row.id; |
| | | form.value.salaryTitle = row.salaryTitle ?? ""; |
| | | // deptIds å端æ¯å符串ï¼å¤ä¸ªç¨éå·åéï¼ï¼å½å表å仿¯åé deptId |
| | | form.value.deptIds = row.deptIds |
| | | ? String(row.deptIds) |
| | | .split(",") |
| | | .map(id => Number(id.trim())) |
| | | .filter(Boolean) |
| | | : []; |
| | | form.value.salaryMonth = row.salaryMonth ?? ""; |
| | | form.value.remark = row.remark ?? ""; |
| | | form.value.payBank = row.payBank ?? ""; |
| | | form.value.auditUserId = row.auditUserId ?? undefined; |
| | | |
| | | // 妿æåå·¥æç»æ°æ®ï¼ç´æ¥åæ¾ |
| | | if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) { |
| | | employeeList.value = row.staffSalaryDetailList.map(e => normalizeEmployeeRow(e)); |
| | | } |
| | | } |
| | | // ç¼è¾ç¶æä¸ææä»½æ¶ï¼å è½½å¯ç¨é¨é¨å表 |
| | | if (type === "edit" && form.value.salaryMonth) { |
| | | loadAvailableDepts(form.value.salaryMonth); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // çå¬å·¥èµæä»½ååï¼æ¸
空é¨é¨éæ©å¹¶å è½½å¯ç¨é¨é¨å表 |
| | | watch(() => form.value.salaryMonth, (newMonth, oldMonth) => { |
| | | // 忢æä»½æ¶æ¸
ç©ºå·²éæ©çé¨é¨ |
| | | if (newMonth !== oldMonth) { |
| | | form.value.deptIds = []; |
| | | } |
| | | loadAvailableDepts(newMonth); |
| | | }); |
| | | }; |
| | | |
| | | const openAddPerson = () => { |
| | | loadDeptStaffTree(); |
| | | addPersonVisible.value = true; |
| | | nextTick(() => { |
| | | personTreeRef.value?.setCheckedKeys([]); |
| | | }); |
| | | }; |
| | | const openAddPerson = () => { |
| | | loadDeptStaffTree(); |
| | | addPersonVisible.value = true; |
| | | nextTick(() => { |
| | | personTreeRef.value?.setCheckedKeys([]); |
| | | }); |
| | | }; |
| | | |
| | | const addPersonClose = () => {}; |
| | | const addPersonClose = () => {}; |
| | | |
| | | const confirmAddPerson = () => { |
| | | const tree = personTreeRef.value; |
| | | if (!tree) { |
| | | addPersonVisible.value = false; |
| | | return; |
| | | } |
| | | const checked = tree.getCheckedNodes(); |
| | | const staffNodes = checked.filter(n => n.type === "staff"); |
| | | const existIds = new Set(employeeList.value.map(e => e.staffId || e.id)); |
| | | staffNodes.forEach(node => { |
| | | const id = node.staffId ?? node.id; |
| | | if (existIds.has(id)) return; |
| | | existIds.add(id); |
| | | const newRow = normalizeEmployeeRow({ |
| | | staffOnJobId: id, |
| | | id, |
| | | staffName: node.label, |
| | | postName: node.postName ?? node.post ?? "", |
| | | deptName: node.deptName ?? "", |
| | | remark: "", |
| | | socialSupplementAmount: null, |
| | | }); |
| | | employeeList.value.push(newRow); |
| | | requestEmployeeRecalculate(newRow); |
| | | }); |
| | | const confirmAddPerson = () => { |
| | | const tree = personTreeRef.value; |
| | | if (!tree) { |
| | | addPersonVisible.value = false; |
| | | }; |
| | | return; |
| | | } |
| | | const checked = tree.getCheckedNodes(); |
| | | const staffNodes = checked.filter((n) => n.type === "staff"); |
| | | const existIds = new Set(employeeList.value.map((e) => e.staffId || e.id)); |
| | | staffNodes.forEach((node) => { |
| | | const id = node.staffId ?? node.id; |
| | | if (existIds.has(id)) return; |
| | | existIds.add(id); |
| | | employeeList.value.push({ |
| | | staffOnJobId: id, |
| | | id, |
| | | staffName: node.label, |
| | | postName: node.postName ?? node.post ?? "", |
| | | deptName: node.deptName ?? "", |
| | | basicSalary: 0, |
| | | pieceSalary: 0, |
| | | hourlySalary: 0, |
| | | otherIncome: 0, |
| | | socialPersonal: 0, |
| | | fundPersonal: 0, |
| | | otherDeduct: 0, |
| | | salaryTax: 0, |
| | | grossSalary: 0, |
| | | deductSalary: 0, |
| | | netSalary: 0, |
| | | remark: "", |
| | | }); |
| | | }); |
| | | addPersonVisible.value = false; |
| | | }; |
| | | |
| | | const removeEmployee = row => { |
| | | clearRowRecalcState(row); |
| | | employeeList.value = employeeList.value.filter( |
| | | e => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id) |
| | | ); |
| | | }; |
| | | const removeEmployee = (row) => { |
| | | employeeList.value = employeeList.value.filter( |
| | | (e) => (e.staffOnJobId || e.id) !== (row.staffOnJobId || row.id) |
| | | ); |
| | | }; |
| | | |
| | | const onEmployeeSelectionChange = selection => { |
| | | selectedEmployees.value = selection; |
| | | }; |
| | | const onEmployeeSelectionChange = (selection) => { |
| | | selectedEmployees.value = selection; |
| | | }; |
| | | |
| | | const handleBatchDelete = () => { |
| | | if (!selectedEmployees.value?.length) { |
| | | proxy.$modal.msgWarning("请å
å¾éè¦å é¤çåå·¥"); |
| | | const handleBatchDelete = () => { |
| | | if (!selectedEmployees.value?.length) { |
| | | proxy.$modal.msgWarning("请å
å¾éè¦å é¤çåå·¥"); |
| | | return; |
| | | } |
| | | const ids = new Set(selectedEmployees.value.map((e) => e.staffOnJobId || e.id)); |
| | | employeeList.value = employeeList.value.filter( |
| | | (e) => !ids.has(e.staffOnJobId || e.id) |
| | | ); |
| | | }; |
| | | |
| | | const handleGenerate = () => { |
| | | if (!form.value.deptIds?.length) { |
| | | proxy.$modal.msgWarning("请å
éæ©é¨é¨"); |
| | | return; |
| | | } |
| | | if (!form.value.salaryMonth) { |
| | | proxy.$modal.msgWarning("请å
éæ©å·¥èµæä»½"); |
| | | return; |
| | | } |
| | | const payload = { |
| | | ids: form.value.deptIds, |
| | | date: form.value.salaryMonth, |
| | | }; |
| | | staffSalaryMainCalculateSalary(payload).then((res) => { |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | if (!list.length) { |
| | | proxy.$modal.msgWarning("æªè®¡ç®å°å·¥èµæ°æ®"); |
| | | return; |
| | | } |
| | | const ids = new Set(selectedEmployees.value.map(e => e.staffOnJobId || e.id)); |
| | | selectedEmployees.value.forEach(item => clearRowRecalcState(item)); |
| | | employeeList.value = employeeList.value.filter( |
| | | e => !ids.has(e.staffOnJobId || e.id) |
| | | ); |
| | | }; |
| | | employeeList.value = list.map((e) => ({ |
| | | ...e, |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName, |
| | | postName: e.postName, |
| | | deptName: e.deptName, |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })); |
| | | proxy.$modal.msgSuccess("çææå"); |
| | | }); |
| | | }; |
| | | |
| | | const handleGenerate = () => { |
| | | if (!form.value.deptIds?.length) { |
| | | proxy.$modal.msgWarning("请å
éæ©é¨é¨"); |
| | | return; |
| | | } |
| | | if (!form.value.salaryMonth) { |
| | | proxy.$modal.msgWarning("请å
éæ©å·¥èµæä»½"); |
| | | return; |
| | | } |
| | | const payload = { |
| | | ids: form.value.deptIds, |
| | | date: form.value.salaryMonth, |
| | | }; |
| | | staffSalaryMainCalculateSalary(payload).then(res => { |
| | | const list = Array.isArray(res?.data) ? res.data : []; |
| | | if (!list.length) { |
| | | proxy.$modal.msgWarning("æªè®¡ç®å°å·¥èµæ°æ®"); |
| | | return; |
| | | } |
| | | resetRecalcState(); |
| | | employeeList.value = list.map(e => normalizeEmployeeRow(e)); |
| | | proxy.$modal.msgSuccess("çææå"); |
| | | const handleClear = () => { |
| | | proxy.$modal.confirm("ç¡®å®æ¸
空å½ååå·¥å表åï¼").then(() => { |
| | | employeeList.value = []; |
| | | }).catch(() => {}); |
| | | }; |
| | | |
| | | const handleTaxForm = () => { |
| | | taxDialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | saveData(3); // 确认æäº¤ï¼ç¶æä¸º3ï¼å¾
å®¡æ ¸ï¼ |
| | | }); |
| | | }; |
| | | |
| | | const saveDraft = () => { |
| | | formRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | saveData(1); // ä¿åè稿ï¼ç¶æä¸º1ï¼èç¨¿ï¼ |
| | | }); |
| | | }; |
| | | |
| | | const saveData = (status) => { |
| | | const payload = { |
| | | id: form.value.id, |
| | | salaryTitle: form.value.salaryTitle, |
| | | deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "", |
| | | salaryMonth: form.value.salaryMonth, |
| | | remark: form.value.remark, |
| | | payBank: form.value.payBank, |
| | | auditUserId: form.value.auditUserId, |
| | | auditUserName: auditUserName.value, |
| | | totalSalary: totalSalary.value, |
| | | staffSalaryDetailList: employeeList.value.map((e) => ({ |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName, |
| | | postName: e.postName ?? "", |
| | | deptName: e.deptName ?? "", |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })), |
| | | }; |
| | | if (props.operationType === "add") { |
| | | staffSalaryMainAdd({ ...payload, status }).then(() => { |
| | | proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | }; |
| | | |
| | | const handleClear = () => { |
| | | proxy.$modal |
| | | .confirm("ç¡®å®æ¸
空å½ååå·¥å表åï¼") |
| | | .then(() => { |
| | | resetRecalcState(); |
| | | employeeList.value = []; |
| | | }) |
| | | .catch(() => {}); |
| | | }; |
| | | |
| | | const handleTaxForm = () => { |
| | | taxDialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return; |
| | | saveData(3); // 确认æäº¤ï¼ç¶æä¸º3ï¼å¾
å®¡æ ¸ï¼ |
| | | } else { |
| | | staffSalaryMainUpdate({ ...payload, status }).then(() => { |
| | | proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | const saveDraft = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return; |
| | | saveData(1); // ä¿åè稿ï¼ç¶æä¸º1ï¼èç¨¿ï¼ |
| | | }); |
| | | }; |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | const saveData = status => { |
| | | const payload = { |
| | | id: form.value.id, |
| | | salaryTitle: form.value.salaryTitle, |
| | | deptIds: form.value.deptIds?.length ? form.value.deptIds.join(",") : "", |
| | | salaryMonth: form.value.salaryMonth, |
| | | remark: form.value.remark, |
| | | payBank: form.value.payBank, |
| | | auditUserId: form.value.auditUserId, |
| | | auditUserName: auditUserName.value, |
| | | totalSalary: totalSalary.value, |
| | | staffSalaryDetailList: employeeList.value.map(e => ({ |
| | | staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id, |
| | | staffName: e.staffName, |
| | | postName: e.postName ?? "", |
| | | deptName: e.deptName ?? "", |
| | | basicSalary: parseNum(e.basicSalary), |
| | | pieceSalary: parseNum(e.pieceSalary), |
| | | hourlySalary: parseNum(e.hourlySalary), |
| | | otherIncome: parseNum(e.otherIncome), |
| | | socialPersonal: parseNum(e.socialPersonal), |
| | | fundPersonal: parseNum(e.fundPersonal), |
| | | otherDeduct: parseNum(e.otherDeduct), |
| | | socialSupplementAmount: parseNum(e.socialSupplementAmount), |
| | | salaryTax: parseNum(e.salaryTax), |
| | | grossSalary: parseNum(e.grossSalary), |
| | | deductSalary: parseNum(e.deductSalary), |
| | | netSalary: parseNum(e.netSalary), |
| | | remark: e.remark ?? "", |
| | | })), |
| | | }; |
| | | if (props.operationType === "add") { |
| | | staffSalaryMainAdd({ ...payload, status }).then(() => { |
| | | proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | staffSalaryMainUpdate({ ...payload, status }).then(() => { |
| | | proxy.$modal.msgSuccess(status === 1 ? "è稿ä¿åæå" : "æäº¤æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | resetRecalcState(); |
| | | dialogVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .form-dia-body { |
| | | padding: 0; |
| | | } |
| | | .card-title-line { |
| | | color: #f56c6c; |
| | | margin-right: 4px; |
| | | } |
| | | .form-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .form-card :deep(.el-card__header) { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px 16px; |
| | | } |
| | | .card-title { |
| | | font-weight: 500; |
| | | } |
| | | .card-collapse { |
| | | color: #999; |
| | | cursor: pointer; |
| | | } |
| | | .toolbar { |
| | | margin-bottom: 16px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | .employee-table-wrap { |
| | | position: relative; |
| | | min-height: 120px; |
| | | } |
| | | .table-empty { |
| | | text-align: center; |
| | | padding: 24px; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | .add-person-tree { |
| | | max-height: 360px; |
| | | overflow-y: auto; |
| | | padding: 8px 0; |
| | | } |
| | | .tax-desc { |
| | | margin-bottom: 12px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .salary-total { |
| | | margin-top: 16px; |
| | | padding: 12px 16px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | text-align: right; |
| | | font-size: 16px; |
| | | } |
| | | .salary-total .total-label { |
| | | color: #606266; |
| | | margin-right: 8px; |
| | | } |
| | | .salary-total .total-value { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | font-size: 18px; |
| | | } |
| | | .form-dia-body { |
| | | padding: 0; |
| | | } |
| | | .card-title-line { |
| | | color: #f56c6c; |
| | | margin-right: 4px; |
| | | } |
| | | .form-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .form-card :deep(.el-card__header) { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px 16px; |
| | | } |
| | | .card-title { |
| | | font-weight: 500; |
| | | } |
| | | .card-collapse { |
| | | color: #999; |
| | | cursor: pointer; |
| | | } |
| | | .toolbar { |
| | | margin-bottom: 16px; |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 10px; |
| | | } |
| | | .employee-table-wrap { |
| | | position: relative; |
| | | min-height: 120px; |
| | | } |
| | | .table-empty { |
| | | text-align: center; |
| | | padding: 24px; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | .add-person-tree { |
| | | max-height: 360px; |
| | | overflow-y: auto; |
| | | padding: 8px 0; |
| | | } |
| | | .tax-desc { |
| | | margin-bottom: 12px; |
| | | font-size: 14px; |
| | | color: #606266; |
| | | } |
| | | .dialog-footer { |
| | | text-align: right; |
| | | } |
| | | .salary-total { |
| | | margin-top: 16px; |
| | | padding: 12px 16px; |
| | | background-color: #f5f7fa; |
| | | border-radius: 4px; |
| | | text-align: right; |
| | | font-size: 16px; |
| | | } |
| | | .salary-total .total-label { |
| | | color: #606266; |
| | | margin-right: 8px; |
| | | } |
| | | .salary-total .total-value { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | font-size: 18px; |
| | | } |
| | | </style> |
| | |
| | | nextTick, |
| | | } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import Cookies from "js-cookie"; |
| | | import FormDia from "./components/formDia.vue"; |
| | | import BankSettingDia from "./components/bankSettingDia.vue"; |
| | | import AuditDia from "./components/auditDia.vue"; |
| | |
| | | { |
| | | name: "å®¡æ ¸", |
| | | type: "text", |
| | | disabled: (row) => { |
| | | const currentUserId = Number(userStore.id); |
| | | const auditUserId = Number(row?.auditUserId); |
| | | // ç¶æä¸æ¯å¾
å®¡æ ¸ æ å½åç¨æ·ä¸æ¯æå®çå®¡æ ¸äººï¼åç¦ç¨å®¡æ ¸æé® |
| | | return Number(row?.status) !== 3 || !currentUserId || currentUserId !== auditUserId; |
| | | }, |
| | | disabled: (row) => Number(row?.status) !== 3, |
| | | clickFun: (row) => openAudit(row), |
| | | }, |
| | | { |
| | |
| | | const operationType = ref("add"); |
| | | const currentRow = ref({}); |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | const bankSetting = ref({}); |
| | | const bankDialogVisible = ref(false); |
| | | const bankDiaRef = ref(null); |
| | |
| | | |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/staffSalaryMain/export", |
| | | { ...searchForm.value }, |
| | | "/compensationPerformance/export", |
| | | { ...searchForm.value, current: page.current, size: page.size }, |
| | | "å·¥èµè¡¨.xlsx" |
| | | ); |
| | | }; |
| | |
| | | .table_list { |
| | | margin-top: 20px; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | <template> |
| | | <div> |
| | | <FormDialog v-model="dialogFormVisible" |
| | | :operation-type="operationType" |
| | | :title="dialogTitle" |
| | | width="80%" |
| | | @close="closeDia" |
| | | @confirm="submitForm" |
| | | @cancel="closeDia"> |
| | | <el-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-position="top"> |
| | | <FormDialog |
| | | v-model="dialogFormVisible" |
| | | :operation-type="operationType" |
| | | :title="dialogTitle" |
| | | width="80%" |
| | | @close="closeDia" |
| | | @confirm="submitForm" |
| | | @cancel="closeDia" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-position="top"> |
| | | <el-row :gutter="24"> |
| | | <!-- 左侧ï¼éç¨äººå --> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éç¨äººåï¼" |
| | | prop="deptIds"> |
| | | <el-form-item label="éç¨äººåï¼" prop="deptIds"> |
| | | <div class="dept-checkbox-wrap"> |
| | | <el-checkbox-group v-model="form.deptIds" |
| | | :disabled="isDetail"> |
| | | <div v-for="dept in deptList" |
| | | :key="dept.deptId" |
| | | class="dept-checkbox-item"> |
| | | <el-checkbox-group |
| | | v-model="form.deptIds" |
| | | :disabled="isDetail" |
| | | > |
| | | <div |
| | | v-for="dept in deptList" |
| | | :key="dept.deptId" |
| | | class="dept-checkbox-item" |
| | | > |
| | | <el-checkbox :value="dept.deptId"> |
| | | {{ dept.deptName }} |
| | | <span v-if="dept.personCount != null" |
| | | class="dept-count">{{ dept.personCount }}人</span> |
| | | <span v-if="dept.personCount != null" class="dept-count" |
| | | >{{ dept.personCount }}人</span |
| | | > |
| | | </el-checkbox> |
| | | </div> |
| | | </el-checkbox-group> |
| | |
| | | <!-- å³ä¾§ï¼åºç¡ä¿¡æ¯ + ä¿é©ç±»å --> |
| | | <el-col :span="16"> |
| | | <!-- åºç¡ä¿¡æ¯ --> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title"><span class="card-title-line">|</span> åºç¡ä¿¡æ¯</span> |
| | | <el-icon class="card-collapse"> |
| | | <ArrowUp /> |
| | | </el-icon> |
| | | <el-icon class="card-collapse"><ArrowUp /></el-icon> |
| | | </template> |
| | | <el-form-item label="æ¹æ¡æ é¢ï¼" |
| | | prop="title"> |
| | | <el-input v-model="form.title" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :disabled="isDetail" /> |
| | | <el-form-item label="æ¹æ¡æ é¢ï¼" prop="title"> |
| | | <el-input |
| | | v-model="form.title" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :disabled="isDetail" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨ï¼" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :disabled="isDetail" /> |
| | | <el-form-item label="夿³¨ï¼" prop="remark"> |
| | | <el-input |
| | | v-model="form.remark" |
| | | type="textarea" |
| | | :rows="2" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :disabled="isDetail" |
| | | /> |
| | | </el-form-item> |
| | | </el-card> |
| | | |
| | | <!-- ä¿é©ç±»å --> |
| | | <el-card class="form-card" |
| | | shadow="never"> |
| | | <el-card class="form-card" shadow="never"> |
| | | <template #header> |
| | | <span class="card-title"><span class="card-title-line">|</span> ä¿é©ç±»å</span> |
| | | <el-button v-if="!isDetail" |
| | | type="primary" |
| | | size="small" |
| | | @click="addInsuranceBenefit"> |
| | | <el-button |
| | | v-if="!isDetail" |
| | | type="primary" |
| | | size="small" |
| | | @click="addInsuranceBenefit" |
| | | > |
| | | æ·»å ä¿é©ç¦å© |
| | | </el-button> |
| | | </template> |
| | | <el-row :gutter="16"> |
| | | <el-col v-for="(item, index) in form.insuranceBenefits" |
| | | :key="item._key" |
| | | :span="12"> |
| | | <el-col |
| | | v-for="(item, index) in form.insuranceBenefits" |
| | | :key="item._key" |
| | | :span="12" |
| | | > |
| | | <div class="insurance-benefit-card"> |
| | | <div class="insurance-benefit-title"> |
| | | ä¿é©ç¦å©{{ index + 1 }} |
| | | <el-button v-if="!isDetail && form.insuranceBenefits.length > 1" |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | class="card-delete-btn" |
| | | @click="removeInsuranceBenefit(index)"> |
| | | <el-button |
| | | v-if="!isDetail && form.insuranceBenefits.length > 1" |
| | | type="danger" |
| | | link |
| | | size="small" |
| | | class="card-delete-btn" |
| | | @click="removeInsuranceBenefit(index)" |
| | | > |
| | | å é¤ |
| | | </el-button> |
| | | </div> |
| | | <el-form-item :prop="'insuranceBenefits.' + index + '.insuranceType'" |
| | | label="ä¿é©ç±»åï¼" |
| | | label-width="100px" |
| | | :rules="[{ required: true, message: 'è¯·éæ©ä¿é©ç±»å', trigger: 'blur' }]" |
| | | <el-form-item |
| | | :prop="'insuranceBenefits.' + index + '.insuranceType'" |
| | | label="ä¿é©ç±»åï¼" |
| | | label-width="100px" |
| | | > |
| | | <el-select v-model="item.insuranceType" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isDetail"> |
| | | <el-option v-for="opt in insuranceTypeOptions" |
| | | :key="opt.value" |
| | | :label="opt.label" |
| | | :value="opt.value" /> |
| | | <el-select |
| | | v-model="item.insuranceType" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | style="width: 100%" |
| | | :disabled="isDetail" |
| | | > |
| | | <el-option |
| | | v-for="opt in insuranceTypeOptions" |
| | | :key="opt.value" |
| | | :label="opt.label" |
| | | :value="opt.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¼´è´¹åºæ°ï¼" |
| | | label-width="100px" |
| | | :prop="'insuranceBenefits.' + index + '.paymentBase'" |
| | | :rules="[{ validator: validatePaymentBase, trigger: 'blur' }]"> |
| | | <el-form-item label="ç¼´è´¹åºæ°ï¼" label-width="100px"> |
| | | <div class="base-salary-wrap"> |
| | | <el-input v-model="item.paymentBase" |
| | | placeholder="æ ¹æ®åºæ¬å·¥èµç¼´çº³" |
| | | clearable |
| | | style="width: 120px" |
| | | type="number" |
| | | :disabled="isDetail || item.useBasicSalary" |
| | | @input="handlePaymentBaseInput(item)" /> |
| | | <el-checkbox v-model="item.useBasicSalary" |
| | | @change="(val) => handleUseBasicSalaryChange(item, index)" |
| | | :disabled="isDetail"> |
| | | <el-input |
| | | v-model="item.paymentBase" |
| | | placeholder="æ ¹æ®åºæ¬å·¥èµç¼´çº³" |
| | | clearable |
| | | style="width: 120px" |
| | | type="number" |
| | | :disabled="isDetail || item.useBasicSalary" |
| | | @input="handlePaymentBaseInput(item)" |
| | | /> |
| | | <el-checkbox |
| | | v-model="item.useBasicSalary" |
| | | @change="handleUseBasicSalaryChange(item)" |
| | | :disabled="isDetail" |
| | | > |
| | | è°ç¨åºæ¬å·¥èµ |
| | | </el-checkbox> |
| | | </div> |
| | | </el-form-item> |
| | | <el-form-item label="个人缴费æ¯ä¾ï¼" |
| | | label-width="100px" |
| | | :prop="'insuranceBenefits.' + index + '.personalRatio'" |
| | | :rules="[{ required: true, message: '请è¾å
¥ä¸ªäººç¼´è´¹æ¯ä¾', trigger: 'blur' }]"> |
| | | <el-form-item label="个人缴费æ¯ä¾ï¼" label-width="100px"> |
| | | <div class="personal-ratio-wrap"> |
| | | <el-input v-model="item.personalRatio" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 100px" |
| | | type="number" |
| | | :disabled="isDetail" |
| | | :min="0" |
| | | @input="handlePersonalRatioInput(item)" /> |
| | | <el-input |
| | | v-model="item.personalRatio" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 100px" |
| | | type="number" |
| | | :disabled="isDetail" |
| | | /> |
| | | <span class="ratio-unit">(%)</span> |
| | | <span class="ratio-plus">+</span> |
| | | <el-input v-model="item.personalFixed" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 100px" |
| | | type="number" |
| | | :disabled="isDetail" |
| | | :min="0" |
| | | @input="handlePersonalFixedInput(item)" /> |
| | | <el-input |
| | | v-model="item.personalFixed" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | style="width: 100px" |
| | | type="number" |
| | | :disabled="isDetail" |
| | | /> |
| | | </div> |
| | | </el-form-item> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | computed, |
| | | } from "vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ArrowUp } from "@element-plus/icons-vue"; |
| | | import { listDept } from "@/api/system/dept.js"; |
| | | import { |
| | | socialSecurityAdd, |
| | | socialSecurityUpdate, |
| | | } from "@/api/personnelManagement/socialSecuritySet.js"; |
| | | import { ref, reactive, toRefs, getCurrentInstance, nextTick, computed } from "vue"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ArrowUp } from "@element-plus/icons-vue"; |
| | | import { listDept } from "@/api/system/dept.js"; |
| | | import { socialSecurityAdd, socialSecurityUpdate } from "@/api/personnelManagement/socialSecuritySet.js"; |
| | | |
| | | const emit = defineEmits(["close"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref("add"); |
| | | const formRef = ref(null); |
| | | const deptList = ref([]); |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref("add"); |
| | | const formRef = ref(null); |
| | | const deptList = ref([]); |
| | | |
| | | const isDetail = computed(() => operationType.value === "detail"); |
| | | const isDetail = computed(() => operationType.value === "detail"); |
| | | |
| | | const dialogTitle = () => |
| | | operationType.value === "add" |
| | | ? "æ°å¢æ¹æ¡" |
| | | : operationType.value === "edit" |
| | | ? "ç¼è¾æ¹æ¡" |
| | | : "æ¹æ¡è¯¦æ
"; |
| | | const dialogTitle = () => |
| | | operationType.value === "add" |
| | | ? "æ°å¢æ¹æ¡" |
| | | : operationType.value === "edit" |
| | | ? "ç¼è¾æ¹æ¡" |
| | | : "æ¹æ¡è¯¦æ
"; |
| | | |
| | | // ä¿é©ç±»åé项ï¼å¯æåå
¸æ¿æ¢ï¼ |
| | | const insuranceTypeOptions = [ |
| | | { label: "å
»èä¿é©", value: "å
»èä¿é©" }, |
| | | { label: "å»çä¿é©", value: "å»çä¿é©" }, |
| | | { label: "失ä¸ä¿é©", value: "失ä¸ä¿é©" }, |
| | | { label: "工伤ä¿é©", value: "工伤ä¿é©" }, |
| | | { label: "çè²ä¿é©", value: "çè²ä¿é©" }, |
| | | { label: "å
¬ç§¯é", value: "å
¬ç§¯é" }, |
| | | ]; |
| | | // ä¿é©ç±»åé项ï¼å¯æåå
¸æ¿æ¢ï¼ |
| | | const insuranceTypeOptions = [ |
| | | { label: "å
»èä¿é©", value: "å
»èä¿é©" }, |
| | | { label: "å»çä¿é©", value: "å»çä¿é©" }, |
| | | { label: "失ä¸ä¿é©", value: "失ä¸ä¿é©" }, |
| | | { label: "工伤ä¿é©", value: "工伤ä¿é©" }, |
| | | { label: "çè²ä¿é©", value: "çè²ä¿é©" }, |
| | | { label: "å
¬ç§¯é", value: "å
¬ç§¯é" }, |
| | | ]; |
| | | |
| | | const defaultBenefit = () => ({ |
| | | _key: Math.random().toString(36).slice(2), |
| | | insuranceType: "", |
| | | paymentBase: "", |
| | | useBasicSalary: false, |
| | | personalRatio: "", |
| | | personalFixed: "", |
| | | }); |
| | | const defaultBenefit = () => ({ |
| | | _key: Math.random().toString(36).slice(2), |
| | | insuranceType: "", |
| | | paymentBase: "", |
| | | useBasicSalary: false, |
| | | personalRatio: "", |
| | | personalFixed: "", |
| | | }); |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | id: undefined, |
| | | title: "", |
| | | remark: "", |
| | | deptIds: [], |
| | | insuranceBenefits: [defaultBenefit()], |
| | | }, |
| | | rules: { |
| | | title: [{ required: true, message: "请è¾å
¥æ¹æ¡æ é¢", trigger: "blur" }], |
| | | deptIds: [ |
| | | { |
| | | required: true, |
| | | type: "array", |
| | | min: 1, |
| | | message: "请è³å°éæ©ä¸ä¸ªéç¨é¨é¨", |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const data = reactive({ |
| | | form: { |
| | | id: undefined, |
| | | title: "", |
| | | remark: "", |
| | | deptIds: [], |
| | | insuranceBenefits: [defaultBenefit()], |
| | | }, |
| | | rules: { |
| | | title: [{ required: true, message: "请è¾å
¥æ¹æ¡æ é¢", trigger: "blur" }], |
| | | deptIds: [ |
| | | { |
| | | required: true, |
| | | type: "array", |
| | | min: 1, |
| | | message: "请è³å°éæ©ä¸ä¸ªéç¨é¨é¨", |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | function flattenDept(tree, list = []) { |
| | | if (!tree || !tree.length) return list; |
| | | tree.forEach(node => { |
| | | list.push({ |
| | | deptId: node.deptId, |
| | | deptName: node.deptName, |
| | | personCount: node.personCount ?? null, |
| | | }); |
| | | if (node.children && node.children.length) { |
| | | flattenDept(node.children, list); |
| | | } |
| | | function flattenDept(tree, list = []) { |
| | | if (!tree || !tree.length) return list; |
| | | tree.forEach((node) => { |
| | | list.push({ |
| | | deptId: node.deptId, |
| | | deptName: node.deptName, |
| | | personCount: node.personCount ?? null, |
| | | }); |
| | | return list; |
| | | if (node.children && node.children.length) { |
| | | flattenDept(node.children, list); |
| | | } |
| | | }); |
| | | return list; |
| | | } |
| | | |
| | | const loadDeptList = () => { |
| | | listDept().then((res) => { |
| | | const tree = res.data ?? []; |
| | | deptList.value = flattenDept(tree); |
| | | }); |
| | | }; |
| | | |
| | | const addInsuranceBenefit = () => { |
| | | form.value.insuranceBenefits.push(defaultBenefit()); |
| | | }; |
| | | |
| | | const removeInsuranceBenefit = (index) => { |
| | | form.value.insuranceBenefits.splice(index, 1); |
| | | }; |
| | | |
| | | const handleUseBasicSalaryChange = (item) => { |
| | | if (item.useBasicSalary) { |
| | | item.paymentBase = ""; |
| | | } |
| | | }; |
| | | |
| | | const loadDeptList = () => { |
| | | listDept().then(res => { |
| | | const tree = res.data ?? []; |
| | | deptList.value = flattenDept(tree); |
| | | }); |
| | | const handlePaymentBaseInput = (item) => { |
| | | if (item.paymentBase !== "" && item.paymentBase != null) { |
| | | item.useBasicSalary = false; |
| | | } |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | form.value = { |
| | | id: undefined, |
| | | title: "", |
| | | remark: "", |
| | | deptIds: [], |
| | | insuranceBenefits: [defaultBenefit()], |
| | | }; |
| | | }; |
| | | |
| | | const addInsuranceBenefit = () => { |
| | | form.value.insuranceBenefits.push(defaultBenefit()); |
| | | }; |
| | | |
| | | const removeInsuranceBenefit = index => { |
| | | form.value.insuranceBenefits.splice(index, 1); |
| | | }; |
| | | |
| | | const handleUseBasicSalaryChange = (item, index) => { |
| | | if (item.useBasicSalary) { |
| | | item.paymentBase = ""; |
| | | } |
| | | nextTick(() => { |
| | | formRef.value?.validateField(`insuranceBenefits.${index}.paymentBase`); |
| | | }); |
| | | }; |
| | | |
| | | const handlePaymentBaseInput = item => { |
| | | if (item.paymentBase !== "" && item.paymentBase != null) { |
| | | item.useBasicSalary = false; |
| | | } |
| | | }; |
| | | |
| | | const handlePersonalRatioInput = item => { |
| | | if (item.personalRatio !== "" && item.personalRatio != null) { |
| | | const value = Number(item.personalRatio); |
| | | if (value < 0) { |
| | | item.personalRatio = ""; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const handlePersonalFixedInput = item => { |
| | | if (item.personalFixed !== "" && item.personalFixed != null) { |
| | | const value = Number(item.personalFixed); |
| | | if (value < 0) { |
| | | item.personalFixed = ""; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | const validatePaymentBase = (rule, value, callback) => { |
| | | const match = rule.field.match(/insuranceBenefits\.(\d+)\.paymentBase/); |
| | | if (match) { |
| | | const index = parseInt(match[1], 10); |
| | | const item = form.value.insuranceBenefits[index]; |
| | | if (item && !item.useBasicSalary && (!value || value === "")) { |
| | | callback(new Error("请è¾å
¥ç¼´è´¹åºæ°")); |
| | | } else { |
| | | callback(); |
| | | } |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | loadDeptList(); |
| | | resetForm(); |
| | | if ((type === "edit" || type === "detail") && row) { |
| | | const d = row || {}; |
| | | form.value.id = d.id; |
| | | form.value.title = d.title; |
| | | form.value.remark = d.remark ?? ""; |
| | | // deptIds å端å¯è½æ¯éå·åéåç¬¦ä¸²ææ°ç»ï¼è¿éç»ä¸è½¬ä¸ºæ°ç»å¹¶å°½éè¿åæ°å¼ç±»å |
| | | if (d.deptIds) { |
| | | form.value.deptIds = String(d.deptIds) |
| | | .split(",") |
| | | .filter((v) => v !== "") |
| | | .map((v) => { |
| | | const num = Number(v); |
| | | return Number.isNaN(num) ? v : num; |
| | | }); |
| | | } else { |
| | | callback(); |
| | | form.value.deptIds = []; |
| | | } |
| | | }; |
| | | const detailList = d.schemeInsuranceDetailList || []; |
| | | form.value.insuranceBenefits = |
| | | detailList.length > 0 |
| | | ? detailList.map((b) => ({ |
| | | _key: Math.random().toString(36).slice(2), |
| | | insuranceType: b.insuranceType || "", |
| | | paymentBase: b.paymentBase ?? "", |
| | | useBasicSalary: b.useBasicSalary === 2, |
| | | personalRatio: b.personalRatio ?? "", |
| | | personalFixed: b.personalFixed ?? "", |
| | | })) |
| | | : [defaultBenefit()]; |
| | | } |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | form.value = { |
| | | id: undefined, |
| | | title: "", |
| | | remark: "", |
| | | deptIds: [], |
| | | insuranceBenefits: [defaultBenefit()], |
| | | }; |
| | | }; |
| | | |
| | | const openDialog = (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | loadDeptList(); |
| | | resetForm(); |
| | | if ((type === "edit" || type === "detail") && row) { |
| | | const d = row || {}; |
| | | form.value.id = d.id; |
| | | form.value.title = d.title; |
| | | form.value.remark = d.remark ?? ""; |
| | | // deptIds å端å¯è½æ¯éå·åéåç¬¦ä¸²ææ°ç»ï¼è¿éç»ä¸è½¬ä¸ºæ°ç»å¹¶å°½éè¿åæ°å¼ç±»å |
| | | if (d.deptIds) { |
| | | form.value.deptIds = String(d.deptIds) |
| | | .split(",") |
| | | .filter(v => v !== "") |
| | | .map(v => { |
| | | const num = Number(v); |
| | | return Number.isNaN(num) ? v : num; |
| | | }); |
| | | } else { |
| | | form.value.deptIds = []; |
| | | } |
| | | const detailList = d.schemeInsuranceDetailList || []; |
| | | form.value.insuranceBenefits = |
| | | detailList.length > 0 |
| | | ? detailList.map(b => ({ |
| | | _key: Math.random().toString(36).slice(2), |
| | | insuranceType: b.insuranceType || "", |
| | | paymentBase: b.paymentBase ?? "", |
| | | useBasicSalary: b.useBasicSalary === 2, |
| | | personalRatio: b.personalRatio ?? "", |
| | | personalFixed: b.personalFixed ?? "", |
| | | })) |
| | | : [defaultBenefit()]; |
| | | } |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // 详æ
模å¼ä¸ä¸æäº¤ï¼åªå
³éå¼¹çª |
| | | if (operationType.value === "detail") { |
| | | closeDia(); |
| | | return; |
| | | } |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return; |
| | | const deptIds = |
| | | Array.isArray(form.value.deptIds) && form.value.deptIds.length |
| | | ? form.value.deptIds.join(",") |
| | | : ""; |
| | | const schemeInsuranceDetailList = (form.value.insuranceBenefits || []).map( |
| | | ({ _key, ...rest }) => ({ |
| | | ...rest, |
| | | useBasicSalary: rest.useBasicSalary ? 2 : 1, |
| | | }) |
| | | ); |
| | | const insuranceTypes = schemeInsuranceDetailList |
| | | .map(item => item.insuranceType) |
| | | .filter(v => v) |
| | | .join(","); |
| | | // é¨é¨åç§°ï¼å¤ä¸ªä½¿ç¨éå·éå¼ï¼æ ¹æ®éä¸ç deptIds ä¸ deptList 计ç®ï¼ |
| | | const deptNames = (deptList.value || []) |
| | | .filter(d => |
| | | (form.value.deptIds || []).some(id => String(id) === String(d.deptId)) |
| | | const submitForm = () => { |
| | | // 详æ
模å¼ä¸ä¸æäº¤ï¼åªå
³éå¼¹çª |
| | | if (operationType.value === "detail") { |
| | | closeDia(); |
| | | return; |
| | | } |
| | | formRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | const deptIds = |
| | | Array.isArray(form.value.deptIds) && form.value.deptIds.length |
| | | ? form.value.deptIds.join(",") |
| | | : ""; |
| | | const schemeInsuranceDetailList = (form.value.insuranceBenefits || []).map( |
| | | ({ _key, ...rest }) => ({ |
| | | ...rest, |
| | | useBasicSalary: rest.useBasicSalary ? 2 : 1, |
| | | }) |
| | | ); |
| | | const insuranceTypes = schemeInsuranceDetailList |
| | | .map((item) => item.insuranceType) |
| | | .filter((v) => v) |
| | | .join(","); |
| | | // é¨é¨åç§°ï¼å¤ä¸ªä½¿ç¨éå·éå¼ï¼æ ¹æ®éä¸ç deptIds ä¸ deptList 计ç®ï¼ |
| | | const deptNames = (deptList.value || []) |
| | | .filter((d) => |
| | | (form.value.deptIds || []).some( |
| | | (id) => String(id) === String(d.deptId) |
| | | ) |
| | | .map(d => d.deptName) |
| | | .join(","); |
| | | const submitData = { |
| | | id: form.value.id, |
| | | title: form.value.title, |
| | | remark: form.value.remark ?? "", |
| | | deptIds, |
| | | insuranceTypes, |
| | | deptNames, |
| | | schemeInsuranceDetailList, |
| | | }; |
| | | if (operationType.value === "add") { |
| | | socialSecurityAdd(submitData).then(() => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | socialSecurityUpdate(submitData).then(() => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | ) |
| | | .map((d) => d.deptName) |
| | | .join(","); |
| | | const submitData = { |
| | | id: form.value.id, |
| | | title: form.value.title, |
| | | remark: form.value.remark ?? "", |
| | | deptIds, |
| | | insuranceTypes, |
| | | deptNames, |
| | | schemeInsuranceDetailList, |
| | | }; |
| | | if (operationType.value === "add") { |
| | | socialSecurityAdd(submitData).then(() => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | socialSecurityUpdate(submitData).then(() => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | proxy.resetForm?.("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | const closeDia = () => { |
| | | proxy.resetForm?.("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .card-title-line { |
| | | color: #f56c6c; |
| | | margin-right: 4px; |
| | | } |
| | | .form-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .form-card :deep(.el-card__header) { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px 16px; |
| | | } |
| | | .card-title { |
| | | font-weight: 500; |
| | | } |
| | | .card-collapse { |
| | | color: #999; |
| | | cursor: pointer; |
| | | } |
| | | .dept-checkbox-wrap { |
| | | max-height: 320px; |
| | | overflow-y: auto; |
| | | padding: 8px 0; |
| | | border: 1px solid var(--el-border-color); |
| | | border-radius: 4px; |
| | | background: #fff; |
| | | } |
| | | .dept-checkbox-item { |
| | | padding: 6px 12px; |
| | | } |
| | | .dept-count { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-left: 4px; |
| | | } |
| | | .insurance-benefit-card { |
| | | border: 1px solid var(--el-border-color-lighter); |
| | | border-radius: 4px; |
| | | padding: 12px 16px; |
| | | margin-bottom: 12px; |
| | | background: #fafafa; |
| | | } |
| | | .insurance-benefit-title { |
| | | font-size: 14px; |
| | | margin-bottom: 12px; |
| | | font-weight: 500; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | .card-delete-btn { |
| | | margin-left: auto; |
| | | } |
| | | .checkbox-group-inline { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | .base-salary-wrap { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .base-salary-text { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | .personal-ratio-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .ratio-unit, |
| | | .ratio-plus { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | | .card-title-line { |
| | | color: #f56c6c; |
| | | margin-right: 4px; |
| | | } |
| | | .form-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | .form-card :deep(.el-card__header) { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px 16px; |
| | | } |
| | | .card-title { |
| | | font-weight: 500; |
| | | } |
| | | .card-collapse { |
| | | color: #999; |
| | | cursor: pointer; |
| | | } |
| | | .dept-checkbox-wrap { |
| | | max-height: 320px; |
| | | overflow-y: auto; |
| | | padding: 8px 0; |
| | | border: 1px solid var(--el-border-color); |
| | | border-radius: 4px; |
| | | background: #fff; |
| | | } |
| | | .dept-checkbox-item { |
| | | padding: 6px 12px; |
| | | } |
| | | .dept-count { |
| | | color: #909399; |
| | | font-size: 12px; |
| | | margin-left: 4px; |
| | | } |
| | | .insurance-benefit-card { |
| | | border: 1px solid var(--el-border-color-lighter); |
| | | border-radius: 4px; |
| | | padding: 12px 16px; |
| | | margin-bottom: 12px; |
| | | background: #fafafa; |
| | | } |
| | | .insurance-benefit-title { |
| | | font-size: 14px; |
| | | margin-bottom: 12px; |
| | | font-weight: 500; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | .card-delete-btn { |
| | | margin-left: auto; |
| | | } |
| | | .checkbox-group-inline { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 16px; |
| | | } |
| | | .base-salary-wrap { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .base-salary-text { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | .personal-ratio-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .ratio-unit, |
| | | .ratio-plus { |
| | | color: #606266; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| src/views/procurementManagement/paymentLedger/index.vue
src/views/procurementManagement/procurementLedger/detail.vue
src/views/procurementManagement/procurementLedger/index.vue
src/views/procurementManagement/procurementPlan/index.vue
src/views/procurementManagement/procurementReport/index.vue
src/views/procurementManagement/purchaseOrder/index.vue
src/views/procurementManagement/purchaseReturnOrder/New.vue
src/views/procurementManagement/purchaseReturnOrder/ProductList.vue
src/views/procurementManagement/purchaseReturnOrder/index.vue
src/views/procurementManagement/qualityInspection/index.vue
src/views/productManagement/productIdentifier/index.vue
src/views/productionManagement/processRoute/processRouteItem/index.vue
src/views/productionManagement/productStructure/Detail/index.vue
src/views/productionManagement/productStructure/StructureEdit.vue
src/views/productionManagement/productStructure/index.vue
src/views/productionManagement/productionOrder/components/MaterialDetailDialog.vue
src/views/productionManagement/productionOrder/components/MaterialLedgerDialog.vue
src/views/productionManagement/productionOrder/components/MaterialSupplementDialog.vue
src/views/productionManagement/productionOrder/index.vue
src/views/productionManagement/productionReporting/index.vue
src/views/productionManagement/productionTraceability/index.vue
src/views/productionManagement/workOrder/index.vue
src/views/productionManagement/workOrderEdit/index.vue
src/views/productionManagement/workOrderManagement/index.vue
src/views/productionPlan/productionPlan/index.vue
src/views/qualityManagement/finalInspection/components/filesDia.vue
src/views/qualityManagement/finalInspection/components/formDia.vue
src/views/qualityManagement/finalInspection/index.vue
src/views/qualityManagement/metricBinding/index.vue
src/views/qualityManagement/nearExpiryReturn/index.vue
src/views/qualityManagement/nonconformingManagement/components/formDia.vue
src/views/qualityManagement/processInspection/components/formDia.vue
src/views/qualityManagement/processInspection/index.vue
src/views/qualityManagement/rawMaterialInspection/components/filesDia.vue
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
src/views/qualityManagement/rawMaterialInspection/index.vue
src/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue
src/views/reportAnalysis/PSIDataAnalysis/components/ProductTypeSwitch.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
src/views/reportAnalysis/PSIDataAnalysis/index.vue
src/views/reportAnalysis/PSIDataAnalysis/psiNavigation.js
src/views/reportAnalysis/dataDashboard/components/PanelHeader.vue
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
src/views/reportAnalysis/productionAnalysis/components/PanelHeader.vue
src/views/reportAnalysis/productionAnalysis/components/center-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/center-center.vue
src/views/reportAnalysis/productionAnalysis/components/center-top.vue
src/views/reportAnalysis/productionAnalysis/components/left-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/left-top.vue
src/views/reportAnalysis/productionAnalysis/components/right-bottom.vue
src/views/reportAnalysis/productionAnalysis/components/right-top.vue
src/views/reportAnalysis/productionAnalysis/productionNavigation.js
src/views/reportAnalysis/reportManagement/index.vue
src/views/safeProduction/dangerInvestigation/index.vue
src/views/safeProduction/safetyTrainingAssessment/index.vue
src/views/salesManagement/deliveryLedger/index.vue
src/views/salesManagement/indicatorStats/index.vue
src/views/salesManagement/receiptPaymentLedger/index.vue
src/views/salesManagement/returnOrder/components/detailDia.vue
src/views/salesManagement/returnOrder/components/formDia.vue
src/views/salesManagement/returnOrder/index.vue
src/views/salesManagement/salesLedger/index.vue
src/views/salesManagement/salesQuotation/index.vue
src/views/system/dept/index.vue
src/views/system/dict/data.vue
src/views/system/dict/index.vue
src/views/system/menu/index.vue
src/views/system/post/index.vue
src/views/system/role/index.vue
src/views/systemArchitecture/index.vue
vite.config.js |