已添加9个文件
已修改30个文件
已删除27个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ ¹æ®ä¾åºåæ¥è¯¢å¯å
³èå
¥åºå */ |
| | | export function getInboundBatchesBySupplier(params) { |
| | | return request({ |
| | | url: "/accountPaymentApplication/getInboundBatchesBySupplier", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢ä»æ¬¾ç³è¯· */ |
| | | export function addAccountPaymentApplication(data) { |
| | | return request({ |
| | | url: "/accountPaymentApplication/addAccountPaymentApplication", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** 仿¬¾ç³è¯·å页å表 */ |
| | | export function listPageAccountPaymentApplication(params) { |
| | | return request({ |
| | | url: "/accountPaymentApplication/listPageAccountPaymentApplication", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** ä¿®æ¹ä»æ¬¾ç³è¯· */ |
| | | export function updateAccountPaymentApplication(data) { |
| | | return request({ |
| | | url: "/accountPaymentApplication/updateAccountPaymentApplication", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å®¡æ ¸ä»æ¬¾ç³è¯· */ |
| | | export function auditAccountPaymentApplication(data) { |
| | | return request({ |
| | | url: "/accountPaymentApplication/auditAccountPaymentApplication", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤ä»æ¬¾ç³è¯·ï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountPaymentApplication(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountPaymentApplication/deleteAccountPaymentApplication?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ ¹æ®ä¾åºåæ¥è¯¢å¯å
³èå
¥åºå */ |
| | | export function getInboundBatchesBySupplier(params) { |
| | | return request({ |
| | | url: "/accountPurchaseInvoice/getInboundBatchesBySupplier", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢è¿é¡¹å票 */ |
| | | export function addAccountPurchaseInvoice(data) { |
| | | return request({ |
| | | url: "/accountPurchaseInvoice/addAccountPurchaseInvoice", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** è¿é¡¹å票å页å表 */ |
| | | export function listPageAccountPurchaseInvoice(params) { |
| | | return request({ |
| | | url: "/accountPurchaseInvoice/listPageAccountPurchaseInvoice", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** ä½åºè¿é¡¹å票 */ |
| | | export function cancelAccountPurchaseInvoice(data) { |
| | | return request({ |
| | | url: "/accountPurchaseInvoice/cancelAccountPurchaseInvoice", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤è¿é¡¹å票ï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountPurchaseInvoice(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountPurchaseInvoice/deleteAccountPurchaseInvoice?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ°å¢ä»æ¬¾åï¼å
³è仿¬¾ç³è¯·ï¼ */ |
| | | export function addAccountPurchasePayment(data) { |
| | | return request({ |
| | | url: "/accountPurchasePayment/addAccountPurchasePayment", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** 仿¬¾åå页å表 */ |
| | | export function listPageAccountPurchasePayment(params) { |
| | | return request({ |
| | | url: "/accountPurchasePayment/listPageAccountPurchasePayment", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤ä»æ¬¾åï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountPurchasePayment(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountPurchasePayment/deleteAccountPurchasePayment?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ ¹æ®å®¢æ·æ¥è¯¢å¯å
³èåºåºå */ |
| | | export function getOutboundBatchesByCustomer(params) { |
| | | return request({ |
| | | url: "/accountSalesCollection/getOutboundBatchesByCustomer", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢æ¶æ¬¾å */ |
| | | export function addAccountSalesCollection(data) { |
| | | return request({ |
| | | url: "/accountSalesCollection/addAccountSalesCollection", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** æ¶æ¬¾åå页å表 */ |
| | | export function listPageAccountSalesCollection(params) { |
| | | return request({ |
| | | url: "/accountSalesCollection/listPageAccountSalesCollection", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** ä¿®æ¹æ¶æ¬¾å */ |
| | | export function updateAccountSalesCollection(data) { |
| | | return request({ |
| | | url: "/accountSalesCollection/updateAccountSalesCollection", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å 餿¶æ¬¾åï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountSalesCollection(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountSalesCollection/deleteAccountSalesCollection?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ°å¢é项å票 */ |
| | | export function addAccountSalesInvoice(data) { |
| | | return request({ |
| | | url: "/accountSalesInvoice/addAccountSalesInvoice", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** é项å票å页å表 */ |
| | | export function listPageAccountSalesInvoice(params) { |
| | | return request({ |
| | | url: "/accountSalesInvoice/listPageAccountSalesInvoice", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** ä½åºé项å票 */ |
| | | export function cancelAccountSalesInvoice(data) { |
| | | return request({ |
| | | url: "/accountSalesInvoice/cancelAccountSalesInvoice", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤é项å票ï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountSalesInvoice(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountSalesInvoice/deleteAccountSalesInvoice?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** ææä»½æ¥è¯¢å¯¹è´¦åæç»ï¼çæåé¢è§ï¼ */ |
| | | export function getAccountStatementDetailsByMonth(params) { |
| | | return request({ |
| | | url: "/accountStatement/getAccountStatementDetailsByMonth", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢å¯¹è´¦å */ |
| | | export function addAccountStatement(data) { |
| | | return request({ |
| | | url: "/accountStatement/addAccountStatement", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** 对账åå页å表 */ |
| | | export function listPageAccountStatement(params) { |
| | | return request({ |
| | | url: "/accountStatement/listPageAccountStatement", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤å¯¹è´¦åï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountStatement(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountStatement/deleteAccountStatement?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | /** æ ¹æ®å®¢æ·æ¥è¯¢å¯å¼ç¥¨åºåºåå·å表 */ |
| | | export function getOutboundBatchesByCustomer(params) { |
| | | return request({ |
| | | url: "/accountInvoiceApplication/getOutboundBatchesByCustomer", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** æ°å¢å¼ç¥¨ç³è¯· */ |
| | | export function addAccountInvoiceApplication(data) { |
| | | return request({ |
| | | url: "/accountInvoiceApplication/addAccountInvoiceApplication", |
| | | method: "post", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å¼ç¥¨ç³è¯·å页å表 */ |
| | | export function listPageAccountInvoiceApplication(params) { |
| | | return request({ |
| | | url: "/accountInvoiceApplication/listPageAccountInvoiceApplication", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | /** å¼ç¥¨ç³è¯·å®¡æ¹ */ |
| | | export function auditAccountInvoiceApplication(data) { |
| | | return request({ |
| | | url: "/accountInvoiceApplication/auditAccountInvoiceApplication", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** ä¿®æ¹å¼ç¥¨ç³è¯· */ |
| | | export function updateAccountInvoiceApplication(data) { |
| | | return request({ |
| | | url: "/accountInvoiceApplication/updateAccountInvoiceApplication", |
| | | method: "put", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | /** å é¤å¼ç¥¨ç³è¯·ï¼Spring è¦æ± ids=1&ids=2 æ¥è¯¢åæ°ï¼ */ |
| | | export function deleteAccountInvoiceApplication(ids) { |
| | | const idList = Array.isArray(ids) ? ids : [ids]; |
| | | const query = idList |
| | | .filter((id) => id !== undefined && id !== null && id !== "") |
| | | .map((id) => `ids=${encodeURIComponent(id)}`) |
| | | .join("&"); |
| | | return request({ |
| | | url: `/accountInvoiceApplication/deleteAccountInvoiceApplication?${query}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | // éè´å°è´¦é¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | /** 仿¬¾å°è´¦ - ä¾åºå徿¥æ±æ» */ |
| | | export function paymentLedgerList(query) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/supplierNameListPage", |
| | | url: "/purchase/report/supplierTransactions", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function paymentRecordList(supplierId) { |
| | | /** 仿¬¾å°è´¦ - ä¾åºå徿¥æç» */ |
| | | export function paymentRecordList(params) { |
| | | return request({ |
| | | url: "/purchase/paymentRegistration/supplierNameListPageDetails", |
| | | url: "/purchase/report/supplierTransactionsDetails", |
| | | method: "get", |
| | | params: supplierId, |
| | | params, |
| | | }); |
| | | } |
| | |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 客æ·å¾æ¥å表 |
| | | export function customewTransactions(query) { |
| | | return request({ |
| | | url: "/metricStatistics/customewTransactions", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 客æ·å¾æ¥æç» |
| | | export function customewTransactionsDetails(query) { |
| | | return request({ |
| | | url: "/metricStatistics/customewTransactionsDetails", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | // å¼ç¥¨å°è´¦é¡µé¢æ¥å£ |
| | | import request from '@/utils/request' |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function invoiceLedgerList(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/page', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢ |
| | | export function invoiceLedgerSaveOrUpdate(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/saveOrUpdate', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // å¼ç¥¨å°è´¦å é¤ |
| | | export function invoiceLedgerDel(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/del', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // 详æ
æ¥è¯¢ |
| | | export function invoiceLedgerDetail(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/info', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // éä»¶æäº¤ |
| | | export function commitFile(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/commitFile', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // å¼ç¥¨å°è´¦é¨å乿¥è¯¢ |
| | | export function invoiceLedgerListNoPage(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/list', |
| | | method: 'get', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // å页æ¥è¯¢ |
| | | /** 忬¾å°è´¦ - 客æ·éå®è´¦æ·å页 */ |
| | | export function invoiceLedgerSalesAccount(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/salesAccount', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | return request({ |
| | | url: '/invoiceLedger/salesAccount', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 产åå¼ç¥¨è®°å½å页æ¥è¯¢ |
| | | export function registrationProductPage(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/registrationProductPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 产åå¼ç¥¨è¯¦æ
æ¥è¯¢ |
| | | export function invoiceLedgerProductInfo(query) { |
| | | return request({ |
| | | url: '/invoiceLedger/invoiceLedgerProductInfo', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | export function delInvoiceLedgerByRegProductId(invoiceRegistrationProductId) { |
| | | return request({ |
| | | url: '/invoiceLedger/delInvoiceLedger/'+ invoiceRegistrationProductId, |
| | | method: 'delete' |
| | | }) |
| | | } |
| | | |
| | | |
| | |
| | | // å¼ç¥¨ç»è®°é¡µé¢æ¥å£ |
| | | import request from '@/utils/request' |
| | | |
| | | // æ°å¢/ä¿®æ¹ |
| | | export function receiptPaymentSaveOrUpdate(query) { |
| | | return request({ |
| | | url: '/receiptPayment/saveOrUpdate', |
| | | method: 'post', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // 客æ·å¾æ¥è®°å½æ¥è¯¢ |
| | | /** 忬¾å°è´¦ - 客æ·å¾æ¥è®°å½ */ |
| | | export function customerInteractions(query) { |
| | | return request({ |
| | | url: '/receiptPayment/customerInteractions', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // 详æ
|
| | | export function receiptPaymentInfo(query) { |
| | | return request({ |
| | | url: '/receiptPayment/info', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // å é¤ |
| | | export function receiptPaymentDel(query) { |
| | | return request({ |
| | | url: '/receiptPayment/del', |
| | | method: 'delete', |
| | | data: query |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢å·²ç»ç»å®å票çå¼ç¥¨å°è´¦ |
| | | export function bindInvoiceNoRegPage(query) { |
| | | return request({ |
| | | url: '/sales/product/listPageSalesLedger', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // å¼ç¥¨å°è´¦è¯¦æ
|
| | | export function invoiceInfo(query) { |
| | | return request({ |
| | | url: '/receiptPayment/invoiceInfo', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | // è¯¢åæ¬¾è®°å½ |
| | | export function receiptPaymentHistoryList(query) { |
| | | return request({ |
| | | url: '/receiptPayment/receiptPaymentHistoryList', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * æ¥è¯¢å款记å½å页æ¥è¯¢ |
| | | */ |
| | | export function receiptPaymentHistoryListPage(query) { |
| | | return request({ |
| | | url: '/receiptPayment/receiptPaymentHistoryListPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| | | |
| | | export function receiptPaymentHistoryListNoPage(query) { |
| | | return request({ |
| | | url: '/receiptPayment/receiptPaymentHistoryListNoPage', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | return request({ |
| | | url: '/receiptPayment/customerInteractions', |
| | | method: 'get', |
| | | params: query |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { Money } from '@element-plus/icons-vue' |
| | | |
| | | export const financeAssistant = { |
| | | key: 'finance', |
| | | label: 'è´¢å¡å©ç', |
| | | title: 'è´¢å¡æºè½å©ç', |
| | | tooltip: 'è´¢å¡æºè½å©ç', |
| | | icon: Money, |
| | | apiBase: '/financial-ai', |
| | | storageKey: 'financial_ai_chat_uuid', |
| | | placeholder: '请è¾å
¥è´¢å¡é®é¢... (Enter åé, Shift+Enter æ¢è¡)', |
| | | welcomeMessage: 'ä½ å¥½', |
| | | description: 'æå¯ä»¥åå©ä½ å®æææ¬æ ¸ç®ã婿¶¦åæãåºåèµéåæãç°éæµé¢æµãå¼å¸¸é¢è¦åç»è¥å¨æ¥è§£è¯»ã', |
| | | allowFileUpload: false, |
| | | emptySessionText: 'ææ è´¢å¡ä¼è¯', |
| | | quickPrompts: [ |
| | | 'æ¥çæ¬æç»è¥é©¾é©¶è±', |
| | | 'æ¥è¯¢è¿30å¤©äºæè®¢å', |
| | | 'åæè¿30天åºåèµéå ç¨', |
| | | '颿µæªæ¥3个æç°éæµ', |
| | | 'çææ¬å¨ç»è¥å¨æ¥', |
| | | '为ä»ä¹å©æ¶¦ä¸é', |
| | | 'åªä¸ªå®¢æ·æèµé±', |
| | | 'åªä¸ªå·¥åºææ¬æé«' |
| | | ] |
| | | } |
| | |
| | | import { purchaseAssistant } from './purchaseAssistant' |
| | | import { productionAssistant } from './productionAssistant' |
| | | import { salesAssistant } from './salesAssistant' |
| | | import { financeAssistant } from './financeAssistant' |
| | | |
| | | export { generalAssistant, purchaseAssistant, productionAssistant, salesAssistant } |
| | | export { generalAssistant, purchaseAssistant, productionAssistant, salesAssistant, financeAssistant } |
| | | |
| | | export const assistantRegistry = { |
| | | general: generalAssistant, |
| | | sales: salesAssistant, |
| | | purchase: purchaseAssistant, |
| | | production: productionAssistant |
| | | production: productionAssistant, |
| | | finance: financeAssistant |
| | | } |
| | | |
| | | export const builtInAssistants = [generalAssistant, salesAssistant, purchaseAssistant, productionAssistant] |
| | | export const builtInAssistants = [generalAssistant, salesAssistant, purchaseAssistant, productionAssistant, financeAssistant] |
| | |
| | | 'æ¬æéè´é颿åååçç©ææåªäºï¼', |
| | | 'åªäºéè´è®¢åè¿æªå
¥åºï¼', |
| | | 'æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼', |
| | | '帮æç»è®¡å¾
仿¬¾éè´å', |
| | | '帮æç»è®¡å¾
仿¬¾éè´åï¼', |
| | | 'ååºæ¬æéè´éè´§æ
åµ' |
| | | ] |
| | | } |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.purchaseData" class="sales-structured-card"> |
| | | <div class="sales-structured-card__title">{{ getPurchaseTypeLabel(message.type) }}</div> |
| | | |
| | | <div v-if="message.purchaseData.summaryEntries?.length" class="sales-summary-grid"> |
| | | <div |
| | | v-for="(entry, entryIndex) in message.purchaseData.summaryEntries" |
| | | :key="`purchase-summary-${entry.key}-${entryIndex}`" |
| | | class="sales-summary-item" |
| | | > |
| | | <span class="sales-summary-label">{{ entry.label }}</span> |
| | | <strong class="sales-summary-value">{{ entry.value }}</strong> |
| | | </div> |
| | | </div> |
| | | |
| | | <div |
| | | v-if="message.purchaseData.listItems?.length && message.purchaseData.columns?.length" |
| | | class="table-wrapper manufacturing-table-wrapper" |
| | | > |
| | | <el-table :data="message.purchaseData.listItems" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in message.purchaseData.columns" |
| | | :key="col" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="140" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.financeData" class="sales-structured-card"> |
| | | <div class="sales-structured-card__title">{{ getFinancialTypeLabel(message.type) }}</div> |
| | | |
| | | <div v-if="message.financeData.summaryEntries?.length" class="sales-summary-grid"> |
| | | <div |
| | | v-for="(entry, entryIndex) in message.financeData.summaryEntries" |
| | | :key="`finance-summary-${entry.key}-${entryIndex}`" |
| | | class="sales-summary-item" |
| | | > |
| | | <span class="sales-summary-label">{{ entry.label }}</span> |
| | | <strong class="sales-summary-value">{{ entry.value }}</strong> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.financeData.headline" class="finance-headline"> |
| | | {{ message.financeData.headline }} |
| | | </div> |
| | | |
| | | <div v-if="message.financeData.conclusions?.length" class="finance-text-section"> |
| | | <div class="sales-section-title">æ ¸å¿ç»è®º</div> |
| | | <ul> |
| | | <li v-for="(item, idx) in message.financeData.conclusions" :key="`finance-conclusion-${idx}`"> |
| | | {{ item }} |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | |
| | | <div v-if="message.financeData.riskSuggestions?.length" class="finance-text-section"> |
| | | <div class="sales-section-title">é£é©å»ºè®®</div> |
| | | <ul> |
| | | <li v-for="(item, idx) in message.financeData.riskSuggestions" :key="`finance-risk-${idx}`"> |
| | | {{ item }} |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | |
| | | <div |
| | | v-for="section in message.financeData.sections" |
| | | :key="`finance-section-${section.key}`" |
| | | class="table-wrapper manufacturing-table-wrapper" |
| | | > |
| | | <div class="sales-section-title">{{ section.title }}</div> |
| | | <el-table :data="section.items" border stripe size="small" style="width: 100%"> |
| | | <el-table-column |
| | | v-for="col in section.columns" |
| | | :key="`finance-${section.key}-${col}`" |
| | | :label="getStructuredFieldLabel(col)" |
| | | min-width="120" |
| | | show-overflow-tooltip |
| | | > |
| | | <template #default="{ row }"> |
| | | {{ formatStructuredValue(row[col]) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.purchaseIntentData?.quickPrompts?.length" class="purchase-intent-quick-prompt-wrap"> |
| | | <div class="purchase-intent-quick-prompt-title">è¯è¯ä»¥ä¸æé®</div> |
| | | <div class="quick-prompt-list purchase-intent-quick-prompt-list"> |
| | | <button |
| | | v-for="prompt in message.purchaseIntentData.quickPrompts" |
| | | :key="`purchase-intent-${prompt}`" |
| | | type="button" |
| | | class="quick-prompt-btn" |
| | | :disabled="isSending" |
| | | @click="sendQuickPrompt(prompt)" |
| | | > |
| | | {{ prompt }} |
| | | </button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-if="message.purchaseAnalysisData" class="purchase-confirm-card"> |
| | | <div class="purchase-confirm-header"> |
| | | <span>{{ businessTypeLabelMap[message.purchaseAnalysisData.businessType] || message.purchaseAnalysisData.businessType || 'éè´ä¸å¡' }}</span> |
| | |
| | | sales_customer_churn_risk: 'å®¢æ·æµå¤±é£é©åæ', |
| | | sales_collection_quote_strategy: '忬¾ä¸æ¥ä»·çç¥å»ºè®®' |
| | | } |
| | | const purchaseTypeLabelMap = { |
| | | purchase_material_rank: 'éè´ç©æé颿è¡', |
| | | purchase_pending_payment_list: 'å¾
仿¬¾éè´å' |
| | | } |
| | | const financialStructuredTypeSet = new Set([ |
| | | 'financial_cost_accounting', |
| | | 'financial_order_profit_analysis', |
| | | 'financial_inventory_capital_analysis', |
| | | 'financial_cashflow_forecast', |
| | | 'financial_business_anomaly_warning', |
| | | 'financial_business_cockpit', |
| | | 'financial_operation_report', |
| | | 'financial_rag_knowledge' |
| | | ]) |
| | | const financialTypeLabelMap = { |
| | | financial_cost_accounting: 'ææ¬æ ¸ç®', |
| | | financial_order_profit_analysis: '订å婿¶¦åæ', |
| | | financial_inventory_capital_analysis: 'åºåèµéåæ', |
| | | financial_cashflow_forecast: 'ç°éæµé¢æµ', |
| | | financial_business_anomaly_warning: 'ç»è¥å¼å¸¸é¢è¦', |
| | | financial_business_cockpit: 'ç»è¥é©¾é©¶è±', |
| | | financial_operation_report: 'ç»è¥æ¥å', |
| | | financial_rag_knowledge: 'è´¢å¡ç¥è¯æ£ç´¢' |
| | | } |
| | | const manufacturingStructuredTypeSet = new Set([ |
| | | 'manufacturing_site_snapshot', |
| | | 'manufacturing_plan_list', |
| | |
| | | collectionStrategy: '忬¾çç¥', |
| | | quotationStrategy: 'æ¥ä»·çç¥', |
| | | nextAction: 'ä¸ä¸æ¥å¨ä½', |
| | | salesContractNo: 'éå®ååå·', |
| | | revenue: 'æ¶å
¥', |
| | | materialCost: 'ææææ¬', |
| | | laborCost: 'äººå·¥ææ¬', |
| | | depreciationCost: 'ææ§ææ¬', |
| | | scrapCost: 'æ¥åºææ¬', |
| | | totalCost: 'æ»ææ¬', |
| | | profit: '婿¶¦', |
| | | profitRate: '婿¶¦ç', |
| | | reasons: 'åå ', |
| | | suggestion: '建议', |
| | | productName: '产ååç§°', |
| | | model: 'åå·', |
| | | quantity: 'æ°é', |
| | | inventoryValue: 'åºåèµé', |
| | | stagnantDays: '忻天æ°', |
| | | overstock: 'æ¯å¦è¶
å¨', |
| | | month: 'æä»½', |
| | | income: 'æ¶å
¥', |
| | | expense: 'æ¯åº', |
| | | netFlow: 'åç°éæµ', |
| | | message: 'é¢è¦ä¿¡æ¯', |
| | | headline: 'æ¥åæ é¢', |
| | | conclusions: 'æ ¸å¿ç»è®º', |
| | | riskSuggestions: 'é£é©å»ºè®®', |
| | | orderProfitTop: '婿¶¦Top', |
| | | contractAmountTotal: 'ååæ»é¢', |
| | | receivedAmountTotal: '已忬¾éé¢', |
| | | pendingAmountTotal: 'å¾
忬¾æ»é¢', |
| | | shipRate: 'åè´§ç' |
| | | shipRate: 'åè´§ç', |
| | | pendingOrderCount: 'å¾
仿¬¾è®¢åæ°', |
| | | totalContractAmount: 'å¾
仿¬¾ååæ»é¢', |
| | | totalPaidAmount: '已仿¬¾æ»é¢', |
| | | totalPendingAmount: 'å¾
仿¬¾æ»é¢' |
| | | }) |
| | | const purchasePayloadFieldLabelMap = { |
| | | purchaseLedgers: 'éè´å°è´¦', |
| | |
| | | |
| | | const getSalesTypeLabel = (type = '') => salesTypeLabelMap[String(type || '')] || 'é宿¥è¯¢ç»æ' |
| | | |
| | | const buildPurchaseStructuredData = (parsedData) => { |
| | | if (parsedData?.success !== true) return null |
| | | |
| | | const type = String(parsedData?.type || '') |
| | | if (!type.startsWith('purchase_')) return null |
| | | |
| | | const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {} |
| | | const listItems = normalizeSalesListItems(rawData.items) |
| | | |
| | | return { |
| | | type, |
| | | summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary), |
| | | listItems, |
| | | columns: inferSalesColumns(listItems) |
| | | } |
| | | } |
| | | |
| | | const getPurchaseTypeLabel = (type = '') => purchaseTypeLabelMap[String(type || '')] || 'éè´æ¥è¯¢ç»æ' |
| | | |
| | | const financialDataSectionConfig = [ |
| | | { key: 'orders', title: '订åæç»' }, |
| | | { key: 'items', title: 'æç»å表' }, |
| | | { key: 'actualMonthly', title: 'åå²ç°éæµ' }, |
| | | { key: 'forecastMonthly', title: '颿µç°éæµ' }, |
| | | { key: 'receivableRiskTop', title: 'åºæ¶é£é©Top' }, |
| | | { key: 'payablePressureTop', title: 'åºä»ååTop' }, |
| | | { key: 'orderProfitTop', title: '婿¶¦Top' } |
| | | ] |
| | | |
| | | const buildFinancialStructuredSections = (rawData = {}) => { |
| | | const sections = [] |
| | | financialDataSectionConfig.forEach((section) => { |
| | | const items = normalizeSalesListItems(rawData?.[section.key]) |
| | | if (!items.length) return |
| | | sections.push({ |
| | | key: section.key, |
| | | title: section.title, |
| | | items, |
| | | columns: inferSalesColumns(items) |
| | | }) |
| | | }) |
| | | return sections |
| | | } |
| | | |
| | | const buildFinancialStructuredData = (parsedData) => { |
| | | const type = String(parsedData?.type || '') |
| | | if (!financialStructuredTypeSet.has(type) || parsedData?.success !== true) return null |
| | | |
| | | const rawData = isPlainObject(parsedData?.data) ? parsedData.data : {} |
| | | const headline = String(rawData?.headline || '').trim() |
| | | |
| | | return { |
| | | type, |
| | | summaryEntries: normalizeManufacturingSummaryEntries(parsedData?.summary), |
| | | headline, |
| | | conclusions: toStructuredStringArray(rawData?.conclusions), |
| | | riskSuggestions: toStructuredStringArray(rawData?.riskSuggestions), |
| | | sections: buildFinancialStructuredSections(rawData) |
| | | } |
| | | } |
| | | |
| | | const getFinancialTypeLabel = (type = '') => financialTypeLabelMap[String(type || '')] || 'è´¢å¡æ¥è¯¢ç»æ' |
| | | |
| | | const isSalesFocusType = (type = '') => salesFocusTypeSet.has(String(type || '')) |
| | | |
| | | const getSalesLevelTagType = (level = '') => { |
| | |
| | | .filter(Boolean) |
| | | } |
| | | return [] |
| | | } |
| | | |
| | | const normalizePurchaseIntentNotRecognizedData = (parsedData) => { |
| | | if (String(parsedData?.type || '') !== 'purchase_intent_not_recognized') return null |
| | | const quickPrompts = toStructuredStringArray(parsedData?.data?.quickPrompts) |
| | | if (!quickPrompts.length) return null |
| | | return { quickPrompts } |
| | | } |
| | | |
| | | const getManufacturingWarningLevelType = (level = '') => { |
| | |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null, |
| | | financeData: null, |
| | | localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : [] |
| | | } |
| | | |
| | |
| | | const candidate = text.slice(i, j + 1) |
| | | try { |
| | | const parsed = JSON.parse(candidate) |
| | | if (parsed?.success === true) { |
| | | if (typeof parsed?.success === 'boolean') { |
| | | return { |
| | | data: parsed, |
| | | startIdx: i, |
| | |
| | | } |
| | | |
| | | const applyStructuredMessageData = (messageObj, parsedData, msgIndex, shouldRenderCharts = true) => { |
| | | if (!messageObj || !parsedData?.success) return |
| | | const isPurchaseIntentNotRecognized = String(parsedData?.type || '') === 'purchase_intent_not_recognized' |
| | | if (!messageObj || (parsedData?.success !== true && !isPurchaseIntentNotRecognized)) return |
| | | |
| | | const previousManufacturingData = messageObj.manufacturingData |
| | | messageObj.type = parsedData.type || '' |
| | |
| | | messageObj.purchaseAnalysisData = null |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | messageObj.purchaseData = null |
| | | messageObj.purchaseIntentData = null |
| | | messageObj.financeData = null |
| | | |
| | | if (isPurchaseIntentNotRecognized) { |
| | | messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData) |
| | | messageObj.chartOptions = null |
| | | messageObj.chartRenderReady = false |
| | | return |
| | | } |
| | | |
| | | if (messageObj.type === 'todo_list' && parsedData.data) { |
| | | messageObj.tableData = parsedData.data |
| | |
| | | messageObj.manufacturingData = manufacturingData |
| | | } |
| | | |
| | | const purchaseData = buildPurchaseStructuredData(parsedData) |
| | | if (purchaseData) { |
| | | messageObj.purchaseData = purchaseData |
| | | } |
| | | |
| | | const financeData = buildFinancialStructuredData(parsedData) |
| | | if (financeData) { |
| | | messageObj.financeData = financeData |
| | | } |
| | | |
| | | if (parsedData.action === 'confirm_required' && parsedData.businessType) { |
| | | messageObj.type = 'purchase_analysis_confirm' |
| | | messageObj.purchaseAnalysisData = parsedData |
| | | messageObj.manufacturingData = null |
| | | messageObj.salesData = null |
| | | messageObj.purchaseData = null |
| | | messageObj.financeData = null |
| | | if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) { |
| | | initializePurchasePayloadTree(messageObj, parsedData.payload || {}) |
| | | } |
| | |
| | | const getStructuredFallbackText = (parsedData) => { |
| | | if (!parsedData) return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | if (parsedData.type === 'todo_list') return 'å·²ä¸ºæ¨æ´ç好ç¸å
³æ°æ®ã' |
| | | if (parsedData.type === 'purchase_intent_not_recognized') return 'æªè¯å«å°å¯æ§è¡çéè´æ¥è¯¢æ¡ä»¶ï¼è¯·è¡¥å
æ¥è¯¢æ¡ä»¶ååè¯ã' |
| | | if (salesStructuredTypeSet.has(parsedData.type)) { |
| | | if (parsedData.type === 'sales_customer_churn_risk') return '已为æ¨çæå®¢æ·æµå¤±é£é©åæã' |
| | | if (parsedData.type === 'sales_collection_quote_strategy') return '已为æ¨çæåæ¬¾ä¸æ¥ä»·çç¥å»ºè®®ã' |
| | |
| | | if (parsedData.type === 'manufacturing_analysis') return '已为æ¨çæå¶é åæç»æã' |
| | | return 'å·²è¿åå¶é æ¥è¯¢ç»æã' |
| | | } |
| | | if (financialStructuredTypeSet.has(parsedData.type)) return 'å·²è¿åè´¢å¡åæç»æã' |
| | | if (String(parsedData.type || '').startsWith('purchase_')) return 'å·²è¿åéè´æ¥è¯¢ç»æã' |
| | | if (parsedData.charts && Object.keys(parsedData.charts).length > 0) return '已为æ¨çæåæå¾è¡¨ã' |
| | | return 'æ£å¨ä¸ºæ¨å±ç¤ºåæç»æ...' |
| | | } |
| | |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null, |
| | | financeData: null |
| | | }) |
| | | |
| | | outputState.value[botMsgIndex] = { |
| | |
| | | payloadHiddenData: null, |
| | | purchaseAnalysisData: null, |
| | | manufacturingData: null, |
| | | salesData: null |
| | | salesData: null, |
| | | purchaseData: null, |
| | | purchaseIntentData: null, |
| | | financeData: null |
| | | } |
| | | messages.value.push(botMsg) |
| | | |
| | |
| | | const extracted = extractEmbeddedSuccessJson(fullText) |
| | | if (extracted) { |
| | | applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex) |
| | | } else { |
| | | const extractJson = (text) => { |
| | | const startIdx = text.indexOf('{"success": true') |
| | | if (startIdx === -1) return null |
| | | |
| | | // ä»åå¾åæ¾æåä¸ä¸ª '}' |
| | | const lastBraceIdx = text.lastIndexOf('}') |
| | | if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null |
| | | |
| | | const potentialJson = text.substring(startIdx, lastBraceIdx + 1) |
| | | try { |
| | | return JSON.parse(potentialJson) |
| | | } catch (err) { |
| | | return null |
| | | } |
| | | } |
| | | |
| | | const parsedData = extractJson(fullText) |
| | | if (parsedData) { |
| | | applyStructuredMessageData(currentMsg, parsedData, botMsgIndex, true) |
| | | } |
| | | |
| | | } |
| | | |
| | | updateOutputState(fullText, botMsgIndex) |
| | |
| | | const extracted = extractEmbeddedSuccessJson(currentMsg.content) |
| | | if (extracted) { |
| | | applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex) |
| | | } else { |
| | | const extractJson = (text) => { |
| | | const startIdx = text.indexOf('{"success": true') |
| | | if (startIdx === -1) return null |
| | | const lastBraceIdx = text.lastIndexOf('}') |
| | | if (lastBraceIdx === -1 || lastBraceIdx < startIdx) return null |
| | | const potentialJson = text.substring(startIdx, lastBraceIdx + 1) |
| | | try { |
| | | return JSON.parse(potentialJson) |
| | | } catch (err) { |
| | | return null |
| | | } |
| | | } |
| | | |
| | | const finalParsed = extractJson(currentMsg.content) |
| | | if (finalParsed) { |
| | | applyStructuredMessageData(currentMsg, finalParsed, botMsgIndex) |
| | | } |
| | | } |
| | | }).catch(err => { |
| | | if (err.name === 'CanceledError' || err.name === 'AbortError') { |
| | |
| | | color: $deep-blue; |
| | | } |
| | | |
| | | .finance-headline { |
| | | margin-top: 4px; |
| | | font-size: 13px; |
| | | line-height: 1.7; |
| | | color: #344054; |
| | | } |
| | | |
| | | .finance-text-section { |
| | | margin-top: 10px; |
| | | |
| | | ul { |
| | | margin: 6px 0 0; |
| | | padding-left: 18px; |
| | | font-size: 13px; |
| | | line-height: 1.7; |
| | | color: #344054; |
| | | } |
| | | } |
| | | |
| | | .purchase-intent-quick-prompt-wrap { |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .purchase-intent-quick-prompt-title { |
| | | font-size: 12px; |
| | | color: #4b5563; |
| | | } |
| | | |
| | | .purchase-intent-quick-prompt-list { |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .purchase-confirm-card { |
| | | margin-top: 12px; |
| | | width: 100%; |
| | |
| | | import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login'
|
| | | import { getToken, setToken, removeToken } from '@/utils/auth'
|
| | | import { isHttp, isEmpty } from "@/utils/validate"
|
| | | import defAva from '@/assets/images/profile.jpg'
|
| | | import { defineStore } from 'pinia'
|
| | |
|
| | | const useUserStore = defineStore(
|
| | | 'user',
|
| | | {
|
| | | import {login, logout, getInfo, loginCheck, loginCheckFactory,tideLogin} from '@/api/login' |
| | | import { getToken, setToken, removeToken } from '@/utils/auth' |
| | | import { isHttp, isEmpty } from "@/utils/validate" |
| | | import defAva from '@/assets/images/profile.jpg' |
| | | import { defineStore } from 'pinia' |
| | | |
| | | const useUserStore = defineStore( |
| | | 'user', |
| | | { |
| | | state: () => ({ |
| | | token: getToken(), |
| | | id: '', |
| | |
| | | roles: [], |
| | | permissions: [], |
| | | aiEnabled: 0 |
| | | }),
|
| | | actions: {
|
| | | // ç»å½
|
| | | }), |
| | | actions: { |
| | | // ç»å½ |
| | | login(userInfo) { |
| | | const username = userInfo.username.trim() |
| | | const password = userInfo.password |
| | |
| | | reject(error) |
| | | }) |
| | | }) |
| | | },
|
| | | getCurrentTime() {
|
| | | const now = new Date();
|
| | | const year = now.getFullYear(); // è·å年份
|
| | | const month = String(now.getMonth() + 1).padStart(2, '0'); // æä»½ä»0å¼å§ï¼è¦+1ï¼å¹¶è¡¥é¶
|
| | | const day = String(now.getDate()).padStart(2, '0'); // æ¥æè¡¥é¶
|
| | | const hours = String(now.getHours()).padStart(2, '0'); // å°æ¶è¡¥é¶
|
| | | const minutes = String(now.getMinutes()).padStart(2, '0'); // åéè¡¥é¶
|
| | | const seconds = String(now.getSeconds()).padStart(2, '0'); // ç§æ°è¡¥é¶
|
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
| | | },
|
| | | // è·åç¨æ·ä¿¡æ¯
|
| | | getInfo() {
|
| | | return new Promise((resolve, reject) => {
|
| | | getInfo().then(res => {
|
| | | const user = res.user || {} |
| | | let avatar = user.avatar || ""
|
| | | avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar
|
| | | if (res.roles && res.roles.length > 0) { // éªè¯è¿åçrolesæ¯å¦æ¯ä¸ä¸ªé空æ°ç»
|
| | | this.roles = res.roles
|
| | | this.permissions = res.permissions
|
| | | } else {
|
| | | this.roles = ['ROLE_DEFAULT']
|
| | | }
|
| | | }, |
| | | getCurrentTime() { |
| | | const now = new Date(); |
| | | const year = now.getFullYear(); // è·å年份 |
| | | const month = String(now.getMonth() + 1).padStart(2, '0'); // æä»½ä»0å¼å§ï¼è¦+1ï¼å¹¶è¡¥é¶ |
| | | const day = String(now.getDate()).padStart(2, '0'); // æ¥æè¡¥é¶ |
| | | const hours = String(now.getHours()).padStart(2, '0'); // å°æ¶è¡¥é¶ |
| | | const minutes = String(now.getMinutes()).padStart(2, '0'); // åéè¡¥é¶ |
| | | const seconds = String(now.getSeconds()).padStart(2, '0'); // ç§æ°è¡¥é¶ |
| | | return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
| | | }, |
| | | // è·åç¨æ·ä¿¡æ¯ |
| | | getInfo() { |
| | | return new Promise((resolve, reject) => { |
| | | getInfo().then(res => { |
| | | const data = res?.data ?? res |
| | | const user = data.user || {} |
| | | let avatar = user.avatar || "" |
| | | avatar = import.meta.env.VITE_APP_BASE_API + '/profile/' + avatar |
| | | if (data.roles && data.roles.length > 0) { // éªè¯è¿åçrolesæ¯å¦æ¯ä¸ä¸ªé空æ°ç» |
| | | this.roles = data.roles |
| | | this.permissions = data.permissions |
| | | } else { |
| | | this.roles = ['ROLE_DEFAULT'] |
| | | } |
| | | this.id = user.userId || '' |
| | | this.name = user.userName || '' |
| | | this.avatar = avatar
|
| | | this.avatar = avatar |
| | | this.currentFactoryName = user.currentFactoryName || '' |
| | | this.nickName = user.nickName || '' |
| | | this.roleName = Array.isArray(user.roles) && user.roles.length > 0 ? (user.roles[0].roleName || '') : '' |
| | | this.currentDeptId = user.tenantId || '' |
| | | this.currentLoginTime = this.getCurrentTime() |
| | | this.aiEnabled = Number(res.aiEnabled) === 1 ? 1 : 0 |
| | | resolve(res) |
| | | this.aiEnabled = Number(data.aiEnabled) === 1 ? 1 : 0 |
| | | resolve(data) |
| | | }).catch(error => { |
| | | reject(error) |
| | | }) |
| | | }) |
| | | },
|
| | | // éåºç³»ç»
|
| | | logOut() {
|
| | | return new Promise((resolve, reject) => {
|
| | | logout(this.token).then(() => {
|
| | | }, |
| | | // éåºç³»ç» |
| | | logOut() { |
| | | return new Promise((resolve, reject) => { |
| | | logout(this.token).then(() => { |
| | | this.token = '' |
| | | this.roles = [] |
| | | this.permissions = [] |
| | |
| | | }).catch(error => { |
| | | reject(error) |
| | | }) |
| | | })
|
| | | },
|
| | | // ç»å½æ ¡éª
|
| | | loginCheck(userInfo) {
|
| | | const username = userInfo.username.trim()
|
| | | const password = userInfo.password
|
| | | return new Promise((resolve, reject) => {
|
| | | loginCheck(username, password).then(res => {
|
| | | resolve(res)
|
| | | }).catch(error => {
|
| | | reject(error)
|
| | | })
|
| | | })
|
| | | },
|
| | | // é¨é¨ç»å½
|
| | | }) |
| | | }, |
| | | // ç»å½æ ¡éª |
| | | loginCheck(userInfo) { |
| | | const username = userInfo.username.trim() |
| | | const password = userInfo.password |
| | | return new Promise((resolve, reject) => { |
| | | loginCheck(username, password).then(res => { |
| | | resolve(res) |
| | | }).catch(error => { |
| | | reject(error) |
| | | }) |
| | | }) |
| | | }, |
| | | // é¨é¨ç»å½ |
| | | loginCheckFactory(userInfo) { |
| | | const username = userInfo.username.trim() |
| | | const password = userInfo.password |
| | |
| | | reject(error) |
| | | }) |
| | | }) |
| | | },
|
| | | }, |
| | | TideLogin(code) { |
| | | return new Promise((resolve, reject) => { |
| | | tideLogin(code) |
| | |
| | | }; |
| | | resolve(); |
| | | }) |
| | | .catch((error) => {
|
| | | reject(error);
|
| | | });
|
| | | });
|
| | | },
|
| | | }
|
| | | })
|
| | |
|
| | | export default useUserStore
|
| | | .catch((error) => { |
| | | reject(error); |
| | | }); |
| | | }); |
| | | }, |
| | | } |
| | | }) |
| | | |
| | | export default useUserStore |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <FormDialog |
| | | v-model="dialogVisible" |
| | | title="ä¸ä¼ å·¡æ£è®°å½" |
| | | width="980px" |
| | | @close="handleClose" |
| | | @cancel="handleClose" |
| | | > |
| | | <main class="upload-content"> |
| | | <el-card v-if="taskInfo" class="section-card"> |
| | | <el-descriptions :column="1" border> |
| | | <el-descriptions-item label="å·¡æ£ä»»å¡åç§°"> |
| | | {{ taskInfo.taskName || "-" }} |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å·¡æ£é¡¹ç®"> |
| | | {{ taskInfo.inspectionProject || "-" }} |
| | | </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-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"> |
| | | <h3>å¼å¸¸æè¿°</h3> |
| | | <el-input |
| | | v-model="abnormalDescription" |
| | | type="textarea" |
| | | maxlength="500" |
| | | show-word-limit |
| | | :rows="4" |
| | | 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-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-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" |
| | | > |
| | | <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> |
| | | <span>è§é¢</span> |
| | | </div> |
| | | |
| | | <el-button |
| | | class="delete-btn" |
| | | type="danger" |
| | | circle |
| | | size="small" |
| | | @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 class="file-size">{{ formatFileSize(file.size) }}</div> |
| | | </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-card> |
| | | |
| | | <el-result |
| | | v-if="hasException === false" |
| | | icon="success" |
| | | title="设å¤è¿è¡æ£å¸¸" |
| | | sub-title="æ éä¸ä¼ ç
§ç" |
| | | /> |
| | | </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> |
| | | <el-button @click="handleClose">åæ¶</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> |
| | | </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"; |
| | | |
| | | 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 beforeModelValue = ref([]); |
| | | const afterModelValue = ref([]); |
| | | const issueModelValue = 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}`; |
| | | } |
| | | } |
| | | |
| | | 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)", |
| | | }); |
| | | |
| | | 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 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, |
| | | }, |
| | | }; |
| | | |
| | | 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; |
| | | } |
| | | |
| | | .upload-content { |
| | | max-width: 960px; |
| | | margin: 20px auto 0; |
| | | } |
| | | |
| | | .section-card { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .section-card h3 { |
| | | margin: 0 0 16px; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | .upload-buttons { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin: 16px 0; |
| | | } |
| | | |
| | | .upload-progress { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .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 { |
| | | 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; |
| | | } |
| | | |
| | | .delete-btn { |
| | | position: absolute; |
| | | top: 6px; |
| | | right: 6px; |
| | | } |
| | | |
| | | .file-info { |
| | | margin-top: 6px; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .file-name { |
| | | color: #606266; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .file-size { |
| | | color: #909399; |
| | | margin-top: 2px; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .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 style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in beforeProductionImgs" :key="index" |
| | | @click="showMedia(beforeProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | |
| | | <div 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')" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | |
| | | <div class="media-list"> |
| | | <div |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | v-for="(videoUrl, index) in beforeProductionVideos" |
| | | :key="`before-video-${index}`" |
| | | class="video-item" |
| | | @click="showMedia(beforeProductionVideos, index, 'video')" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | <div class="video-thumb"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" class="video-icon" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | <div class="video-text">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç产å --> |
| | | |
| | | <div class="form-container"> |
| | | <div class="title">ç产ä¸</div> |
| | | |
| | | <div class="media-list"> |
| | | <img |
| | | v-for="(item, index) in afterProductionImgs" |
| | | :key="`during-img-${index}`" |
| | | :src="item" |
| | | alt="" |
| | | class="media-image" |
| | | @click="showMedia(afterProductionImgs, index, 'image')" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="media-list"> |
| | | <div |
| | | v-for="(videoUrl, index) in afterProductionVideos" |
| | | :key="`during-video-${index}`" |
| | | class="video-item" |
| | | @click="showMedia(afterProductionVideos, index, 'video')" |
| | | > |
| | | <div class="video-thumb"> |
| | | <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="title">ç产å</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in afterProductionImgs" :key="index" |
| | | @click="showMedia(afterProductionImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | |
| | | <div class="media-list"> |
| | | <img |
| | | v-for="(item, index) in productionIssuesImgs" |
| | | :key="`after-img-${index}`" |
| | | :src="item" |
| | | alt="" |
| | | class="media-image" |
| | | @click="showMedia(productionIssuesImgs, index, 'image')" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | |
| | | <div class="media-list"> |
| | | <div |
| | | v-for="(videoUrl, index) in afterProductionVideos" |
| | | :key="index" |
| | | @click="showMedia(afterProductionVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | v-for="(videoUrl, index) in productionIssuesVideos" |
| | | :key="`after-video-${index}`" |
| | | class="video-item" |
| | | @click="showMedia(productionIssuesVideos, index, 'video')" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | <div class="video-thumb"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" class="video-icon" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ç产é®é¢ --> |
| | | <div class="form-container"> |
| | | <div class="title">ç产é®é¢</div> |
| | | |
| | | <!-- å¾çå表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <img v-for="(item, index) in productionIssuesImgs" :key="index" |
| | | @click="showMedia(productionIssuesImgs, index, 'image')" |
| | | :src="item" style="max-width: 100px; height: 100px; margin: 5px;" alt=""> |
| | | </div> |
| | | |
| | | <!-- è§é¢å表 --> |
| | | <div style="display: flex; flex-wrap: wrap;"> |
| | | <div |
| | | v-for="(videoUrl, index) in productionIssuesVideos" |
| | | :key="index" |
| | | @click="showMedia(productionIssuesVideos, index, 'video')" |
| | | style="position: relative; margin: 10px; cursor: pointer;" |
| | | > |
| | | <div style="width: 160px; height: 90px; background-color: #333; display: flex; align-items: center; justify-content: center;"> |
| | | <img src="@/assets/images/video.png" alt="ææ¾" style="width: 30px; height: 30px; opacity: 0.8;" /> |
| | | </div> |
| | | <div style="text-align: center; font-size: 12px; color: #666;">ç¹å»ææ¾</div> |
| | | <div class="video-text">ç¹å»ææ¾</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-dialog> |
| | | |
| | | <!-- ç»ä¸åªä½æ¥çå¨ --> |
| | | |
| | | <div v-if="isMediaViewerVisible" class="media-viewer-overlay" @click.self="closeMediaViewer"> |
| | | <div class="media-viewer-content" @click.stop> |
| | | <!-- å¾ç --> |
| | | <vue-easy-lightbox |
| | | v-if="mediaType === 'image'" |
| | | :visible="isMediaViewerVisible" |
| | | :imgs="mediaList" |
| | | :index="currentMediaIndex" |
| | | @hide="closeMediaViewer" |
| | | ></vue-easy-lightbox> |
| | | |
| | | <!-- è§é¢ --> |
| | | <div v-else-if="mediaType === 'video'" style="position: relative;"> |
| | | <video |
| | | :src="mediaList[currentMediaIndex]" |
| | | autoplay |
| | | controls |
| | | style="max-width: 90vw; max-height: 80vh;" |
| | | /> |
| | | 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> |
| | | </div> |
| | | </template> |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import VueEasyLightbox from 'vue-easy-lightbox'; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // æ§å¶å¼¹çªæ¾ç¤º |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import VueEasyLightbox from "vue-easy-lightbox"; |
| | | |
| | | const dialogVisitable = ref(false); |
| | | |
| | | // å¾çæ°ç» |
| | | const beforeProductionImgs = ref([]); |
| | | const afterProductionImgs = ref([]); |
| | | const productionIssuesImgs = 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'); // image | video |
| | | const mediaList = ref([]); |
| | | const mediaType = ref("image"); |
| | | |
| | | // å¤çæ¯ä¸ç±»æ°æ®ï¼å离å¾çåè§é¢ |
| | | function processItems(items) { |
| | | 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 processItems = items => { |
| | | const images = []; |
| | | const videos = []; |
| | | |
| | | // æ£æ¥ items æ¯å¦åå¨ä¸ä¸ºæ°ç» |
| | | if (!items || !Array.isArray(items)) { |
| | | |
| | | if (!Array.isArray(items)) { |
| | | return { images, videos }; |
| | | } |
| | | |
| | | |
| | | items.forEach(item => { |
| | | if (!item || !item.previewURL || !item.contentType) return; |
| | | if (!item) return; |
| | | |
| | | |
| | | // å¤çæä»¶ URL |
| | | const fileUrl = item.previewURL; |
| | | const contentType = String(item.contentType).toLowerCase(); |
| | | const fileUrl = processFileUrl( |
| | | item.previewURL || item.url || item.downloadUrl || item.path || "" |
| | | ); |
| | | const contentType = String(item.contentType || "").toLowerCase(); |
| | | |
| | | // æ ¹æ® contentType 夿æ¯å¾çè¿æ¯è§é¢ |
| | | if (contentType.startsWith('image/')) { |
| | | images.push(fileUrl); |
| | | } else if (contentType.startsWith('video/')) { |
| | | if (!fileUrl) return; |
| | | |
| | | if (contentType.startsWith("video/")) { |
| | | videos.push(fileUrl); |
| | | return; |
| | | } |
| | | }); |
| | | |
| | | return { images, videos }; |
| | | } |
| | | |
| | | // æå¼å¼¹çªå¹¶å è½½æ°æ® |
| | | const openDialog = async (row) => { |
| | | // ä½¿ç¨æ£ç¡®çåæ®µåï¼commonFileListBefore, commonFileListAfter |
| | | const { images: beforeImgs, videos: beforeVids } = processItems(row.commonFileListBeforeVO || []); |
| | | const { images: afterImgs, videos: afterVids } = processItems(row.commonFileListAfterVO || []); |
| | | const { images: issueImgs, videos: issueVids } = processItems(row.commonFileListVO || []); |
| | | |
| | | images.push(fileUrl); |
| | | }); |
| | | |
| | | return { images, videos }; |
| | | }; |
| | | |
| | | 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; |
| | | }; |
| | | |
| | | // æ¾ç¤ºåªä½ï¼å¾ç or è§é¢ï¼ |
| | | function showMedia(mediaArray, index, type) { |
| | | mediaList.value = mediaArray; |
| | | const showMedia = (items, index, type) => { |
| | | mediaList.value = items; |
| | | currentMediaIndex.value = index; |
| | | mediaType.value = type; |
| | | isMediaViewerVisible.value = true; |
| | | } |
| | | }; |
| | | |
| | | // å
³éåªä½æ¥çå¨ |
| | | function closeMediaViewer() { |
| | | const closeMediaViewer = () => { |
| | | isMediaViewerVisible.value = false; |
| | | mediaList.value = []; |
| | | mediaType.value = 'image'; |
| | | } |
| | | mediaType.value = "image"; |
| | | }; |
| | | |
| | | // 表åå
³éæ¹æ³ |
| | | const cancel = () => { |
| | | dialogVisitable.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .upload-container { |
| | | display: flex; |
| | |
| | | padding: 20px; |
| | | border: 1px solid #dcdfe6; |
| | | box-sizing: border-box; |
| | | |
| | | |
| | | .form-container { |
| | | flex: 1; |
| | | width: 100%; |
| | |
| | | padding-left: 10px; |
| | | position: relative; |
| | | margin: 6px 0; |
| | | |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | |
| | | } |
| | | } |
| | | |
| | | .media-list { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .media-image { |
| | | max-width: 100px; |
| | | height: 100px; |
| | | margin: 5px; |
| | | 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-icon { |
| | | width: 30px; |
| | | height: 30px; |
| | | opacity: 0.8; |
| | | } |
| | | |
| | | .video-text { |
| | | text-align: center; |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | |
| | | .media-viewer-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | inset: 0; |
| | | background-color: rgba(0, 0, 0, 0.8); |
| | | z-index: 9999; |
| | | display: flex; |
| | |
| | | max-height: 90vh; |
| | | overflow: hidden; |
| | | } |
| | | </style> |
| | | |
| | | .video-player-wrap { |
| | | position: relative; |
| | | } |
| | | |
| | | .video-player { |
| | | max-width: 90vw; |
| | | max-height: 80vh; |
| | | } |
| | | </style> |
| | |
| | | <form-dia ref="formDia" |
| | | @closeDia="handleQuery"></form-dia> |
| | | <view-files ref="viewFiles"></view-files> |
| | | <upload-files ref="uploadFiles" |
| | | @success="handleQuery" |
| | | @closeDia="handleQuery"></upload-files> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | // ç»ä»¶å¼å
¥ |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import FormDia from "@/views/equipmentManagement/inspectionManagement/components/formDia.vue"; |
| | | import UploadFiles from "@/views/equipmentManagement/inspectionManagement/components/uploadFiles.vue"; |
| | | import ViewFiles from "@/views/equipmentManagement/inspectionManagement/components/viewFiles.vue"; |
| | | |
| | | // æ¥å£å¼å
¥ |
| | |
| | | const { proxy } = getCurrentInstance(); |
| | | const formDia = ref(); |
| | | const viewFiles = ref(); |
| | | const uploadFiles = ref(); |
| | | |
| | | // æ¥è¯¢åæ° |
| | | const queryParams = reactive({ |
| | |
| | | |
| | | const operationConfig = { |
| | | label: "æä½", |
| | | width: 130, |
| | | width: operations.length > 1 ? 180 : 130, |
| | | fixed: "right", |
| | | align: 'center', |
| | | dataType: "action", |
| | | operation: operations |
| | | .map(op => { |
| | |
| | | return { |
| | | name: "ç¼è¾", |
| | | clickFun: handleAdd, |
| | | color: "#409EFF", |
| | | }; |
| | | case "upload": |
| | | return { |
| | | name: "ä¸ä¼ ", |
| | | clickFun: openUploadDialog, |
| | | color: "#409EFF", |
| | | }; |
| | | case "viewFile": |
| | |
| | | ]; |
| | | operationsArr.value = ["edit"]; |
| | | } else if (value === "task") { |
| | | const operationColumn = getOperationColumn(["viewFile"]); |
| | | const operationColumn = getOperationColumn(["upload", "viewFile"]); |
| | | // 宿¶ä»»å¡è®°å½ä¸å±ç¤º"æ¯å¦å¯ç¨"å |
| | | const taskColumns = columns.value.filter(col => col.prop !== "isEnabled"); |
| | | tableColumns.value = [ |
| | | ...taskColumns, |
| | | ...(operationColumn ? [operationColumn] : []), |
| | | ]; |
| | | operationsArr.value = ["viewFile"]; |
| | | operationsArr.value = ["upload", "viewFile"]; |
| | | } |
| | | pageNum.value = 1; |
| | | pageSize.value = 10; |
| | |
| | | // å¤ç inspector åæ®µï¼å°å符串转æ¢ä¸ºæ°ç»ï¼éç¨äºæææ
åµï¼ |
| | | tableData.value = rawData.map(item => { |
| | | const processedItem = { ...item }; |
| | | processedItem.__raw = { ...item }; |
| | | |
| | | // å¤ç inspector åæ®µ |
| | | if (processedItem.inspector) { |
| | |
| | | const viewFile = row => { |
| | | nextTick(() => { |
| | | viewFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | | const openUploadDialog = row => { |
| | | nextTick(() => { |
| | | uploadFiles.value?.openDialog(row); |
| | | }); |
| | | }; |
| | | |
| | |
| | | color: #909399; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å票代ç :"> |
| | | <el-input v-model="filters.invoiceCode" placeholder="请è¾å
¥å票代ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="å票å·ç :"> |
| | | <el-input v-model="filters.invoiceNo" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.invoiceNumber" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable filterable style="width: 200px;"> |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="认è¯ç¶æ:"> |
| | | <el-select v-model="filters.certifyStatus" placeholder="è¯·éæ©è®¤è¯ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="æªè®¤è¯" value="uncertified" /> |
| | | <el-option label="已认è¯" value="certified" /> |
| | | <el-option label="认è¯å¤±è´¥" value="failed" /> |
| | | <el-form-item label="å¼ç¥¨æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="æ£å¸¸" :value="0" /> |
| | | <el-option label="ä½åº" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-button type="success" @click="handleBatchCertify" icon="Check" :disabled="selectedRows.length === 0">æ¹é认è¯</el-button> |
| | | </div> |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <el-button @click="handleExport" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | |
| | | <template #totalAmount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.totalAmount) }}</span> |
| | | </template> |
| | | <template #certifyStatus="{ row }"> |
| | | <el-tag :type="getCertifyStatusType(row.certifyStatus)">{{ getCertifyStatusLabel(row.certifyStatus) }}</el-tag> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)" effect="light" round> |
| | | {{ getStatusLabel(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleCertify(row)" v-if="row.certifyStatus === 'uncertified'">认è¯</el-button> |
| | | <el-button type="warning" link @click="handleCancel(row)" v-if="isNormalStatus(row.status)">ä½åº</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <FormDialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | :operation-type="isView ? 'detail' : ''" |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog" |
| | | > |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-row v-if="isView" :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票代ç " prop="invoiceCode"> |
| | | <el-input v-model="form.invoiceCode" placeholder="请è¾å
¥å票代ç " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " /> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-tag :type="getStatusType(form.status)" effect="light" round> |
| | | {{ getStatusLabel(form.status) }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="form.supplierId" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | style="width: 100%;" |
| | | filterable |
| | | :disabled="isView" |
| | | @change="handleSupplierChange" |
| | | > |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :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="stockInRecordIds"> |
| | | <el-input |
| | | :model-value="inboundBatchDisplayText" |
| | | placeholder="请å
éæ©ä¾åºå" |
| | | readonly |
| | | :disabled="!form.supplierId || isView" |
| | | class="inbound-batch-input" |
| | | @click="handleInboundInputClick" |
| | | > |
| | | <template v-if="!isView" #append> |
| | | <el-button |
| | | :disabled="!form.supplierId" |
| | | :loading="inboundBatchLoading" |
| | | @click.stop="openInboundSelectDialog" |
| | | > |
| | | éæ© |
| | | </el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker v-model="form.invoiceDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | <el-date-picker |
| | | v-model="form.invoiceDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" /> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类å" prop="invoiceType"> |
| | | <el-select |
| | | v-model="form.invoiceType" |
| | | placeholder="è¯·éæ©å票类å" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | > |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="å¢å¼ç¨ä¸ç¨å票" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="å¢å¼ç¨æ®éå票" /> |
| | | <el-option label="çµåå票" value="çµåå票" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;" @change="calculateTax"> |
| | | <el-select |
| | | v-model="form.taxRate" |
| | | placeholder="è¯·éæ©ç¨ç" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="handleTaxRateChange" |
| | | > |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨é¢"> |
| | | <el-input v-model="form.taxAmount" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="认è¯ç¶æ" prop="certifyStatus"> |
| | | <el-select v-model="form.certifyStatus" placeholder="è¯·éæ©è®¤è¯ç¶æ" style="width: 100%;" disabled> |
| | | <el-option label="æªè®¤è¯" value="uncertified" /> |
| | | <el-option label="已认è¯" value="certified" /> |
| | | <el-option label="认è¯å¤±è´¥" value="failed" /> |
| | | </el-select> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | placeholder="æ ¹æ®å
¥åºåå«ç¨éé¢èªå¨æ¢ç®ï¼å¯ä¿®æ¹" |
| | | @change="calculateTaxFromExclusive" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è®¤è¯æ¥æ" prop="certifyDate"> |
| | | <el-date-picker v-model="form.certifyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" disabled /> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ç¨é¢"> |
| | | <el-input-number |
| | | v-model="form.taxAmount" |
| | | :min="0" |
| | | :precision="2" |
| | | :controls="false" |
| | | style="width: 100%;" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="ä»·ç¨å计"> |
| | | <el-input-number |
| | | v-model="form.totalAmount" |
| | | :min="0" |
| | | :precision="2" |
| | | :controls="false" |
| | | style="width: 100%;" |
| | | disabled |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" /> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" :disabled="isView" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <template v-if="!isView" #footer> |
| | | <el-button type="primary" :loading="submitLoading" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <el-dialog |
| | | v-model="inboundSelectVisible" |
| | | title="éæ©å
¥åºåå·" |
| | | width="1100px" |
| | | append-to-body |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | | @closed="handleInboundDialogClosed" |
| | | > |
| | | <el-table |
| | | ref="inboundTableRef" |
| | | v-loading="inboundBatchLoading" |
| | | :data="inboundBatchList" |
| | | row-key="id" |
| | | border |
| | | stripe |
| | | max-height="480" |
| | | @selection-change="handleInboundDialogSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="inboundBatches" label="å
¥åºåå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="supplierName" label="ä¾åºå" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="purchaseContractNumber" label="éè´è®¢åå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="inboundDate" label="å
¥åºæ¥æ" width="110" align="center" /> |
| | | <el-table-column prop="inboundAmount" label="å
¥åºéé¢(å«ç¨)" width="120" align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(getInboundRowTaxInclusiveAmount(row)) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmInboundSelection">ç¡®å®</el-button> |
| | | <el-button @click="inboundSelectVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { |
| | | getInboundBatchesBySupplier, |
| | | addAccountPurchaseInvoice, |
| | | listPageAccountPurchaseInvoice, |
| | | cancelAccountPurchaseInvoice, |
| | | deleteAccountPurchaseInvoice, |
| | | } from "@/api/financialManagement/accountPurchaseInvoice.js"; |
| | | |
| | | defineOptions({ |
| | | name: "è¿é¡¹å票", |
| | |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | const filters = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | invoiceNumber: "", |
| | | supplierId: "", |
| | | certifyStatus: "", |
| | | dateRange: [], |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å票代ç ", prop: "invoiceCode", width: "130" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "120" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "140" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "å¼ç¥¨æ¥æ", prop: "invoiceDate", width: "120" }, |
| | | { label: "éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", slot: "totalAmount" }, |
| | | { label: "认è¯ç¶æ", prop: "certifyStatus", slot: "certifyStatus" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "180", fixed: "right" }, |
| | | { label: "éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", dataType: "slot", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", dataType: "slot", slot: "totalAmount" }, |
| | | { label: "å票类å", prop: "invoiceType", width: "130" }, |
| | | { label: "ç¶æ", prop: "status", dataType: "slot", slot: "status", width: "90", align: "center" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | const isView = ref(false); |
| | | const submitLoading = ref(false); |
| | | const supplierList = ref([]); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | const inboundBatchList = ref([]); |
| | | const inboundBatchOptions = ref([]); |
| | | const inboundBatchLoading = ref(false); |
| | | const inboundSelectVisible = ref(false); |
| | | const inboundTableRef = ref(null); |
| | | const dialogInboundSelection = ref([]); |
| | | |
| | | const STATUS_LABEL_MAP = { 0: "æ£å¸¸", 1: "ä½åº" }; |
| | | const STATUS_TYPE_MAP = { 0: "success", 1: "info" }; |
| | | |
| | | const form = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | invoiceDate: "", |
| | | amount: 0, |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | certifyStatus: "uncertified", |
| | | certifyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | storageAttachmentId: undefined, |
| | | status: 0, |
| | | }); |
| | | |
| | | const rules = { |
| | | invoiceCode: [{ required: true, message: "请è¾å
¥å票代ç ", trigger: "blur" }], |
| | | invoiceNo: [{ required: true, message: "请è¾å
¥å票å·ç ", trigger: "blur" }], |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | stockInRecordIds: [{ required: true, type: "array", min: 1, message: "è¯·éæ©å
³èå
¥åºå", trigger: "change" }], |
| | | invoiceDate: [{ required: true, message: "è¯·éæ©å¼ç¥¨æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", supplierId: 1, supplierName: "åäº¬åææä¾åºå", invoiceDate: "2024-01-08", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, certifyStatus: "certified", certifyDate: "2024-01-15", content: "åææéè´", remark: "" }, |
| | | { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", invoiceDate: "2024-01-10", amount: 12000, taxRate: 13, taxAmount: 1560, totalAmount: 13560, certifyStatus: "uncertified", certifyDate: "", content: "çµåå
å¨ä»¶", remark: "" }, |
| | | { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", supplierId: 3, supplierName: "广å·å
è£
ææå", invoiceDate: "2024-01-12", amount: 3500, taxRate: 13, taxAmount: 455, totalAmount: 3955, certifyStatus: "certified", certifyDate: "2024-01-18", content: "å
è£
ææ", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const calculateTax = () => { |
| | | const normalizeStatus = (status) => { |
| | | if (status === undefined || status === null || status === "") return 0; |
| | | const num = Number(status); |
| | | return Number.isNaN(num) ? 0 : num; |
| | | }; |
| | | |
| | | const isNormalStatus = (status) => normalizeStatus(status) === 0; |
| | | |
| | | const getStatusLabel = (status) => STATUS_LABEL_MAP[normalizeStatus(status)] ?? "æ£å¸¸"; |
| | | |
| | | const getStatusType = (status) => STATUS_TYPE_MAP[normalizeStatus(status)] ?? "success"; |
| | | |
| | | const parseStockInRecordIds = (value) => { |
| | | if (!value) return []; |
| | | if (Array.isArray(value)) return value; |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .map((s) => (/^\d+$/.test(s) ? Number(s) : s)); |
| | | }; |
| | | |
| | | const formatInboundBatches = (value) => { |
| | | if (value === undefined || value === null || value === "") return ""; |
| | | if (Array.isArray(value)) return value.filter(Boolean).join("ã"); |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | }; |
| | | |
| | | const isSameInboundId = (a, b) => String(a) === String(b); |
| | | |
| | | const getInboundRowId = (row) => row?.id ?? row?.stockInRecordId; |
| | | |
| | | /** å
¥åºåéé¢ä¸ºå«ç¨ä»· */ |
| | | const getInboundRowTaxInclusiveAmount = (row) => |
| | | Number(row?.inboundAmount ?? row?.taxInclusivePrice ?? row?.totalAmount ?? 0); |
| | | |
| | | const normalizeInboundBatchOptions = (data) => { |
| | | const list = Array.isArray(data) ? data : []; |
| | | return list.map((item, index) => { |
| | | if (typeof item === "string" || typeof item === "number") { |
| | | const text = String(item); |
| | | return { label: text, value: text, inboundAmount: 0 }; |
| | | } |
| | | const label = |
| | | item.inboundBatches ?? item.batchNo ?? item.inboundNo ?? item.label ?? `å
¥åºå${index + 1}`; |
| | | const value = item.id ?? item.stockInRecordId ?? label; |
| | | return { |
| | | label: String(label), |
| | | value, |
| | | inboundAmount: getInboundRowTaxInclusiveAmount(item), |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | /** ä¸å«ç¨éé¢åæ´ï¼ç¨é¢ãä»·ç¨å计æ£åè®¡ç® */ |
| | | const calculateTaxFromExclusive = () => { |
| | | form.taxAmount = Number((form.amount * form.taxRate / 100).toFixed(2)); |
| | | form.totalAmount = Number((form.amount + form.taxAmount).toFixed(2)); |
| | | }; |
| | | |
| | | const getCertifyStatusLabel = (status) => { |
| | | const map = { uncertified: "æªè®¤è¯", certified: "已认è¯", failed: "认è¯å¤±è´¥" }; |
| | | return map[status] || status; |
| | | /** ä»·ç¨åè®¡åæ´ï¼æç¨çåç®ä¸å«ç¨éé¢ãç¨é¢ */ |
| | | 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 getCertifyStatusType = (status) => { |
| | | const map = { uncertified: "info", certified: "success", failed: "danger" }; |
| | | return map[status] || ""; |
| | | const handleTaxRateChange = () => { |
| | | if (form.totalAmount > 0) { |
| | | calculateTaxFromInclusive(form.totalAmount); |
| | | } else { |
| | | calculateTaxFromExclusive(); |
| | | } |
| | | }; |
| | | |
| | | /** æ ¹æ®å·²éå
¥åºåæ±æ»å«ç¨éé¢ï¼åç®ä¸å«ç¨éé¢ä¸ç¨é¢ */ |
| | | const syncInvoiceAmount = () => { |
| | | const selected = form.stockInRecordIds || []; |
| | | const sumFromOptions = inboundBatchOptions.value |
| | | .filter((opt) => selected.some((id) => isSameInboundId(id, opt.value))) |
| | | .reduce((acc, opt) => acc + (Number(opt.inboundAmount) || 0), 0); |
| | | |
| | | let taxInclusiveSum = sumFromOptions; |
| | | if (taxInclusiveSum <= 0 && selected.length) { |
| | | taxInclusiveSum = inboundBatchList.value |
| | | .filter((row) => selected.some((id) => isSameInboundId(id, getInboundRowId(row)))) |
| | | .reduce((acc, row) => acc + getInboundRowTaxInclusiveAmount(row), 0); |
| | | } |
| | | |
| | | calculateTaxFromInclusive(taxInclusiveSum > 0 ? Number(taxInclusiveSum.toFixed(2)) : 0); |
| | | }; |
| | | |
| | | const inboundBatchDisplayText = computed(() => { |
| | | if (form.inboundBatches) return form.inboundBatches; |
| | | const ids = form.stockInRecordIds || []; |
| | | if (!ids.length) return ""; |
| | | const labels = inboundBatchOptions.value |
| | | .filter((opt) => ids.some((id) => isSameInboundId(id, opt.value))) |
| | | .map((opt) => opt.label); |
| | | if (labels.length) return labels.join("ã"); |
| | | return ids.join("ã"); |
| | | }); |
| | | |
| | | const normalizeTableRow = (row) => ({ |
| | | ...row, |
| | | invoiceNo: row.invoiceNumber ?? row.invoiceNo, |
| | | invoiceDate: row.issueDate ?? row.invoiceDate, |
| | | amount: row.taxExclusivelPrice ?? row.amount, |
| | | taxAmount: row.taxPrice ?? row.taxAmount, |
| | | totalAmount: row.taxInclusivePrice ?? row.totalAmount, |
| | | content: row.invoiceContent ?? row.content, |
| | | status: normalizeStatus(row.status), |
| | | stockInRecordIds: row.stockInRecordIds ?? "", |
| | | inboundBatches: formatInboundBatches(row.inboundBatches), |
| | | }); |
| | | |
| | | const toFormNumber = (val) => { |
| | | const n = Number(val); |
| | | return Number.isFinite(n) ? n : 0; |
| | | }; |
| | | |
| | | const resolveFormAmounts = (row) => { |
| | | let amount = toFormNumber(row.taxExclusivelPrice ?? row.amount); |
| | | let taxAmount = toFormNumber(row.taxPrice ?? row.taxAmount); |
| | | let totalAmount = toFormNumber(row.taxInclusivePrice ?? row.totalAmount); |
| | | const taxRate = toFormNumber(row.taxRate) || 13; |
| | | |
| | | if (totalAmount > 0 && amount === 0 && taxAmount === 0) { |
| | | amount = Number((totalAmount / (1 + taxRate / 100)).toFixed(2)); |
| | | taxAmount = Number((totalAmount - amount).toFixed(2)); |
| | | } else if (totalAmount > 0 && amount > 0 && taxAmount === 0) { |
| | | taxAmount = Number((totalAmount - amount).toFixed(2)); |
| | | } else if (amount > 0 && taxAmount === 0 && totalAmount === 0) { |
| | | taxAmount = Number((amount * taxRate / 100).toFixed(2)); |
| | | totalAmount = Number((amount + taxAmount).toFixed(2)); |
| | | } else if (amount > 0 && taxAmount > 0 && totalAmount === 0) { |
| | | totalAmount = Number((amount + taxAmount).toFixed(2)); |
| | | } |
| | | |
| | | return { amount, taxAmount, totalAmount }; |
| | | }; |
| | | |
| | | const fillFormFromRow = (row) => { |
| | | const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds); |
| | | const { amount, taxAmount, totalAmount } = resolveFormAmounts(row); |
| | | Object.assign(form, { |
| | | invoiceNo: row.invoiceNo ?? row.invoiceNumber ?? "", |
| | | supplierId: row.supplierId, |
| | | invoiceDate: row.invoiceDate ?? row.issueDate ?? "", |
| | | invoiceType: row.invoiceType ?? "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: row.taxRate ?? 13, |
| | | amount, |
| | | taxAmount, |
| | | totalAmount, |
| | | content: row.content ?? row.invoiceContent ?? "", |
| | | remark: row.remark ?? "", |
| | | stockInRecordIds, |
| | | inboundBatches: formatInboundBatches(row.inboundBatches), |
| | | storageAttachmentId: row.storageAttachmentId, |
| | | status: normalizeStatus(row.status), |
| | | }); |
| | | }; |
| | | |
| | | const buildCancelPayload = (row) => ({ |
| | | id: row.id, |
| | | invoiceNumber: row.invoiceNumber ?? row.invoiceNo, |
| | | taxRate: row.taxRate, |
| | | invoiceType: row.invoiceType, |
| | | issueDate: row.issueDate ?? row.invoiceDate, |
| | | taxExclusivelPrice: row.taxExclusivelPrice ?? row.amount, |
| | | taxPrice: row.taxPrice ?? row.taxAmount, |
| | | taxInclusivePrice: row.taxInclusivePrice ?? row.totalAmount, |
| | | remark: row.remark ?? "", |
| | | invoiceContent: row.invoiceContent ?? row.content, |
| | | supplierId: row.supplierId, |
| | | storageAttachmentId: row.storageAttachmentId, |
| | | stockInRecordIds: row.stockInRecordIds ?? "", |
| | | status: 1, |
| | | }); |
| | | |
| | | const buildSubmitPayload = () => ({ |
| | | invoiceNumber: form.invoiceNo, |
| | | supplierId: form.supplierId, |
| | | issueDate: form.invoiceDate, |
| | | invoiceType: form.invoiceType, |
| | | taxRate: form.taxRate, |
| | | taxExclusivelPrice: form.amount, |
| | | taxPrice: form.taxAmount, |
| | | taxInclusivePrice: form.totalAmount, |
| | | invoiceContent: form.content, |
| | | remark: form.remark || "", |
| | | stockInRecordIds: (form.stockInRecordIds || []).join(","), |
| | | status: 0, |
| | | storageAttachmentId: form.storageAttachmentId, |
| | | }); |
| | | |
| | | const getSupplierList = () => { |
| | | getOptions().then((res) => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data ?? []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const appendFilterParams = (params) => { |
| | | if (filters.invoiceNumber) { |
| | | params.invoiceNumber = filters.invoiceNumber; |
| | | } |
| | | if (filters.supplierId) { |
| | | params.supplierId = filters.supplierId; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | if (filters.status !== "" && filters.status != null) { |
| | | params.status = filters.status; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => appendFilterParams({}); |
| | | |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/accountPurchaseInvoice/exportAccountPurchaseInvoice", |
| | | buildExportParams(), |
| | | `è¿é¡¹å票_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.invoiceCode) { |
| | | result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode)); |
| | | } |
| | | if (filters.invoiceNo) { |
| | | result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.certifyStatus) { |
| | | result = result.filter(item => item.certifyStatus === filters.certifyStatus); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountPurchaseInvoice(buildListParams()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const records = res.data?.records ?? []; |
| | | dataList.value = records.map(normalizeTableRow); |
| | | pagination.total = res.data?.total ?? 0; |
| | | } else { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.invoiceCode = ""; |
| | | filters.invoiceNo = ""; |
| | | filters.supplierId = ""; |
| | | filters.certifyStatus = ""; |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | const resetFilters = () => { |
| | | filters.invoiceNumber = ""; |
| | | filters.supplierId = ""; |
| | | filters.dateRange = []; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | isView.value = false; |
| | | inboundSelectVisible.value = false; |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | invoiceDate: new Date().toISOString().split("T")[0], |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | storageAttachmentId: undefined, |
| | | status: 0, |
| | | }); |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "å½å
¥å票"; |
| | | Object.assign(form, { |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | supplierId: "", |
| | | invoiceDate: new Date().toISOString().split('T')[0], |
| | | amount: 0, |
| | | taxRate: 13, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | certifyStatus: "uncertified", |
| | | certifyDate: "", |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å票"; |
| | | Object.assign(form, row); |
| | | resetForm(); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çå票: ${row.invoiceCode}-${row.invoiceNo}`); |
| | | isView.value = true; |
| | | dialogTitle.value = "æ¥çå票"; |
| | | fillFormFromRow(row); |
| | | if (row.supplierId) { |
| | | loadInboundBatches(row.supplierId, true, false); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleCertify = (row) => { |
| | | ElMessageBox.confirm("确认认è¯è¯¥å票åï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm(`确认ä½åºå票ã${row.invoiceNo ?? row.invoiceNumber}ãåï¼`, "ä½åºç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].certifyStatus = "certified"; |
| | | mockData[index].certifyDate = new Date().toISOString().split('T')[0]; |
| | | } |
| | | ElMessage.success("è®¤è¯æå"); |
| | | getTableData(); |
| | | cancelAccountPurchaseInvoice(buildCancelPayload(row)) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("ä½åºæå"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "ä½åºå¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("ä½åºå¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchCertify = () => { |
| | | ElMessageBox.confirm(`确认æ¹é认è¯éä¸ç ${selectedRows.value.length} å¼ å票åï¼`, "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤å票ã${row.invoiceNo ?? row.invoiceNumber}ãåï¼`, "å é¤ç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | type: "warning", |
| | | }).then(() => { |
| | | selectedRows.value.forEach(row => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1 && mockData[index].certifyStatus === "uncertified") { |
| | | mockData[index].certifyStatus = "certified"; |
| | | mockData[index].certifyDate = new Date().toISOString().split('T')[0]; |
| | | } |
| | | }); |
| | | ElMessage.success("æ¹éè®¤è¯æå"); |
| | | getTableData(); |
| | | deleteAccountPurchaseInvoice([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | formRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | submitLoading.value = true; |
| | | addAccountPurchaseInvoice(buildSubmitPayload()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å½å
¥æå"); |
| | | closeDialog(); |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å½å
¥å¤±è´¥"); |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name }); |
| | | ElMessage.success("å½å
¥æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å½å
¥å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const ensureInboundOptionsForSelected = () => { |
| | | const ids = form.stockInRecordIds || []; |
| | | ids.forEach((id) => { |
| | | const exists = inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, id)); |
| | | if (exists) return; |
| | | const fromList = inboundBatchList.value.find((row) => isSameInboundId(getInboundRowId(row), id)); |
| | | if (fromList) { |
| | | const [option] = normalizeInboundBatchOptions([fromList]); |
| | | if (option) inboundBatchOptions.value.push(option); |
| | | return; |
| | | } |
| | | inboundBatchOptions.value.push({ |
| | | label: String(id), |
| | | value: id, |
| | | inboundAmount: 0, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const restoreInboundTableSelection = () => { |
| | | nextTick(() => { |
| | | const table = inboundTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | const selectedIds = new Set((form.stockInRecordIds || []).map((id) => String(id))); |
| | | inboundBatchList.value.forEach((row) => { |
| | | const rowId = getInboundRowId(row); |
| | | if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) { |
| | | table.toggleRowSelection(row, true); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const loadInboundBatches = (supplierId, keepSelected = false, syncAmount = true) => { |
| | | if (!supplierId) { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | if (!keepSelected) { |
| | | form.stockInRecordIds = []; |
| | | form.inboundBatches = ""; |
| | | form.amount = 0; |
| | | form.taxAmount = 0; |
| | | form.totalAmount = 0; |
| | | } |
| | | return Promise.resolve(); |
| | | } |
| | | inboundBatchLoading.value = true; |
| | | return getInboundBatchesBySupplier({ supplierId }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const list = res.data?.records ?? res.data ?? []; |
| | | inboundBatchList.value = Array.isArray(list) ? list : []; |
| | | inboundBatchOptions.value = normalizeInboundBatchOptions(list); |
| | | } else { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | inboundBatchLoading.value = false; |
| | | if (keepSelected) { |
| | | ensureInboundOptionsForSelected(); |
| | | restoreInboundTableSelection(); |
| | | if (syncAmount && !isView.value) { |
| | | syncInvoiceAmount(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleSupplierChange = (supplierId) => { |
| | | form.stockInRecordIds = []; |
| | | form.inboundBatches = ""; |
| | | form.amount = 0; |
| | | form.taxAmount = 0; |
| | | form.totalAmount = 0; |
| | | loadInboundBatches(supplierId); |
| | | }; |
| | | |
| | | const handleInboundInputClick = () => { |
| | | if (isView.value) return; |
| | | openInboundSelectDialog(); |
| | | }; |
| | | |
| | | const openInboundSelectDialog = () => { |
| | | if (!form.supplierId || isView.value) return; |
| | | inboundSelectVisible.value = true; |
| | | loadInboundBatches(form.supplierId, true).then(() => { |
| | | restoreInboundTableSelection(); |
| | | }); |
| | | }; |
| | | |
| | | const handleInboundDialogSelectionChange = (selection) => { |
| | | dialogInboundSelection.value = selection; |
| | | }; |
| | | |
| | | const confirmInboundSelection = () => { |
| | | if (dialogInboundSelection.value.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸æ¡å
¥åºå"); |
| | | return; |
| | | } |
| | | form.stockInRecordIds = dialogInboundSelection.value |
| | | .map((row) => getInboundRowId(row)) |
| | | .filter((id) => id !== undefined && id !== null); |
| | | form.inboundBatches = dialogInboundSelection.value |
| | | .map((row) => row.inboundBatches ?? row.batchNo ?? "") |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | dialogInboundSelection.value.forEach((row) => { |
| | | const [option] = normalizeInboundBatchOptions([row]); |
| | | if (option && !inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, option.value))) { |
| | | inboundBatchOptions.value.push(option); |
| | | } |
| | | }); |
| | | inboundSelectVisible.value = false; |
| | | syncInvoiceAmount(); |
| | | formRef.value?.validateField("stockInRecordIds"); |
| | | }; |
| | | |
| | | const handleInboundDialogClosed = () => { |
| | | dialogInboundSelection.value = []; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .inbound-batch-input :deep(.el-input__wrapper) { |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| | |
| | | <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.paymentCode" placeholder="请è¾å
¥ä»æ¬¾åå·" clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.paymentNumber" |
| | | placeholder="请è¾å
¥ä»æ¬¾åå·" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.supplierId" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | clearable |
| | | filterable |
| | | style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾æ¹å¼:"> |
| | | <el-select v-model="filters.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" clearable style="width: 150px;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-select v-model="filters.paymentMethod" |
| | | placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" |
| | | clearable |
| | | style="width: 150px;"> |
| | | <el-option v-for="item in checkout_payment" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
仿¬¾" value="pending" /> |
| | | <el-option label="已宿" value="completed" /> |
| | | <el-option label="已忶" value="cancelled" /> |
| | | </el-select> |
| | | <el-form-item label="仿¬¾æ¥æ:"> |
| | | <el-date-picker v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" |
| | | @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="æ¬æä»æ¬¾å计" :value="totalPaymentAmount" precision="2" prefix="Â¥" /> |
| | | <el-statistic title="æ¬é¡µä»æ¬¾å计" |
| | | :value="totalPaymentAmount" |
| | | :precision="2" |
| | | prefix="Â¥" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ä»æ¬¾</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <el-button @click="handleExport" |
| | | icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | <PIMTable rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | @pagination="changePage"> |
| | | <template #amount="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #paymentMethod="{ row }"> |
| | | <el-tag>{{ getPaymentMethodLabel(row.paymentMethod) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="row.status === 'completed' ? 'success' : row.status === 'pending' ? 'warning' : 'info'"> |
| | | {{ row.status === 'completed' ? '已宿' : row.status === 'pending' ? 'å¾
仿¬¾' : '已忶' }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleComplete(row)" v-if="row.status === 'pending'">宿</el-button> |
| | | <el-button type="danger" link @click="handleCancel(row)" v-if="row.status === 'pending'">åæ¶</el-button> |
| | | <el-button :disabled="row.accountStatemen" |
| | | type="danger" |
| | | link |
| | | @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾åå·" prop="paymentCode"> |
| | | <el-input v-model="form.paymentCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
³èç³è¯·å" prop="applyCode"> |
| | | <el-select v-model="form.applyCode" placeholder="è¯·éæ©å
³èç³è¯·å" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in applyList" :key="item.applyCode" :label="item.applyCode" :value="item.applyCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¥æ" prop="paymentDate"> |
| | | <el-date-picker v-model="form.paymentDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·" prop="bankAccount" v-if="form.paymentMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankAccount" placeholder="请è¾å
¥é¶è¡è´¦å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡" prop="bankName" v-if="form.paymentMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankName" placeholder="请è¾å
¥å¼æ·è¡" /> |
| | | </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> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { ref, reactive, computed, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { |
| | | listPageAccountPurchasePayment, |
| | | deleteAccountPurchasePayment, |
| | | } from "@/api/financialManagement/accountPurchasePayment.js"; |
| | | |
| | | defineOptions({ |
| | | name: "仿¬¾å", |
| | | }); |
| | | defineOptions({ |
| | | name: "仿¬¾å", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | paymentCode: "", |
| | | supplierId: "", |
| | | paymentMethod: "", |
| | | status: "", |
| | | }); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "仿¬¾åå·", prop: "paymentCode", width: "150" }, |
| | | { label: "å
³èç³è¯·å", prop: "applyCode", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "仿¬¾æ¥æ", prop: "paymentDate", width: "120" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "仿¬¾æ¹å¼", prop: "paymentMethod", slot: "paymentMethod" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "220", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | |
| | | const applyList = [ |
| | | { applyCode: "FK2024001", supplierId: 1, amount: 5000 }, |
| | | { applyCode: "FK2024002", supplierId: 2, amount: 8000 }, |
| | | { applyCode: "FK2024003", supplierId: 3, amount: 3000 }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | paymentCode: "", |
| | | applyCode: "", |
| | | supplierId: "", |
| | | paymentDate: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | applyCode: [{ required: true, message: "è¯·éæ©å
³èç³è¯·å", trigger: "change" }], |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | paymentDate: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, paymentCode: "FKD2024001", applyCode: "FK2024001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", paymentDate: "2024-01-15", amount: 5000, paymentMethod: "bank_transfer", status: "completed", bankAccount: "6222021234567890123", bankName: "å·¥åé¶è¡", remark: "" }, |
| | | { id: 2, paymentCode: "FKD2024002", applyCode: "FK2024002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", paymentDate: "2024-01-18", amount: 8000, paymentMethod: "bank_transfer", status: "pending", bankAccount: "6222029876543210987", bankName: "建设é¶è¡", remark: "" }, |
| | | { id: 3, paymentCode: "FKD2024003", applyCode: "FK2024003", supplierId: 3, supplierName: "广å·å
è£
ææå", paymentDate: "2024-01-20", amount: 3000, paymentMethod: "cash", status: "completed", remark: "" }, |
| | | ]; |
| | | |
| | | const totalPaymentAmount = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getPaymentMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | }; |
| | | return map[method] || method; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.paymentCode) { |
| | | result = result.filter(item => item.paymentCode.includes(filters.paymentCode)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.paymentMethod) { |
| | | result = result.filter(item => item.paymentMethod === filters.paymentMethod); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.paymentCode = ""; |
| | | filters.supplierId = ""; |
| | | filters.paymentMethod = ""; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢ä»æ¬¾"; |
| | | Object.assign(form, { |
| | | paymentCode: "FKD" + Date.now().toString().slice(-8), |
| | | applyCode: "", |
| | | const filters = reactive({ |
| | | paymentNumber: "", |
| | | supplierId: "", |
| | | paymentDate: new Date().toISOString().split('T')[0], |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | paymentMethod: "", |
| | | dateRange: [], |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾ä»æ¬¾"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥ç仿¬¾å: ${row.paymentCode}`); |
| | | }; |
| | | |
| | | const handleComplete = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤è¯¥ä»æ¬¾å已宿åï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "completed"; |
| | | } |
| | | ElMessage.success("仿¬¾å®æ"); |
| | | getTableData(); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | }; |
| | | |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤åæ¶è¯¥ä»æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "cancelled"; |
| | | } |
| | | ElMessage.success("已忶"); |
| | | getTableData(); |
| | | const columns = [ |
| | | { label: "仿¬¾åå·", prop: "paymentNumber", width: "150" }, |
| | | { label: "å
³èç³è¯·å", prop: "invoiceApplicationNo", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "仿¬¾æ¥æ", prop: "paymentDate", width: "120" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { |
| | | label: "仿¬¾æ¹å¼", |
| | | prop: "paymentMethod", |
| | | dataType: "slot", |
| | | slot: "paymentMethod", |
| | | width: "120", |
| | | }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { |
| | | label: "æä½", |
| | | prop: "operation", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | width: "80", |
| | | fixed: "right", |
| | | }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const supplierList = ref([]); |
| | | |
| | | const totalPaymentAmount = computed(() => |
| | | dataList.value.reduce((sum, item) => sum + Number(item.amount ?? 0), 0) |
| | | ); |
| | | |
| | | const formatMoney = value => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value) |
| | | .toFixed(2) |
| | | .replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getPaymentMethodLabel = value => { |
| | | if (value === undefined || value === null || value === "") return "-"; |
| | | const item = checkout_payment.value?.find( |
| | | m => String(m.value) === String(value) |
| | | ); |
| | | return item?.label ?? value; |
| | | }; |
| | | |
| | | const normalizeTableRow = row => ({ |
| | | ...row, |
| | | paymentNumber: row.paymentNumber ?? row.paymentCode, |
| | | invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "", |
| | | amount: row.paymentAmount ?? row.amount, |
| | | bankAccountNum: row.bankAccountNum ?? row.bankAccount ?? "", |
| | | bankAccountName: row.bankAccountName ?? row.bankName ?? "", |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | const getSupplierList = () => { |
| | | getOptions().then(res => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data ?? []; |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | const appendFilterParams = params => { |
| | | if (filters.paymentNumber) { |
| | | params.paymentNumber = filters.paymentNumber; |
| | | } |
| | | if (filters.supplierId) { |
| | | params.supplierId = filters.supplierId; |
| | | } |
| | | if (filters.paymentMethod) { |
| | | params.paymentMethod = filters.paymentMethod; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => appendFilterParams({}); |
| | | |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/accountPurchasePayment/exportAccountPurchasePayment", |
| | | buildExportParams(), |
| | | `仿¬¾å_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountPurchasePayment(buildListParams()) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | dataList.value = (res.data?.records ?? []).map(normalizeTableRow); |
| | | pagination.total = res.data?.total ?? 0; |
| | | } else { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.paymentNumber = ""; |
| | | filters.supplierId = ""; |
| | | filters.paymentMethod = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm(`确认å é¤ä»æ¬¾åã${row.paymentNumber}ãåï¼`, "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountPurchasePayment([row.id]) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="ç³è¯·åå·:"> |
| | | <el-input v-model="filters.applyCode" placeholder="请è¾å
¥ç³è¯·åå·" clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.invoiceApplicationNo" placeholder="请è¾å
¥ç³è¯·åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable filterable style="width: 200px;"> |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-form-item label="å®¡æ ¸ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
审æ¹" value="pending" /> |
| | | <el-option label="已审æ¹" value="approved" /> |
| | | <el-option label="已驳å" value="rejected" /> |
| | | <el-option label="已仿¬¾" value="paid" /> |
| | | <el-option label="å¾
å®¡æ ¸" :value="0" /> |
| | | <el-option label="å®¡æ ¸éè¿" :value="1" /> |
| | | <el-option label="å®¡æ ¸ä¸éè¿" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ç³è¯·</el-button> |
| | | <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">æ¹éç³è¯·</el-button> |
| | | <el-button @click="handleExport" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #amount="{ row }"> |
| | |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">审æ¹</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="isPendingStatus(row.status)">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="isPendingStatus(row.status)">å®¡æ ¸</el-button> |
| | | <el-button type="warning" link @click="openPaymentDialog(row)" v-if="isApprovedStatus(row.status)">仿¬¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)" v-if="isPendingStatus(row.status)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <FormDialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | :operation-type="isView ? 'detail' : ''" |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog" |
| | | > |
| | | <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-tag :type="getStatusType(form.status)">{{ getStatusLabel(form.status) }}</el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·åå·" prop="applyCode"> |
| | | <el-input v-model="form.applyCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | <el-form-item label="ç³è¯·åå·" prop="invoiceApplicationNo"> |
| | | <el-input v-model="form.invoiceApplicationNo" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="form.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="form.supplierId" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | style="width: 100%;" |
| | | filterable |
| | | :disabled="isEdit || isView" |
| | | @change="handleSupplierChange" |
| | | > |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :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="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | <el-form-item label="å
³èå
¥åºå" prop="stockInRecordIds"> |
| | | <el-input |
| | | :model-value="inboundBatchDisplayText" |
| | | placeholder="请å
éæ©ä¾åºå" |
| | | readonly |
| | | :disabled="!form.supplierId || isEdit || isView" |
| | | class="inbound-batch-input" |
| | | @click="handleInboundInputClick" |
| | | > |
| | | <template v-if="!isEdit && !isView" #append> |
| | | <el-button |
| | | :disabled="!form.supplierId" |
| | | :loading="inboundBatchLoading" |
| | | @click.stop="openInboundSelectDialog" |
| | | > |
| | | éæ© |
| | | </el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æ" prop="applyDate"> |
| | | <el-date-picker |
| | | v-model="form.applyDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number |
| | | v-model="form.paymentAmount" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | placeholder="æ ¹æ®å
¥åºåèªå¨æ±æ»ï¼å¯ä¿®æ¹" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="form.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-select |
| | | v-model="form.paymentMethod" |
| | | placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | > |
| | | <el-option |
| | | v-for="item in checkout_payment" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="仿¬¾äºç±" prop="paymentContent"> |
| | | <el-input |
| | | v-model="form.paymentContent" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥ä»æ¬¾äºç±" |
| | | :disabled="isView" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template v-if="!isView" #footer> |
| | | <el-button type="primary" :loading="submitLoading" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <FormDialog |
| | | title="仿¬¾" |
| | | v-model="paymentDialogVisible" |
| | | width="800px" |
| | | @confirm="submitPayment" |
| | | @cancel="paymentDialogVisible = false" |
| | | > |
| | | <el-form :model="paymentForm" :rules="paymentRules" ref="paymentFormRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾åå·" prop="paymentNumber"> |
| | | <el-input v-model="paymentForm.paymentNumber" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å
³èç³è¯·å" prop="invoiceApplicationNo"> |
| | | <el-input v-model="paymentForm.invoiceApplicationNo" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æ" prop="applyDate"> |
| | | <el-date-picker v-model="form.applyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | <el-form-item label="ä¾åºå"> |
| | | <el-input v-model="paymentForm.supplierName" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ææä»æ¬¾æ¥æ" prop="expectedDate"> |
| | | <el-date-picker v-model="form.expectedDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | <el-form-item label="仿¬¾æ¥æ" prop="paymentDate"> |
| | | <el-date-picker |
| | | v-model="paymentForm.paymentDate" |
| | | type="date" |
| | | placeholder="éæ©æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
³èå
¥åºå" prop="relatedDocs"> |
| | | <el-select v-model="form.relatedDocs" multiple placeholder="è¯·éæ©å
³èå
¥åºå" style="width: 100%;"> |
| | | <el-option v-for="item in inList" :key="item.inCode" :label="item.inCode" :value="item.inCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="仿¬¾äºç±" prop="reason"> |
| | | <el-input v-model="form.reason" type="textarea" :rows="3" placeholder="请è¾å
¥ä»æ¬¾äºç±" /> |
| | | </el-form-item> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾éé¢" prop="paymentAmount"> |
| | | <el-input-number |
| | | v-model="paymentForm.paymentAmount" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="仿¬¾æ¹å¼" prop="paymentMethod"> |
| | | <el-select v-model="paymentForm.paymentMethod" placeholder="è¯·éæ©ä»æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option |
| | | v-for="item in checkout_payment" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="isBankTransferPayment(paymentForm.paymentMethod)" :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·" prop="bankAccount"> |
| | | <el-input v-model="paymentForm.bankAccount" placeholder="é¶è¡è´¦å·" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="弿·è¡" prop="bankName"> |
| | | <el-input v-model="paymentForm.bankName" placeholder="弿·è¡" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | <el-input v-model="paymentForm.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="paymentSubmitLoading" @click="submitPayment">ç¡®å®</el-button> |
| | | <el-button @click="paymentDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <el-dialog |
| | | v-model="inboundSelectVisible" |
| | | title="éæ©å
¥åºåå·" |
| | | width="1100px" |
| | | append-to-body |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | | @closed="handleInboundDialogClosed" |
| | | > |
| | | <el-table |
| | | ref="inboundTableRef" |
| | | v-loading="inboundBatchLoading" |
| | | :data="inboundBatchList" |
| | | row-key="id" |
| | | border |
| | | stripe |
| | | max-height="480" |
| | | @selection-change="handleInboundDialogSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="inboundBatches" label="å
¥åºåå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="supplierName" label="ä¾åºå" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="purchaseContractNumber" label="éè´è®¢åå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="inboundDate" label="å
¥åºæ¥æ" width="110" align="center" /> |
| | | <el-table-column prop="inboundAmount" label="å
¥åºéé¢(å«ç¨)" width="120" align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(getInboundRowTaxInclusiveAmount(row)) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmInboundSelection">ç¡®å®</el-button> |
| | | <el-button @click="inboundSelectVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from "vue"; |
| | | import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { |
| | | getInboundBatchesBySupplier, |
| | | addAccountPaymentApplication, |
| | | listPageAccountPaymentApplication, |
| | | updateAccountPaymentApplication, |
| | | auditAccountPaymentApplication, |
| | | deleteAccountPaymentApplication, |
| | | } from "@/api/financialManagement/accountPaymentApplication.js"; |
| | | import { addAccountPurchasePayment } from "@/api/financialManagement/accountPurchasePayment.js"; |
| | | |
| | | defineOptions({ |
| | | name: "仿¬¾ç³è¯·", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { checkout_payment } = proxy.useDict("checkout_payment"); |
| | | |
| | | const filters = reactive({ |
| | | applyCode: "", |
| | | invoiceApplicationNo: "", |
| | | supplierId: "", |
| | | status: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | const columns = [ |
| | | { label: "ç³è¯·åå·", prop: "applyCode", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "仿¬¾æ¹å¼", prop: "paymentMethod", slot: "paymentMethod" }, |
| | | { label: "仿¬¾éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { label: "仿¬¾æ¹å¼", prop: "paymentMethod", dataType: "slot", slot: "paymentMethod", width: "120" }, |
| | | { label: "ç³è¯·æ¥æ", prop: "applyDate", width: "120" }, |
| | | { label: "ææä»æ¬¾æ¥", prop: "expectedDate", width: "120" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "200", fixed: "right" }, |
| | | { label: "ç¶æ", prop: "status", dataType: "slot", slot: "status", width: "100" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const submitLoading = ref(false); |
| | | const currentId = ref(null); |
| | | const supplierList = ref([]); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | const inboundBatchList = ref([]); |
| | | const inboundBatchOptions = ref([]); |
| | | const inboundBatchLoading = ref(false); |
| | | const inboundSelectVisible = ref(false); |
| | | const inboundTableRef = ref(null); |
| | | const dialogInboundSelection = ref([]); |
| | | |
| | | const inList = [ |
| | | { inCode: "RK2024001", supplierId: 1 }, |
| | | { inCode: "RK2024002", supplierId: 2 }, |
| | | { inCode: "RK2024003", supplierId: 3 }, |
| | | ]; |
| | | const paymentDialogVisible = ref(false); |
| | | const paymentFormRef = ref(null); |
| | | const paymentSubmitLoading = ref(false); |
| | | |
| | | const paymentForm = reactive({ |
| | | paymentNumber: "", |
| | | invoiceApplicationNo: "", |
| | | supplierName: "", |
| | | supplierId: "", |
| | | accountPaymentApplicationId: null, |
| | | paymentDate: "", |
| | | paymentAmount: 0, |
| | | paymentMethod: "", |
| | | bankAccount: "", |
| | | bankName: "", |
| | | remark: "", |
| | | }); |
| | | |
| | | const paymentRules = { |
| | | paymentDate: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¥æ", trigger: "change" }], |
| | | paymentAmount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | }; |
| | | |
| | | const STATUS_LABEL_MAP = { 0: "å¾
å®¡æ ¸", 1: "å®¡æ ¸éè¿", 2: "å®¡æ ¸ä¸éè¿" }; |
| | | const STATUS_TYPE_MAP = { 0: "warning", 1: "success", 2: "danger" }; |
| | | |
| | | const form = reactive({ |
| | | applyCode: "", |
| | | invoiceApplicationNo: "", |
| | | supplierId: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | paymentAmount: 0, |
| | | paymentMethod: "", |
| | | applyDate: "", |
| | | expectedDate: "", |
| | | relatedDocs: [], |
| | | reason: "", |
| | | paymentContent: "", |
| | | remark: "", |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | status: 0, |
| | | }); |
| | | |
| | | const rules = { |
| | | supplierId: [{ required: true, message: "è¯·éæ©ä¾åºå", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | stockInRecordIds: [{ required: true, type: "array", min: 1, message: "è¯·éæ©å
³èå
¥åºå", trigger: "change" }], |
| | | paymentAmount: [{ required: true, message: "请è¾å
¥ä»æ¬¾éé¢", trigger: "blur" }], |
| | | paymentMethod: [{ required: true, message: "è¯·éæ©ä»æ¬¾æ¹å¼", trigger: "change" }], |
| | | applyDate: [{ required: true, message: "è¯·éæ©ç³è¯·æ¥æ", trigger: "change" }], |
| | | expectedDate: [{ required: true, message: "è¯·éæ©ææä»æ¬¾æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, applyCode: "FK2024001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", amount: 5000, paymentMethod: "bank_transfer", applyDate: "2024-01-12", expectedDate: "2024-01-15", status: "pending", relatedDocs: ["RK2024001"], reason: "æ¯ä»åææè´§æ¬¾", remark: "" }, |
| | | { id: 2, applyCode: "FK2024002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", amount: 8000, paymentMethod: "bank_transfer", applyDate: "2024-01-14", expectedDate: "2024-01-18", status: "approved", relatedDocs: ["RK2024002"], reason: "æ¯ä»çµåå
å¨ä»¶è´§æ¬¾", remark: "" }, |
| | | { id: 3, applyCode: "FK2024003", supplierId: 3, supplierName: "广å·å
è£
ææå", amount: 3000, paymentMethod: "cash", applyDate: "2024-01-16", expectedDate: "2024-01-20", status: "paid", relatedDocs: ["RK2024003"], reason: "æ¯ä»å
è£
ææè´§æ¬¾", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getPaymentMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | const normalizeStatus = (status) => { |
| | | if (status === undefined || status === null || status === "") return 0; |
| | | const num = Number(status); |
| | | return Number.isNaN(num) ? 0 : num; |
| | | }; |
| | | |
| | | const isPendingStatus = (status) => normalizeStatus(status) === 0; |
| | | |
| | | const isApprovedStatus = (status) => normalizeStatus(status) === 1; |
| | | |
| | | const isBankTransferPayment = (method) => { |
| | | if (method === undefined || method === null || method === "") return false; |
| | | const item = checkout_payment.value?.find((m) => String(m.value) === String(method)); |
| | | if (item?.label?.includes("é¶è¡")) return true; |
| | | return String(method) === "bank_transfer" || String(method).toLowerCase().includes("bank"); |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => STATUS_LABEL_MAP[normalizeStatus(status)] ?? "å¾
å®¡æ ¸"; |
| | | |
| | | const getStatusType = (status) => STATUS_TYPE_MAP[normalizeStatus(status)] ?? "warning"; |
| | | |
| | | const getPaymentMethodLabel = (value) => { |
| | | if (value === undefined || value === null || value === "") return "-"; |
| | | const item = checkout_payment.value?.find((m) => String(m.value) === String(value)); |
| | | return item?.label ?? value; |
| | | }; |
| | | |
| | | const getDefaultPaymentMethod = () => checkout_payment.value?.[0]?.value ?? ""; |
| | | |
| | | const parseStockInRecordIds = (value) => { |
| | | if (!value) return []; |
| | | if (Array.isArray(value)) return value; |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .map((s) => (/^\d+$/.test(s) ? Number(s) : s)); |
| | | }; |
| | | |
| | | const formatInboundBatches = (value) => { |
| | | if (value === undefined || value === null || value === "") return ""; |
| | | if (Array.isArray(value)) return value.filter(Boolean).join("ã"); |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | }; |
| | | |
| | | const isSameInboundId = (a, b) => String(a) === String(b); |
| | | |
| | | const getInboundRowId = (row) => row?.id ?? row?.stockInRecordId; |
| | | |
| | | const getInboundRowTaxInclusiveAmount = (row) => |
| | | Number(row?.inboundAmount ?? row?.taxInclusivePrice ?? row?.totalAmount ?? row?.amount ?? 0); |
| | | |
| | | const normalizeInboundBatchOptions = (data) => { |
| | | const list = Array.isArray(data) ? data : []; |
| | | return list.map((item, index) => { |
| | | const label = |
| | | item.inboundBatches ?? item.batchNo ?? item.inboundNo ?? `å
¥åºå${index + 1}`; |
| | | const value = item.id ?? item.stockInRecordId ?? label; |
| | | return { |
| | | label: String(label), |
| | | value, |
| | | inboundAmount: getInboundRowTaxInclusiveAmount(item), |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const syncPaymentAmount = () => { |
| | | const selected = form.stockInRecordIds || []; |
| | | let sum = inboundBatchOptions.value |
| | | .filter((opt) => selected.some((id) => isSameInboundId(id, opt.value))) |
| | | .reduce((acc, opt) => acc + (Number(opt.inboundAmount) || 0), 0); |
| | | |
| | | if (sum <= 0 && selected.length) { |
| | | sum = inboundBatchList.value |
| | | .filter((row) => selected.some((id) => isSameInboundId(id, getInboundRowId(row)))) |
| | | .reduce((acc, row) => acc + getInboundRowTaxInclusiveAmount(row), 0); |
| | | } |
| | | |
| | | form.paymentAmount = sum > 0 ? Number(sum.toFixed(2)) : 0; |
| | | }; |
| | | |
| | | const inboundBatchDisplayText = computed(() => { |
| | | if (form.inboundBatches) return form.inboundBatches; |
| | | const ids = form.stockInRecordIds || []; |
| | | if (!ids.length) return ""; |
| | | const labels = inboundBatchOptions.value |
| | | .filter((opt) => ids.some((id) => isSameInboundId(id, opt.value))) |
| | | .map((opt) => opt.label); |
| | | if (labels.length) return labels.join("ã"); |
| | | return ids.join("ã"); |
| | | }); |
| | | |
| | | const normalizeTableRow = (row) => ({ |
| | | ...row, |
| | | applyCode: row.invoiceApplicationNo ?? row.applyCode, |
| | | amount: row.paymentAmount ?? row.amount, |
| | | reason: row.paymentContent ?? row.reason, |
| | | status: normalizeStatus(row.status), |
| | | stockInRecordIds: row.stockInRecordIds ?? "", |
| | | inboundBatches: formatInboundBatches(row.inboundBatches), |
| | | }); |
| | | |
| | | const fillFormFromRow = (row) => { |
| | | const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds); |
| | | Object.assign(form, { |
| | | invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "", |
| | | supplierId: row.supplierId, |
| | | paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0), |
| | | paymentMethod: row.paymentMethod ?? getDefaultPaymentMethod(), |
| | | applyDate: row.applyDate ?? "", |
| | | paymentContent: row.paymentContent ?? row.reason ?? "", |
| | | remark: row.remark ?? "", |
| | | stockInRecordIds, |
| | | inboundBatches: formatInboundBatches(row.inboundBatches), |
| | | status: normalizeStatus(row.status), |
| | | }); |
| | | }; |
| | | |
| | | const buildPayloadFromRow = (row, statusOverride) => ({ |
| | | id: row.id, |
| | | supplierId: row.supplierId, |
| | | stockInRecordIds: |
| | | typeof row.stockInRecordIds === "string" |
| | | ? row.stockInRecordIds |
| | | : (row.stockInRecordIds || []).join(","), |
| | | invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "", |
| | | paymentMethod: row.paymentMethod, |
| | | paymentContent: row.paymentContent ?? row.reason ?? "", |
| | | applyDate: row.applyDate, |
| | | remark: row.remark ?? "", |
| | | status: statusOverride !== undefined ? statusOverride : normalizeStatus(row.status), |
| | | paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0), |
| | | }); |
| | | |
| | | const buildSubmitPayload = (forUpdate = false) => { |
| | | const payload = { |
| | | supplierId: form.supplierId, |
| | | stockInRecordIds: (form.stockInRecordIds || []).join(","), |
| | | invoiceApplicationNo: form.invoiceApplicationNo || "", |
| | | paymentMethod: form.paymentMethod, |
| | | paymentContent: form.paymentContent || "", |
| | | applyDate: form.applyDate, |
| | | remark: form.remark || "", |
| | | status: 0, |
| | | paymentAmount: form.paymentAmount, |
| | | }; |
| | | return map[method] || method; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | | } |
| | | return payload; |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const map = { pending: "å¾
审æ¹", approved: "已审æ¹", rejected: "已驳å", paid: "已仿¬¾" }; |
| | | return map[status] || status; |
| | | const getSupplierList = () => { |
| | | getOptions().then((res) => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data ?? []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { pending: "warning", approved: "success", rejected: "danger", paid: "primary" }; |
| | | return map[status] || ""; |
| | | const appendFilterParams = (params) => { |
| | | if (filters.invoiceApplicationNo) { |
| | | params.invoiceApplicationNo = filters.invoiceApplicationNo; |
| | | } |
| | | if (filters.supplierId) { |
| | | params.supplierId = filters.supplierId; |
| | | } |
| | | if (filters.status !== "" && filters.status != null) { |
| | | params.status = filters.status; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => appendFilterParams({}); |
| | | |
| | | const handleExport = () => { |
| | | proxy.download( |
| | | "/accountPaymentApplication/exportAccountPaymentApplication", |
| | | buildExportParams(), |
| | | `仿¬¾ç³è¯·_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.applyCode) { |
| | | result = result.filter(item => item.applyCode.includes(filters.applyCode)); |
| | | } |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountPaymentApplication(buildListParams()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | dataList.value = (res.data?.records ?? []).map(normalizeTableRow); |
| | | pagination.total = res.data?.total ?? 0; |
| | | } else { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.applyCode = ""; |
| | | filters.supplierId = ""; |
| | | filters.status = ""; |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | const resetFilters = () => { |
| | | filters.invoiceApplicationNo = ""; |
| | | filters.supplierId = ""; |
| | | filters.status = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | isView.value = false; |
| | | isEdit.value = false; |
| | | inboundSelectVisible.value = false; |
| | | }; |
| | | |
| | | const resetForm = () => { |
| | | Object.assign(form, { |
| | | invoiceApplicationNo: "", |
| | | supplierId: "", |
| | | paymentAmount: 0, |
| | | paymentMethod: getDefaultPaymentMethod(), |
| | | applyDate: new Date().toISOString().split("T")[0], |
| | | paymentContent: "", |
| | | remark: "", |
| | | stockInRecordIds: [], |
| | | inboundBatches: "", |
| | | status: 0, |
| | | }); |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "æ°å¢ä»æ¬¾ç³è¯·"; |
| | | Object.assign(form, { |
| | | applyCode: "FK" + Date.now().toString().slice(-8), |
| | | supplierId: "", |
| | | amount: 0, |
| | | paymentMethod: "bank_transfer", |
| | | applyDate: new Date().toISOString().split('T')[0], |
| | | expectedDate: "", |
| | | relatedDocs: [], |
| | | reason: "", |
| | | remark: "", |
| | | }); |
| | | resetForm(); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾ä»æ¬¾ç³è¯·"; |
| | | Object.assign(form, row); |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çç³è¯·å: ${row.applyCode}`); |
| | | isView.value = true; |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ¥ç仿¬¾ç³è¯·"; |
| | | fillFormFromRow(row); |
| | | if (row.supplierId) { |
| | | loadInboundBatches(row.supplierId, true, false); |
| | | } |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitAudit = (row, status) => { |
| | | auditAccountPaymentApplication(buildPayloadFromRow(row, status)) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success(status === 1 ? "å®¡æ ¸éè¿" : "å®¡æ ¸ä¸éè¿"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å®¡æ ¸å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å®¡æ ¸å¤±è´¥"); |
| | | }); |
| | | }; |
| | | |
| | | const handleAudit = (row) => { |
| | | ElMessageBox.confirm("确认审æ¹éè¿è¯¥ä»æ¬¾ç³è¯·åï¼", "æç¤º", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ ¸ç»æ", "仿¬¾ç³è¯·å®¡æ ¸", { |
| | | confirmButtonText: "å®¡æ ¸éè¿", |
| | | cancelButtonText: "å®¡æ ¸ä¸éè¿", |
| | | distinguishCancelAndClose: true, |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "approved"; |
| | | } |
| | | ElMessage.success("审æ¹éè¿"); |
| | | getTableData(); |
| | | }).catch((action) => { |
| | | if (action === "cancel") { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "rejected"; |
| | | }) |
| | | .then(() => { |
| | | submitAudit(row, 1); |
| | | }) |
| | | .catch((action) => { |
| | | if (action === "cancel") { |
| | | submitAudit(row, 2); |
| | | } |
| | | ElMessage.warning("已驳å"); |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const openPaymentDialog = (row) => { |
| | | Object.assign(paymentForm, { |
| | | paymentNumber: "", |
| | | invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "", |
| | | supplierName: row.supplierName ?? "", |
| | | supplierId: row.supplierId, |
| | | accountPaymentApplicationId: row.id, |
| | | paymentDate: new Date().toISOString().split("T")[0], |
| | | paymentAmount: Number(row.paymentAmount ?? row.amount ?? 0), |
| | | paymentMethod: row.paymentMethod ?? getDefaultPaymentMethod(), |
| | | bankAccount: row.bankAccountNum ?? row.bankAccount ?? "", |
| | | bankName: row.bankAccountName ?? row.bankName ?? "", |
| | | remark: "", |
| | | }); |
| | | paymentDialogVisible.value = true; |
| | | nextTick(() => { |
| | | paymentFormRef.value?.clearValidate(); |
| | | }); |
| | | }; |
| | | |
| | | const handleBatchApply = () => { |
| | | ElMessage.success(`æ¹éç³è¯· ${selectedRows.value.length} æ¡è®°å½`); |
| | | const submitPayment = () => { |
| | | paymentFormRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | paymentSubmitLoading.value = true; |
| | | addAccountPurchasePayment({ |
| | | accountPaymentApplicationId: paymentForm.accountPaymentApplicationId, |
| | | supplierId: paymentForm.supplierId, |
| | | paymentDate: paymentForm.paymentDate, |
| | | paymentMethod: paymentForm.paymentMethod, |
| | | paymentAmount: paymentForm.paymentAmount, |
| | | paymentNumber: paymentForm.paymentNumber || "", |
| | | remark: paymentForm.remark || "", |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("仿¬¾æå"); |
| | | paymentDialogVisible.value = false; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "仿¬¾å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("仿¬¾å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | paymentSubmitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤ç³è¯·åã${row.applyCode ?? row.invoiceApplicationNo}ãåï¼`, "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountPaymentApplication([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const supplier = supplierList.find(item => item.id === form.supplierId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, supplierName: supplier?.name }; |
| | | formRef.value?.validate((valid) => { |
| | | if (!valid) return; |
| | | submitLoading.value = true; |
| | | const request = isEdit.value |
| | | ? updateAccountPaymentApplication(buildSubmitPayload(true)) |
| | | : addAccountPaymentApplication(buildSubmitPayload(false)); |
| | | |
| | | request |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success(isEdit.value ? "ç¼è¾æå" : "æ°å¢æå"); |
| | | closeDialog(); |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "ä¿å失败"); |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, supplierName: supplier?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("ä¿å失败"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const ensureInboundOptionsForSelected = () => { |
| | | const ids = form.stockInRecordIds || []; |
| | | ids.forEach((id) => { |
| | | const exists = inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, id)); |
| | | if (exists) return; |
| | | const fromList = inboundBatchList.value.find((row) => isSameInboundId(getInboundRowId(row), id)); |
| | | if (fromList) { |
| | | const [option] = normalizeInboundBatchOptions([fromList]); |
| | | if (option) inboundBatchOptions.value.push(option); |
| | | return; |
| | | } |
| | | inboundBatchOptions.value.push({ |
| | | label: String(id), |
| | | value: id, |
| | | inboundAmount: 0, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const restoreInboundTableSelection = () => { |
| | | nextTick(() => { |
| | | const table = inboundTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | const selectedIds = new Set((form.stockInRecordIds || []).map((id) => String(id))); |
| | | inboundBatchList.value.forEach((row) => { |
| | | const rowId = getInboundRowId(row); |
| | | if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) { |
| | | table.toggleRowSelection(row, true); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const loadInboundBatches = (supplierId, keepSelected = false, syncAmount = true) => { |
| | | if (!supplierId) { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | if (!keepSelected) { |
| | | form.stockInRecordIds = []; |
| | | form.inboundBatches = ""; |
| | | form.paymentAmount = 0; |
| | | } |
| | | return Promise.resolve(); |
| | | } |
| | | inboundBatchLoading.value = true; |
| | | return getInboundBatchesBySupplier({ supplierId }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const list = res.data?.records ?? res.data ?? []; |
| | | inboundBatchList.value = Array.isArray(list) ? list : []; |
| | | inboundBatchOptions.value = normalizeInboundBatchOptions(list); |
| | | } else { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | inboundBatchList.value = []; |
| | | inboundBatchOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | inboundBatchLoading.value = false; |
| | | if (keepSelected) { |
| | | ensureInboundOptionsForSelected(); |
| | | restoreInboundTableSelection(); |
| | | if (syncAmount && !isView.value) { |
| | | syncPaymentAmount(); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleSupplierChange = (supplierId) => { |
| | | form.stockInRecordIds = []; |
| | | form.inboundBatches = ""; |
| | | form.paymentAmount = 0; |
| | | loadInboundBatches(supplierId); |
| | | }; |
| | | |
| | | const handleInboundInputClick = () => { |
| | | if (isEdit.value || isView.value) return; |
| | | openInboundSelectDialog(); |
| | | }; |
| | | |
| | | const openInboundSelectDialog = () => { |
| | | if (!form.supplierId || isEdit.value || isView.value) return; |
| | | inboundSelectVisible.value = true; |
| | | loadInboundBatches(form.supplierId, true, false).then(() => { |
| | | restoreInboundTableSelection(); |
| | | }); |
| | | }; |
| | | |
| | | const handleInboundDialogSelectionChange = (selection) => { |
| | | dialogInboundSelection.value = selection; |
| | | }; |
| | | |
| | | const confirmInboundSelection = () => { |
| | | if (dialogInboundSelection.value.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸æ¡å
¥åºå"); |
| | | return; |
| | | } |
| | | form.stockInRecordIds = dialogInboundSelection.value |
| | | .map((row) => getInboundRowId(row)) |
| | | .filter((id) => id !== undefined && id !== null); |
| | | form.inboundBatches = dialogInboundSelection.value |
| | | .map((row) => row.inboundBatches ?? row.batchNo ?? "") |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | dialogInboundSelection.value.forEach((row) => { |
| | | const [option] = normalizeInboundBatchOptions([row]); |
| | | if (option && !inboundBatchOptions.value.some((opt) => isSameInboundId(opt.value, option.value))) { |
| | | inboundBatchOptions.value.push(option); |
| | | } |
| | | }); |
| | | inboundSelectVisible.value = false; |
| | | syncPaymentAmount(); |
| | | formRef.value?.validateField("stockInRecordIds"); |
| | | }; |
| | | |
| | | const handleInboundDialogClosed = () => { |
| | | dialogInboundSelection.value = []; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | color: #f56c6c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .inbound-batch-input :deep(.el-input__wrapper) { |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| | |
| | | <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.inboundBatches" placeholder="请è¾å
¥å
¥åºåå·" clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.inboundBatches" |
| | | placeholder="请è¾å
¥å
¥åºåå·" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-input v-model="filters.supplierName" placeholder="请è¾å
¥ä¾åºå" clearable style="width: 200px;" /> |
| | | <el-select v-model="filters.supplierId" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | clearable |
| | | filterable |
| | | style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </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" |
| | | :page="{ |
| | | <PIMTable rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | @pagination="changePage"> |
| | | <template #inboundDate="{ row }"> |
| | | {{ row.InboundDate || row.inboundDate || "" }} |
| | | {{ row.inboundDate ?? row.InboundDate ?? "" }} |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase"; |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listPageAccountPurchase } from "@/api/financialManagement/accountPurchase"; |
| | | import { listSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | |
| | | defineOptions({ |
| | | name: "éè´å
¥åº", |
| | | }); |
| | | defineOptions({ |
| | | name: "éè´å
¥åº", |
| | | }); |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const filters = reactive({ |
| | | inboundBatches: "", |
| | | supplierName: "", |
| | | dateRange: [], |
| | | }); |
| | | const filters = reactive({ |
| | | inboundBatches: "", |
| | | supplierId: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | { |
| | | label: "å
¥åºæ¥æ", |
| | | prop: "InboundDate", |
| | | minWidth: "170", |
| | | dataType: "slot", |
| | | slot: "inboundDate", |
| | | }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { label: "éè´è®¢åå·", prop: "purchaseContractNumber", minWidth: "150" }, |
| | | ]; |
| | | const columns = [ |
| | | { label: "å
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | { |
| | | label: "å
¥åºæ¥æ", |
| | | prop: "inboundDate", |
| | | minWidth: "170", |
| | | dataType: "slot", |
| | | slot: "inboundDate", |
| | | }, |
| | | { label: "产ååç§°", prop: "productName", minWidth: "140" }, |
| | | { label: "产åè§æ ¼", prop: "specificationModel", minWidth: "140" }, |
| | | { |
| | | label: "éé¢", |
| | | prop: "inboundAmount", |
| | | minWidth: "120", |
| | | align: "right", |
| | | formatData: val => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | | : Number(val).toLocaleString("zh-CN", { |
| | | minimumFractionDigits: 2, |
| | | maximumFractionDigits: 2, |
| | | }), |
| | | }, |
| | | { label: "éè´è®¢åå·", prop: "purchaseContractNumber", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const supplierList = ref([]); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | inboundBatches: filters.inboundBatches || undefined, |
| | | supplierName: filters.supplierName || undefined, |
| | | const buildFilterParams = () => { |
| | | const params = {}; |
| | | if (filters.inboundBatches) { |
| | | params.inboundBatches = filters.inboundBatches; |
| | | } |
| | | if (filters.supplierId) { |
| | | params.supplierId = filters.supplierId; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | }; |
| | | 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 getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountPurchase({ |
| | | ...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 = []; |
| | | const getSupplierList = () => { |
| | | listSupplier({ current: -1, size: -1, isWhite: 0 }).then(res => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data?.records ?? []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.inboundBatches = ""; |
| | | filters.supplierName = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountPurchase({ |
| | | ...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 = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseInbound", |
| | | buildFilterParams(), |
| | | `éè´å
¥åº_${new Date().getTime()}.xlsx` |
| | | ); |
| | | }; |
| | | const resetFilters = () => { |
| | | filters.inboundBatches = ""; |
| | | filters.supplierId = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseInbound", |
| | | buildFilterParams(), |
| | | `éè´å
¥åº_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | 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> |
| | |
| | | <template> |
| | | <!-- éè´éè´§ --> |
| | | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="éè´§åå·:"> |
| | | <el-input |
| | | v-model="filters.returnNo" |
| | | placeholder="请è¾å
¥éè´§åå·" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | <el-input v-model="filters.returnNo" placeholder="请è¾å
¥éè´§åå·" clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-input |
| | | v-model="filters.supplierName" |
| | | placeholder="请è¾å
¥ä¾åºå" |
| | | clearable |
| | | style="width: 200px" |
| | | /> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable filterable style="width: 200px;"> |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="éè´§æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | |
| | | clearable |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div></div> |
| | | |
| | | <div> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | |
| | | size: pagination.pageSize, |
| | | |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | |
| | | </div> |
| | | </template> |
| | | |
| | | |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | import { listPageAccountPurchaseReturn } from "@/api/financialManagement/accountPurchase"; |
| | | import { listSupplier } from "@/api/basicData/supplierManageFile.js"; |
| | | |
| | | defineOptions({ |
| | | name: "éè´éè´§", |
| | |
| | | |
| | | const filters = reactive({ |
| | | returnNo: "", |
| | | |
| | | supplierName: "", |
| | | |
| | | supplierId: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | |
| | | pageSize: 10, |
| | | |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "éè´§åå·", prop: "returnNo", minWidth: "150" }, |
| | | |
| | | { label: "ä¾åºå", prop: "supplierName", minWidth: "180" }, |
| | | |
| | | { label: "å
³èå
¥åºåå·", prop: "inboundBatches", minWidth: "150" }, |
| | | |
| | | { label: "éè´§æ¥æ", prop: "preparedAt", minWidth: "170" }, |
| | | |
| | | { |
| | | label: "鿬¾æ»é¢", |
| | | |
| | | prop: "totalAmount", |
| | | |
| | | minWidth: "150", |
| | | |
| | | align: "right", |
| | | |
| | | formatData: (val) => |
| | | val === null || val === undefined || val === "" |
| | | ? "" |
| | |
| | | maximumFractionDigits: 2, |
| | | }), |
| | | }, |
| | | |
| | | { label: "éè´§æ¹å¼", prop: "returnType", minWidth: "150" }, |
| | | |
| | | { label: "éè´è®¢åå·", prop: "purchaseContractNumber", minWidth: "150" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | |
| | | const tableLoading = ref(false); |
| | | const supplierList = ref([]); |
| | | |
| | | function buildFilterParams() { |
| | | const params = { |
| | | returnNo: filters.returnNo || undefined, |
| | | |
| | | supplierName: filters.supplierName || undefined, |
| | | }; |
| | | |
| | | if (filters.dateRange && filters.dateRange.length === 2) { |
| | | const buildFilterParams = () => { |
| | | const params = {}; |
| | | if (filters.returnNo) { |
| | | params.returnNo = filters.returnNo; |
| | | } |
| | | if (filters.supplierId) { |
| | | params.supplierId = filters.supplierId; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | |
| | | return params; |
| | | } |
| | | }; |
| | | |
| | | const getSupplierList = () => { |
| | | listSupplier({ current: -1, size: -1, isWhite: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data?.records ?? []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | |
| | | listPageAccountPurchaseReturn({ |
| | | ...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 = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | |
| | | |
| | | const resetFilters = () => { |
| | | filters.returnNo = ""; |
| | | |
| | | filters.supplierName = ""; |
| | | |
| | | filters.supplierId = ""; |
| | | filters.dateRange = []; |
| | | |
| | | pagination.currentPage = 1; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | |
| | | pagination.pageSize = limit; |
| | | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | proxy.download( |
| | | "/accountPurchase/exportAccountPurchaseReturn", |
| | | |
| | | buildFilterParams(), |
| | | |
| | | `éè´éè´§_${new Date().getTime()}.xlsx` |
| | | `éè´éè´§_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | |
| | | justify-content: space-between; |
| | | |
| | | margin-bottom: 15px; |
| | | } |
| | | </style> |
| | | |
| | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="ä¾åºå:"> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable style="width: 200px;"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.supplierId" placeholder="è¯·éæ©ä¾åºå" clearable filterable style="width: 200px;"> |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #beginBalance="{ row }"> |
| | | <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.beginBalance) }}</span> |
| | | <template #openingBalance="{ row }"> |
| | | <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.openingBalance) }}</span> |
| | | </template> |
| | | <template #currentPayable="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.currentPayable) }}</span> |
| | | <template #currentPlan="{ row }"> |
| | | <span class="text-danger">Â¥{{ formatMoney(row.currentPlan) }}</span> |
| | | </template> |
| | | <template #currentPayment="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentPayment) }}</span> |
| | | <template #currentActually="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentActually) }}</span> |
| | | </template> |
| | | <template #endBalance="{ row }"> |
| | | <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.endBalance) }}</span> |
| | | <template #closingBalance="{ row }"> |
| | | <span :class="row.closingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.closingBalance) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="viewDetail(row)">æ¥çæç»</el-button> |
| | | <el-button type="primary" link @click="printStatement(row)">æå°</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | <h3>{{ currentSupplier }} åºä»å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%"> |
| | | <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | |
| | | <el-table-column prop="credit" label="è´·æ¹(åºä»)" width="120"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-danger">Â¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else-if="row.credit < 0" class="text-success">Â¥{{ formatMoney(Math.abs(row.credit)) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éæ©ä¾åºå" prop="supplierId"> |
| | | <el-select v-model="generateForm.supplierId" placeholder="è¯·éæ©ä¾åºå" style="width: 100%;" @change="onSupplierChange"> |
| | | <el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="generateForm.supplierId" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | style="width: 100%;" |
| | | filterable |
| | | @change="onSupplierChange" |
| | | > |
| | | <el-option |
| | | v-for="item in supplierList" |
| | | :key="item.id" |
| | | :label="item.supplierName" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="period"> |
| | | <el-date-picker v-model="generateForm.period" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" /> |
| | | <el-form-item label="对账æä»½" prop="statementMonth"> |
| | | <el-date-picker |
| | | v-model="generateForm.statementMonth" |
| | | type="month" |
| | | placeholder="éæ©æä»½" |
| | | value-format="YYYY-MM" |
| | | style="width: 100%;" |
| | | @change="onStatementMonthChange" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="purchaseData.length > 0" class="purchase-section"> |
| | | <div class="section-title">æ¬æéè´æ°æ®</div> |
| | | <el-table :data="purchaseData" border style="width: 100%; margin-bottom: 15px;" v-loading="purchaseLoading" @selection-change="handlePurchaseSelectionChange"> |
| | | <div v-if="statementDetailLoaded" class="purchase-section"> |
| | | <div v-if="purchaseData.length > 0" class="section-title">æ¬æéè´æ°æ®</div> |
| | | <el-table |
| | | v-if="purchaseData.length > 0" |
| | | ref="purchaseTableRef" |
| | | :data="purchaseData" |
| | | border |
| | | row-key="id" |
| | | style="width: 100%; margin-bottom: 15px;" |
| | | v-loading="purchaseLoading" |
| | | @selection-change="handlePurchaseSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="occurrenceDate" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="receiptNumber" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'å
¥åº' ? 'success' : 'danger'">{{ row.type }}</el-tag> |
| | | <el-tag :type="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.type === 'å
¥åº' ? 'text-danger' : 'text-success'">Â¥{{ formatMoney(row.amount) }}</span> |
| | | <span :class="getDetailAmountClass(row.type)">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | <el-empty v-else description="该ä¾åºåæ¬æææ æç»æ°æ®" :image-size="80" /> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.beginBalance) }}</strong></span> |
| | | <span>æ¬æåºä»: <strong class="text-danger">Â¥{{ formatMoney(generateForm.currentPayable) }}</strong></span> |
| | | <span>æ¬æä»æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentPayment) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong class="text-primary">Â¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment)) }}</strong></span> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.openingBalance) }}</strong></span> |
| | | <span>æ¬æåºä»: <strong class="text-danger">Â¥{{ formatMoney(generateForm.currentPlan) }}</strong></span> |
| | | <span>æ¬æä»æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentActually) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong :class="displayClosingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(displayClosingBalance) }}</strong></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.supplierId && !purchaseLoading" class="empty-tip"> |
| | | <div v-else-if="generateForm.supplierId && generateForm.statementMonth && !purchaseLoading" class="empty-tip"> |
| | | <el-empty description="该ä¾åºåæ¬æææ éè´æ°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认çæ</el-button> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">确认çæ</el-button> |
| | | <el-button @click="generateDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { ref, reactive, onMounted, computed, nextTick, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { |
| | | getAccountStatementDetailsByMonth, |
| | | addAccountStatement, |
| | | listPageAccountStatement, |
| | | deleteAccountStatement, |
| | | } from "@/api/financialManagement/accountStatement.js"; |
| | | |
| | | const ACCOUNT_TYPE_PAYABLE = 2; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | defineOptions({ |
| | | name: "åºä»å¯¹è´¦", |
| | |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementCode", width: "150" }, |
| | | { label: "对账åå·", prop: "statementNumber", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "对账æé´", prop: "period", width: "150" }, |
| | | { label: "æåä½é¢", prop: "beginBalance", slot: "beginBalance" }, |
| | | { label: "æ¬æåºä»", prop: "currentPayable", slot: "currentPayable" }, |
| | | { label: "æ¬æä»æ¬¾", prop: "currentPayment", slot: "currentPayment" }, |
| | | { label: "ææ«ä½é¢", prop: "endBalance", slot: "endBalance" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "150", fixed: "right" }, |
| | | { label: "对账æé´", prop: "statementMonth", width: "150" }, |
| | | { label: "æåä½é¢", prop: "openingBalance", dataType: "slot", slot: "openingBalance" }, |
| | | { label: "æ¬æåºä»", prop: "currentPlan", dataType: "slot", slot: "currentPlan" }, |
| | | { label: "æ¬æä»æ¬¾", prop: "currentActually", dataType: "slot", slot: "currentActually" }, |
| | | { label: "ææ«ä½é¢", prop: "closingBalance", dataType: "slot", slot: "closingBalance" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const submitLoading = ref(false); |
| | | const detailDialogVisible = ref(false); |
| | | const currentSupplier = ref(""); |
| | | const currentPeriod = ref(""); |
| | | const detailData = ref([]); |
| | | const detailLoading = ref(false); |
| | | |
| | | const generateDialogVisible = ref(false); |
| | | const purchaseLoading = ref(false); |
| | | const statementDetailLoaded = ref(false); |
| | | const purchaseData = ref([]); |
| | | const selectedPurchases = ref([]); |
| | | const purchaseTableRef = ref(null); |
| | | const supplierList = ref([]); |
| | | |
| | | /** æç» typeï¼1åºåº 2å
¥åº 3æ¶æ¬¾ 4仿¬¾ 5éè´§ */ |
| | | const STATEMENT_DETAIL_TYPE_MAP = { |
| | | 1: "åºåº", |
| | | 2: "å
¥åº", |
| | | 3: "æ¶æ¬¾", |
| | | 4: "仿¬¾", |
| | | 5: "éè´§", |
| | | }; |
| | | |
| | | const calculateEndBalance = (openingBalance, currentPlan, currentActually) => { |
| | | return openingBalance + currentPlan - currentActually; |
| | | }; |
| | | |
| | | const getDetailTypeLabel = (type) => STATEMENT_DETAIL_TYPE_MAP[Number(type)] ?? ""; |
| | | |
| | | const getDetailTypeTagType = (type) => { |
| | | const t = Number(type); |
| | | if (t === 2) return "success"; |
| | | if (t === 4) return "primary"; |
| | | if (t === 5) return "danger"; |
| | | return "info"; |
| | | }; |
| | | |
| | | const getDetailAmountClass = (type) => { |
| | | const t = Number(type); |
| | | if (t === 2) return "text-danger"; |
| | | if (t === 4) return "text-success"; |
| | | return "text-danger"; |
| | | }; |
| | | |
| | | const generateForm = reactive({ |
| | | supplierId: "", |
| | | supplierName: "", |
| | | period: "", |
| | | beginBalance: 0, |
| | | currentPayable: 0, |
| | | currentPayment: 0, |
| | | statementMonth: "", |
| | | openingBalance: 0, |
| | | currentPlan: 0, |
| | | currentActually: 0, |
| | | closingBalance: 0, |
| | | }); |
| | | |
| | | const canGenerate = computed(() => { |
| | | return generateForm.supplierId && generateForm.period && selectedPurchases.value.length > 0; |
| | | }); |
| | | const displayClosingBalance = computed(() => |
| | | calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ) |
| | | ); |
| | | |
| | | const supplierList = [ |
| | | { id: 1, name: "åäº¬åææä¾åºå" }, |
| | | { id: 2, name: "䏿µ·çµåå
å¨ä»¶å
¬å¸" }, |
| | | { id: 3, name: "广å·å
è£
ææå" }, |
| | | { id: 4, name: "æ·±å³äºéé
ä»¶å
¬å¸" }, |
| | | ]; |
| | | const canGenerate = computed( |
| | | () => generateForm.supplierId && generateForm.statementMonth && selectedPurchases.value.length > 0 |
| | | ); |
| | | |
| | | const mockData = [ |
| | | { id: 1, statementCode: "DZ202401001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", period: "2024-01", beginBalance: 20000, currentPayable: 15000, currentPayment: 10000, endBalance: 25000 }, |
| | | { id: 2, statementCode: "DZ202401002", supplierId: 2, supplierName: "䏿µ·çµåå
å¨ä»¶å
¬å¸", period: "2024-01", beginBalance: 10000, currentPayable: 20000, currentPayment: 15000, endBalance: 15000 }, |
| | | { id: 3, statementCode: "DZ202402001", supplierId: 1, supplierName: "åäº¬åææä¾åºå", period: "2024-02", beginBalance: 25000, currentPayable: 18000, currentPayment: 20000, endBalance: 23000 }, |
| | | ]; |
| | | |
| | | const calculateEndBalance = (beginBalance, currentPayable, currentPayment) => { |
| | | return beginBalance + currentPayable - currentPayment; |
| | | const applyStatementSummary = (data) => { |
| | | generateForm.openingBalance = Number(data.openingBalance ?? 0); |
| | | generateForm.currentPlan = Number(data.currentPlan ?? 0); |
| | | generateForm.currentActually = Number(data.currentActually ?? 0); |
| | | generateForm.closingBalance = Number( |
| | | data.closingBalance ?? |
| | | calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ) |
| | | ); |
| | | }; |
| | | |
| | | const getSupplierList = () => { |
| | | getOptions().then((res) => { |
| | | if (res.code === 200) { |
| | | supplierList.value = res.data ?? []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const normalizePurchaseRows = (list) => { |
| | | const rows = Array.isArray(list) ? list : []; |
| | | return rows.map((item, index) => { |
| | | const type = Number(item.type); |
| | | return { |
| | | id: item.id ?? `detail-${index}`, |
| | | accountStatementId: item.accountStatementId, |
| | | occurrenceDate: item.occurrenceDate ?? "", |
| | | receiptNumber: item.receiptNumber ?? "", |
| | | type, |
| | | typeLabel: getDetailTypeLabel(type), |
| | | amount: Math.abs(Number(item.amount ?? 0)), |
| | | remark: item.remark ?? "", |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const selectAllPurchaseRows = (keepApiSummary = false) => { |
| | | nextTick(() => { |
| | | const table = purchaseTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | purchaseData.value.forEach((row) => table.toggleRowSelection(row, true)); |
| | | selectedPurchases.value = [...purchaseData.value]; |
| | | if (!keepApiSummary) { |
| | | calculateSummary(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const isNumericId = (id) => id !== undefined && id !== null && id !== "" && /^\d+$/.test(String(id)); |
| | | |
| | | const buildFilterParams = (params = {}) => { |
| | | const result = { ...params, accountType: ACCOUNT_TYPE_PAYABLE }; |
| | | if (filters.supplierId) { |
| | | result.customerId = filters.supplierId; |
| | | } |
| | | if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) { |
| | | result.statementMonth = filters.startMonth; |
| | | } else if (filters.startMonth) { |
| | | result.startMonth = filters.startMonth; |
| | | } |
| | | if (filters.endMonth && filters.startMonth !== filters.endMonth) { |
| | | result.endMonth = filters.endMonth; |
| | | } |
| | | return result; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | buildFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => buildFilterParams({}); |
| | | |
| | | const buildDetailSubmitItem = (row) => { |
| | | const item = { |
| | | occurrenceDate: row.occurrenceDate, |
| | | receiptNumber: row.receiptNumber, |
| | | type: row.type, |
| | | amount: row.amount, |
| | | remark: row.remark ?? "", |
| | | }; |
| | | if (isNumericId(row.id)) { |
| | | item.id = Number(row.id); |
| | | } |
| | | if (row.accountStatementId) { |
| | | item.accountStatementId = row.accountStatementId; |
| | | } |
| | | return item; |
| | | }; |
| | | |
| | | const buildAddPayload = () => ({ |
| | | customerId: generateForm.supplierId, |
| | | customerName: generateForm.supplierName, |
| | | statementMonth: generateForm.statementMonth, |
| | | accountType: ACCOUNT_TYPE_PAYABLE, |
| | | statementNumber: "", |
| | | openingBalance: generateForm.openingBalance, |
| | | currentPlan: generateForm.currentPlan, |
| | | currentActually: generateForm.currentActually, |
| | | closingBalance: generateForm.closingBalance, |
| | | accountStatementDetails: selectedPurchases.value.map(buildDetailSubmitItem), |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.supplierId) { |
| | | result = result.filter(item => item.supplierId === filters.supplierId); |
| | | } |
| | | if (filters.startMonth && filters.endMonth) { |
| | | result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountStatement(buildListParams()) |
| | | .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 ?? []).map((row) => ({ |
| | | ...row, |
| | | supplierName: row.supplierName ?? row.customerName, |
| | | })); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | const changePage = ({ page, limit }) => { |
| | | pagination.currentPage = page; |
| | | pagination.pageSize = limit; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const generateStatement = () => { |
| | | generateForm.supplierId = ""; |
| | | generateForm.supplierName = ""; |
| | | generateForm.period = ""; |
| | | generateForm.beginBalance = 0; |
| | | generateForm.currentPayable = 0; |
| | | generateForm.currentPayment = 0; |
| | | generateForm.statementMonth = ""; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | statementDetailLoaded.value = false; |
| | | purchaseData.value = []; |
| | | selectedPurchases.value = []; |
| | | generateDialogVisible.value = true; |
| | | }; |
| | | |
| | | const onSupplierChange = (supplierId) => { |
| | | const supplier = supplierList.find(item => item.id === supplierId); |
| | | if (supplier) { |
| | | generateForm.supplierName = supplier.name; |
| | | } |
| | | const supplier = supplierList.value.find((item) => item.id === supplierId); |
| | | generateForm.supplierName = supplier?.supplierName ?? ""; |
| | | loadPurchaseData(); |
| | | }; |
| | | |
| | | const onPeriodChange = () => { |
| | | const onStatementMonthChange = () => { |
| | | loadPurchaseData(); |
| | | }; |
| | | |
| | | const loadPurchaseData = () => { |
| | | if (!generateForm.supplierId || !generateForm.period) { |
| | | if (!generateForm.supplierId || !generateForm.statementMonth) { |
| | | purchaseData.value = []; |
| | | selectedPurchases.value = []; |
| | | statementDetailLoaded.value = false; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | return; |
| | | } |
| | | |
| | | purchaseLoading.value = true; |
| | | selectedPurchases.value = []; |
| | | statementDetailLoaded.value = false; |
| | | |
| | | setTimeout(() => { |
| | | const mockPurchaseData = [ |
| | | { id: 1, date: generateForm.period + "-05", code: "RK2024001", type: "å
¥åº", amount: 8000, remark: "åææéè´" }, |
| | | { id: 2, date: generateForm.period + "-10", code: "FK2024001", type: "仿¬¾", amount: 5000, remark: "æ¯ä»è´§æ¬¾" }, |
| | | { id: 3, date: generateForm.period + "-15", code: "RK2024002", type: "å
¥åº", amount: 12000, remark: "çµåå
å¨ä»¶" }, |
| | | { id: 4, date: generateForm.period + "-18", code: "TH2024001", type: "éè´§", amount: 2000, remark: "è´¨éé®é¢éè´§" }, |
| | | { id: 5, date: generateForm.period + "-22", code: "RK2024003", type: "å
¥åº", amount: 6000, remark: "å
è£
ææ" }, |
| | | { id: 6, date: generateForm.period + "-25", code: "FK2024002", type: "仿¬¾", amount: 8000, remark: "æ¯ä»è´§æ¬¾" }, |
| | | ]; |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_PAYABLE, |
| | | customerId: generateForm.supplierId, |
| | | statementMonth: generateForm.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const data = res.data ?? {}; |
| | | const details = data.accountStatementDetails; |
| | | const list = Array.isArray(details) ? details : []; |
| | | purchaseData.value = normalizePurchaseRows(list); |
| | | applyStatementSummary(data); |
| | | statementDetailLoaded.value = true; |
| | | |
| | | purchaseData.value = mockPurchaseData; |
| | | |
| | | const lastPeriod = getLastPeriod(generateForm.period); |
| | | const lastStatement = mockData.find(item => |
| | | item.supplierId === generateForm.supplierId && item.period === lastPeriod |
| | | ); |
| | | generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0; |
| | | |
| | | calculateSummary(); |
| | | |
| | | purchaseLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | const getLastPeriod = (period) => { |
| | | const [year, month] = period.split("-").map(Number); |
| | | if (month === 1) { |
| | | return `${year - 1}-12`; |
| | | } |
| | | return `${year}-${String(month - 1).padStart(2, "0")}`; |
| | | if (purchaseData.value.length > 0) { |
| | | selectAllPurchaseRows(true); |
| | | } |
| | | } else { |
| | | purchaseData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¯¹è´¦æç»å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | purchaseData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error("æ¥è¯¢å¯¹è´¦æç»å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | purchaseLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const calculateSummary = () => { |
| | | let payable = 0; |
| | | let payment = 0; |
| | | |
| | | selectedPurchases.value.forEach(item => { |
| | | if (item.type === "å
¥åº") { |
| | | selectedPurchases.value.forEach((item) => { |
| | | if (item.type === 2) { |
| | | payable += item.amount; |
| | | } else if (item.type === "éè´§") { |
| | | } else if (item.type === 5) { |
| | | payable -= item.amount; |
| | | } else if (item.type === "仿¬¾") { |
| | | } else if (item.type === 4) { |
| | | payment += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentPayable = payable; |
| | | generateForm.currentPayment = payment; |
| | | generateForm.currentPlan = payable; |
| | | generateForm.currentActually = payment; |
| | | generateForm.closingBalance = calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }; |
| | | |
| | | const handlePurchaseSelectionChange = (selection) => { |
| | |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentPayable, generateForm.currentPayment); |
| | | if (!canGenerate.value) return; |
| | | submitLoading.value = true; |
| | | addAccountStatement(buildAddPayload()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "çæå¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("çæå¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | mockData.unshift({ |
| | | id: newId, |
| | | statementCode: "DZ" + Date.now(), |
| | | supplierId: generateForm.supplierId, |
| | | supplierName: generateForm.supplierName, |
| | | period: generateForm.period, |
| | | beginBalance: generateForm.beginBalance, |
| | | currentPayable: generateForm.currentPayable, |
| | | currentPayment: generateForm.currentPayment, |
| | | endBalance, |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤å¯¹è´¦åã${row.statementNumber || row.id}ãåï¼`, "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountStatement([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const buildDetailTableFromApi = (data, statementMonth) => { |
| | | const details = Array.isArray(data.accountStatementDetails) ? data.accountStatementDetails : []; |
| | | let runningBalance = Number(data.openingBalance ?? 0); |
| | | const rows = [ |
| | | { |
| | | date: statementMonth ?? "", |
| | | type: "æå", |
| | | code: "-", |
| | | debit: 0, |
| | | credit: 0, |
| | | balance: runningBalance, |
| | | remark: "æåä½é¢", |
| | | }, |
| | | ]; |
| | | |
| | | details.forEach((item) => { |
| | | const amount = Math.abs(Number(item.amount ?? 0)); |
| | | const type = Number(item.type); |
| | | let debit = 0; |
| | | let credit = 0; |
| | | |
| | | if (type === 2) { |
| | | credit = amount; |
| | | runningBalance += amount; |
| | | } else if (type === 4) { |
| | | debit = amount; |
| | | runningBalance -= amount; |
| | | } else if (type === 5) { |
| | | credit = -amount; |
| | | runningBalance -= amount; |
| | | } |
| | | |
| | | rows.push({ |
| | | date: item.occurrenceDate ?? "", |
| | | type: getDetailTypeLabel(type), |
| | | code: item.receiptNumber ?? "", |
| | | debit, |
| | | credit, |
| | | balance: runningBalance, |
| | | remark: item.remark ?? "", |
| | | }); |
| | | }); |
| | | |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | getTableData(); |
| | | return rows; |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | currentSupplier.value = row.supplierName; |
| | | currentPeriod.value = row.period; |
| | | const partnerId = row.customerId ?? row.supplierId; |
| | | if (!partnerId || !row.statementMonth) { |
| | | ElMessage.warning("缺å°ä¾åºåæå¯¹è´¦æä»½ï¼æ æ³æ¥è¯¢æç»"); |
| | | return; |
| | | } |
| | | |
| | | const purchaseInAmount = Math.floor(row.currentPayable * 0.7); |
| | | const returnAmount = Math.floor(row.currentPayable * 0.1); |
| | | const firstPayment = Math.floor(row.currentPayment * 0.5); |
| | | const secondPayment = row.currentPayment - firstPayment; |
| | | |
| | | let runningBalance = row.beginBalance; |
| | | |
| | | detailData.value = [ |
| | | { date: row.period + "-01", type: "æå", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "æåä½é¢" }, |
| | | { date: row.period + "-05", type: "å
¥åº", code: "RK2024001", debit: 0, credit: purchaseInAmount, balance: runningBalance += purchaseInAmount, remark: "éè´å
¥åº" }, |
| | | { date: row.period + "-10", type: "仿¬¾", code: "FK2024001", debit: firstPayment, credit: 0, balance: runningBalance -= firstPayment, remark: "æ¯ä»è´§æ¬¾" }, |
| | | { date: row.period + "-15", type: "å
¥åº", code: "RK2024002", debit: 0, credit: row.currentPayable - purchaseInAmount - returnAmount, balance: runningBalance += (row.currentPayable - purchaseInAmount - returnAmount), remark: "éè´å
¥åº" }, |
| | | { date: row.period + "-20", type: "éè´§", code: "TH2024001", debit: 0, credit: -returnAmount, balance: runningBalance -= returnAmount, remark: "éè´éè´§" }, |
| | | { date: row.period + "-25", type: "仿¬¾", code: "FK2024002", debit: secondPayment, credit: 0, balance: runningBalance -= secondPayment, remark: "æ¯ä»è´§æ¬¾" }, |
| | | ]; |
| | | |
| | | currentSupplier.value = row.supplierName ?? row.customerName ?? ""; |
| | | currentPeriod.value = row.statementMonth ?? ""; |
| | | detailData.value = []; |
| | | detailDialogVisible.value = true; |
| | | }; |
| | | detailLoading.value = true; |
| | | |
| | | const printStatement = (row) => { |
| | | ElMessage.info(`æå°å¯¹è´¦å: ${row.statementCode}`); |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_PAYABLE, |
| | | customerId: partnerId, |
| | | statementMonth: row.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | detailData.value = buildDetailTableFromApi(res.data ?? {}, row.statementMonth); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢æç»å¤±è´¥"); |
| | | detailDialogVisible.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("æ¥è¯¢æç»å¤±è´¥"); |
| | | detailDialogVisible.value = false; |
| | | }) |
| | | .finally(() => { |
| | | detailLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | proxy.download( |
| | | "/accountStatement/exportAccountStatement", |
| | | buildExportParams(), |
| | | `åºä»å¯¹è´¦å_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getSupplierList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | |
| | | .text-danger { |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | } |
| | | |
| | | .statement-header { |
| | |
| | | |
| | | .empty-tip { |
| | | margin-top: 30px; |
| | | } |
| | | |
| | | .text-primary { |
| | | color: #409eff; |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
å®¡æ ¸" value="pending" /> |
| | | <el-option label="å·²å®¡æ ¸" value="approved" /> |
| | | <el-option label="已驳å" value="rejected" /> |
| | | <el-option label="å·²å¼ç¥¨" value="invoiced" /> |
| | | <el-form-item label="å®¡æ ¸ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©å®¡æ ¸ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="å¾
å®¡æ ¸" :value="0" /> |
| | | <el-option label="å®¡æ ¸éè¿" :value="1" /> |
| | | <el-option label="å®¡æ ¸ä¸éè¿" :value="2" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="ç³è¯·æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | <div></div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢ç³è¯·</el-button> |
| | | <el-button @click="handleBatchApply" icon="Document" :disabled="selectedRows.length === 0">æ¹éç³è¯·</el-button> |
| | | <el-button type="success" @click="handleExport" icon="Download">导åºå¼ç¥¨ç³è¯·</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | v-loading="tableLoading" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | |
| | | <span>{{ row.taxRate }}%</span> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusLabel(row.status) }}</el-tag> |
| | | <el-tag :type="getStatusType(row.status)" effect="light" round> |
| | | {{ getStatusLabel(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="row.status === 'pending'">å®¡æ ¸</el-button> |
| | | <el-button type="warning" link @click="handleInvoice(row)" v-if="row.status === 'approved'">å¼ç¥¨</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="isPendingStatus(row.status)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)" v-if="isPendingStatus(row.status)">å é¤</el-button> |
| | | <el-button type="success" link @click="handleAudit(row)" v-if="isPendingStatus(row.status)">å®¡æ ¸</el-button> |
| | | <el-button type="primary" link @click="openFileDialog(row)" v-if="isApprovedStatus(row.status)">éä»¶</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <FormDialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | :operation-type="isView ? 'detail' : ''" |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog" |
| | | > |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-row v-if="isView" :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å®¡æ ¸ç¶æ"> |
| | | <el-tag :type="getStatusType(form.status)" effect="light" round> |
| | | {{ getStatusLabel(form.status) }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç³è¯·åå·" prop="applyCode"> |
| | | <el-input v-model="form.applyCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="form.customerId" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | style="width: 100%;" |
| | | :disabled="isEdit || isView" |
| | | filterable |
| | | @change="handleCustomerChange" |
| | | > |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåºåå·" prop="outboundBatchNos"> |
| | | <el-input |
| | | :model-value="outboundBatchDisplayText" |
| | | placeholder="请å
鿩客æ·" |
| | | readonly |
| | | :disabled="!form.customerId || isEdit || isView" |
| | | class="outbound-batch-input" |
| | | @click="handleOutboundInputClick" |
| | | > |
| | | <template v-if="!isEdit && !isView" #append> |
| | | <el-button |
| | | :disabled="!form.customerId" |
| | | :loading="outboundBatchLoading" |
| | | @click.stop="openOutboundSelectDialog" |
| | | > |
| | | éæ© |
| | | </el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨éé¢" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :precision="2" |
| | | :disabled="isView" |
| | | style="width: 100%;" |
| | | placeholder="æ ¹æ®æéåºåºåèªå¨æ±æ»ï¼å¯ä¿®æ¹" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç¨ç" prop="taxRate"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;"> |
| | | <el-select v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;" :disabled="isView"> |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票类å" prop="invoiceType"> |
| | | <el-select v-model="form.invoiceType" placeholder="è¯·éæ©å票类å" style="width: 100%;"> |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="special" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="normal" /> |
| | | <el-option label="çµåå票" value="electronic" /> |
| | | <el-select v-model="form.invoiceType" placeholder="è¯·éæ©å票类å" style="width: 100%;" :disabled="isView"> |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="å¢å¼ç¨ä¸ç¨å票" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="å¢å¼ç¨æ®éå票" /> |
| | | <el-option label="çµåå票" value="çµåå票" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ç³è¯·æ¥æ" prop="applyDate"> |
| | | <el-date-picker v-model="form.applyDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | <el-date-picker |
| | | v-model="form.applyDate" |
| | | 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="请è¾å
¥å票å
容" /> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" :disabled="isView" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <template v-if="!isView" #footer> |
| | | <el-button type="primary" :loading="submitLoading" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <el-dialog |
| | | v-model="outboundSelectVisible" |
| | | title="éæ©åºåºå" |
| | | width="1200px" |
| | | append-to-body |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | | @closed="handleOutboundDialogClosed" |
| | | > |
| | | <el-table |
| | | ref="outboundTableRef" |
| | | v-loading="outboundBatchLoading" |
| | | :data="outboundBatchList" |
| | | row-key="id" |
| | | border |
| | | stripe |
| | | max-height="480" |
| | | @selection-change="handleOutboundDialogSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="outboundBatches" label="åºåºåå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="customerName" label="客æ·åç§°" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" label="è§æ ¼åå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="salesContractNo" label="éå®ååå·" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column prop="shippingNo" label="åè´§åå·" min-width="130" show-overflow-tooltip /> |
| | | <el-table-column prop="shippingDate" label="åè´§æ¥æ" width="110" align="center" /> |
| | | <el-table-column prop="outboundAmount" label="åºåºéé¢" width="110" align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(row.outboundAmount) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxRate" label="ç¨ç" width="80" align="center"> |
| | | <template #default="{ row }">{{ row.taxRate }}%</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmOutboundSelection">ç¡®å®</el-button> |
| | | <el-button @click="outboundSelectVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <FileList |
| | | v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | record-type="account_invoice_application" |
| | | :record-id="currentRecordId" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ref, reactive, computed, onMounted, nextTick, getCurrentInstance, defineAsyncComponent } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | getOutboundBatchesByCustomer, |
| | | addAccountInvoiceApplication, |
| | | listPageAccountInvoiceApplication, |
| | | auditAccountInvoiceApplication, |
| | | updateAccountInvoiceApplication, |
| | | deleteAccountInvoiceApplication, |
| | | } from "@/api/financialManagement/invoiceApply.js"; |
| | | |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | | name: "å¼ç¥¨ç³è¯·", |
| | |
| | | applyCode: "", |
| | | customerId: "", |
| | | status: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | const columns = [ |
| | | { label: "ç³è¯·åå·", prop: "applyCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "å¼ç¥¨éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨ç", prop: "taxRate", slot: "taxRate" }, |
| | | { label: "å票类å", prop: "invoiceTypeLabel", width: "130" }, |
| | | { label: "å¼ç¥¨éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { label: "ç¨ç", prop: "taxRate", dataType: "slot", slot: "taxRate" }, |
| | | { label: "å票类å", prop: "invoiceType", width: "130" }, |
| | | { label: "ç³è¯·æ¥æ", prop: "applyDate", width: "120" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "200", fixed: "right" }, |
| | | { label: "å®¡æ ¸ç¶æ", prop: "status", dataType: "slot", slot: "status", width: "110", align: "center" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "300", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const selectedRows = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | outboundSelectVisible.value = false; |
| | | isView.value = false; |
| | | isEdit.value = false; |
| | | }; |
| | | |
| | | const customerList = ref([]); |
| | | const outboundBatchList = ref([]); |
| | | const outboundBatchOptions = ref([]); |
| | | const outboundBatchLoading = ref(false); |
| | | const outboundSelectVisible = ref(false); |
| | | const outboundTableRef = ref(null); |
| | | const dialogOutboundSelection = ref([]); |
| | | |
| | | const getCustomerList = () => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | customerList.value = res.data?.records || []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const normalizeOutboundBatchOptions = (data) => { |
| | | const list = Array.isArray(data) ? data : []; |
| | | return list.map((item, index) => { |
| | | if (typeof item === "string" || typeof item === "number") { |
| | | const text = String(item); |
| | | return { label: text, value: text, outboundAmount: 0 }; |
| | | } |
| | | const label = |
| | | item.outboundBatches ?? |
| | | item.batchNo ?? |
| | | item.shippingNo ?? |
| | | item.outboundNo ?? |
| | | item.label ?? |
| | | `åºåºå${index + 1}`; |
| | | const value = item.id ?? item.stockOutRecordId ?? item.stockOutRecordIds ?? label; |
| | | const outboundAmount = Number(item.outboundAmount) || 0; |
| | | const taxRate = |
| | | item.taxRate !== undefined && item.taxRate !== null && item.taxRate !== "" |
| | | ? Number(item.taxRate) |
| | | : undefined; |
| | | return { label: String(label), value, outboundAmount, taxRate }; |
| | | }); |
| | | }; |
| | | |
| | | const isSameOutboundId = (a, b) => String(a) === String(b); |
| | | |
| | | const getSelectedOutboundOptions = () => { |
| | | const selected = form.outboundBatchNos || []; |
| | | return outboundBatchOptions.value.filter((opt) => |
| | | selected.some((id) => isSameOutboundId(id, opt.value)) |
| | | ); |
| | | }; |
| | | |
| | | /** æ ¡éªæéåºåºåç¨çæ¯å¦ä¸è´ï¼ä¸è´ååå¡« form.taxRate */ |
| | | const checkTaxRateConsistency = (showMessage = true) => { |
| | | const selected = getSelectedOutboundOptions(); |
| | | if (selected.length === 0) return true; |
| | | |
| | | const withTaxRate = selected.filter( |
| | | (opt) => opt.taxRate !== undefined && opt.taxRate !== null && !Number.isNaN(opt.taxRate) |
| | | ); |
| | | if (withTaxRate.length === 0) return true; |
| | | |
| | | const uniqueRates = [...new Set(withTaxRate.map((opt) => Number(opt.taxRate)))]; |
| | | if (uniqueRates.length > 1) { |
| | | if (showMessage) { |
| | | const detail = withTaxRate.map((opt) => `${opt.label}(${opt.taxRate}%)`).join("ã"); |
| | | ElMessage.error(`æéåºåºåç¨çä¸ä¸è´ï¼æ æ³å¼ç¥¨ï¼${detail}`); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | form.taxRate = uniqueRates[0]; |
| | | return true; |
| | | }; |
| | | |
| | | /** æ ¹æ®æéåºåºåæ±æ» outboundAmount ä½ä¸ºå¼ç¥¨éé¢ */ |
| | | const syncInvoiceAmount = () => { |
| | | const selected = form.outboundBatchNos || []; |
| | | const sum = outboundBatchOptions.value |
| | | .filter((opt) => selected.some((id) => isSameOutboundId(id, opt.value))) |
| | | .reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0); |
| | | form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0; |
| | | }; |
| | | |
| | | const getOutboundRowId = (row) => row?.id ?? row?.stockOutRecordId; |
| | | |
| | | const outboundBatchDisplayText = computed(() => { |
| | | if (isEdit.value || isView.value) { |
| | | return form.outboundBatches || ""; |
| | | } |
| | | if (form.outboundBatches) return form.outboundBatches; |
| | | const ids = form.outboundBatchNos || []; |
| | | if (!ids.length) return ""; |
| | | return outboundBatchOptions.value |
| | | .filter((opt) => ids.some((id) => isSameOutboundId(id, opt.value))) |
| | | .map((opt) => opt.label) |
| | | .join("ã"); |
| | | }); |
| | | |
| | | const handleOutboundInputClick = () => { |
| | | if (isEdit.value || isView.value) return; |
| | | openOutboundSelectDialog(); |
| | | }; |
| | | |
| | | const restoreOutboundTableSelection = () => { |
| | | nextTick(() => { |
| | | const table = outboundTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | const selectedIds = new Set((form.outboundBatchNos || []).map((id) => String(id))); |
| | | outboundBatchList.value.forEach((row) => { |
| | | const rowId = getOutboundRowId(row); |
| | | if (rowId !== undefined && rowId !== null && selectedIds.has(String(rowId))) { |
| | | table.toggleRowSelection(row, true); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const openOutboundSelectDialog = () => { |
| | | if (!form.customerId || isEdit.value || isView.value) return; |
| | | outboundSelectVisible.value = true; |
| | | loadOutboundBatches(form.customerId, true).then(() => { |
| | | restoreOutboundTableSelection(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOutboundDialogSelectionChange = (selection) => { |
| | | dialogOutboundSelection.value = selection; |
| | | }; |
| | | |
| | | const confirmOutboundSelection = () => { |
| | | if (dialogOutboundSelection.value.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸æ¡åºåºå"); |
| | | return; |
| | | } |
| | | const prevIds = [...(form.outboundBatchNos || [])]; |
| | | const prevBatches = form.outboundBatches; |
| | | form.outboundBatchNos = dialogOutboundSelection.value |
| | | .map((row) => getOutboundRowId(row)) |
| | | .filter((id) => id !== undefined && id !== null); |
| | | form.outboundBatches = dialogOutboundSelection.value |
| | | .map((row) => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "") |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | if (!checkTaxRateConsistency()) { |
| | | form.outboundBatchNos = prevIds; |
| | | form.outboundBatches = prevBatches; |
| | | return; |
| | | } |
| | | outboundSelectVisible.value = false; |
| | | syncInvoiceAmount(); |
| | | formRef.value?.validateField("outboundBatchNos"); |
| | | }; |
| | | |
| | | const handleOutboundDialogClosed = () => { |
| | | dialogOutboundSelection.value = []; |
| | | }; |
| | | |
| | | const loadOutboundBatches = (customerId, keepSelected = false) => { |
| | | if (!customerId) { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | if (!keepSelected) { |
| | | form.outboundBatchNos = []; |
| | | form.amount = 0; |
| | | } |
| | | return Promise.resolve(); |
| | | } |
| | | outboundBatchLoading.value = true; |
| | | return getOutboundBatchesByCustomer({ customerId }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const list = res.data?.records ?? res.data ?? []; |
| | | outboundBatchList.value = Array.isArray(list) ? list : []; |
| | | outboundBatchOptions.value = normalizeOutboundBatchOptions(list); |
| | | } else { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | outboundBatchLoading.value = false; |
| | | if (keepSelected) { |
| | | syncInvoiceAmount(); |
| | | checkTaxRateConsistency(false); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleCustomerChange = (customerId) => { |
| | | form.outboundBatchNos = []; |
| | | form.outboundBatches = ""; |
| | | form.amount = 0; |
| | | loadOutboundBatches(customerId); |
| | | }; |
| | | |
| | | const form = reactive({ |
| | | applyCode: "", |
| | | customerId: "", |
| | | outboundBatchNos: [], |
| | | outboundBatches: "", |
| | | amount: 0, |
| | | taxRate: 13, |
| | | invoiceType: "special", |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | applyDate: "", |
| | | content: "", |
| | | remark: "", |
| | |
| | | |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | outboundBatchNos: [{ required: true, type: "array", min: 1, message: "è¯·éæ©åºåºåå·", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥å¼ç¥¨éé¢", trigger: "blur" }], |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | invoiceType: [{ required: true, message: "è¯·éæ©å票类å", trigger: "change" }], |
| | | applyDate: [{ required: true, message: "è¯·éæ©ç³è¯·æ¥æ", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, applyCode: "KP2024001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", amount: 5000, taxRate: 13, invoiceType: "special", invoiceTypeLabel: "å¢å¼ç¨ä¸ç¨å票", applyDate: "2024-01-15", status: "pending", content: "软件æå¡è´¹", remark: "" }, |
| | | { id: 2, applyCode: "KP2024002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", amount: 8000, taxRate: 13, invoiceType: "normal", invoiceTypeLabel: "å¢å¼ç¨æ®éå票", applyDate: "2024-01-16", status: "approved", content: "ååéå®", remark: "" }, |
| | | { id: 3, applyCode: "KP2024003", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", amount: 12000, taxRate: 6, invoiceType: "electronic", invoiceTypeLabel: "çµåå票", applyDate: "2024-01-18", status: "invoiced", content: "ææ¯æå¡è´¹", remark: "" }, |
| | | ]; |
| | | /** å®¡æ ¸ç¶æï¼0å¾
å®¡æ ¸ 1å®¡æ ¸éè¿ 2å®¡æ ¸ä¸éè¿ */ |
| | | const STATUS_LABEL_MAP = { |
| | | 0: "å¾
å®¡æ ¸", |
| | | 1: "å®¡æ ¸éè¿", |
| | | 2: "å®¡æ ¸ä¸éè¿", |
| | | }; |
| | | |
| | | const STATUS_TYPE_MAP = { |
| | | 0: "warning", |
| | | 1: "success", |
| | | 2: "danger", |
| | | }; |
| | | |
| | | const normalizeStatus = (status) => { |
| | | if (status === undefined || status === null || status === "") return status; |
| | | const num = Number(status); |
| | | return Number.isNaN(num) ? status : num; |
| | | }; |
| | | |
| | | const isPendingStatus = (status) => normalizeStatus(status) === 0; |
| | | const isApprovedStatus = (status) => normalizeStatus(status) === 1; |
| | | |
| | | const fileDialogVisible = ref(false); |
| | | const currentRecordId = ref(0); |
| | | |
| | | const openFileDialog = (row) => { |
| | | currentRecordId.value = row.id; |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | const formatOutboundBatches = (value) => { |
| | | if (value === undefined || value === null || value === "") return ""; |
| | | if (Array.isArray(value)) return value.filter(Boolean).join("ã"); |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | }; |
| | | |
| | | const normalizeTableRow = (row) => ({ |
| | | ...row, |
| | | applyCode: row.invoiceApplicationNo ?? row.applyCode, |
| | | amount: row.invoiceAmount ?? row.amount, |
| | | content: row.invoiceContent ?? row.content, |
| | | status: normalizeStatus(row.status ?? row.auditStatus), |
| | | stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "", |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | }); |
| | | |
| | | const appendFilterParams = (params) => { |
| | | if (filters.applyCode) { |
| | | params.invoiceApplicationNo = filters.applyCode; |
| | | } |
| | | if (filters.customerId) { |
| | | params.customerId = filters.customerId; |
| | | } |
| | | if (filters.status !== "" && filters.status != null) { |
| | | params.status = filters.status; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => { |
| | | return appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | }; |
| | | |
| | | const buildExportParams = () => { |
| | | const params = appendFilterParams({}); |
| | | if (selectedRows.value.length > 0) { |
| | | params.ids = selectedRows.value.map((row) => row.id).join(","); |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | const params = buildExportParams(); |
| | | const filename = |
| | | selectedRows.value.length > 0 |
| | | ? `å¼ç¥¨ç³è¯·_å·²é${selectedRows.value.length}æ¡_${Date.now()}.xlsx` |
| | | : `å¼ç¥¨ç³è¯·_${Date.now()}.xlsx`; |
| | | proxy.download("/accountInvoiceApplication/exportAccountInvoiceApplication", params, filename); |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | }; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const map = { pending: "å¾
å®¡æ ¸", approved: "å·²å®¡æ ¸", rejected: "已驳å", invoiced: "å·²å¼ç¥¨" }; |
| | | return map[status] || status; |
| | | const num = normalizeStatus(status); |
| | | if (num === 0 || num === 1 || num === 2) { |
| | | return STATUS_LABEL_MAP[num]; |
| | | } |
| | | return "-"; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const map = { pending: "warning", approved: "success", rejected: "danger", invoiced: "primary" }; |
| | | return map[status] || ""; |
| | | const num = normalizeStatus(status); |
| | | if (num === 0 || num === 1 || num === 2) { |
| | | return STATUS_TYPE_MAP[num]; |
| | | } |
| | | return "info"; |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.applyCode) { |
| | | result = result.filter(item => item.applyCode.includes(filters.applyCode)); |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.status) { |
| | | result = result.filter(item => item.status === filters.status); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountInvoiceApplication(buildListParams()) |
| | | .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 ?? []).map(normalizeTableRow); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.applyCode = ""; |
| | | filters.customerId = ""; |
| | | filters.status = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | |
| | | selectedRows.value = selection; |
| | | }; |
| | | |
| | | const fillFormFromRow = (row) => { |
| | | const outboundBatchNos = Array.isArray(row.outboundBatchNos) |
| | | ? row.outboundBatchNos |
| | | : parseStockOutRecordIds(row.stockOutRecordIds ?? row.stockOutRecordId); |
| | | Object.assign(form, { |
| | | ...row, |
| | | applyCode: row.applyCode ?? row.invoiceApplicationNo ?? "", |
| | | amount: Number(row.amount ?? row.invoiceAmount ?? 0), |
| | | content: row.content ?? row.invoiceContent, |
| | | status: normalizeStatus(row.status ?? row.auditStatus), |
| | | outboundBatchNos, |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | }); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "æ°å¢å¼ç¥¨ç³è¯·"; |
| | | Object.assign(form, { |
| | | applyCode: "KP" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | outboundBatchNos: [], |
| | | outboundBatches: "", |
| | | amount: 0, |
| | | taxRate: 13, |
| | | invoiceType: "special", |
| | | applyDate: new Date().toISOString().split('T')[0], |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | applyDate: new Date().toISOString().split("T")[0], |
| | | content: "", |
| | | remark: "", |
| | | }); |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const parseStockOutRecordIds = (value) => { |
| | | if (!value) return []; |
| | | if (Array.isArray(value)) return value; |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map((s) => s.trim()) |
| | | .filter(Boolean) |
| | | .map((s) => (/^\d+$/.test(s) ? Number(s) : s)); |
| | | }; |
| | | |
| | | const buildSubmitPayload = (forUpdate = false) => { |
| | | const payload = { |
| | | customerId: form.customerId, |
| | | stockOutRecordIds: (form.outboundBatchNos || []).join(","), |
| | | invoiceApplicationNo: form.applyCode || "", |
| | | invoiceType: form.invoiceType, |
| | | applyDate: form.applyDate, |
| | | invoiceContent: form.content, |
| | | remark: form.remark || "", |
| | | invoiceAmount: form.amount, |
| | | taxRate: form.taxRate, |
| | | status: 0, |
| | | }; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | | } |
| | | return payload; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å¼ç¥¨ç³è¯·"; |
| | | Object.assign(form, row); |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çç³è¯·å: ${row.applyCode}`); |
| | | isView.value = true; |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ¥çå¼ç¥¨ç³è¯·"; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const submitAudit = (row, status) => { |
| | | auditAccountInvoiceApplication({ id: row.id, status }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success(status === 1 ? "å®¡æ ¸éè¿" : "å®¡æ ¸ä¸éè¿"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "审æ¹å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("审æ¹å¤±è´¥"); |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤ç³è¯·åã${row.applyCode ?? row.invoiceApplicationNo}ãåï¼`, "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountInvoiceApplication([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleAudit = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤å®¡æ ¸éè¿è¯¥å¼ç¥¨ç³è¯·åï¼", "æç¤º", { |
| | | confirmButtonText: "éè¿", |
| | | cancelButtonText: "驳å", |
| | | ElMessageBox.confirm("è¯·éæ©å®¡æ¹ç»æ", "å¼ç¥¨ç³è¯·å®¡æ ¸", { |
| | | confirmButtonText: "å®¡æ ¸éè¿", |
| | | cancelButtonText: "å®¡æ ¸ä¸éè¿", |
| | | distinguishCancelAndClose: true, |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "approved"; |
| | | } |
| | | ElMessage.success("å®¡æ ¸éè¿"); |
| | | getTableData(); |
| | | }).catch((action) => { |
| | | if (action === "cancel") { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "rejected"; |
| | | }) |
| | | .then(() => { |
| | | submitAudit(row, 1); |
| | | }) |
| | | .catch((action) => { |
| | | if (action === "cancel") { |
| | | submitAudit(row, 2); |
| | | } |
| | | ElMessage.warning("已驳å"); |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleInvoice = (row) => { |
| | |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "invoiced"; |
| | | } |
| | | ElMessage.success("å¼ç¥¨å®æ"); |
| | | getTableData(); |
| | | }); |
| | |
| | | ElMessage.success(`æ¹éç³è¯· ${selectedRows.value.length} æ¡è®°å½`); |
| | | }; |
| | | |
| | | const submitLoading = ref(false); |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | const invoiceTypeMap = { special: "å¢å¼ç¨ä¸ç¨å票", normal: "å¢å¼ç¨æ®éå票", electronic: "çµåå票" }; |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }; |
| | | if (!valid) return; |
| | | if (!checkTaxRateConsistency()) return; |
| | | |
| | | submitLoading.value = true; |
| | | const request = isEdit.value |
| | | ? updateAccountInvoiceApplication(buildSubmitPayload(true)) |
| | | : addAccountInvoiceApplication(buildSubmitPayload()); |
| | | |
| | | request |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success(isEdit.value ? "ä¿®æ¹æå" : "æ°å¢æå"); |
| | | closeDialog(); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || (isEdit.value ? "ä¿®æ¹å¤±è´¥" : "æ°å¢å¤±è´¥")); |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType], status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error(isEdit.value ? "ä¿®æ¹å¤±è´¥" : "æ°å¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | color: #409eff; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .outbound-batch-input:not(.is-disabled) { |
| | | :deep(.el-input__wrapper) { |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="å票代ç :"> |
| | | <el-input v-model="filters.invoiceCode" placeholder="请è¾å
¥å票代ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="å票å·ç :"> |
| | | <el-input v-model="filters.invoiceNo" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.invoiceNumber" placeholder="请è¾å
¥å票å·ç " clearable style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="å¼ç¥¨æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="ç¶æ:"> |
| | | <el-select v-model="filters.status" placeholder="è¯·éæ©ç¶æ" clearable style="width: 150px;"> |
| | | <el-option label="æ£å¸¸" :value="0" /> |
| | | <el-option label="ä½åº" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</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 type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> |
| | | <el-button @click="handleImport" icon="Upload">导å
¥</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <!-- <el-button type="primary" @click="add" icon="Plus">å½å
¥å票</el-button> --> |
| | | <el-button type="success" @click="handleExport" icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | v-loading="tableLoading" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | |
| | | <template #totalAmount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.totalAmount) }}</span> |
| | | </template> |
| | | <template #invoiceType="{ row }"> |
| | | <el-tag :type="row.invoiceType === 'special' ? 'danger' : 'primary'">{{ row.invoiceTypeLabel }}</el-tag> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)" effect="light" round> |
| | | {{ getStatusLabel(row.status) }} |
| | | </el-tag> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">ä½åº</el-button> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | @click="openFileDialog(row)" |
| | | v-if="row.accountInvoiceApplicationId" |
| | | > |
| | | éä»¶ |
| | | </el-button> |
| | | <el-button type="warning" link @click="handleCancel(row)" v-if="isNormalStatus(row.status)">ä½åº</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <FormDialog |
| | | :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | :operation-type="isView ? 'detail' : ''" |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog" |
| | | > |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-row v-if="isView" :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票代ç " prop="invoiceCode"> |
| | | <el-input v-model="form.invoiceCode" placeholder="请è¾å
¥å票代ç " /> |
| | | <el-form-item label="ç¶æ"> |
| | | <el-tag :type="getStatusType(form.status)" effect="light" round> |
| | | {{ getStatusLabel(form.status) }} |
| | | </el-tag> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å票å·ç " prop="invoiceNo"> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " /> |
| | | <el-input v-model="form.invoiceNo" placeholder="请è¾å
¥å票å·ç " :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <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-col :span="12"> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker v-model="form.invoiceDate" type="date" placeholder="éæ©æ¥æ" value-format="YYYY-MM-DD" style="width: 100%;" /> |
| | | </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%;" @change="handleInvoiceTypeChange"> |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="special" /> |
| | | <el-option label="å¢å¼ç¨æ®éå票" value="normal" /> |
| | | <el-option label="çµåå票" value="electronic" /> |
| | | </el-select> |
| | | <el-form-item label="å¼ç¥¨æ¥æ" prop="invoiceDate"> |
| | | <el-date-picker |
| | | v-model="form.invoiceDate" |
| | | 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="invoiceType"> |
| | | <el-select |
| | | v-model="form.invoiceType" |
| | | placeholder="è¯·éæ©å票类å" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="handleInvoiceTypeChange" |
| | | > |
| | | <el-option label="å¢å¼ç¨ä¸ç¨å票" value="å¢å¼ç¨ä¸ç¨å票" /> |
| | | <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 v-model="form.taxRate" placeholder="è¯·éæ©ç¨ç" style="width: 100%;" @change="calculateTax"> |
| | | <el-select |
| | | v-model="form.taxRate" |
| | | placeholder="è¯·éæ©ç¨ç" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="calculateTax" |
| | | > |
| | | <el-option |
| | | v-for="dict in tax_rate" |
| | | :key="dict.value" |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="éé¢(ä¸å«ç¨)" prop="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" @change="calculateTax" /> |
| | | <el-input-number |
| | | v-model="form.amount" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | @change="calculateTax" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å票å
容" prop="content"> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" /> |
| | | <el-input v-model="form.content" type="textarea" :rows="3" placeholder="请è¾å
¥å票å
容" :disabled="isView" /> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请è¾å
¥å¤æ³¨" :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <template v-if="!isView" #footer> |
| | | <el-button type="primary" :loading="submitLoading" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <FileList |
| | | v-if="fileDialogVisible" |
| | | v-model:visible="fileDialogVisible" |
| | | record-type="account_invoice_application" |
| | | :record-id="currentRecordId" |
| | | :editable="false" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed, getCurrentInstance } from "vue"; |
| | | import { ref, reactive, onMounted, getCurrentInstance, defineAsyncComponent } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | addAccountSalesInvoice, |
| | | listPageAccountSalesInvoice, |
| | | cancelAccountSalesInvoice, |
| | | deleteAccountSalesInvoice, |
| | | } from "@/api/financialManagement/accountSalesInvoice.js"; |
| | | |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | |
| | | defineOptions({ |
| | | name: "é项å票", |
| | |
| | | const { tax_rate } = proxy.useDict("tax_rate"); |
| | | |
| | | const filters = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | invoiceNumber: "", |
| | | customerId: "", |
| | | dateRange: [], |
| | | status: "", |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "å票代ç ", prop: "invoiceCode", width: "130" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "120" }, |
| | | { label: "å票å·ç ", prop: "invoiceNo", width: "140" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "å¼ç¥¨æ¥æ", prop: "invoiceDate", width: "120" }, |
| | | { label: "éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", slot: "totalAmount" }, |
| | | { label: "å票类å", prop: "invoiceType", slot: "invoiceType" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "180", fixed: "right" }, |
| | | { label: "éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { label: "ç¨é¢", prop: "taxAmount", dataType: "slot", slot: "taxAmount" }, |
| | | { label: "ä»·ç¨å计", prop: "totalAmount", dataType: "slot", slot: "totalAmount" }, |
| | | { label: "å票类å", prop: "invoiceType", width: "130" }, |
| | | { label: "ç¶æ", prop: "status", dataType: "slot", slot: "status", width: "90", align: "center" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | const isView = ref(false); |
| | | const submitLoading = ref(false); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | const customerList = ref([]); |
| | | const fileDialogVisible = ref(false); |
| | | const currentRecordId = ref(0); |
| | | |
| | | const openFileDialog = (row) => { |
| | | if (!row.accountInvoiceApplicationId) { |
| | | ElMessage.warning("æªå
³èå¼ç¥¨ç³è¯·ï¼æ æ³æ¥çéä»¶"); |
| | | return; |
| | | } |
| | | currentRecordId.value = row.accountInvoiceApplicationId; |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | /** ç¶æï¼0æ£å¸¸ 1ä½åº */ |
| | | const STATUS_LABEL_MAP = { 0: "æ£å¸¸", 1: "ä½åº" }; |
| | | const STATUS_TYPE_MAP = { 0: "success", 1: "info" }; |
| | | |
| | | const normalizeStatus = (status) => { |
| | | if (status === undefined || status === null || status === "") return 0; |
| | | const num = Number(status); |
| | | return Number.isNaN(num) ? 0 : num; |
| | | }; |
| | | |
| | | const isNormalStatus = (status) => normalizeStatus(status) === 0; |
| | | |
| | | const getStatusLabel = (status) => { |
| | | const num = normalizeStatus(status); |
| | | return STATUS_LABEL_MAP[num] ?? "æ£å¸¸"; |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const num = normalizeStatus(status); |
| | | return STATUS_TYPE_MAP[num] ?? "success"; |
| | | }; |
| | | |
| | | const form = reactive({ |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | customerId: "", |
| | | invoiceDate: "", |
| | | invoiceType: "special", |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | accountInvoiceApplicationId: undefined, |
| | | storageAttachmentId: undefined, |
| | | }); |
| | | |
| | | const rules = { |
| | | invoiceCode: [{ required: true, message: "请è¾å
¥å票代ç ", trigger: "blur" }], |
| | | invoiceNo: [{ required: true, message: "请è¾å
¥å票å·ç ", trigger: "blur" }], |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | invoiceDate: [{ required: true, message: "è¯·éæ©å¼ç¥¨æ¥æ", trigger: "change" }], |
| | |
| | | taxRate: [{ required: true, message: "è¯·éæ©ç¨ç", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥éé¢", trigger: "blur" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, invoiceCode: "0440021001", invoiceNo: "12345678", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", invoiceDate: "2024-01-15", amount: 5000, taxRate: 13, taxAmount: 650, totalAmount: 5650, invoiceType: "special", invoiceTypeLabel: "å¢å¼ç¨ä¸ç¨å票", content: "软件æå¡è´¹", remark: "" }, |
| | | { id: 2, invoiceCode: "0440021002", invoiceNo: "87654321", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", invoiceDate: "2024-01-16", amount: 8000, taxRate: 13, taxAmount: 1040, totalAmount: 9040, invoiceType: "normal", invoiceTypeLabel: "å¢å¼ç¨æ®éå票", content: "ååéå®", remark: "" }, |
| | | { id: 3, invoiceCode: "0440021003", invoiceNo: "11112222", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", invoiceDate: "2024-01-18", amount: 12000, taxRate: 6, taxAmount: 720, totalAmount: 12720, invoiceType: "electronic", invoiceTypeLabel: "çµåå票", content: "ææ¯æå¡è´¹", remark: "" }, |
| | | ]; |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | }; |
| | | |
| | | const handleInvoiceTypeChange = () => { |
| | | if (form.invoiceType === "special") { |
| | | form.taxRate = 13; |
| | | } else { |
| | | form.taxRate = 13; |
| | | } |
| | | calculateTax(); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.invoiceCode) { |
| | | result = result.filter(item => item.invoiceCode.includes(filters.invoiceCode)); |
| | | } |
| | | if (filters.invoiceNo) { |
| | | result = result.filter(item => item.invoiceNo.includes(filters.invoiceNo)); |
| | | const normalizeTableRow = (row) => ({ |
| | | ...row, |
| | | invoiceNo: row.invoiceNumber ?? row.invoiceNo, |
| | | invoiceDate: row.issueDate ?? row.invoiceDate, |
| | | amount: row.taxExclusivelPrice ?? row.amount, |
| | | taxAmount: row.taxPrice ?? row.taxAmount, |
| | | totalAmount: row.taxInclusivePrice ?? row.totalAmount, |
| | | content: row.invoiceContent ?? row.content, |
| | | status: normalizeStatus(row.status), |
| | | }); |
| | | |
| | | const fillFormFromRow = (row) => { |
| | | Object.assign(form, { |
| | | invoiceNo: row.invoiceNo ?? row.invoiceNumber ?? "", |
| | | customerId: row.customerId, |
| | | invoiceDate: row.invoiceDate ?? row.issueDate ?? "", |
| | | invoiceType: row.invoiceType ?? "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: row.taxRate ?? 13, |
| | | amount: row.amount ?? row.taxExclusivelPrice ?? 0, |
| | | taxAmount: row.taxAmount ?? row.taxPrice ?? 0, |
| | | totalAmount: row.totalAmount ?? row.taxInclusivePrice ?? 0, |
| | | content: row.content ?? row.invoiceContent ?? "", |
| | | remark: row.remark ?? "", |
| | | accountInvoiceApplicationId: row.accountInvoiceApplicationId, |
| | | storageAttachmentId: row.storageAttachmentId, |
| | | status: normalizeStatus(row.status), |
| | | }); |
| | | }; |
| | | |
| | | const buildCancelPayload = (row) => ({ |
| | | id: row.id, |
| | | accountInvoiceApplicationId: row.accountInvoiceApplicationId, |
| | | invoiceNumber: row.invoiceNumber ?? row.invoiceNo, |
| | | taxRate: row.taxRate, |
| | | invoiceType: row.invoiceType, |
| | | issueDate: row.issueDate ?? row.invoiceDate, |
| | | taxExclusivelPrice: row.taxExclusivelPrice ?? row.amount, |
| | | taxPrice: row.taxPrice ?? row.taxAmount, |
| | | taxInclusivePrice: row.taxInclusivePrice ?? row.totalAmount, |
| | | remark: row.remark ?? "", |
| | | invoiceContent: row.invoiceContent ?? row.content, |
| | | customerId: row.customerId, |
| | | storageAttachmentId: row.storageAttachmentId, |
| | | status: 1, |
| | | }); |
| | | |
| | | const buildSubmitPayload = () => ({ |
| | | invoiceNumber: form.invoiceNo, |
| | | customerId: form.customerId, |
| | | issueDate: form.invoiceDate, |
| | | invoiceType: form.invoiceType, |
| | | taxRate: form.taxRate, |
| | | taxExclusivelPrice: form.amount, |
| | | taxPrice: form.taxAmount, |
| | | taxInclusivePrice: form.totalAmount, |
| | | invoiceContent: form.content, |
| | | remark: form.remark || "", |
| | | accountInvoiceApplicationId: form.accountInvoiceApplicationId, |
| | | storageAttachmentId: form.storageAttachmentId, |
| | | }); |
| | | |
| | | const getCustomerList = () => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | customerList.value = res.data?.records || []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const appendFilterParams = (params) => { |
| | | if (filters.invoiceNumber) { |
| | | params.invoiceNumber = filters.invoiceNumber; |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | params.customerId = filters.customerId; |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | if (filters.status !== "" && filters.status != null) { |
| | | params.status = filters.status; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => appendFilterParams({}); |
| | | |
| | | const handleExport = () => { |
| | | const params = buildExportParams(); |
| | | proxy.download("/accountSalesInvoice/exportAccountSalesInvoice", params, `é项å票_${Date.now()}.xlsx`); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSalesInvoice(buildListParams()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const records = res.data?.records ?? []; |
| | | dataList.value = records.map(normalizeTableRow); |
| | | pagination.total = res.data?.total ?? 0; |
| | | } else { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.invoiceCode = ""; |
| | | filters.invoiceNo = ""; |
| | | filters.invoiceNumber = ""; |
| | | filters.customerId = ""; |
| | | filters.dateRange = []; |
| | | filters.status = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | |
| | | getTableData(); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | isView.value = false; |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "å½å
¥å票"; |
| | | Object.assign(form, { |
| | | invoiceCode: "", |
| | | invoiceNo: "", |
| | | customerId: "", |
| | | invoiceDate: new Date().toISOString().split('T')[0], |
| | | invoiceType: "special", |
| | | invoiceDate: new Date().toISOString().split("T")[0], |
| | | invoiceType: "å¢å¼ç¨ä¸ç¨å票", |
| | | taxRate: 13, |
| | | amount: 0, |
| | | taxAmount: 0, |
| | | totalAmount: 0, |
| | | content: "", |
| | | remark: "", |
| | | accountInvoiceApplicationId: undefined, |
| | | storageAttachmentId: undefined, |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾å票"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çå票: ${row.invoiceCode}-${row.invoiceNo}`); |
| | | isView.value = true; |
| | | dialogTitle.value = "æ¥çå票"; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认ä½åºè¯¥å票åï¼", "æç¤º", { |
| | | const handleCancel = (row) => { |
| | | ElMessageBox.confirm(`确认ä½åºå票ã${row.invoiceNo ?? row.invoiceNumber}ãåï¼`, "ä½åºç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | ElMessage.success("ä½åºæå"); |
| | | getTableData(); |
| | | cancelAccountSalesInvoice(buildCancelPayload(row)) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("ä½åºæå"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "ä½åºå¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("ä½åºå¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleImport = () => { |
| | | ElMessage.info("导å
¥åè½"); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤å票ã${row.invoiceNo ?? row.invoiceNumber}ãåï¼å é¤åä¸å¯æ¢å¤ã`, "å é¤ç¡®è®¤", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountSalesInvoice([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | const invoiceTypeMap = { special: "å¢å¼ç¨ä¸ç¨å票", normal: "å¢å¼ç¨æ®éå票", electronic: "çµåå票" }; |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }; |
| | | if (!valid) return; |
| | | submitLoading.value = true; |
| | | addAccountSalesInvoice(buildSubmitPayload()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å½å
¥æå"); |
| | | closeDialog(); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å½å
¥å¤±è´¥"); |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, invoiceTypeLabel: invoiceTypeMap[form.invoiceType] }); |
| | | ElMessage.success("å½å
¥æå"); |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å½å
¥å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | <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.receiptCode" placeholder="请è¾å
¥æ¶æ¬¾åå·" clearable style="width: 200px;" /> |
| | | <el-input v-model="filters.collectionNumber" |
| | | placeholder="请è¾å
¥æ¶æ¬¾åå·" |
| | | clearable |
| | | style="width: 200px;" /> |
| | | </el-form-item> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.customerId" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | clearable |
| | | filterable |
| | | style="width: 200px;"> |
| | | <el-option v-for="item in customerList" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼:"> |
| | | <el-select v-model="filters.receiptMethod" placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" clearable style="width: 150px;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-option label="æ¯ä»å®" value="alipay" /> |
| | | <el-option label="微信" value="wechat" /> |
| | | <el-select v-model="filters.collectionMethod" |
| | | placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" |
| | | clearable |
| | | style="width: 150px;"> |
| | | <el-option v-for="item in payment_methods" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="æ¶æ¬¾æ¥æ:"> |
| | | <el-date-picker v-model="filters.dateRange" |
| | | type="daterange" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | clearable |
| | | style="width: 240px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | | <el-button type="primary" |
| | | @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetFilters">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="table_list"> |
| | | <div class="actions"> |
| | | <div> |
| | | <el-statistic title="æ¬ææ¶æ¬¾å计" :value="totalReceiptAmount" precision="2" prefix="Â¥" /> |
| | | <el-statistic title="æ¬é¡µæ¶æ¬¾å计" |
| | | :value="totalReceiptAmount" |
| | | :precision="2" |
| | | prefix="Â¥" /> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="add" icon="Plus">æ°å¢æ¶æ¬¾</el-button> |
| | | <el-button @click="handleOut" icon="Download">导åº</el-button> |
| | | <el-button type="primary" |
| | | @click="add" |
| | | icon="Plus">æ°å¢æ¶æ¬¾</el-button> |
| | | <el-button type="success" |
| | | @click="handleExport" |
| | | icon="Download">导åº</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | <PIMTable rowKey="id" |
| | | v-loading="tableLoading" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | @pagination="changePage"> |
| | | <template #amount="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | <template #receiptMethod="{ row }"> |
| | | <el-tag>{{ getReceiptMethodLabel(row.receiptMethod) }}</el-tag> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <el-tag :type="row.status === 'confirmed' ? 'success' : 'warning'">{{ row.status === 'confirmed' ? '已确认' : 'å¾
确认' }}</el-tag> |
| | | <span>{{ getReceiptMethodLabel(row.receiptMethod) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="view(row)">æ¥ç</el-button> |
| | | <el-button type="primary" link @click="edit(row)" v-if="row.status === 'pending'">ç¼è¾</el-button> |
| | | <el-button type="success" link @click="handleConfirm(row)" v-if="row.status === 'pending'">确认</el-button> |
| | | <el-button type="danger" link @click="handleDelete(row)" v-if="row.status === 'pending'">å é¤</el-button> |
| | | <el-button type="primary" |
| | | link |
| | | @click="view(row)">æ¥ç</el-button> |
| | | <el-button :disabled="row.accountStatemen" |
| | | type="primary" |
| | | link |
| | | @click="edit(row)">ç¼è¾</el-button> |
| | | <el-button :disabled="row.accountStatemen" |
| | | type="danger" |
| | | link |
| | | @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <FormDialog :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | | width="800px" |
| | | :operation-type="isView ? 'detail' : ''" |
| | | @confirm="submitForm" |
| | | @cancel="closeDialog"> |
| | | <el-form :model="form" |
| | | :rules="rules" |
| | | ref="formRef" |
| | | label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾åå·" prop="receiptCode"> |
| | | <el-input v-model="form.receiptCode" placeholder="ç³»ç»èªå¨çæ" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="客æ·" prop="customerId"> |
| | | <el-select v-model="form.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" :disabled="isEdit"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¶æ¬¾åå·" |
| | | prop="receiptCode"> |
| | | <el-input v-model="form.receiptCode" |
| | | placeholder="ç³»ç»èªå¨çæ" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¥æ" prop="receiptDate"> |
| | | <el-date-picker v-model="form.receiptDate" 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="amount"> |
| | | <el-input-number v-model="form.amount" :min="0" :precision="2" style="width: 100%;" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼" prop="receiptMethod"> |
| | | <el-select v-model="form.receiptMethod" placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" style="width: 100%;"> |
| | | <el-option label="é¶è¡è½¬è´¦" value="bank_transfer" /> |
| | | <el-option label="ç°é" value="cash" /> |
| | | <el-option label="æ¯ç¥¨" value="check" /> |
| | | <el-option label="æ±ç¥¨" value="draft" /> |
| | | <el-option label="æ¯ä»å®" value="alipay" /> |
| | | <el-option label="微信" value="wechat" /> |
| | | <el-form-item label="客æ·" |
| | | prop="customerId"> |
| | | <el-select v-model="form.customerId" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | style="width: 100%;" |
| | | :disabled="isEdit || isView" |
| | | filterable |
| | | @change="handleCustomerChange"> |
| | | <el-option v-for="item in customerList" |
| | | :key="item.id" |
| | | :label="item.customerName" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="é¶è¡è´¦å·" prop="bankAccount" v-if="form.receiptMethod === 'bank_transfer'"> |
| | | <el-input v-model="form.bankAccount" placeholder="请è¾å
¥é¶è¡è´¦å·" /> |
| | | <el-form-item label="å
³èåæ®" |
| | | prop="stockOutRecordIds"> |
| | | <el-input :model-value="outboundBatchDisplayText" |
| | | placeholder="请å
鿩客æ·" |
| | | readonly |
| | | :disabled="!form.customerId || isEdit || isView" |
| | | class="outbound-batch-input" |
| | | @click="handleOutboundInputClick"> |
| | | <template v-if="!isEdit && !isView" |
| | | #append> |
| | | <el-button :disabled="!form.customerId" |
| | | :loading="outboundBatchLoading" |
| | | @click.stop="openOutboundSelectDialog"> |
| | | éæ© |
| | | </el-button> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="å
³èåæ®" prop="relatedDocs"> |
| | | <el-select v-model="form.relatedDocs" multiple placeholder="è¯·éæ©å
³èåæ®" style="width: 100%;"> |
| | | <el-option v-for="item in outList" :key="item.outCode" :label="item.outCode" :value="item.outCode" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="夿³¨" prop="remark"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¥æ" |
| | | prop="receiptDate"> |
| | | <el-date-picker v-model="form.receiptDate" |
| | | 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="amount"> |
| | | <el-input-number v-model="form.amount" |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%;" |
| | | :disabled="isView" |
| | | placeholder="æ ¹æ®å
³èåæ®èªå¨æ±æ»ï¼å¯ä¿®æ¹" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¶æ¬¾æ¹å¼" |
| | | prop="receiptMethod"> |
| | | <el-select v-model="form.receiptMethod" |
| | | placeholder="è¯·éæ©æ¶æ¬¾æ¹å¼" |
| | | style="width: 100%;" |
| | | :disabled="isView"> |
| | | <el-option v-for="item in payment_methods" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <el-input v-model="form.remark" |
| | | type="textarea" |
| | | :rows="3" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | :disabled="isView" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <template v-if="!isView" |
| | | #footer> |
| | | <el-button type="primary" |
| | | :loading="submitLoading" |
| | | @click="submitForm">ç¡®å®</el-button> |
| | | <el-button @click="closeDialog">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | | <el-dialog v-model="outboundSelectVisible" |
| | | title="éæ©å
³èåæ®" |
| | | width="1200px" |
| | | append-to-body |
| | | destroy-on-close |
| | | :close-on-click-modal="false" |
| | | @closed="handleOutboundDialogClosed"> |
| | | <el-table ref="outboundTableRef" |
| | | v-loading="outboundBatchLoading" |
| | | :data="outboundBatchList" |
| | | row-key="id" |
| | | border |
| | | stripe |
| | | max-height="480" |
| | | @selection-change="handleOutboundDialogSelectionChange"> |
| | | <el-table-column type="selection" |
| | | width="55" |
| | | align="center" /> |
| | | <el-table-column prop="outboundBatches" |
| | | label="åºåºåå·" |
| | | min-width="140" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="customerName" |
| | | label="客æ·åç§°" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="productName" |
| | | label="产ååç§°" |
| | | min-width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="specificationModel" |
| | | label="è§æ ¼åå·" |
| | | min-width="140" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="salesContractNo" |
| | | label="éå®ååå·" |
| | | min-width="140" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="shippingNo" |
| | | label="åè´§åå·" |
| | | min-width="130" |
| | | show-overflow-tooltip /> |
| | | <el-table-column prop="shippingDate" |
| | | label="åè´§æ¥æ" |
| | | width="110" |
| | | align="center" /> |
| | | <el-table-column prop="outboundAmount" |
| | | label="åºåºéé¢" |
| | | width="110" |
| | | align="right"> |
| | | <template #default="{ row }">Â¥{{ formatMoney(row.outboundAmount) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxRate" |
| | | label="ç¨ç" |
| | | width="80" |
| | | align="center"> |
| | | <template #default="{ row }">{{ row.taxRate }}%</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button type="primary" |
| | | @click="confirmOutboundSelection">ç¡®å®</el-button> |
| | | <el-button @click="outboundSelectVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | | ref, |
| | | reactive, |
| | | computed, |
| | | onMounted, |
| | | nextTick, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | getOutboundBatchesByCustomer, |
| | | addAccountSalesCollection, |
| | | listPageAccountSalesCollection, |
| | | updateAccountSalesCollection, |
| | | deleteAccountSalesCollection, |
| | | } from "@/api/financialManagement/accountSalesCollection.js"; |
| | | |
| | | defineOptions({ |
| | | name: "æ¶æ¬¾å", |
| | | }); |
| | | defineOptions({ |
| | | name: "æ¶æ¬¾å", |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | receiptCode: "", |
| | | customerId: "", |
| | | receiptMethod: "", |
| | | }); |
| | | const { proxy } = getCurrentInstance(); |
| | | const { payment_methods } = proxy.useDict("payment_methods"); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "æ¶æ¬¾åå·", prop: "receiptCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "æ¶æ¬¾æ¥æ", prop: "receiptDate", width: "120" }, |
| | | { label: "æ¶æ¬¾éé¢", prop: "amount", slot: "amount" }, |
| | | { label: "æ¶æ¬¾æ¹å¼", prop: "receiptMethod", slot: "receiptMethod" }, |
| | | { label: "ç¶æ", prop: "status", slot: "status" }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "220", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const outList = [ |
| | | { outCode: "CK2024001", customerId: 1 }, |
| | | { outCode: "CK2024002", customerId: 2 }, |
| | | { outCode: "CK2024003", customerId: 3 }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | receiptCode: "", |
| | | customerId: "", |
| | | receiptDate: "", |
| | | amount: 0, |
| | | receiptMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | relatedDocs: [], |
| | | remark: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | receiptDate: [{ required: true, message: "è¯·éæ©æ¶æ¬¾æ¥æ", trigger: "change" }], |
| | | amount: [{ required: true, message: "请è¾å
¥æ¶æ¬¾éé¢", trigger: "blur" }], |
| | | receiptMethod: [{ required: true, message: "è¯·éæ©æ¶æ¬¾æ¹å¼", trigger: "change" }], |
| | | }; |
| | | |
| | | const mockData = [ |
| | | { id: 1, receiptCode: "SK2024001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", receiptDate: "2024-01-16", amount: 3000, receiptMethod: "bank_transfer", status: "confirmed", relatedDocs: ["CK2024001"], remark: "" }, |
| | | { id: 2, receiptCode: "SK2024002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", receiptDate: "2024-01-18", amount: 5000, receiptMethod: "cash", status: "pending", relatedDocs: ["CK2024002"], remark: "" }, |
| | | { id: 3, receiptCode: "SK2024003", customerId: 3, customerName: "广å·å®ä¸æéå
¬å¸", receiptDate: "2024-01-20", amount: 8000, receiptMethod: "alipay", status: "confirmed", relatedDocs: ["CK2024003"], remark: "" }, |
| | | ]; |
| | | |
| | | const totalReceiptAmount = computed(() => { |
| | | return dataList.value.reduce((sum, item) => sum + Number(item.amount), 0); |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const getReceiptMethodLabel = (method) => { |
| | | const map = { |
| | | bank_transfer: "é¶è¡è½¬è´¦", |
| | | cash: "ç°é", |
| | | check: "æ¯ç¥¨", |
| | | draft: "æ±ç¥¨", |
| | | alipay: "æ¯ä»å®", |
| | | wechat: "微信", |
| | | }; |
| | | return map[method] || method; |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.receiptCode) { |
| | | result = result.filter(item => item.receiptCode.includes(filters.receiptCode)); |
| | | } |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.receiptMethod) { |
| | | result = result.filter(item => item.receiptMethod === filters.receiptMethod); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.receiptCode = ""; |
| | | filters.customerId = ""; |
| | | filters.receiptMethod = ""; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ°å¢æ¶æ¬¾"; |
| | | Object.assign(form, { |
| | | receiptCode: "SK" + Date.now().toString().slice(-8), |
| | | const filters = reactive({ |
| | | collectionNumber: "", |
| | | customerId: "", |
| | | receiptDate: new Date().toISOString().split('T')[0], |
| | | collectionMethod: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "æ¶æ¬¾åå·", prop: "receiptCode", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "æ¶æ¬¾æ¥æ", prop: "receiptDate", width: "120" }, |
| | | { label: "æ¶æ¬¾éé¢", prop: "amount", dataType: "slot", slot: "amount" }, |
| | | { |
| | | label: "æ¶æ¬¾æ¹å¼", |
| | | prop: "receiptMethod", |
| | | dataType: "slot", |
| | | slot: "receiptMethod", |
| | | width: "120", |
| | | }, |
| | | { label: "夿³¨", prop: "remark", showOverflowTooltip: true }, |
| | | { |
| | | label: "æä½", |
| | | prop: "operation", |
| | | dataType: "slot", |
| | | slot: "operation", |
| | | width: "200", |
| | | fixed: "right", |
| | | }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const submitLoading = ref(false); |
| | | |
| | | const customerList = ref([]); |
| | | const outboundBatchList = ref([]); |
| | | const outboundBatchOptions = ref([]); |
| | | const outboundBatchLoading = ref(false); |
| | | const outboundSelectVisible = ref(false); |
| | | const outboundTableRef = ref(null); |
| | | const dialogOutboundSelection = ref([]); |
| | | |
| | | const getReceiptMethodLabel = value => { |
| | | if (value === undefined || value === null || value === "") return "-"; |
| | | const item = payment_methods.value?.find( |
| | | m => String(m.value) === String(value) |
| | | ); |
| | | return item?.label ?? value; |
| | | }; |
| | | |
| | | const getDefaultReceiptMethod = () => payment_methods.value?.[0]?.value ?? ""; |
| | | |
| | | const form = reactive({ |
| | | receiptCode: "", |
| | | customerId: "", |
| | | receiptDate: "", |
| | | amount: 0, |
| | | receiptMethod: "bank_transfer", |
| | | bankAccount: "", |
| | | relatedDocs: [], |
| | | receiptMethod: "", |
| | | stockOutRecordIds: [], |
| | | outboundBatches: "", |
| | | remark: "", |
| | | }); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾æ¶æ¬¾"; |
| | | Object.assign(form, row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | const rules = { |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | stockOutRecordIds: [ |
| | | { |
| | | required: true, |
| | | type: "array", |
| | | min: 1, |
| | | message: "è¯·éæ©å
³èåæ®", |
| | | trigger: "change", |
| | | }, |
| | | ], |
| | | receiptDate: [ |
| | | { required: true, message: "è¯·éæ©æ¶æ¬¾æ¥æ", trigger: "change" }, |
| | | ], |
| | | amount: [{ required: true, message: "请è¾å
¥æ¶æ¬¾éé¢", trigger: "blur" }], |
| | | receiptMethod: [ |
| | | { required: true, message: "è¯·éæ©æ¶æ¬¾æ¹å¼", trigger: "change" }, |
| | | ], |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`æ¥çæ¶æ¬¾å: ${row.receiptCode}`); |
| | | }; |
| | | const totalReceiptAmount = computed(() => |
| | | dataList.value.reduce((sum, item) => sum + Number(item.amount || 0), 0) |
| | | ); |
| | | |
| | | const handleConfirm = (row) => { |
| | | ElMessageBox.confirm("ç¡®è®¤è¯¥æ¶æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "info", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData[index].status = "confirmed"; |
| | | } |
| | | ElMessage.success("确认æå"); |
| | | getTableData(); |
| | | const formatMoney = value => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | | return Number(value) |
| | | .toFixed(2) |
| | | .replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | const parseStockOutRecordIds = value => { |
| | | if (!value) return []; |
| | | if (Array.isArray(value)) return value; |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map(s => s.trim()) |
| | | .filter(Boolean) |
| | | .map(s => (/^\d+$/.test(s) ? Number(s) : s)); |
| | | }; |
| | | |
| | | const formatOutboundBatches = value => { |
| | | if (value === undefined || value === null || value === "") return ""; |
| | | if (Array.isArray(value)) return value.filter(Boolean).join("ã"); |
| | | return String(value) |
| | | .split(/[,ï¼]/) |
| | | .map(s => s.trim()) |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | }; |
| | | |
| | | const normalizeTableRow = row => ({ |
| | | ...row, |
| | | receiptCode: row.collectionNumber ?? row.receiptCode, |
| | | receiptDate: row.collectionDate ?? row.receiptDate, |
| | | amount: row.collectionAmount ?? row.amount, |
| | | receiptMethod: row.collectionMethod ?? row.receiptMethod ?? "", |
| | | stockOutRecordIds: row.stockOutRecordIds ?? row.stockOutRecordId ?? "", |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm("确认å é¤è¯¥æ¶æ¬¾ååï¼", "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | const index = mockData.findIndex(item => item.id === row.id); |
| | | if (index !== -1) { |
| | | mockData.splice(index, 1); |
| | | } |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate((valid) => { |
| | | if (valid) { |
| | | const customer = customerList.find(item => item.id === form.customerId); |
| | | if (isEdit.value) { |
| | | const index = mockData.findIndex(item => item.id === currentId.value); |
| | | if (index !== -1) { |
| | | mockData[index] = { ...mockData[index], ...form, customerName: customer?.name }; |
| | | } |
| | | ElMessage.success("ç¼è¾æå"); |
| | | } else { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | mockData.push({ id: newId, ...form, customerName: customer?.name, status: "pending" }); |
| | | ElMessage.success("æ°å¢æå"); |
| | | const getCustomerList = () => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then(res => { |
| | | if (res.code === 200) { |
| | | customerList.value = res.data?.records || []; |
| | | } |
| | | dialogVisible.value = false; |
| | | getTableData(); |
| | | } |
| | | }); |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getTableData(); |
| | | }); |
| | | const normalizeOutboundBatchOptions = data => { |
| | | const list = Array.isArray(data) ? data : []; |
| | | return list.map((item, index) => { |
| | | if (typeof item === "string" || typeof item === "number") { |
| | | const text = String(item); |
| | | return { label: text, value: text, outboundAmount: 0 }; |
| | | } |
| | | const label = |
| | | item.outboundBatches ?? |
| | | item.batchNo ?? |
| | | item.shippingNo ?? |
| | | item.outboundNo ?? |
| | | item.label ?? |
| | | `åºåºå${index + 1}`; |
| | | const value = item.id ?? item.stockOutRecordId ?? label; |
| | | return { |
| | | label: String(label), |
| | | value, |
| | | outboundAmount: Number(item.outboundAmount) || 0, |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const isSameOutboundId = (a, b) => String(a) === String(b); |
| | | |
| | | const getOutboundRowId = row => row?.id ?? row?.stockOutRecordId; |
| | | |
| | | const outboundBatchDisplayText = computed(() => { |
| | | if (isEdit.value || isView.value) { |
| | | return form.outboundBatches || ""; |
| | | } |
| | | if (form.outboundBatches) return form.outboundBatches; |
| | | const ids = form.stockOutRecordIds || []; |
| | | if (!ids.length) return ""; |
| | | const labels = outboundBatchOptions.value |
| | | .filter(opt => ids.some(id => isSameOutboundId(id, opt.value))) |
| | | .map(opt => opt.label); |
| | | if (labels.length) return labels.join("ã"); |
| | | return ids.join("ã"); |
| | | }); |
| | | |
| | | const handleOutboundInputClick = () => { |
| | | if (isEdit.value || isView.value) return; |
| | | openOutboundSelectDialog(); |
| | | }; |
| | | |
| | | /** 为已é ID è¡¥å
¨é项ï¼ç¼è¾/æ¥çåæ¾ï¼ */ |
| | | const ensureOutboundOptionsForSelected = () => { |
| | | const ids = form.stockOutRecordIds || []; |
| | | ids.forEach(id => { |
| | | const exists = outboundBatchOptions.value.some(opt => |
| | | isSameOutboundId(opt.value, id) |
| | | ); |
| | | if (exists) return; |
| | | const fromList = outboundBatchList.value.find(row => |
| | | isSameOutboundId(getOutboundRowId(row), id) |
| | | ); |
| | | if (fromList) { |
| | | const [option] = normalizeOutboundBatchOptions([fromList]); |
| | | if (option) outboundBatchOptions.value.push(option); |
| | | return; |
| | | } |
| | | outboundBatchOptions.value.push({ |
| | | label: String(id), |
| | | value: id, |
| | | outboundAmount: 0, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const syncCollectionAmount = () => { |
| | | const selected = form.stockOutRecordIds || []; |
| | | const sum = outboundBatchOptions.value |
| | | .filter(opt => selected.some(id => isSameOutboundId(id, opt.value))) |
| | | .reduce((acc, opt) => acc + (Number(opt.outboundAmount) || 0), 0); |
| | | form.amount = sum > 0 ? Number(sum.toFixed(2)) : 0; |
| | | }; |
| | | |
| | | const restoreOutboundTableSelection = () => { |
| | | nextTick(() => { |
| | | const table = outboundTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | const selectedIds = new Set( |
| | | (form.stockOutRecordIds || []).map(id => String(id)) |
| | | ); |
| | | outboundBatchList.value.forEach(row => { |
| | | const rowId = getOutboundRowId(row); |
| | | if ( |
| | | rowId !== undefined && |
| | | rowId !== null && |
| | | selectedIds.has(String(rowId)) |
| | | ) { |
| | | table.toggleRowSelection(row, true); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const loadOutboundBatches = (customerId, keepSelected = false) => { |
| | | if (!customerId) { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | if (!keepSelected) { |
| | | form.stockOutRecordIds = []; |
| | | form.amount = 0; |
| | | } |
| | | return Promise.resolve(); |
| | | } |
| | | outboundBatchLoading.value = true; |
| | | return getOutboundBatchesByCustomer({ customerId }) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | const list = res.data?.records ?? res.data ?? []; |
| | | outboundBatchList.value = Array.isArray(list) ? list : []; |
| | | outboundBatchOptions.value = normalizeOutboundBatchOptions(list); |
| | | } else { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | }) |
| | | .finally(() => { |
| | | outboundBatchLoading.value = false; |
| | | if (keepSelected) { |
| | | ensureOutboundOptionsForSelected(); |
| | | restoreOutboundTableSelection(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleCustomerChange = customerId => { |
| | | form.stockOutRecordIds = []; |
| | | form.outboundBatches = ""; |
| | | form.amount = 0; |
| | | loadOutboundBatches(customerId); |
| | | }; |
| | | |
| | | const openOutboundSelectDialog = () => { |
| | | if (!form.customerId || isEdit.value || isView.value) return; |
| | | outboundSelectVisible.value = true; |
| | | loadOutboundBatches(form.customerId, true).then(() => { |
| | | restoreOutboundTableSelection(); |
| | | }); |
| | | }; |
| | | |
| | | const handleOutboundDialogSelectionChange = selection => { |
| | | dialogOutboundSelection.value = selection; |
| | | }; |
| | | |
| | | const confirmOutboundSelection = () => { |
| | | if (dialogOutboundSelection.value.length === 0) { |
| | | ElMessage.warning("请è³å°éæ©ä¸æ¡å
³èåæ®"); |
| | | return; |
| | | } |
| | | form.stockOutRecordIds = dialogOutboundSelection.value |
| | | .map(row => getOutboundRowId(row)) |
| | | .filter(id => id !== undefined && id !== null); |
| | | form.outboundBatches = dialogOutboundSelection.value |
| | | .map(row => row.outboundBatches ?? row.batchNo ?? row.shippingNo ?? "") |
| | | .filter(Boolean) |
| | | .join("ã"); |
| | | outboundSelectVisible.value = false; |
| | | syncCollectionAmount(); |
| | | formRef.value?.validateField("stockOutRecordIds"); |
| | | }; |
| | | |
| | | const handleOutboundDialogClosed = () => { |
| | | dialogOutboundSelection.value = []; |
| | | }; |
| | | |
| | | const appendFilterParams = params => { |
| | | if (filters.collectionNumber) { |
| | | params.collectionNumber = filters.collectionNumber; |
| | | } |
| | | if (filters.customerId) { |
| | | params.customerId = filters.customerId; |
| | | } |
| | | if (filters.collectionMethod) { |
| | | params.collectionMethod = filters.collectionMethod; |
| | | } |
| | | if (filters.dateRange?.length === 2) { |
| | | params.startDate = filters.dateRange[0]; |
| | | params.endDate = filters.dateRange[1]; |
| | | } |
| | | return params; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | appendFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => appendFilterParams({}); |
| | | |
| | | const buildSubmitPayload = (forUpdate = false) => { |
| | | const payload = { |
| | | customerId: form.customerId, |
| | | collectionDate: form.receiptDate, |
| | | collectionAmount: form.amount, |
| | | collectionMethod: form.receiptMethod, |
| | | collectionNumber: form.receiptCode || "", |
| | | remark: form.remark || "", |
| | | stockOutRecordIds: (form.stockOutRecordIds || []).join(","), |
| | | }; |
| | | if (forUpdate) { |
| | | payload.id = currentId.value; |
| | | } |
| | | return payload; |
| | | }; |
| | | |
| | | const fillFormFromRow = row => { |
| | | const stockOutRecordIds = parseStockOutRecordIds( |
| | | row.stockOutRecordIds ?? row.stockOutRecordId |
| | | ); |
| | | Object.assign(form, { |
| | | receiptCode: row.receiptCode ?? row.collectionNumber ?? "", |
| | | customerId: row.customerId, |
| | | receiptDate: row.receiptDate ?? row.collectionDate ?? "", |
| | | amount: Number(row.amount ?? row.collectionAmount ?? 0), |
| | | receiptMethod: row.receiptMethod ?? row.collectionMethod ?? "", |
| | | stockOutRecordIds, |
| | | outboundBatches: formatOutboundBatches(row.outboundBatches), |
| | | remark: row.remark ?? "", |
| | | }); |
| | | }; |
| | | |
| | | const closeDialog = () => { |
| | | dialogVisible.value = false; |
| | | outboundSelectVisible.value = false; |
| | | isView.value = false; |
| | | isEdit.value = false; |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | const params = buildExportParams(); |
| | | proxy.download( |
| | | "/accountSalesCollection/exportAccountSalesCollection", |
| | | params, |
| | | `æ¶æ¬¾å_${Date.now()}.xlsx` |
| | | ); |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | tableLoading.value = true; |
| | | listPageAccountSalesCollection(buildListParams()) |
| | | .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 ?? []).map(normalizeTableRow); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¤±è´¥"); |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const onSearch = () => { |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.collectionNumber = ""; |
| | | filters.customerId = ""; |
| | | filters.collectionMethod = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const changePage = ({ current, size }) => { |
| | | pagination.currentPage = current; |
| | | pagination.pageSize = size; |
| | | getTableData(); |
| | | }; |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | dialogTitle.value = "æ°å¢æ¶æ¬¾"; |
| | | Object.assign(form, { |
| | | receiptCode: "SK" + Date.now().toString().slice(-8), |
| | | customerId: "", |
| | | receiptDate: new Date().toISOString().split("T")[0], |
| | | amount: 0, |
| | | receiptMethod: getDefaultReceiptMethod(), |
| | | stockOutRecordIds: [], |
| | | outboundBatches: "", |
| | | remark: "", |
| | | }); |
| | | outboundBatchList.value = []; |
| | | outboundBatchOptions.value = []; |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = row => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "ç¼è¾æ¶æ¬¾"; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const view = row => { |
| | | isView.value = true; |
| | | isEdit.value = false; |
| | | dialogTitle.value = "æ¥çæ¶æ¬¾"; |
| | | fillFormFromRow(row); |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleDelete = row => { |
| | | ElMessageBox.confirm( |
| | | `确认å 餿¶æ¬¾åã${row.receiptCode ?? row.collectionNumber}ãåï¼`, |
| | | "æç¤º", |
| | | { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | } |
| | | ).then(() => { |
| | | deleteAccountSalesCollection([row.id]) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate(valid => { |
| | | if (!valid) return; |
| | | submitLoading.value = true; |
| | | const request = isEdit.value |
| | | ? updateAccountSalesCollection(buildSubmitPayload(true)) |
| | | : addAccountSalesCollection(buildSubmitPayload()); |
| | | request |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success(isEdit.value ? "ä¿®æ¹æå" : "æ°å¢æå"); |
| | | closeDialog(); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || (isEdit.value ? "ä¿®æ¹å¤±è´¥" : "æ°å¢å¤±è´¥")); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error(isEdit.value ? "ä¿®æ¹å¤±è´¥" : "æ°å¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | .actions { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | .text-success { |
| | | color: #67c23a; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .outbound-batch-input:not(.is-disabled) { |
| | | :deep(.el-input__wrapper) { |
| | | cursor: pointer; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="客æ·:"> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select v-model="filters.customerId" placeholder="è¯·éæ©å®¢æ·" clearable filterable style="width: 200px;"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | |
| | | rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :tableLoading="tableLoading" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | |
| | | }" |
| | | @pagination="changePage" |
| | | > |
| | | <template #beginBalance="{ row }"> |
| | | <span :class="row.beginBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.beginBalance) }}</span> |
| | | <template #openingBalance="{ row }"> |
| | | <span :class="row.openingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.openingBalance) }}</span> |
| | | </template> |
| | | <template #currentReceivable="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.currentReceivable) }}</span> |
| | | <template #currentPlan="{ row }"> |
| | | <span class="text-primary">Â¥{{ formatMoney(row.currentPlan) }}</span> |
| | | </template> |
| | | <template #currentReceipt="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentReceipt) }}</span> |
| | | <template #currentActually="{ row }"> |
| | | <span class="text-success">Â¥{{ formatMoney(row.currentActually) }}</span> |
| | | </template> |
| | | <template #endBalance="{ row }"> |
| | | <span :class="row.endBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.endBalance) }}</span> |
| | | <template #closingBalance="{ row }"> |
| | | <span :class="row.closingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.closingBalance) }}</span> |
| | | </template> |
| | | <template #operation="{ row }"> |
| | | <el-button type="primary" link @click="viewDetail(row)">æ¥çæç»</el-button> |
| | | <el-button type="primary" link @click="printStatement(row)">æå°</el-button> |
| | | <!-- <el-button type="primary" link @click="printStatement(row)">æå°</el-button> --> |
| | | <el-button type="danger" link @click="handleDelete(row)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | <h3>{{ currentCustomer }} åºæ¶å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%"> |
| | | <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="鿩客æ·" prop="customerId"> |
| | | <el-select v-model="generateForm.customerId" placeholder="è¯·éæ©å®¢æ·" style="width: 100%;" @change="onCustomerChange"> |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | <el-select |
| | | v-model="generateForm.customerId" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | style="width: 100%;" |
| | | filterable |
| | | @change="onCustomerChange" |
| | | > |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="period"> |
| | | <el-date-picker v-model="generateForm.period" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onPeriodChange" /> |
| | | <el-form-item label="对账æä»½" prop="statementMonth"> |
| | | <el-date-picker v-model="generateForm.statementMonth" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onStatementMonthChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="salesData.length > 0" class="sales-section"> |
| | | <div class="section-title">æ¬æé宿°æ®</div> |
| | | <el-table :data="salesData" border style="width: 100%; margin-bottom: 15px;" v-loading="salesLoading" @selection-change="handleSalesSelectionChange"> |
| | | <div v-if="statementDetailLoaded" class="sales-section"> |
| | | <div v-if="salesData.length > 0" class="section-title">æ¬æé宿°æ®</div> |
| | | <el-table |
| | | v-if="salesData.length > 0" |
| | | ref="salesTableRef" |
| | | :data="salesData" |
| | | border |
| | | row-key="id" |
| | | style="width: 100%; margin-bottom: 15px;" |
| | | v-loading="salesLoading" |
| | | @selection-change="handleSalesSelectionChange" |
| | | > |
| | | <el-table-column type="selection" width="55" align="center" /> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="code" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="occurrenceDate" label="æ¥æ" width="120" /> |
| | | <el-table-column prop="receiptNumber" label="åæ®ç¼å·" width="150" /> |
| | | <el-table-column prop="type" label="ç±»å" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.type === 'åºåº' ? 'success' : row.type === 'æ¶æ¬¾' ? 'primary' : 'danger'">{{ row.type }}</el-tag> |
| | | <el-tag :type="getDetailTypeTagType(row.type)">{{ row.typeLabel }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="amount" label="éé¢" width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.type === 'åºåº' ? 'text-primary' : row.type === 'æ¶æ¬¾' ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(row.amount) }}</span> |
| | | <span :class="getDetailAmountClass(row.type)">Â¥{{ formatMoney(row.amount) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | <el-empty v-else description="è¯¥å®¢æ·æ¬æææ æç»æ°æ®" :image-size="80" /> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.beginBalance) }}</strong></span> |
| | | <span>æ¬æåºæ¶: <strong class="text-primary">Â¥{{ formatMoney(generateForm.currentReceivable) }}</strong></span> |
| | | <span>æ¬ææ¶æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentReceipt) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong :class="calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt) >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt)) }}</strong></span> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.openingBalance) }}</strong></span> |
| | | <span>æ¬æåºæ¶: <strong class="text-primary">Â¥{{ formatMoney(generateForm.currentPlan) }}</strong></span> |
| | | <span>æ¬ææ¶æ¬¾: <strong class="text-success">Â¥{{ formatMoney(generateForm.currentActually) }}</strong></span> |
| | | <span>ææ«ä½é¢: <strong :class="displayClosingBalance >= 0 ? 'text-success' : 'text-danger'">Â¥{{ formatMoney(displayClosingBalance) }}</strong></span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.customerId && !salesLoading" class="empty-tip"> |
| | | <div v-else-if="generateForm.customerId && generateForm.statementMonth && !salesLoading" class="empty-tip"> |
| | | <el-empty description="è¯¥å®¢æ·æ¬æææ é宿°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate">确认çæ</el-button> |
| | | <el-button type="primary" @click="confirmGenerate" :disabled="!canGenerate" :loading="submitLoading">确认çæ</el-button> |
| | | <el-button @click="generateDialogVisible = false">åæ¶</el-button> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { ref, reactive, onMounted, computed, nextTick, getCurrentInstance } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | import { |
| | | getAccountStatementDetailsByMonth, |
| | | addAccountStatement, |
| | | listPageAccountStatement, |
| | | deleteAccountStatement, |
| | | } from "@/api/financialManagement/accountStatement.js"; |
| | | |
| | | const ACCOUNT_TYPE_RECEIVABLE = 1; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | defineOptions({ |
| | | name: "åºæ¶å¯¹è´¦", |
| | |
| | | }); |
| | | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementCode", width: "150" }, |
| | | { label: "对账åå·", prop: "statementNumber", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "对账æé´", prop: "period", width: "150" }, |
| | | { label: "æåä½é¢", prop: "beginBalance", slot: "beginBalance" }, |
| | | { label: "æ¬æåºæ¶", prop: "currentReceivable", slot: "currentReceivable" }, |
| | | { label: "æ¬ææ¶æ¬¾", prop: "currentReceipt", slot: "currentReceipt" }, |
| | | { label: "ææ«ä½é¢", prop: "endBalance", slot: "endBalance" }, |
| | | { label: "æä½", prop: "operation", slot: "operation", width: "150", fixed: "right" }, |
| | | { label: "对账æé´", prop: "statementMonth", width: "150" }, |
| | | { label: "æåä½é¢", prop: "openingBalance", dataType: "slot", slot: "openingBalance" }, |
| | | { label: "æ¬æåºæ¶", prop: "currentPlan", dataType: "slot", slot: "currentPlan" }, |
| | | { label: "æ¬ææ¶æ¬¾", prop: "currentActually", dataType: "slot", slot: "currentActually" }, |
| | | { label: "ææ«ä½é¢", prop: "closingBalance", dataType: "slot", slot: "closingBalance" }, |
| | | { label: "æä½", prop: "operation", dataType: "slot", slot: "operation", width: "200", fixed: "right" }, |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const tableLoading = ref(false); |
| | | const submitLoading = ref(false); |
| | | const detailDialogVisible = ref(false); |
| | | const currentCustomer = ref(""); |
| | | const currentPeriod = ref(""); |
| | | const detailData = ref([]); |
| | | const detailLoading = ref(false); |
| | | |
| | | const generateDialogVisible = ref(false); |
| | | const salesLoading = ref(false); |
| | | const statementDetailLoaded = ref(false); |
| | | const salesData = ref([]); |
| | | const selectedSales = ref([]); |
| | | const salesTableRef = ref(null); |
| | | const customerList = ref([]); |
| | | |
| | | /** æç» typeï¼1åºåº 2å
¥åº 3æ¶æ¬¾ 4仿¬¾ 5éè´§ */ |
| | | const STATEMENT_DETAIL_TYPE_MAP = { |
| | | 1: "åºåº", |
| | | 2: "å
¥åº", |
| | | 3: "æ¶æ¬¾", |
| | | 4: "仿¬¾", |
| | | 5: "éè´§", |
| | | }; |
| | | |
| | | const calculateEndBalance = (openingBalance, currentPlan, currentActually) => { |
| | | return openingBalance + currentPlan - currentActually; |
| | | }; |
| | | |
| | | const getDetailTypeLabel = (type) => STATEMENT_DETAIL_TYPE_MAP[Number(type)] ?? ""; |
| | | |
| | | const getDetailTypeTagType = (type) => { |
| | | const t = Number(type); |
| | | if (t === 1) return "success"; |
| | | if (t === 3) return "primary"; |
| | | if (t === 5) return "danger"; |
| | | return "info"; |
| | | }; |
| | | |
| | | const getDetailAmountClass = (type) => { |
| | | const t = Number(type); |
| | | if (t === 1) return "text-primary"; |
| | | if (t === 3) return "text-success"; |
| | | return "text-danger"; |
| | | }; |
| | | |
| | | const generateForm = reactive({ |
| | | customerId: "", |
| | | customerName: "", |
| | | period: "", |
| | | beginBalance: 0, |
| | | currentReceivable: 0, |
| | | currentReceipt: 0, |
| | | statementMonth: "", |
| | | openingBalance: 0, |
| | | currentPlan: 0, |
| | | currentActually: 0, |
| | | closingBalance: 0, |
| | | }); |
| | | |
| | | const displayClosingBalance = computed(() => { |
| | | return calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }); |
| | | |
| | | const canGenerate = computed(() => { |
| | | return generateForm.customerId && generateForm.period && selectedSales.value.length > 0; |
| | | return generateForm.customerId && generateForm.statementMonth && selectedSales.value.length > 0; |
| | | }); |
| | | |
| | | const customerList = [ |
| | | { id: 1, name: "åäº¬ç§ææéå
¬å¸" }, |
| | | { id: 2, name: "䏿µ·è´¸æå
¬å¸" }, |
| | | { id: 3, name: "广å·å®ä¸æéå
¬å¸" }, |
| | | { id: 4, name: "æ·±å³çµåå
¬å¸" }, |
| | | ]; |
| | | |
| | | const mockData = [ |
| | | { id: 1, statementCode: "DZ202401001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", period: "2024-01", beginBalance: 10000, currentReceivable: 15000, currentReceipt: 8000, endBalance: 17000 }, |
| | | { id: 2, statementCode: "DZ202401002", customerId: 2, customerName: "䏿µ·è´¸æå
¬å¸", period: "2024-01", beginBalance: 5000, currentReceivable: 12000, currentReceipt: 10000, endBalance: 7000 }, |
| | | { id: 3, statementCode: "DZ202402001", customerId: 1, customerName: "åäº¬ç§ææéå
¬å¸", period: "2024-02", beginBalance: 17000, currentReceivable: 20000, currentReceipt: 15000, endBalance: 22000 }, |
| | | ]; |
| | | |
| | | const calculateEndBalance = (beginBalance, currentReceivable, currentReceipt) => { |
| | | return beginBalance + currentReceivable - currentReceipt; |
| | | const applyStatementSummary = (data) => { |
| | | generateForm.openingBalance = Number(data.openingBalance ?? 0); |
| | | generateForm.currentPlan = Number(data.currentPlan ?? 0); |
| | | generateForm.currentActually = Number(data.currentActually ?? 0); |
| | | generateForm.closingBalance = Number( |
| | | data.closingBalance ?? |
| | | calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ) |
| | | ); |
| | | }; |
| | | |
| | | const getCustomerList = () => { |
| | | listCustomer({ current: -1, size: -1, type: 0 }).then((res) => { |
| | | if (res.code === 200) { |
| | | customerList.value = res.data?.records || []; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const normalizeSalesRows = (list) => { |
| | | const rows = Array.isArray(list) ? list : []; |
| | | return rows.map((item, index) => { |
| | | const type = Number(item.type); |
| | | return { |
| | | id: item.id ?? `detail-${index}`, |
| | | accountStatementId: item.accountStatementId, |
| | | occurrenceDate: item.occurrenceDate ?? "", |
| | | receiptNumber: item.receiptNumber ?? "", |
| | | type, |
| | | typeLabel: getDetailTypeLabel(type), |
| | | amount: Math.abs(Number(item.amount ?? 0)), |
| | | remark: item.remark ?? "", |
| | | }; |
| | | }); |
| | | }; |
| | | |
| | | const selectAllSalesRows = (keepApiSummary = false) => { |
| | | nextTick(() => { |
| | | const table = salesTableRef.value; |
| | | if (!table) return; |
| | | table.clearSelection(); |
| | | salesData.value.forEach((row) => table.toggleRowSelection(row, true)); |
| | | selectedSales.value = [...salesData.value]; |
| | | if (!keepApiSummary) { |
| | | calculateSummary(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const isNumericId = (id) => id !== undefined && id !== null && id !== "" && /^\d+$/.test(String(id)); |
| | | |
| | | const buildFilterParams = (params = {}) => { |
| | | const result = { ...params, accountType: ACCOUNT_TYPE_RECEIVABLE }; |
| | | if (filters.customerId) { |
| | | result.customerId = filters.customerId; |
| | | } |
| | | if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) { |
| | | result.statementMonth = filters.startMonth; |
| | | } else if (filters.startMonth) { |
| | | result.startMonth = filters.startMonth; |
| | | } |
| | | if (filters.endMonth && filters.startMonth !== filters.endMonth) { |
| | | result.endMonth = filters.endMonth; |
| | | } |
| | | return result; |
| | | }; |
| | | |
| | | const buildListParams = () => |
| | | buildFilterParams({ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | }); |
| | | |
| | | const buildExportParams = () => buildFilterParams({}); |
| | | |
| | | const buildDetailSubmitItem = (row) => { |
| | | const item = { |
| | | occurrenceDate: row.occurrenceDate, |
| | | receiptNumber: row.receiptNumber, |
| | | type: row.type, |
| | | amount: row.amount, |
| | | remark: row.remark ?? "", |
| | | }; |
| | | if (isNumericId(row.id)) { |
| | | item.id = Number(row.id); |
| | | } |
| | | if (row.accountStatementId) { |
| | | item.accountStatementId = row.accountStatementId; |
| | | } |
| | | return item; |
| | | }; |
| | | |
| | | const buildAddPayload = () => ({ |
| | | customerId: generateForm.customerId, |
| | | customerName: generateForm.customerName, |
| | | statementMonth: generateForm.statementMonth, |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | statementNumber: "", |
| | | openingBalance: generateForm.openingBalance, |
| | | currentPlan: generateForm.currentPlan, |
| | | currentActually: generateForm.currentActually, |
| | | closingBalance: generateForm.closingBalance, |
| | | accountStatementDetails: selectedSales.value.map(buildDetailSubmitItem), |
| | | }); |
| | | |
| | | const formatMoney = (value) => { |
| | | if (value === undefined || value === null) return "0.00"; |
| | |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | let result = [...mockData]; |
| | | if (filters.customerId) { |
| | | result = result.filter(item => item.customerId === filters.customerId); |
| | | } |
| | | if (filters.startMonth && filters.endMonth) { |
| | | result = result.filter(item => item.period >= filters.startMonth && item.period <= filters.endMonth); |
| | | } |
| | | pagination.total = result.length; |
| | | dataList.value = result.slice((pagination.currentPage - 1) * pagination.pageSize, pagination.currentPage * pagination.pageSize); |
| | | tableLoading.value = true; |
| | | listPageAccountStatement(buildListParams()) |
| | | .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 = []; |
| | | pagination.total = 0; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | dataList.value = []; |
| | | pagination.total = 0; |
| | | ElMessage.error("æ¥è¯¢å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | |
| | | const generateStatement = () => { |
| | | generateForm.customerId = ""; |
| | | generateForm.customerName = ""; |
| | | generateForm.period = ""; |
| | | generateForm.beginBalance = 0; |
| | | generateForm.currentReceivable = 0; |
| | | generateForm.currentReceipt = 0; |
| | | generateForm.statementMonth = ""; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | statementDetailLoaded.value = false; |
| | | salesData.value = []; |
| | | selectedSales.value = []; |
| | | generateDialogVisible.value = true; |
| | | }; |
| | | |
| | | const onCustomerChange = (customerId) => { |
| | | const customer = customerList.find(item => item.id === customerId); |
| | | if (customer) { |
| | | generateForm.customerName = customer.name; |
| | | } |
| | | const customer = customerList.value.find((item) => item.id === customerId); |
| | | generateForm.customerName = customer?.customerName ?? ""; |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const onPeriodChange = () => { |
| | | const onStatementMonthChange = () => { |
| | | loadSalesData(); |
| | | }; |
| | | |
| | | const loadSalesData = () => { |
| | | if (!generateForm.customerId || !generateForm.period) { |
| | | if (!generateForm.customerId || !generateForm.statementMonth) { |
| | | salesData.value = []; |
| | | selectedSales.value = []; |
| | | statementDetailLoaded.value = false; |
| | | generateForm.openingBalance = 0; |
| | | generateForm.currentPlan = 0; |
| | | generateForm.currentActually = 0; |
| | | generateForm.closingBalance = 0; |
| | | return; |
| | | } |
| | | |
| | | salesLoading.value = true; |
| | | selectedSales.value = []; |
| | | statementDetailLoaded.value = false; |
| | | |
| | | setTimeout(() => { |
| | | const mockSalesData = [ |
| | | { id: 1, date: generateForm.period + "-03", code: "CK2024001", type: "åºåº", amount: 8000, remark: "产åAéå®" }, |
| | | { id: 2, date: generateForm.period + "-08", code: "SK2024001", type: "æ¶æ¬¾", amount: 5000, remark: "客æ·å款" }, |
| | | { id: 3, date: generateForm.period + "-12", code: "CK2024002", type: "åºåº", amount: 12000, remark: "产åBéå®" }, |
| | | { id: 4, date: generateForm.period + "-15", code: "TH2024001", type: "éè´§", amount: 2000, remark: "è´¨éé®é¢éè´§" }, |
| | | { id: 5, date: generateForm.period + "-20", code: "CK2024003", type: "åºåº", amount: 5000, remark: "产åCéå®" }, |
| | | { id: 6, date: generateForm.period + "-25", code: "SK2024002", type: "æ¶æ¬¾", amount: 8000, remark: "客æ·å款" }, |
| | | ]; |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | customerId: generateForm.customerId, |
| | | statementMonth: generateForm.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | const data = res.data ?? {}; |
| | | const details = data.accountStatementDetails; |
| | | const list = Array.isArray(details) ? details : []; |
| | | salesData.value = normalizeSalesRows(list); |
| | | applyStatementSummary(data); |
| | | statementDetailLoaded.value = true; |
| | | |
| | | salesData.value = mockSalesData; |
| | | |
| | | const lastPeriod = getLastPeriod(generateForm.period); |
| | | const lastStatement = mockData.find(item => |
| | | item.customerId === generateForm.customerId && item.period === lastPeriod |
| | | ); |
| | | generateForm.beginBalance = lastStatement ? lastStatement.endBalance : 0; |
| | | |
| | | calculateSummary(); |
| | | |
| | | salesLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | const getLastPeriod = (period) => { |
| | | const [year, month] = period.split("-").map(Number); |
| | | if (month === 1) { |
| | | return `${year - 1}-12`; |
| | | } |
| | | return `${year}-${String(month - 1).padStart(2, "0")}`; |
| | | if (salesData.value.length > 0) { |
| | | selectAllSalesRows(true); |
| | | } |
| | | } else { |
| | | salesData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error(res.msg || "æ¥è¯¢å¯¹è´¦æç»å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | salesData.value = []; |
| | | statementDetailLoaded.value = false; |
| | | ElMessage.error("æ¥è¯¢å¯¹è´¦æç»å¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | salesLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const calculateSummary = () => { |
| | | let receivable = 0; |
| | | let receipt = 0; |
| | | |
| | | selectedSales.value.forEach(item => { |
| | | if (item.type === "åºåº") { |
| | | selectedSales.value.forEach((item) => { |
| | | if (item.type === 1) { |
| | | receivable += item.amount; |
| | | } else if (item.type === "éè´§") { |
| | | } else if (item.type === 5) { |
| | | receivable -= item.amount; |
| | | } else if (item.type === "æ¶æ¬¾") { |
| | | } else if (item.type === 3) { |
| | | receipt += item.amount; |
| | | } |
| | | }); |
| | | |
| | | generateForm.currentReceivable = receivable; |
| | | generateForm.currentReceipt = receipt; |
| | | generateForm.currentPlan = receivable; |
| | | generateForm.currentActually = receipt; |
| | | generateForm.closingBalance = calculateEndBalance( |
| | | generateForm.openingBalance, |
| | | generateForm.currentPlan, |
| | | generateForm.currentActually |
| | | ); |
| | | }; |
| | | |
| | | const handleSalesSelectionChange = (selection) => { |
| | |
| | | }; |
| | | |
| | | const confirmGenerate = () => { |
| | | const newId = mockData.length > 0 ? Math.max(...mockData.map(item => item.id)) + 1 : 1; |
| | | const endBalance = calculateEndBalance(generateForm.beginBalance, generateForm.currentReceivable, generateForm.currentReceipt); |
| | | if (!canGenerate.value) return; |
| | | submitLoading.value = true; |
| | | addAccountStatement(buildAddPayload()) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "çæå¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("çæå¤±è´¥"); |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | mockData.unshift({ |
| | | id: newId, |
| | | statementCode: "DZ" + Date.now(), |
| | | customerId: generateForm.customerId, |
| | | customerName: generateForm.customerName, |
| | | period: generateForm.period, |
| | | beginBalance: generateForm.beginBalance, |
| | | currentReceivable: generateForm.currentReceivable, |
| | | currentReceipt: generateForm.currentReceipt, |
| | | endBalance, |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm(`确认å é¤å¯¹è´¦åã${row.statementNumber || row.id}ãåï¼`, "æç¤º", { |
| | | confirmButtonText: "ç¡®å®", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | deleteAccountStatement([row.id]) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("å 餿å"); |
| | | getTableData(); |
| | | } else { |
| | | ElMessage.error(res.msg || "å é¤å¤±è´¥"); |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("å é¤å¤±è´¥"); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const buildDetailTableFromApi = (data, statementMonth) => { |
| | | const details = Array.isArray(data.accountStatementDetails) ? data.accountStatementDetails : []; |
| | | let runningBalance = Number(data.openingBalance ?? 0); |
| | | const rows = [ |
| | | { |
| | | date: statementMonth ?? "", |
| | | type: "æå", |
| | | code: "-", |
| | | debit: 0, |
| | | credit: 0, |
| | | balance: runningBalance, |
| | | remark: "æåä½é¢", |
| | | }, |
| | | ]; |
| | | |
| | | details.forEach((item) => { |
| | | const amount = Math.abs(Number(item.amount ?? 0)); |
| | | const type = Number(item.type); |
| | | let debit = 0; |
| | | let credit = 0; |
| | | |
| | | if (type === 1) { |
| | | debit = amount; |
| | | runningBalance += amount; |
| | | } else if (type === 3 || type === 5) { |
| | | credit = amount; |
| | | runningBalance -= amount; |
| | | } |
| | | |
| | | rows.push({ |
| | | date: item.occurrenceDate ?? "", |
| | | type: getDetailTypeLabel(type), |
| | | code: item.receiptNumber ?? "", |
| | | debit, |
| | | credit, |
| | | balance: runningBalance, |
| | | remark: item.remark ?? "", |
| | | }); |
| | | }); |
| | | |
| | | generateDialogVisible.value = false; |
| | | ElMessage.success("对账åçææå"); |
| | | getTableData(); |
| | | return rows; |
| | | }; |
| | | |
| | | const viewDetail = (row) => { |
| | | currentCustomer.value = row.customerName; |
| | | currentPeriod.value = row.period; |
| | | if (!row.customerId || !row.statementMonth) { |
| | | ElMessage.warning("缺å°å®¢æ·æå¯¹è´¦æä»½ï¼æ æ³æ¥è¯¢æç»"); |
| | | return; |
| | | } |
| | | |
| | | const saleOutAmount = Math.floor(row.currentReceivable * 0.6); |
| | | const returnAmount = Math.floor(row.currentReceivable * 0.1); |
| | | const firstReceipt = Math.floor(row.currentReceipt * 0.4); |
| | | const secondReceipt = row.currentReceipt - firstReceipt; |
| | | |
| | | let runningBalance = row.beginBalance; |
| | | |
| | | detailData.value = [ |
| | | { date: row.period + "-01", type: "æå", code: "-", debit: 0, credit: 0, balance: runningBalance, remark: "æåä½é¢" }, |
| | | { date: row.period + "-05", type: "åºåº", code: "CK2024001", debit: saleOutAmount, credit: 0, balance: runningBalance += saleOutAmount, remark: "éå®åºåº" }, |
| | | { date: row.period + "-10", type: "æ¶æ¬¾", code: "SK2024001", debit: 0, credit: firstReceipt, balance: runningBalance -= firstReceipt, remark: "客æ·å款" }, |
| | | { date: row.period + "-15", type: "åºåº", code: "CK2024002", debit: row.currentReceivable - saleOutAmount - returnAmount, credit: 0, balance: runningBalance += (row.currentReceivable - saleOutAmount - returnAmount), remark: "éå®åºåº" }, |
| | | { date: row.period + "-20", type: "éè´§", code: "TH2024001", debit: 0, credit: returnAmount, balance: runningBalance -= returnAmount, remark: "éå®éè´§" }, |
| | | { date: row.period + "-25", type: "æ¶æ¬¾", code: "SK2024002", debit: 0, credit: secondReceipt, balance: runningBalance -= secondReceipt, remark: "客æ·å款" }, |
| | | ]; |
| | | |
| | | currentCustomer.value = row.customerName ?? ""; |
| | | currentPeriod.value = row.statementMonth ?? ""; |
| | | detailData.value = []; |
| | | detailDialogVisible.value = true; |
| | | detailLoading.value = true; |
| | | |
| | | getAccountStatementDetailsByMonth({ |
| | | accountType: ACCOUNT_TYPE_RECEIVABLE, |
| | | customerId: row.customerId, |
| | | statementMonth: row.statementMonth, |
| | | }) |
| | | .then((res) => { |
| | | if (res.code === 200) { |
| | | detailData.value = buildDetailTableFromApi(res.data ?? {}, row.statementMonth); |
| | | } else { |
| | | ElMessage.error(res.msg || "æ¥è¯¢æç»å¤±è´¥"); |
| | | detailDialogVisible.value = false; |
| | | } |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("æ¥è¯¢æç»å¤±è´¥"); |
| | | detailDialogVisible.value = false; |
| | | }) |
| | | .finally(() => { |
| | | detailLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const printStatement = (row) => { |
| | | ElMessage.info(`æå°å¯¹è´¦å: ${row.statementCode}`); |
| | | ElMessage.info(`æå°å¯¹è´¦å: ${row.statementNumber}`); |
| | | }; |
| | | |
| | | const printDetail = () => { |
| | |
| | | }; |
| | | |
| | | const handleOut = () => { |
| | | ElMessage.success("å¯¼åºæå"); |
| | | const params = buildExportParams(); |
| | | proxy.download("/accountStatement/exportAccountStatement", params, `åºæ¶å¯¹è´¦å_${Date.now()}.xlsx`); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getCustomerList(); |
| | | getTableData(); |
| | | }); |
| | | </script> |
| | |
| | | <div class="panel-title">ç产订åè¿åº¦</div> |
| | | <el-radio-group v-model="orderFilter" size="small"> |
| | | <el-radio-button label="all">å
¨é¨({{ orderProgressMeta.total }})</el-radio-button> |
| | | <el-radio-button label="waiting">å¾
å¼å§({{ orderProgressMeta.waitingCount }})</el-radio-button> |
| | | <el-radio-button label="inProgress">è¿è¡ä¸({{ orderProgressMeta.inProgressCount }})</el-radio-button> |
| | | <el-radio-button label="completed">已宿({{ orderProgressMeta.completedCount }})</el-radio-button> |
| | | <el-radio-button label="paused">å·²æå({{ orderProgressMeta.pausedCount }})</el-radio-button> |
| | |
| | | }); |
| | | |
| | | const orderProgressMeta = ref({ |
| | | status: "all", |
| | | tab: "all", |
| | | bizDate: null, |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | waitingCount: 0, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | |
| | | |
| | | const productionOrders = ref([]); |
| | | |
| | | const orderFilterOptions = ["all", "waiting", "inProgress", "completed", "paused"]; |
| | | const orderFilterAliasMap = { |
| | | 1: "waiting", |
| | | 2: "inProgress", |
| | | 3: "completed", |
| | | 4: "paused", |
| | | }; |
| | | const orderFilter = ref("all"); |
| | | const filteredOrders = computed(() => productionOrders.value); |
| | | |
| | | const normalizeOrderFilter = (value, fallback = "all") => { |
| | | const safeFallback = orderFilterOptions.includes(fallback) ? fallback : "all"; |
| | | const text = String(value ?? "").trim(); |
| | | if (orderFilterAliasMap[text]) { |
| | | return orderFilterAliasMap[text]; |
| | | } |
| | | return orderFilterOptions.includes(text) ? text : safeFallback; |
| | | }; |
| | | |
| | | const parseCount = (value) => { |
| | | if (value === null || value === undefined || value === "") return null; |
| | | const num = Number(value); |
| | | return Number.isFinite(num) ? num : null; |
| | | }; |
| | | |
| | | const resolveProgressCount = (rawValue, currentStatus, targetStatus, total) => { |
| | | const count = parseCount(rawValue); |
| | | if (count !== null) return count; |
| | | return currentStatus === targetStatus ? total : 0; |
| | | }; |
| | | |
| | | const getCompareTrend = (value) => { |
| | | const num = Number(value || 0); |
| | |
| | | const refreshProductionOrderProgress = async () => { |
| | | try { |
| | | const res = await productionOrderProgress({ |
| | | status: orderFilter.value, |
| | | tab: orderFilter.value, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | }); |
| | | const data = res?.data || {}; |
| | | const statusValue = normalizeOrderFilter(data.status, orderFilter.value); |
| | | const total = Number(data.total || 0); |
| | | orderProgressMeta.value = { |
| | | status: statusValue, |
| | | tab: data.tab || orderFilter.value, |
| | | total: Number(data.total || 0), |
| | | bizDate: data.bizDate || null, |
| | | total, |
| | | pageNum: Number(data.pageNum || 1), |
| | | pageSize: Number(data.pageSize || 10), |
| | | inProgressCount: Number(data.inProgressCount || 0), |
| | | completedCount: Number(data.completedCount || 0), |
| | | pausedCount: Number(data.pausedCount || 0), |
| | | waitingCount: resolveProgressCount(data.waitingCount, statusValue, "waiting", total), |
| | | inProgressCount: resolveProgressCount(data.inProgressCount, statusValue, "inProgress", total), |
| | | completedCount: resolveProgressCount(data.completedCount, statusValue, "completed", total), |
| | | pausedCount: resolveProgressCount(data.pausedCount, statusValue, "paused", total), |
| | | }; |
| | | productionOrders.value = (data.records || []).map(mapOrderProgressRecord); |
| | | } catch { |
| | | orderProgressMeta.value = { |
| | | status: orderFilter.value, |
| | | tab: orderFilter.value, |
| | | bizDate: null, |
| | | total: 0, |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | waitingCount: 0, |
| | | inProgressCount: 0, |
| | | completedCount: 0, |
| | | pausedCount: 0, |
| | |
| | | |
| | | const refreshTodayProductionPlan = async () => { |
| | | try { |
| | | const res = await todayProductionPlan({ limit: 4 }); |
| | | const res = await todayProductionPlan({ |
| | | limit: 4, |
| | | planDate: nowDate.value, |
| | | }); |
| | | const data = res?.data || {}; |
| | | todayPlanTotal.value = Number(data.total || 0); |
| | | todayPlanList.value = (data.records || []).map(mapTodayPlanRecord); |
| | |
| | | <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.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></div> |
| | | </div> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="14"> |
| | | <div class="table_list"> |
| | | <el-table |
| | | ref="multipleTable" |
| | | border |
| | | v-loading="tableLoading" |
| | | :data="tableData" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | height="calc(100vh - 18.5em)" |
| | | :highlight-current-row="true" |
| | | style="width: 100%" |
| | | tooltip-effect="dark" |
| | | @row-click="rowClick" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summarizeMainTable" |
| | | class="lims-table" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="ä¾åºååç§°" prop="supplierName" /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="invoiceAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="仿¬¾éé¢(å
)" |
| | | prop="paymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | /> |
| | | <el-table-column |
| | | label="åºä»éé¢(å
)" |
| | | prop="payableAmount" |
| | | show-overflow-tooltip |
| | | > |
| | | <el-table ref="multipleTable" |
| | | border |
| | | v-loading="tableLoading" |
| | | :data="tableData" |
| | | :header-cell-style="{ background: '#F0F1F5', color: '#333333' }" |
| | | height="calc(100vh - 18.5em)" |
| | | :highlight-current-row="true" |
| | | style="width: 100%" |
| | | tooltip-effect="dark" |
| | | @row-click="rowClick" |
| | | :show-summary="isShowSummary" |
| | | :summary-method="summarizeMainTable" |
| | | class="lims-table"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="ä¾åºååç§°" |
| | | prop="supplierName" /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmounts" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="仿¬¾éé¢(å
)" |
| | | prop="paymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" /> |
| | | <el-table-column label="åºä»éé¢(å
)" |
| | | prop="payableAmount" |
| | | show-overflow-tooltip> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.payableAmount) }} |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination |
| | | v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" |
| | | /> |
| | | <pagination v-show="total > 0" |
| | | @pagination="paginationSearch" |
| | | :total="total" |
| | | :layout="page.layout" |
| | | :page="page.current" |
| | | :limit="page.size" /> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="10"> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumnSon" |
| | | :tableData="originalTableDataSon" |
| | | :isSelection="false" |
| | | :isShowPagination="false" |
| | | :tableLoading="tableLoadingSon" |
| | | :isShowSummary="isShowSummarySon" |
| | | :summaryMethod="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumnSon" |
| | | :tableData="originalTableDataSon" |
| | | :isSelection="false" |
| | | :isShowPagination="true" |
| | | :page="sonPage" |
| | | :tableLoading="tableLoadingSon" |
| | | :isShowSummary="isShowSummarySon" |
| | | :summaryMethod="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)" |
| | | @pagination="sonPaginationSearch"> |
| | | <template #payableAmountSlot="{ row }"> |
| | | <el-text type="danger"> |
| | | {{ parseFloat(row.payableAmount).toFixed(2) }} |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, toRefs } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | paymentLedgerList, |
| | | paymentRecordList, |
| | | } from "@/api/procurementManagement/paymentLedger.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | import { ref, toRefs } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { |
| | | paymentLedgerList, |
| | | paymentRecordList, |
| | | } from "@/api/procurementManagement/paymentLedger.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierNameOrContractNo: "", |
| | | }, |
| | | }); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const sonPage = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const sonTotal = ref(0); |
| | | const isShowSummary = ref(true); |
| | | const { searchForm } = toRefs(data); |
| | | const currentSupplierId = ref(""); |
| | | const rowClick = (row) => { |
| | | currentSupplierId.value = row.supplierId; |
| | | getPaymenRecordtList(row.supplierId); |
| | | }; |
| | | // 忍¡å |
| | | const tableColumnSon = ref([ |
| | | { |
| | | label: "åçæ¥æ", |
| | | prop: "paymentDate", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "éè´ååå·", |
| | | prop: "purchaseContractNumber", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ååéé¢(å
)", |
| | | prop: "invoiceAmount", |
| | | width: 200, |
| | | formatData: (params) => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: "", |
| | | }, |
| | | }, |
| | | { |
| | | label: "仿¬¾éé¢(å
)", |
| | | prop: "paymentAmount", |
| | | width: 200, |
| | | formatData: (params) => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | }, |
| | | }, |
| | | { |
| | | label: "åºä»éé¢(å
)", |
| | | dataType: "slot", |
| | | width: 200, |
| | | prop: "payableAmount", |
| | | slot: "payableAmountSlot", |
| | | }, |
| | | ]); |
| | | const tableDataSon = ref([]); |
| | | const originalTableDataSon = ref([]); |
| | | const tableLoadingSon = ref(false); |
| | | const isShowSummarySon = ref(true); |
| | | const detailPageNum = ref(1); |
| | | const detailPageSize = ref(10); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["invoiceAmount", "paymentAmount", "payableAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = (param) => { |
| | | let summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["invoiceAmount", "paymentAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | if (originalTableDataSon.value.length > 0) { |
| | | summarizeTable[summarizeTable.length - 1] = |
| | | originalTableDataSon.value[ |
| | | originalTableDataSon.value.length - 1 |
| | | ].payableAmount.toFixed(2); |
| | | } else { |
| | | summarizeTable[summarizeTable.length - 1] = 0.0; |
| | | } |
| | | return summarizeTable; |
| | | }; |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationSearch = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | paymentLedgerList({ |
| | | ...searchForm.value, |
| | | ...page, |
| | | }).then((res) => { |
| | | let result = res.data; |
| | | tableLoading.value = false; |
| | | tableData.value = result.records || []; |
| | | total.value = result.total || 0; |
| | | if (tableData.value.length > 0) { |
| | | getPaymenRecordtList(tableData.value[0].supplierId); |
| | | currentSupplierId.value = tableData.value[0].supplierId; |
| | | } |
| | | }); |
| | | }; |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const sonPage = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | total: 0, |
| | | layout: "total, sizes, prev, pager, next, jumper", |
| | | }); |
| | | const total = ref(0); |
| | | const isShowSummary = ref(true); |
| | | const { searchForm } = toRefs(data); |
| | | const currentSupplierId = ref(""); |
| | | const rowClick = row => { |
| | | currentSupplierId.value = row.supplierId; |
| | | sonPage.current = 1; |
| | | getPaymenRecordtList(row.supplierId); |
| | | }; |
| | | // 忍¡å |
| | | const tableColumnSon = ref([ |
| | | { |
| | | label: "ååç¾è®¢æ¥æ", |
| | | prop: "executionDate", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: "éè´ååå·", |
| | | prop: "purchaseContractNumber", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ååéé¢(å
)", |
| | | prop: "contractAmount", |
| | | width: 200, |
| | | formatData: params => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | }, |
| | | }, |
| | | { |
| | | label: "仿¬¾éé¢(å
)", |
| | | prop: "paymentAmount", |
| | | width: 200, |
| | | formatData: params => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | }, |
| | | }, |
| | | { |
| | | label: "åºä»éé¢(å
)", |
| | | dataType: "slot", |
| | | width: 200, |
| | | prop: "payableAmount", |
| | | slot: "payableAmountSlot", |
| | | }, |
| | | ]); |
| | | const tableDataSon = ref([]); |
| | | const originalTableDataSon = ref([]); |
| | | const tableLoadingSon = ref(false); |
| | | const isShowSummarySon = ref(true); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const getPaymenRecordtList = (supplierId) => { |
| | | tableLoadingSon.value = true; |
| | | paymentRecordList({supplierId: supplierId}) |
| | | .then((res) => { |
| | | tableLoadingSon.value = false; |
| | | tableDataSon.value = res.data; |
| | | handlePagination({ page: 1, limit: sonPage.size }); |
| | | sonTotal.value = res.data.length; |
| | | }) |
| | | .catch((e) => { |
| | | tableLoadingSon.value = false; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["contractAmounts", "paymentAmount", "payableAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = param => { |
| | | let summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount", "invoiceAmount", "paymentAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | if (originalTableDataSon.value.length > 0) { |
| | | summarizeTable[summarizeTable.length - 1] = |
| | | originalTableDataSon.value[ |
| | | originalTableDataSon.value.length - 1 |
| | | ].payableAmount.toFixed(2); |
| | | } else { |
| | | summarizeTable[summarizeTable.length - 1] = 0.0; |
| | | } |
| | | return summarizeTable; |
| | | }; |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationSearch = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | paymentLedgerList({ |
| | | ...searchForm.value, |
| | | ...page, |
| | | }).then(res => { |
| | | let result = res.data; |
| | | tableLoading.value = false; |
| | | tableData.value = result.records || []; |
| | | total.value = result.total || 0; |
| | | if (tableData.value.length > 0) { |
| | | currentSupplierId.value = tableData.value[0].supplierId; |
| | | sonPage.current = 1; |
| | | getPaymenRecordtList(tableData.value[0].supplierId); |
| | | } |
| | | }); |
| | | }; |
| | | const handlePagination = ({ page, limit }) => { |
| | | sonPage.current = page; |
| | | sonPage.size = limit; |
| | | }; |
| | | |
| | | const start = (page - 1) * limit; |
| | | const end = start + limit; |
| | | const getPaymenRecordtList = supplierId => { |
| | | tableLoadingSon.value = true; |
| | | paymentRecordList({ |
| | | supplierId: supplierId, |
| | | current: sonPage.current, |
| | | size: sonPage.size, |
| | | }) |
| | | .then(res => { |
| | | tableLoadingSon.value = false; |
| | | let result = res.data; |
| | | if (Array.isArray(result)) { |
| | | tableDataSon.value = result; |
| | | sonPage.total = result.length; |
| | | handlePagination({ page: sonPage.current, limit: sonPage.size }); |
| | | } else { |
| | | originalTableDataSon.value = result.records || []; |
| | | sonPage.total = result.total || 0; |
| | | } |
| | | }) |
| | | .catch(e => { |
| | | tableLoadingSon.value = false; |
| | | }); |
| | | }; |
| | | const handlePagination = ({ page, limit }) => { |
| | | console.log(page, limit); |
| | | sonPage.current = page; |
| | | sonPage.size = limit; |
| | | |
| | | originalTableDataSon.value = tableDataSon.value.slice(start, end); |
| | | }; |
| | | const start = (page - 1) * limit; |
| | | const end = start + limit; |
| | | |
| | | const sonPaginationSearch = (pagination) => { |
| | | // æ¥æ¶å页å¨åæ° { page, limit } |
| | | handlePagination(pagination); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (column.property !== "supplierName") { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | } else { |
| | | return cellValue; |
| | | } |
| | | }; |
| | | getList(); |
| | | originalTableDataSon.value = tableDataSon.value.slice(start, end); |
| | | }; |
| | | |
| | | const sonPaginationSearch = pagination => { |
| | | // æ¥æ¶å页å¨åæ° { page, limit } |
| | | sonPage.current = pagination.page; |
| | | sonPage.size = pagination.limit; |
| | | getPaymenRecordtList(currentSupplierId.value); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (column.property !== "supplierName") { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | } else { |
| | | return cellValue; |
| | | } |
| | | }; |
| | | getList(); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .el-pagination { |
| | | width: 100%; |
| | | height: 55px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | float: right; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | background: #fff; |
| | | margin: -20px 0 0 0; |
| | | padding: 0 20px; |
| | | } |
| | | .pagination-container { |
| | | margin-top: 0; |
| | | } |
| | | .el-pagination { |
| | | width: 100%; |
| | | height: 55px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | float: right; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | background: #fff; |
| | | margin: -20px 0 0 0; |
| | | padding: 0 20px; |
| | | } |
| | | .pagination-container { |
| | | margin-top: 0; |
| | | } |
| | | </style> |
| | |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (!formData.products || formData.products.length === 0) { |
| | | ElMessage.error('请è³å°æ·»å 䏿¡è´¨æ£åå') |
| | | return |
| | | } |
| | | |
| | | for (let i = 0; i < formData.products.length; i++) { |
| | | const product = formData.products[i] |
| | | if (product.qualifiedQuantity === null || product.qualifiedQuantity === undefined) { |
| | | ElMessage.error(`第${i + 1}æ¡ååçåæ ¼æ°éä¸è½ä¸ºç©º`) |
| | | return |
| | | } |
| | | if (product.unqualifiedQuantity === null || product.unqualifiedQuantity === undefined) { |
| | | ElMessage.error(`第${i + 1}æ¡ååçä¸åæ ¼æ°éä¸è½ä¸ºç©º`) |
| | | return |
| | | } |
| | | } |
| | | |
| | | const totalQualified = formData.products.reduce((sum, p) => sum + (p.qualifiedQuantity || 0), 0) |
| | | const totalUnqualified = formData.products.reduce((sum, p) => sum + (p.unqualifiedQuantity || 0), 0) |
| | | |
| | | if (dialogType.value === 'add') { |
| | | const newInspection = { |
| | | id: Date.now(), |
| | |
| | | arrivalNo: formData.arrivalNo, |
| | | supplierName: formData.supplierName, |
| | | status: 'pending', |
| | | qualifiedQuantity: 0, |
| | | unqualifiedQuantity: 0, |
| | | qualifiedQuantity: totalQualified, |
| | | unqualifiedQuantity: totalUnqualified, |
| | | inspectionTime: new Date().toLocaleString(), |
| | | inspector: formData.inspector, |
| | | remark: formData.remark |
| | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { registrationList } from "@/api/procurementManagement/paymentEntry.js"; |
| | | import { registrationList } from "@/api/procurementManagement/paymentLedger.js"; |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "仿¬¾æ¥æ", |
| | |
| | | import EditProcess from "@/views/productionManagement/processRoute/Edit.vue"; |
| | | import RouteItemForm from "@/views/productionManagement/processRoute/ItemsForm.vue"; |
| | | import { listPage, del } from "@/api/productionManagement/processRoute.js"; |
| | | const FileList = defineAsyncComponent(() => import("@/components/Dialog/FileList.vue")); |
| | | const FileList = defineAsyncComponent(() => |
| | | import("@/components/Dialog/FileList.vue") |
| | | ); |
| | | |
| | | import { useRouter } from "vue-router"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | |
| | | <span class="info-value">{{ routeInfo.quantity || '-' }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-item full-width" |
| | | v-if="routeInfo.description"> |
| | | <div class="info-item"> |
| | | <div class="info-label-wrapper"> |
| | | <span class="info-label">æè¿°</span> |
| | | <span class="info-label">夿³¨</span> |
| | | </div> |
| | | <div class="info-value-wrapper"> |
| | | <span class="info-value">{{ routeInfo.description }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | | <!-- é件模å --> |
| | | <div v-if="pageType === 'order'" |
| | | class="section-header"> |
| | | <div class="section-title">éä»¶</div> |
| | | </div> |
| | | <el-card v-if="pageType === 'order'" |
| | | class="attachment-card" |
| | | shadow="hover" |
| | | style="margin-top: 10px; margin-bottom: 20px;"> |
| | | <el-table :data="attachmentTableData" |
| | | border |
| | | class="attachment-table"> |
| | | <el-table-column label="éä»¶åç§°" |
| | | prop="originalFilename" |
| | | show-overflow-tooltip /> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | width="200" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-button link |
| | | type="primary" |
| | | size="small" |
| | | @click="downloadAttachmentFile(scope.row.downloadURL)"> |
| | | ä¸è½½ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-card> |
| | | <!-- è¡¨æ ¼è§å¾ --> |
| | | <div v-if="viewMode === 'table'" |
| | |
| | | v-model="bomDataValue.showProductDialog" |
| | | :single="true" |
| | | @confirm="handleBomProduct" /> |
| | | <!-- ä¸ä¼ ç»ä»¶å¼¹çª --> |
| | | <el-dialog v-model="uploadDialogVisible" |
| | | title="ä¸ä¼ éä»¶" |
| | | width="50%" |
| | | @close="closeAttachmentUpload"> |
| | | <AttachmentUpload v-model:file-list="newFileList" /> |
| | | <template #footer> |
| | | <el-button @click="saveAttachmentUpload" |
| | | type="primary">ä¿å</el-button> |
| | | <el-button @click="closeAttachmentUpload">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- æ°å¢/ç¼è¾å¼¹çª --> |
| | | <el-dialog v-model="dialogVisible" |
| | | :title="operationType === 'add' ? 'æ°å¢å·¥èºè·¯çº¿é¡¹ç®' : 'ç¼è¾å·¥èºè·¯çº¿é¡¹ç®'" |
| | |
| | | queryList2, |
| | | add2, |
| | | } from "@/api/productionManagement/productStructure.js"; |
| | | import AttachmentUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { |
| | | attachmentList, |
| | | deleteAttachment, |
| | | createAttachment, |
| | | } from "@/api/basicData/storageAttachment.js"; |
| | | |
| | | import { useRoute } from "vue-router"; |
| | | import { ElMessageBox, ElMessage } from "element-plus"; |
| | |
| | | const orderId = computed(() => route.query.orderId); |
| | | const pageType = computed(() => route.query.type); |
| | | const editable = computed(() => route.query.editable !== "false"); |
| | | const technologyRoutingId = computed(() => route.query.technologyRoutingId); |
| | | |
| | | const tableLoading = ref(false); |
| | | const tableData = ref([]); |
| | |
| | | bomNo: "", |
| | | description: "", |
| | | quantity: 0, |
| | | technologyRoutingId: "", |
| | | }); |
| | | |
| | | // éä»¶ç¸å
³ |
| | | const attachmentTableData = ref([]); |
| | | const uploadDialogVisible = ref(false); |
| | | const newFileList = ref([]); |
| | | |
| | | const getAttachmentList = () => { |
| | | if (!technologyRoutingId.value) return; |
| | | attachmentList({ |
| | | recordType: "technology_routing", |
| | | recordId: technologyRoutingId.value, |
| | | }).then(res => { |
| | | attachmentTableData.value = (res && res.data) || []; |
| | | }); |
| | | }; |
| | | |
| | | const handleUploadAttachment = () => { |
| | | uploadDialogVisible.value = true; |
| | | }; |
| | | |
| | | const saveAttachmentUpload = async () => { |
| | | if (newFileList.value.length > 0) { |
| | | createAttachment({ |
| | | application: "file", |
| | | recordType: "technology_routing", |
| | | recordId: technologyRoutingId.value, |
| | | storageBlobDTOs: [...newFileList.value, ...attachmentTableData.value], |
| | | }) |
| | | .then(res => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("ä¸ä¼ æå"); |
| | | newFileList.value = []; |
| | | getAttachmentList(); |
| | | } |
| | | }) |
| | | .finally(() => { |
| | | uploadDialogVisible.value = false; |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const closeAttachmentUpload = () => { |
| | | newFileList.value = []; |
| | | uploadDialogVisible.value = false; |
| | | }; |
| | | |
| | | const handleDeleteAttachment = async row => { |
| | | deleteAttachment([row.storageAttachmentId]).then(res => { |
| | | if (res && res.code === 200) { |
| | | proxy?.$modal?.msgSuccess("å 餿å"); |
| | | getAttachmentList(); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const downloadAttachmentFile = url => { |
| | | window.open(url, "_blank"); |
| | | }; |
| | | |
| | | const processOptions = ref([]); |
| | | const showProductSelectDialog = ref(false); |
| | |
| | | bomId: route.query.bomId || "", |
| | | description: route.query.description || "", |
| | | quantity: route.query.quantity || 0, |
| | | technologyRoutingId: route.query.technologyRoutingId || "", |
| | | status: !(route.query.status == 1 || route.query.status === "false"), |
| | | }; |
| | | bomTableData.value[0].productName = routeInfo.value.productName; |
| | |
| | | const handleBomProcessChange = (row, value) => { |
| | | row.processId = value || ""; |
| | | syncProcessOperationFields(row); |
| | | |
| | | |
| | | // æ£æ¥åä¸å±çº§æ¯å¦å·²ç»æå
¶ä»ä¸åçå·¥åºè¢«éä¸ |
| | | const siblings = findSiblings(bomDataValue.value.dataList, row.tempId); |
| | | if (siblings && value) { |
| | | const hasDifferentProcess = siblings.some(sibling => { |
| | | return sibling.tempId !== row.tempId && sibling.processId && sibling.processId !== value; |
| | | return ( |
| | | sibling.tempId !== row.tempId && |
| | | sibling.processId && |
| | | sibling.processId !== value |
| | | ); |
| | | }); |
| | | if (hasDifferentProcess) { |
| | | ElMessage.warning("åä¸å±çº§å·²åå¨ä¸åçå·¥åºï¼è¯·å
ç»ä¸å·¥åºååè¿è¡ä¿®æ¹"); |
| | |
| | | }; |
| | | |
| | | // æ ¡éªåä¸å±çº§çå·¥åºæ¯å¦ä¸è´ |
| | | const validateProcessConsistency = (items) => { |
| | | const validateProcessConsistency = items => { |
| | | if (!items || items.length === 0) return; |
| | | |
| | | // æ£æ¥å½åå±çº§ |
| | | const processes = items.filter(item => item.processId).map(item => item.processId); |
| | | const processes = items |
| | | .filter(item => item.processId) |
| | | .map(item => item.processId); |
| | | if (processes.length > 1) { |
| | | const uniqueProcesses = [...new Set(processes)]; |
| | | if (uniqueProcesses.length > 1) { |
| | |
| | | getList(); |
| | | getProcessList(); |
| | | fetchBomData(); |
| | | if (pageType.value === "order") { |
| | | getAttachmentList(); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | |
| | | bomNo: row.bomNo || "", |
| | | description: data.description || "", |
| | | quantity: row.quantity || 0, |
| | | technologyRoutingId: data.technologyRoutingId, |
| | | orderId, |
| | | type: "order", |
| | | editable: !row.endOrder, |
| | |
| | | import {getRepairPage} from "@/api/equipmentManagement/repair.js"; |
| | | import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js"; |
| | | import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js"; |
| | | import {productOrderListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | |
| | | // å
¨å±ç¸å
³ç¶æ |
| | | const isFullscreen = ref(false); |
| | |
| | | |
| | | <script setup> |
| | | import { onMounted, ref } from "vue"; |
| | | import { invoiceLedgerSalesAccount } from "@/api/salesManagement/invoiceLedger.js"; |
| | | import { customerInteractions } from "@/api/salesManagement/receiptPayment.js"; |
| | | import Pagination from "@/components/PIMTable/Pagination.vue"; |
| | | import { |
| | | safeTrainingDetailListPage, |
| | |
| | | <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.searchText" |
| | | 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.searchText" |
| | | style="width: 240px" |
| | | placeholder="è¾å
¥å®¢æ·åç§°æç´¢" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | | <el-button type="primary" |
| | | @click="handleQuery" |
| | | style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | </div> |
| | | <div style="display: flex"> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @row-click="rowClickMethod" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column |
| | | label="客æ·åç§°" |
| | | prop="customerName" |
| | | show-overflow-tooltip |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="invoiceTotal" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="åºæ¶éé¢(å
)" |
| | | prop="unReceiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | width="200" |
| | | > |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }} |
| | | </el-text> |
| | | </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" |
| | | /> |
| | | </div> |
| | | <div class="table_list"> |
| | | <el-table |
| | | :data="receiptRecord" |
| | | border |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)" |
| | | > |
| | | <el-table-column |
| | | align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column |
| | | label="åçæ¥æ" |
| | | prop="receiptPaymentDate" |
| | | show-overflow-tooltip |
| | | width="110" |
| | | /> |
| | | <el-table-column |
| | | label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | show-overflow-tooltip |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="ååéé¢(å
)" |
| | | prop="invoiceTotal" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" |
| | | /> |
| | | <el-table-column |
| | | label="åºæ¶éé¢(å
)" |
| | | prop="unReceiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | width="200" |
| | | > |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }} |
| | | </el-text> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <div class="table_list" |
| | | style="width: 100%"> |
| | | <el-table :data="tableData" |
| | | border |
| | | v-loading="tableLoading" |
| | | :row-key="(row) => row.customerId" |
| | | show-summary |
| | | :summary-method="summarizeMainTable" |
| | | @row-click="rowClickMethod" |
| | | highlight-current-row |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="客æ·åç§°" |
| | | prop="customerName" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="invoiceTotal" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="åºæ¶éé¢(å
)" |
| | | prop="unReceiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | width="200"> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.unReceiptPaymentAmount) }} |
| | | </el-text> |
| | | </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" /> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <div class="table_list" |
| | | style="width: 100%"> |
| | | <el-table :data="receiptRecord" |
| | | border |
| | | :row-key="(row) => row.id" |
| | | show-summary |
| | | :summary-method="summarizeMainTable1" |
| | | height="calc(100vh - 18.5em)"> |
| | | <el-table-column align="center" |
| | | label="åºå·" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="ååç¾è®¢æ¥æ" |
| | | prop="executionDate" |
| | | show-overflow-tooltip |
| | | width="110" /> |
| | | <el-table-column label="éå®ååå·" |
| | | prop="salesContractNo" |
| | | show-overflow-tooltip |
| | | width="200" /> |
| | | <el-table-column label="ååéé¢(å
)" |
| | | prop="contractAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="忬¾éé¢(å
)" |
| | | prop="receiptPaymentAmount" |
| | | show-overflow-tooltip |
| | | :formatter="formattedNumber" |
| | | width="200" /> |
| | | <el-table-column label="åºæ¶éé¢(å
)" |
| | | prop="receiptableAmount" |
| | | show-overflow-tooltip |
| | | width="200"> |
| | | <template #default="{ row, column }"> |
| | | <el-text type="danger"> |
| | | {{ formattedNumber(row, column, row.receiptableAmount) }} |
| | | </el-text> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="recordTotal > 0" |
| | | :total="recordTotal" |
| | | layout="total, sizes, prev, pager, next, jumper" |
| | | :page="recordPage.current" |
| | | :limit="recordPage.size" |
| | | @pagination="recordPaginationChange" /> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {onMounted, ref} from "vue"; |
| | | import { invoiceLedgerSalesAccount } from "../../../api/salesManagement/invoiceLedger.js"; |
| | | import { customerInteractions } from "../../../api/salesManagement/receiptPayment.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const receiptRecord = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const recordPage = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const recordTotal = ref(0); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | searchText: "", |
| | | invoiceDate: "", |
| | | }, |
| | | }); |
| | | const customerId = ref(""); |
| | | const { searchForm } = toRefs(data); |
| | | const originReceiptRecord = ref([]); |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | invoiceLedgerSalesAccount({ ...searchForm.value, ...page }).then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | if (tableData.value.length > 0) { |
| | | recordPage.current = 1; |
| | | customerId.value = tableData.value[0].id; |
| | | receiptPaymentList(customerId.value); |
| | | } |
| | | import { onMounted, ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { |
| | | customewTransactions, |
| | | customewTransactionsDetails, |
| | | } from "@/api/salesManagement/indicatorStats.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const receiptRecord = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = (param) => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = (param) => { |
| | | var summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["invoiceAmount", "receiptAmount", "unReceiptAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | // åæåä¸è¡æ°æ®; |
| | | if (receiptRecord.value?.length > 0) { |
| | | const index = tableData.value.findIndex( |
| | | (item) => item.id == customerId.value |
| | | ); |
| | | summarizeTable[summarizeTable.length - 1] = |
| | | tableData.value[index].unReceiptPaymentAmount.toFixed(2); |
| | | } else { |
| | | summarizeTable[summarizeTable.length - 1] = 0.0; |
| | | } |
| | | return summarizeTable; |
| | | }; |
| | | |
| | | const receiptPaymentList = (id) => { |
| | | const param = { |
| | | customerId: id, |
| | | const recordPage = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }); |
| | | const total = ref(0); |
| | | const recordTotal = ref(0); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | searchText: "", |
| | | invoiceDate: "", |
| | | }, |
| | | }); |
| | | const customerId = ref(""); |
| | | const { searchForm } = toRefs(data); |
| | | const originReceiptRecord = ref([]); |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | console.log("param", param); |
| | | customerInteractions(param).then((res) => { |
| | | originReceiptRecord.value = res.data; |
| | | handlePagination({ page: 1, limit: recordPage.size }); |
| | | recordTotal.value = res.data.length; |
| | | const paginationChange = obj => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | customewTransactions({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | total.value = res.data.total; |
| | | if (tableData.value.length > 0) { |
| | | recordPage.current = 1; |
| | | customerId.value = tableData.value[0].customerId; |
| | | receiptPaymentList(customerId.value); |
| | | } |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return cellValue ? parseFloat(cellValue).toFixed(2) : "0.00"; |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["invoiceTotal", "receiptPaymentAmount", "unReceiptPaymentAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeMainTable1 = param => { |
| | | var summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount", "receiptPaymentAmount", "receiptableAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | } |
| | | ); |
| | | return summarizeTable; |
| | | }; |
| | | |
| | | const receiptPaymentList = id => { |
| | | const param = { |
| | | customerId: id, |
| | | current: recordPage.current, |
| | | size: recordPage.size, |
| | | }; |
| | | customewTransactionsDetails(param).then(res => { |
| | | if (Array.isArray(res.data)) { |
| | | originReceiptRecord.value = res.data; |
| | | recordTotal.value = res.data.length; |
| | | handlePagination({ page: 1, limit: recordPage.size }); |
| | | } else { |
| | | receiptRecord.value = res.data.records; |
| | | recordTotal.value = res.data.total; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // æ±æ¬¾è®°å½å表å页 |
| | | const recordPaginationChange = pagination => { |
| | | recordPage.current = pagination.page; |
| | | recordPage.size = pagination.limit; |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | |
| | | const rowClickMethod = row => { |
| | | customerId.value = row.customerId; |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | |
| | | const handlePagination = ({ page, limit }) => { |
| | | recordPage.current = page; |
| | | recordPage.size = limit; |
| | | |
| | | const start = (page - 1) * limit; |
| | | const end = start + limit; |
| | | |
| | | receiptRecord.value = originReceiptRecord.value.slice(start, end); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | }; |
| | | |
| | | // æ±æ¬¾è®°å½å表å页 |
| | | const recordPaginationChange = (pagination) => { |
| | | handlePagination(pagination); |
| | | }; |
| | | |
| | | const rowClickMethod = (row) => { |
| | | customerId.value = row.id; |
| | | receiptPaymentList(customerId.value); |
| | | }; |
| | | |
| | | const handlePagination = ({ page, limit }) => { |
| | | recordPage.current = page; |
| | | recordPage.size = limit; |
| | | |
| | | const start = (page - 1) * limit; |
| | | const end = start + limit; |
| | | |
| | | receiptRecord.value = originReceiptRecord.value.slice(start, end); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .table_list { |
| | | width: 50%; |
| | | } |
| | | .table_list { |
| | | width: 50%; |
| | | } |
| | | </style> |