Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁
| | |
| | | "logo": "logo/XSWHLogo.png", |
| | | "favicon": "favicon/XSWHfavicon.ico" |
| | | }, |
| | | "TDDQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ²³ååå¾·çµå¨æéå
¬å¸", |
| | | "VITE_BASE_API": "http://localhost:9001", |
| | | "VITE_JAVA_API": "http://localhost:9002" |
| | | }, |
| | | "logo": "logo/TDDQLogo.png", |
| | | "favicon": "favicon/TDDQfavicon.ico" |
| | | }, |
| | | "JHJM": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "鹤å£å¸é¦æ±ç²¾å¯æºæ¢°æéå
¬å¸", |
| | | "VITE_BASE_API": "http://103.189.141.42:9001", |
| | | "VITE_JAVA_API": "http://103.189.141.42:9002" |
| | | }, |
| | | "logo": "logo/JHJMLogo.png", |
| | | "favicon": "favicon/JHJMfavicon.ico" |
| | | }, |
| | | "logo": "/src/assets/logo/logo.png", |
| | | "favicon": "/public/favicon.ico" |
| | | } |
| | |
| | | "element-plus": "2.7.7", |
| | | "file-saver": "2.0.5", |
| | | "fuse.js": "6.6.2", |
| | | "html2canvas": "^1.4.1", |
| | | "js-beautify": "1.14.11", |
| | | "js-cookie": "3.0.5", |
| | | "jsencrypt": "3.3.2", |
| | |
| | | params: id, |
| | | }); |
| | | } |
| | | // è·åéè´åæå°æ°æ® |
| | | export function getPrintData(id) { |
| | | return request({ |
| | | url: '/purchase/ledger/printData/' + id, |
| | | method: 'get', |
| | | }); |
| | | } |
| | |
| | | params: query, |
| | | }); |
| | | } |
| | | // è·åéå®åæå°æ°æ® |
| | | export function getPrintData(id) { |
| | | return request({ |
| | | url: '/sales/ledger/printData/' + id, |
| | | method: 'get', |
| | | }); |
| | | } |
| | |
| | | :highlight-current-row="highlightCurrentRow" |
| | | :row-class-name="rowClassName" |
| | | :row-style="rowStyle" |
| | | :cell-style="cellStyle" |
| | | :row-key="rowKey" |
| | | :style="tableStyle" |
| | | tooltip-effect="dark" |
| | |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | cellStyle: { |
| | | type: [Object, Function], |
| | | default: () => ({}), |
| | | }, |
| | | tableData: { |
| | | type: Array, |
| | | default: () => [], |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import html2canvas from 'html2canvas' |
| | | |
| | | function esc(s) { |
| | | if (!s) return '' |
| | | return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') |
| | | } |
| | | |
| | | function fmtDate(v) { |
| | | if (!v) return '' |
| | | const d = new Date(v) |
| | | const y = d.getFullYear() |
| | | const m = String(d.getMonth() + 1).padStart(2, '0') |
| | | const day = String(d.getDate()).padStart(2, '0') |
| | | return y + '-' + m + '-' + day |
| | | } |
| | | |
| | | function fmtMoney(v) { |
| | | if (v == null) return '0.00' |
| | | return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) |
| | | } |
| | | |
| | | function digitUppercase(n) { |
| | | const digits = ['é¶', '壹', 'è´°', 'å', 'è', 'ä¼', 'é', 'æ', 'æ', 'ç'] |
| | | const radices = ['', 'æ¾', 'ä½°', 'ä»', 'ä¸', 'æ¾', 'ä½°', 'ä»', '亿'] |
| | | let num = Math.abs(n) |
| | | let result = '' |
| | | const intPart = Math.floor(num) |
| | | const intStr = String(intPart) |
| | | const len = intStr.length |
| | | if (intPart === 0) { |
| | | result = 'é¶' |
| | | } else { |
| | | let zeroFlag = false |
| | | for (let i = 0; i < len; i++) { |
| | | const d = parseInt(intStr[i]) |
| | | const pos = len - 1 - i |
| | | if (d === 0) { |
| | | zeroFlag = true |
| | | if (pos % 4 === 0) { |
| | | result += radices[pos] |
| | | zeroFlag = false |
| | | } |
| | | } else { |
| | | if (zeroFlag) { |
| | | result += 'é¶' |
| | | zeroFlag = false |
| | | } |
| | | result += digits[d] + radices[pos] |
| | | } |
| | | } |
| | | if (result.endsWith('é¶')) result = result.slice(0, -1) |
| | | } |
| | | result += 'å
' |
| | | const decPart = Math.round((num - intPart) * 100) |
| | | if (decPart === 0) { |
| | | result += 'æ´' |
| | | } else { |
| | | const jiao = Math.floor(decPart / 10) |
| | | const fen = decPart % 10 |
| | | if (jiao > 0) result += digits[jiao] + 'è§' |
| | | if (fen > 0) result += digits[fen] + 'å' |
| | | } |
| | | return result |
| | | } |
| | | |
| | | function coreCss() { |
| | | return `*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:30px 36px;color:#333;background:#fff}.title{text-align:center;font-size:18px;font-weight:bold;letter-spacing:6px;margin-bottom:4px}.sub{text-align:center;font-size:12px;color:#999;margin-bottom:18px}.info-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px dashed #ccc}.info-top .left,.info-top .right{line-height:2}.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 36px;font-size:13px;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid #ccc}.info-grid .l{color:#666}.info-grid .v{font-weight:500}table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:14px}th,td{border:1px solid #333;padding:5px 6px;text-align:center}th{background:#eee;font-weight:500}td.r{text-align:right}.summary{text-align:right;font-size:13px;margin-bottom:16px;line-height:2.2}.summary .cn{font-weight:bold;color:#cf1322}` |
| | | } |
| | | |
| | | function productRows(products) { |
| | | if (!products || !products.length) return '' |
| | | let html = '' |
| | | products.forEach((p, i) => { |
| | | const name = p.productCategory || '' |
| | | const spec = p.specificationModel || '' |
| | | html += '<tr>' |
| | | html += '<td>' + (i + 1) + '</td>' |
| | | html += '<td style="text-align:left">' + esc(name) |
| | | if (spec) html += '<br><span style="color:#888;font-size:12px">' + esc(spec) + '</span>' |
| | | html += '</td>' |
| | | html += '<td>' + esc(p.unit || '') + '</td>' |
| | | html += '<td class="r">' + (p.quantity != null ? Number(p.quantity).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '') + '</td>' |
| | | html += '<td class="r">' + fmtMoney(p.taxInclusiveUnitPrice) + '</td>' |
| | | html += '<td class="r">' + fmtMoney(p.taxInclusiveTotalPrice) + '</td>' |
| | | html += '<td>' + (p.taxRate != null ? p.taxRate + '%' : '') + '</td>' |
| | | html += '<td>' + esc(p.invoiceType || '') + '</td>' |
| | | html += '</tr>' |
| | | }) |
| | | return html |
| | | } |
| | | |
| | | function calcTotals(products) { |
| | | let total = 0, totalNoTax = 0 |
| | | if (products) { |
| | | products.forEach(p => { |
| | | if (p.taxInclusiveTotalPrice != null) total += Number(p.taxInclusiveTotalPrice) |
| | | if (p.taxExclusiveTotalPrice != null) totalNoTax += Number(p.taxExclusiveTotalPrice) |
| | | }) |
| | | } |
| | | return { total, totalNoTax } |
| | | } |
| | | |
| | | function buildSalesHtml(companyName, ledger, forPrint) { |
| | | const products = ledger.productData || [] |
| | | const { total, totalNoTax } = calcTotals(products) |
| | | const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">æå° / å¦å为 PDF</button></div>' : '' |
| | | const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : '' |
| | | const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : '' |
| | | |
| | | return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éå®å - ${esc(ledger.salesContractNo)} - ${esc(companyName)}</title> |
| | | <style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body> |
| | | ${toolbar} |
| | | <div class="title">${esc(companyName)}</div> |
| | | <div class="sub">éå®åå / éå®å</div> |
| | | <div class="info-top"> |
| | | <div class="left"><div><strong>éæ¹åç§°ï¼</strong>${esc(companyName)}</div></div> |
| | | <div class="right"><div><strong>ååç¼å·ï¼</strong>${esc(ledger.salesContractNo)}</div><div><strong>ç¾è®¢æ¥æï¼</strong>${fmtDate(ledger.executionDate)}</div></div> |
| | | </div> |
| | | <div class="info-grid"> |
| | | <div><span class="l">客æ·åç§°ï¼</span><span class="v">${esc(ledger.customerName)}</span></div> |
| | | <div><span class="l">项ç®åç§°ï¼</span><span class="v">${esc(ledger.projectName)}</span></div> |
| | | <div><span class="l">ä¸å¡åï¼</span><span class="v">${esc(ledger.salesman)}</span></div> |
| | | <div><span class="l">仿¬¾æ¹å¼ï¼</span><span class="v">${esc(ledger.paymentMethod)}</span></div> |
| | | <div><span class="l">å½å
¥æ¥æï¼</span><span class="v">${fmtDate(ledger.entryDate)}</span></div> |
| | | <div><span class="l">äº¤è´§æ¥æï¼</span><span class="v">${fmtDate(ledger.deliveryDate)}</span></div> |
| | | ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">夿³¨ï¼</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''} |
| | | </div> |
| | | <table><thead><tr><th>åºå·</th><th>产ååç§° / è§æ ¼åå·</th><th>åä½</th><th>æ°é</th><th>å«ç¨åä»·</th><th>å«ç¨æ»ä»·</th><th>ç¨ç</th><th>å票类å</th></tr></thead><tbody> |
| | | ${productRows(products)} |
| | | </tbody></table> |
| | | <div class="summary"> |
| | | <div>ä¸å«ç¨å计ï¼<strong>${fmtMoney(totalNoTax)}</strong></div> |
| | | <div>å«ç¨å计ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(total)}</span></div> |
| | | <div>å«ç¨å计ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(total)}</strong></div> |
| | | </div> |
| | | ${printScript} |
| | | </body></html>` |
| | | } |
| | | |
| | | function buildPurchaseHtml(companyName, ledger, products, forPrint) { |
| | | const { total, totalNoTax } = calcTotals(products) |
| | | const contractAmount = ledger.contractAmount != null ? Number(ledger.contractAmount) : total |
| | | const toolbar = forPrint ? '<div class="toolbar"><button onclick="window.print()">æå° / å¦å为 PDF</button></div>' : '' |
| | | const printCss = forPrint ? '@media print{body{padding:15px 24px}.toolbar{display:none}}' : '' |
| | | const printScript = forPrint ? '<script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script>' : '' |
| | | |
| | | return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éè´å - ${esc(ledger.purchaseContractNumber)} - ${esc(companyName)}</title> |
| | | <style>.toolbar{text-align:center;margin-bottom:16px}.toolbar button{padding:6px 20px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px}.toolbar button:hover{background:#40a9ff}${coreCss()}${printCss}</style></head><body> |
| | | ${toolbar} |
| | | <div class="title">${esc(companyName)}</div> |
| | | <div class="info-top"> |
| | | <div class="left"><div><strong>è´æ¹åç§°ï¼</strong>${esc(companyName)}</div></div> |
| | | <div class="right"><div><strong>ååç¼å·ï¼</strong>${esc(ledger.purchaseContractNumber)}</div><div><strong>ç¾è®¢æ¥æï¼</strong>${fmtDate(ledger.executionDate)}</div></div> |
| | | </div> |
| | | <div class="info-grid"> |
| | | <div><span class="l">ä¾åºååç§°ï¼</span><span class="v">${esc(ledger.supplierName)}</span></div> |
| | | <div><span class="l">项ç®åç§°ï¼</span><span class="v">${esc(ledger.projectName)}</span></div> |
| | | <div><span class="l">å½å
¥äººï¼</span><span class="v">${esc(ledger.recorderName)}</span></div> |
| | | <div><span class="l">仿¬¾æ¹å¼ï¼</span><span class="v">${esc(ledger.paymentMethod)}</span></div> |
| | | <div><span class="l">å½å
¥æ¥æï¼</span><span class="v">${fmtDate(ledger.entryDate)}</span></div> |
| | | ${ledger.salesContractNo ? `<div><span class="l">å
³èéå®ååå·ï¼</span><span class="v">${esc(ledger.salesContractNo)}</span></div>` : ''} |
| | | ${ledger.remarks ? `<div style="grid-column:1/-1"><span class="l">夿³¨ï¼</span><span class="v">${esc(ledger.remarks)}</span></div>` : ''} |
| | | </div> |
| | | <table><thead><tr><th>åºå·</th><th>产ååç§° / è§æ ¼åå·</th><th>åä½</th><th>æ°é</th><th>å«ç¨åä»·</th><th>å«ç¨æ»ä»·</th><th>ç¨ç</th><th>å票类å</th></tr></thead><tbody> |
| | | ${productRows(products)} |
| | | </tbody></table> |
| | | <div class="summary"> |
| | | <div>ä¸å«ç¨å计ï¼<strong>${fmtMoney(totalNoTax)}</strong></div> |
| | | <div>å«ç¨å计ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(total)}</span></div> |
| | | <div>å«ç¨å计ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(total)}</strong></div> |
| | | <div>ååéé¢ï¼å¤§åï¼ï¼<span class="cn">${digitUppercase(contractAmount)}</span></div> |
| | | <div>ååéé¢ï¼å°åï¼ï¼<strong>Â¥${fmtMoney(contractAmount)}</strong></div> |
| | | </div> |
| | | ${printScript} |
| | | </body></html>` |
| | | } |
| | | |
| | | export function buildSalesDocument(companyName, ledger) { |
| | | return buildSalesHtml(companyName, ledger, false) |
| | | } |
| | | |
| | | export function buildPurchaseDocument(companyName, ledger, products) { |
| | | return buildPurchaseHtml(companyName, ledger, products, false) |
| | | } |
| | | |
| | | function buildSalesPrintHtml(companyName, ledger) { |
| | | return buildSalesHtml(companyName, ledger, true) |
| | | } |
| | | |
| | | function buildPurchasePrintHtml(companyName, ledger, products) { |
| | | return buildPurchaseHtml(companyName, ledger, products, true) |
| | | } |
| | | |
| | | export function openPrintWindow(html) { |
| | | const w = window.open('', '_blank', 'width=900,height=700') |
| | | if (!w) return |
| | | w.document.write(html) |
| | | w.document.close() |
| | | } |
| | | |
| | | export async function downloadAsJpg(html, filename) { |
| | | const iframe = document.createElement('iframe') |
| | | iframe.style.position = 'fixed' |
| | | iframe.style.left = '0' |
| | | iframe.style.top = '0' |
| | | iframe.style.width = '900px' |
| | | iframe.style.height = '600px' |
| | | iframe.style.opacity = '0' |
| | | iframe.style.pointerEvents = 'none' |
| | | iframe.style.zIndex = '99999' |
| | | document.body.appendChild(iframe) |
| | | |
| | | return new Promise((resolve, reject) => { |
| | | const iframeDoc = iframe.contentDocument || iframe.contentWindow.document |
| | | iframeDoc.open() |
| | | iframeDoc.write(html) |
| | | iframeDoc.close() |
| | | |
| | | const doCapture = async () => { |
| | | try { |
| | | const body = iframeDoc.body |
| | | iframe.style.height = Math.max(body.scrollHeight, 600) + 'px' |
| | | await new Promise(r => setTimeout(r, 100)) |
| | | const canvas = await html2canvas(body, { |
| | | scale: 2, |
| | | useCORS: true, |
| | | backgroundColor: '#ffffff', |
| | | windowWidth: 900 |
| | | }) |
| | | document.body.removeChild(iframe) |
| | | canvas.toBlob(blob => { |
| | | const url = URL.createObjectURL(blob) |
| | | const a = document.createElement('a') |
| | | a.href = url |
| | | a.download = filename |
| | | a.click() |
| | | URL.revokeObjectURL(url) |
| | | resolve() |
| | | }, 'image/jpeg', 0.95) |
| | | } catch (e) { |
| | | document.body.removeChild(iframe) |
| | | reject(e) |
| | | } |
| | | } |
| | | |
| | | iframe.onload = () => setTimeout(doCapture, 300) |
| | | setTimeout(doCapture, 1000) |
| | | }) |
| | | } |
| | | |
| | | export function downloadSalesJpg(companyName, ledger, filename) { |
| | | const html = buildSalesHtml(companyName, ledger, false) |
| | | return downloadAsJpg(html, filename) |
| | | } |
| | | |
| | | export function downloadPurchaseJpg(companyName, ledger, products, filename) { |
| | | const html = buildPurchaseHtml(companyName, ledger, products, false) |
| | | return downloadAsJpg(html, filename) |
| | | } |
| | | |
| | | export function printSales(companyName, ledger) { |
| | | const html = buildSalesPrintHtml(companyName, ledger) |
| | | openPrintWindow(html) |
| | | } |
| | | |
| | | export function printPurchase(companyName, ledger, products) { |
| | | const html = buildPurchasePrintHtml(companyName, ledger, products) |
| | | openPrintWindow(html) |
| | | } |
| | | |
| | | export function printSalesMulti(companyName, orders) { |
| | | let bodies = '' |
| | | orders.forEach((ledger, i) => { |
| | | const html = buildSalesHtml(companyName, ledger, true) |
| | | const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/) |
| | | if (bodyMatch) { |
| | | const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '') |
| | | bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>' |
| | | } |
| | | }) |
| | | const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éå®å - ${esc(companyName)}</title> |
| | | <style> |
| | | *{margin:0;padding:0;box-sizing:border-box} |
| | | body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff} |
| | | .toolbar{text-align:center;margin-bottom:20px} |
| | | .toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px} |
| | | .toolbar button:hover{background:#40a9ff} |
| | | ${coreCss()} |
| | | .print-page{page-break-inside:avoid} |
| | | .page-break{page-break-after:always} |
| | | @media print{body{padding:15px 24px}.toolbar{display:none}} |
| | | </style></head><body> |
| | | <div class="toolbar"><button onclick="window.print()">æå°å
¨é¨ (å
±${orders.length}份)</button></div> |
| | | ${bodies} |
| | | <script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script> |
| | | </body></html>` |
| | | openPrintWindow(combined) |
| | | } |
| | | |
| | | export function printPurchaseMulti(companyName, orders) { |
| | | let bodies = '' |
| | | orders.forEach((item, i) => { |
| | | const html = buildPurchaseHtml(companyName, item.ledger, item.products, true) |
| | | const bodyMatch = html.match(/<body>([\s\S]*)<\/body>/) |
| | | if (bodyMatch) { |
| | | const body = bodyMatch[1].replace(/<script>[\s\S]*?<\/script>/g, '') |
| | | bodies += '<div class="print-page' + (i < orders.length - 1 ? ' page-break' : '') + '">' + body + '</div>' |
| | | } |
| | | }) |
| | | const combined = `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"> |
| | | <title>éè´å - ${esc(companyName)}</title> |
| | | <style> |
| | | *{margin:0;padding:0;box-sizing:border-box} |
| | | body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:20px 36px;color:#333;background:#fff} |
| | | .toolbar{text-align:center;margin-bottom:20px} |
| | | .toolbar button{padding:6px 24px;font-size:14px;cursor:pointer;background:#1890ff;color:#fff;border:none;border-radius:4px} |
| | | .toolbar button:hover{background:#40a9ff} |
| | | ${coreCss()} |
| | | .print-page{page-break-inside:avoid} |
| | | .page-break{page-break-after:always} |
| | | @media print{body{padding:15px 24px}.toolbar{display:none}} |
| | | </style></head><body> |
| | | <div class="toolbar"><button onclick="window.print()">æå°å
¨é¨ (å
±${orders.length}份)</button></div> |
| | | ${bodies} |
| | | <script>window.onload=function(){setTimeout(function(){window.print();},300);};<\/script> |
| | | </body></html>` |
| | | openPrintWindow(combined) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** éé¢ç±»å段é»è®¤æå¤§å°æ°ä½æ° */ |
| | | export const AMOUNT_DECIMAL_SCALE = 6 |
| | | |
| | | /** |
| | | * æ ¼å¼åæ°åï¼æå¤ä¿ç scale ä½å°æ°ï¼å¹¶å»é¤æ«å°¾å¤ä½ç 0 |
| | | */ |
| | | export function formatDecimal(value, scale = AMOUNT_DECIMAL_SCALE, fallback = '0') { |
| | | if (value === null || value === undefined || value === '') { |
| | | return fallback |
| | | } |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) { |
| | | return fallback |
| | | } |
| | | return parseFloat(num.toFixed(scale)).toString() |
| | | } |
| | | |
| | | /** 带货å¸ç¬¦å·çéé¢å±ç¤º */ |
| | | export function formatCurrency(value, scale = AMOUNT_DECIMAL_SCALE) { |
| | | return `Â¥${formatDecimal(value, scale)}` |
| | | } |
| | | |
| | | /** el-table-column formatter */ |
| | | export function tableAmountFormatter(row, column, cellValue) { |
| | | return formatDecimal(cellValue) |
| | | } |
| | | |
| | | /** 计ç®åä¿çæå®ä½å°æ° */ |
| | | export function roundAmount(value, scale = AMOUNT_DECIMAL_SCALE) { |
| | | if (value === null || value === undefined || value === '') { |
| | | return 0 |
| | | } |
| | | const num = Number(value) |
| | | if (Number.isNaN(num)) { |
| | | return 0 |
| | | } |
| | | return parseFloat(num.toFixed(scale)) |
| | | } |
| | | |
| | | /** æå»º summarizeTable éé¢åæ ¼å¼é
ç½® */ |
| | | export function buildAmountSummaryFormat(fields, extraFormat = {}) { |
| | | const format = { ...extraFormat } |
| | | fields.forEach(field => { |
| | | format[field] = { decimalPlaces: AMOUNT_DECIMAL_SCALE } |
| | | }) |
| | | return format |
| | | } |
| | |
| | | import { formatDecimal, AMOUNT_DECIMAL_SCALE } from './numberFormat' |
| | | |
| | | /** |
| | | * éç¨çè¡¨æ ¼åè®¡æ¹æ³ |
| | | * @param {Object} param - å
å«è¡¨æ ¼åé
ç½®åæ°æ®æºç对象 |
| | |
| | | // 妿æå®äºä¸éè¦ä¿çå°æ°ï¼åç´æ¥è½¬æ¢ä¸ºæ´æ° |
| | | sums[index] = Math.round(sum).toString(); |
| | | } else { |
| | | // é»è®¤ä¿ç两ä½å°æ° |
| | | sums[index] = parseFloat(sum).toFixed( |
| | | sums[index] = formatDecimal( |
| | | sum, |
| | | specialFormat[prop]?.decimalPlaces ?? 2 |
| | | ); |
| | | } |
| | | } else { |
| | | sums[index] = ""; |
| | | sums[index] = ''; |
| | | } |
| | | } else { |
| | | sums[index] = ""; |
| | | sums[index] = ''; |
| | | } |
| | | }); |
| | | return sums; |
| | | }; |
| | | |
| | | // ä¸å«ç¨æ»ä»·è®¡ç® |
| | | const calculateTaxExclusiveTotalPrice = (taxInclusiveTotalPrice, taxRate) => { |
| | | const taxRateDecimal = taxRate / 100; |
| | | return (taxInclusiveTotalPrice / (1 + taxRateDecimal)).toFixed(2); |
| | | return formatDecimal( |
| | | Number(taxInclusiveTotalPrice) / (1 + taxRateDecimal), |
| | | AMOUNT_DECIMAL_SCALE |
| | | ); |
| | | }; |
| | | // å«ç¨æ»ä»·è®¡ç® |
| | | const calculateTaxIncludeTotalPrice = (taxInclusiveUnitPrice, quantity) => { |
| | | return (taxInclusiveUnitPrice * quantity).toFixed(2); |
| | | return formatDecimal( |
| | | Number(taxInclusiveUnitPrice) * Number(quantity), |
| | | AMOUNT_DECIMAL_SCALE |
| | | ); |
| | | }; |
| | | // 导åºå½æ°ä¾å
¶ä»æä»¶ä½¿ç¨ |
| | | export { |
| | |
| | | <el-dialog v-model="visible" title="éæ©äº§å" width="900px" destroy-on-close :close-on-click-modal="false"> |
| | | <el-form :inline="true" :model="query" class="mb-2"> |
| | | <el-form-item label="产ååç§°"> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§ååç§°" clearable @keyup.enter="onSearch" /> |
| | | <el-input v-model="query.productName" placeholder="è¾å
¥äº§ååç§°" clearable @keyup.enter="onSearch" style="width: 200px" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item label="产ååå·"> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥äº§ååå·" clearable @keyup.enter="onSearch" /> |
| | | <el-input v-model="query.model" placeholder="è¾å
¥äº§ååå·" clearable @keyup.enter="onSearch" style="width: 200px" /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | |
| | | @selection-change="handleSelectionChange" @select="handleSelect"> |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column type="index" label="åºå·" width="60" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="160" /> |
| | | <el-table-column prop="model" label="产ååå·" min-width="200" /> |
| | | <el-table-column prop="unit" label="åä½" min-width="160" /> |
| | | <el-table-column prop="topProductName" label="产å大类" min-width="120" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="140" /> |
| | | <el-table-column prop="model" label="产ååå·" min-width="180" /> |
| | | <el-table-column prop="unit" label="åä½" min-width="100" /> |
| | | </el-table> |
| | | |
| | | <div class="mt-3 flex justify-end"> |
| | |
| | | rules: { |
| | | productName: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | { max: 20, message: "产ååç§°ä¸è½è¶
è¿20个å符", trigger: "blur" }, |
| | | { max: 50, message: "产ååç§°ä¸è½è¶
è¿50个å符", trigger: "blur" }, |
| | | ], |
| | | }, |
| | | modelForm: { |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" |
| | | prop="supplierType"> |
| | | <el-select v-model="form.supplierType" |
| | |
| | | value="ä¸" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" |
| | | prop="isWhite"> |
| | |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 250, |
| | | // width: 250, |
| | | }, |
| | | { |
| | | label: "ä¾åºåç±»å", |
| | | prop: "supplierType", |
| | | width: 120, |
| | | }, |
| | | // { |
| | | // label: "ä¾åºåç±»å", |
| | | // prop: "supplierType", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | width: 230, |
| | | // width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | width: 220, |
| | | // width: 300, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | |
| | | { |
| | | label: "ç»´æ¤æ¶é´", |
| | | prop: "maintainTime", |
| | | width: 100, |
| | | // width: 100, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | //èµè´¨éä»¶ |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openFileDialog(row); |
| | | }, |
| | | }, |
| | | // { |
| | | // //èµè´¨éä»¶ |
| | | // name: "èµè´¨æä»¶", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openFileDialog(row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | supplierType: [ |
| | | { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | ], |
| | | // supplierType: [ |
| | | // { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | // ], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <!-- <el-col :span="12"> |
| | | <el-form-item label="ä¾åºåç±»åï¼" |
| | | prop="supplierType"> |
| | | <el-select v-model="form.supplierType" |
| | |
| | | value="ä¸" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-col> --> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ç½ååï¼" |
| | | prop="isWhite"> |
| | |
| | | { |
| | | label: "ä¾åºååç§°", |
| | | prop: "supplierName", |
| | | width: 250, |
| | | // width: 250, |
| | | }, |
| | | { |
| | | label: "ä¾åºåç±»å", |
| | | prop: "supplierType", |
| | | width: 120, |
| | | }, |
| | | // { |
| | | // label: "ä¾åºåç±»å", |
| | | // prop: "supplierType", |
| | | // width: 120, |
| | | // }, |
| | | { |
| | | label: "纳ç¨äººè¯å«å·", |
| | | prop: "taxpayerIdentificationNum", |
| | | width: 230, |
| | | // width: 230, |
| | | }, |
| | | { |
| | | label: "å
¬å¸å°å", |
| | | prop: "companyAddress", |
| | | width: 220, |
| | | // width: 220, |
| | | }, |
| | | { |
| | | label: "ç»´æ¤äºº", |
| | | prop: "maintainUserName", |
| | | // width: 220, |
| | | }, |
| | | |
| | | { |
| | |
| | | openForm("edit", row); |
| | | }, |
| | | }, |
| | | { |
| | | //èµè´¨éä»¶ |
| | | name: "èµè´¨æä»¶", |
| | | type: "text", |
| | | clickFun: row => { |
| | | openFileDialog(row); |
| | | }, |
| | | }, |
| | | // { |
| | | // //èµè´¨éä»¶ |
| | | // name: "èµè´¨æä»¶", |
| | | // type: "text", |
| | | // clickFun: row => { |
| | | // openFileDialog(row); |
| | | // }, |
| | | // }, |
| | | ], |
| | | }, |
| | | ]); |
| | |
| | | companyAddress: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | maintainUserId: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | maintainTime: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | supplierType: [ |
| | | { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | ], |
| | | // supplierType: [ |
| | | // { required: true, message: "è¯·éæ©ä¾åºåç±»å", trigger: "change" }, |
| | | // ], |
| | | }, |
| | | }); |
| | | const { searchForm, form, rules } = toRefs(data); |
| | |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="ç³è¯·é¨é¨ï¼"> |
| | | <el-select disabled |
| | | v-model="form.approveDeptId" |
| | | placeholder="éæ©é¨é¨"> |
| | | <el-option v-for="user in productOptions" |
| | | :key="user.deptId" |
| | | :label="user.deptName" |
| | | :value="user.deptId" /> |
| | | </el-select> |
| | | <el-input v-model="form.approveDeptName" |
| | | placeholder="" |
| | | clearable |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | clearable |
| | | type="textarea" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="Number(props.approveType) === 4"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="æ¥ééé¢ï¼"> |
| | | <el-input-number v-model="form.price" |
| | | disabled |
| | | :min="0" |
| | | :precision="2" |
| | | style="width: 100%" |
| | | placeholder="" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentQuotation.totalAmount ?? 0).toFixed(2) }} |
| | | {{ formatCurrency(currentQuotation.totalAmount) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | label="åä½" /> |
| | | <el-table-column prop="unitPrice" |
| | | label="åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.unitPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.unitPrice) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | |
| | | <el-descriptions-item label="ååéé¢" |
| | | :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;"> |
| | | ¥{{ Number(currentPurchase.contractAmount ?? 0).toFixed(2) }} |
| | | {{ formatCurrency(currentPurchase.contractAmount) }} |
| | | </span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | |
| | | label="æ°é" /> |
| | | <el-table-column prop="taxInclusiveUnitPrice" |
| | | label="å«ç¨åä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveUnitPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveUnitPrice) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="taxInclusiveTotalPrice" |
| | | label="å«ç¨æ»ä»·"> |
| | | <template #default="scope">Â¥{{ Number(scope.row.taxInclusiveTotalPrice ?? 0).toFixed(2) }}</template> |
| | | <template #default="scope">{{ formatCurrency(scope.row.taxInclusiveTotalPrice) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { getDeliveryDetailByShippingNo } from "@/api/salesManagement/deliveryLedger.js"; |
| | | import ImagePreview from "@/components/AttachmentPreview/image/index.vue"; |
| | | import { formatCurrency } from "@/utils/numberFormat"; |
| | | const emit = defineEmits(["close"]); |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | |
| | | formatData: (v) => (v === "active" ? "çæä¸" : "å·²åºæ¢"), |
| | | formatType: (v) => (v === "active" ? "success" : "info"), |
| | | }, |
| | | { label: "已读人æ°", prop: "readCount", width: 100 }, |
| | | // { label: "已读人æ°", prop: "readCount", width: 100 }, |
| | | { |
| | | dataType: "action", |
| | | label: "æä½", |
| | |
| | | :tableLoading="loading" |
| | | :page="pagination" |
| | | :isShowPagination="true" |
| | | :row-class-name="rowClassName" |
| | | @pagination="handleSizeChange" |
| | | > |
| | | <template #status="{ row }"> |
| | |
| | | </el-dialog> |
| | | </el-tab-pane> |
| | | |
| | | <el-tab-pane label="å¤ä»¶é¢ç¨è®°å½" name="usage"> |
| | | <el-tab-pane label="å¤ä»¶ä½¿ç¨è®°å½" name="usage"> |
| | | <div class="search_form"> |
| | | <el-form :inline="true" :model="usageQuery" class="search-form"> |
| | | <el-form-item label="å¤ä»¶åç§°"> |
| | |
| | | onMounted(() => { |
| | | fetchListData(); |
| | | }); |
| | | |
| | | // æ ¹æ®æ°éè¿è¡å¤æèæ¯é¢è² |
| | | const rowClassName = ({ row }) => { |
| | | const num = Number(row.quantity); |
| | | if (num <= 1) return 'row-qty-low'; |
| | | if (num >= 8) return 'row-qty-high'; |
| | | return 'row-qty-mid'; |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped> |
| | | <style> |
| | | .spare-part-category { |
| | | padding: 20px; |
| | | } |
| | |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .el-table__row:hover > td { |
| | | background-color: #fafafa; |
| | | .el-table .row-qty-low td.el-table__cell, |
| | | .el-table .row-qty-low:hover > td.el-table__cell { |
| | | background-color: #fde8e8 !important; |
| | | } |
| | | .el-table .row-qty-mid td.el-table__cell, |
| | | .el-table .row-qty-mid:hover > td.el-table__cell { |
| | | background-color: #f0f4f8 !important; |
| | | } |
| | | .el-table .row-qty-high td.el-table__cell, |
| | | .el-table .row-qty-high:hover > td.el-table__cell { |
| | | background-color: #e8f5e9 !important; |
| | | } |
| | | |
| | | /* æé®ç»æ ·å¼ */ |
| | |
| | | font-size: 12px; |
| | | margin-right: 4px; |
| | | } |
| | | |
| | | </style> |
| | |
| | | <el-table-column |
| | | label="åºåºæ¥æ" |
| | | prop="createTime" |
| | | width="180" |
| | | width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | > |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="产å大类" |
| | | prop="productName" |
| | |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | const userStore = useUserStore(); |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | show-overflow-tooltip/> |
| | | <el-table-column label="å
¥åºæ¶é´" |
| | | prop="createTime" |
| | | width="160" |
| | | show-overflow-tooltip/> |
| | | width="120" |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="产å大类" |
| | | prop="productName" |
| | | show-overflow-tooltip/> |
| | |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | // findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | import { parseTime } from "@/utils/ruoyi"; |
| | | |
| | | const {proxy} = getCurrentInstance(); |
| | | |
| | |
| | | prop="unQualifiedLockedQuantity" |
| | | show-overflow-tooltip |
| | | /> --> |
| | | <el-table-column |
| | | <!-- <el-table-column |
| | | label="åºåé¢è¦æ°é" |
| | | prop="warnNum" |
| | | show-overflow-tooltip |
| | | /> |
| | | />--> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column |
| | | label="æè¿æ´æ°æ¶é´" |
| | |
| | | @pagination="sonPaginationSearch"> |
| | | <template #payableAmountSlot="{ row }"> |
| | | <el-text type="danger"> |
| | | {{ parseFloat(row.unshippedAmount).toFixed(2) }} |
| | | {{ formatDecimal(row.unshippedAmount) }} |
| | | </el-text> |
| | | </template> |
| | | </PIMTable> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, toRefs } from "vue"; |
| | | import { ref, toRefs, reactive, getCurrentInstance } 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 { tableAmountFormatter, formatDecimal, buildAmountSummaryFormat } from "@/utils/numberFormat"; |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | |
| | | label: "ååéé¢(å
)", |
| | | prop: "contractAmount", |
| | | width: 200, |
| | | formatData: params => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | }, |
| | | formatData: params => formatDecimal(params), |
| | | }, |
| | | { |
| | | label: "å·²å
¥åºéé¢(å
)", |
| | | prop: "shippedAmount", |
| | | width: 200, |
| | | formatData: params => { |
| | | return params ? parseFloat(params).toFixed(2) : 0; |
| | | }, |
| | | formatData: params => formatDecimal(params), |
| | | }, |
| | | { |
| | | label: "æªå
¥åºéé¢(å
)", |
| | |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["contractAmounts", "shippedAmount", "unshippedAmount"], |
| | | buildAmountSummaryFormat( |
| | | ["contractAmounts", "shippedAmount", "unshippedAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | ticketsNum: { noDecimal: true }, |
| | | futureTickets: { noDecimal: true }, |
| | | } |
| | | ) |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | |
| | | let summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount", "shippedAmount", "unshippedAmount"], |
| | | buildAmountSummaryFormat( |
| | | ["contractAmount", "shippedAmount", "unshippedAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | ticketsNum: { noDecimal: true }, |
| | | futureTickets: { noDecimal: true }, |
| | | } |
| | | ) |
| | | ); |
| | | return summarizeTable; |
| | | }; |
| | |
| | | sonPage.size = pagination.limit; |
| | | getPaymenRecordtList(currentSupplierId.value); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (column.property !== "supplierName") { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | } else { |
| | | return cellValue; |
| | | } |
| | | }; |
| | | const formattedNumber = tableAmountFormatter; |
| | | getList(); |
| | | </script> |
| | | |
| | |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue" |
| | | import filePreview from '@/components/filePreview/index.vue' |
| | | import { getPurchaseById } from "@/api/procurementManagement/procurementLedger" |
| | | import { tableAmountFormatter } from '@/utils/numberFormat' |
| | | |
| | | const visible = ref(false) |
| | | // æ¯å¦æ¾ç¤ºéå®ååå·ç»å® |
| | |
| | | fileList.value = [] |
| | | } |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (cellValue != null && !isNaN(cellValue)) { |
| | | return Number(cellValue).toFixed(2) |
| | | } |
| | | return cellValue |
| | | } |
| | | const formattedNumber = tableAmountFormatter |
| | | |
| | | const sumTaxInclusiveUnitPrice = computed(() => { |
| | | return productData.value.reduce((sum, item) => { |
| | |
| | | <el-button @click="handleOut">导åº</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤ |
| | | </el-button> |
| | | <el-button type="warning" plain @click="handlePrint">æå°åæ®</el-button> |
| | | </div> |
| | | <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange" |
| | | :expand-row-keys="expandedRowKeys" :row-key="(row) => row.id" show-summary :summary-method="summarizeMainTable" |
| | |
| | | <el-table-column label="å½å
¥äºº" prop="recorderName" width="120" show-overflow-tooltip /> |
| | | <el-table-column label="å½å
¥æ¥æ" prop="entryDate" width="100" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remarks" width="200" show-overflow-tooltip /> |
| | | <el-table-column fixed="right" label="æä½" width="180" align="center"> |
| | | <el-table-column fixed="right" label="æä½" width="220" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="openForm('detail', scope.row)">详æ
|
| | | </el-button> |
| | | <el-button link type="primary" @click="openForm('edit', scope.row)" |
| | | :disabled="scope.row.stockInStatus === 'å®å
¨å
¥åº'">ç¼è¾ |
| | | </el-button> |
| | | <el-button link type="primary" @click="openFileDialog(scope.row)">éä»¶</el-button> |
| | | <el-button link type="primary" @click="downloadOrder(scope.row.id)">ä¸è½½</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å½å
¥äººï¼" prop="recorderId"> |
| | | <el-select v-model="form.recorderId" placeholder="è¯·éæ©" clearable filterable> |
| | | <el-select v-model="form.recorderId" placeholder="è¯·éæ©" clearable filterable disabled> |
| | | <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <el-table-column label="è§æ ¼åå·" prop="specificationModel" /> |
| | | <el-table-column label="åä½" prop="unit" width="70" /> |
| | | <el-table-column label="æ°é" prop="quantity" width="70" /> |
| | | <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" width="120" show-overflow-tooltip /> |
| | | <!-- <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" width="120" show-overflow-tooltip />--> |
| | | <el-table-column label="ç¨ç(%)" prop="taxRate" width="80" /> |
| | | <el-table-column label="å«ç¨åä»·(å
)" prop="taxInclusiveUnitPrice" :formatter="formattedNumber" width="150" /> |
| | | <el-table-column label="å«ç¨æ»ä»·(å
)" prop="taxInclusiveTotalPrice" :formatter="formattedNumber" width="150" /> |
| | |
| | | @cancel="closeProductDia"> |
| | | <el-form :model="productForm" label-width="140px" label-position="top" :rules="productRules" ref="productFormRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产å大类ï¼" prop="productId"> |
| | | <el-tree-select v-model="productForm.productId" placeholder="è¯·éæ©" clearable filterable check-strictly |
| | | @change="getModels" :data="productOptions" :render-after-expand="false" style="width: 100%" /> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input v-model="productForm.productCategory" |
| | | placeholder="ç¹å»éæ©" |
| | | readonly |
| | | @click="showProductSelectDialog = true" |
| | | style="cursor:pointer" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-select v-model="productForm.productModelId" placeholder="è¯·éæ©" filterable clearable |
| | | @change="getProductModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | </el-select> |
| | | <el-input v-model="productForm.specificationModel" |
| | | placeholder="ç¹å»éæ©" |
| | | readonly |
| | | @click="showProductSelectDialog = true" |
| | | style="cursor:pointer" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨åä»·(å
)ï¼" prop="taxInclusiveUnitPrice"> |
| | | <el-input-number v-model="productForm.taxInclusiveUnitPrice" :precision="2" :step="0.1" :min="0" clearable |
| | | <el-input-number v-model="productForm.taxInclusiveUnitPrice" :precision="6" :step="0.000001" :min="0" clearable |
| | | style="width: 100%" @change="mathNum" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨æ»ä»·(å
)ï¼" prop="taxInclusiveTotalPrice"> |
| | | <el-input-number v-model="productForm.taxInclusiveTotalPrice" :precision="2" :step="0.1" :min="0" |
| | | <el-input-number v-model="productForm.taxInclusiveTotalPrice" :precision="6" :step="0.000001" :min="0" |
| | | clearable style="width: 100%" @change="reverseMathNum('taxInclusiveTotalPrice')" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä¸å«ç¨æ»ä»·(å
)ï¼" prop="taxExclusiveTotalPrice"> |
| | | <el-input-number v-model="productForm.taxExclusiveTotalPrice" :precision="2" :step="0.1" :min="0" |
| | | <el-input-number v-model="productForm.taxExclusiveTotalPrice" :precision="6" :step="0.000001" :min="0" |
| | | clearable style="width: 100%" @change="reverseMathNum('taxExclusiveTotalPrice')" /> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="åºåé¢è¦æ°éï¼" prop="warnNum"> |
| | | <el-input-number v-model="productForm.warnNum" :precision="2" :step="0.1" :min="0" clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <!-- <el-col :span="12">--> |
| | | <!-- <el-form-item label="åºåé¢è¦æ°éï¼" prop="warnNum">--> |
| | | <!-- <el-input-number v-model="productForm.warnNum" :precision="2" :step="0.1" :min="0" clearable--> |
| | | <!-- style="width: 100%" />--> |
| | | <!-- </el-form-item>--> |
| | | <!-- </el-col>--> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦è´¨æ£ï¼" prop="isChecked"> |
| | | <el-radio-group v-model="productForm.isChecked"> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <!-- <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦è´¨æ£ï¼" prop="isChecked"> |
| | | <el-radio-group v-model="productForm.isChecked"> |
| | | <el-radio label="æ¯" :value="true" /> |
| | | <el-radio label="å¦" :value="false" /> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row>--> |
| | | </el-form> |
| | | </FormDialog> |
| | | <ProductSelectDialog v-model="showProductSelectDialog" |
| | | @confirm="handleProductSelect" |
| | | single /> |
| | | <FileList v-if="fileListDialogVisible" v-model:visible="fileListDialogVisible" record-type="purchase_ledger" |
| | | :record-id="recordId" /> |
| | | <Detail ref="detailRef" /> |
| | |
| | | productList, |
| | | getPurchaseById, |
| | | getOptions, |
| | | getPrintData, |
| | | getPurchaseTemplateList, |
| | | delPurchaseTemplate, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | |
| | | const fileList = ref([]); |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import dayjs from "dayjs"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { tableAmountFormatter, formatDecimal, buildAmountSummaryFormat } from '@/utils/numberFormat'; |
| | | import { downloadPurchaseJpg, printPurchaseMulti } from "@/utils/documentPrint.js"; |
| | | |
| | | const userStore = useUserStore(); |
| | | |
| | |
| | | |
| | | // 产å表åå¼¹æ¡æ°æ® |
| | | const productFormVisible = ref(false); |
| | | const showProductSelectDialog = ref(false); |
| | | const productOperationType = ref(""); |
| | | const productOperationIndex = ref(""); |
| | | const currentId = ref(""); |
| | |
| | | isChecked: false, |
| | | }, |
| | | productRules: { |
| | | productId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productCategory: [{ required: true, message: "è¯·éæ©äº§å大类", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©è§æ ¼åå·", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | | taxRate: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | warnNum: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | // warnNum: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请è¾å
¥", trigger: "blur" }, |
| | | ], |
| | |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | const formattedNumber = tableAmountFormatter; |
| | | // æ¥è¯¢å表 |
| | | /** æç´¢æé®æä½ */ |
| | | const handleQuery = () => { |
| | |
| | | "futureTickets", |
| | | "futureTicketsAmount", |
| | | ], |
| | | buildAmountSummaryFormat( |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | "ticketsAmount", |
| | | "futureTicketsAmount", |
| | | ], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | ticketsNum: { noDecimal: true }, |
| | | futureTickets: { noDecimal: true }, |
| | | } |
| | | ) |
| | | ); |
| | | }; |
| | | const paginationChange = obj => { |
| | |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, ["contractAmount"]); |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount"], |
| | | buildAmountSummaryFormat(["contractAmount"]) |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeProTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | ], |
| | | buildAmountSummaryFormat([ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]) |
| | | ); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | |
| | | ); |
| | | |
| | | form.value.entryDate = getCurrentDate(); |
| | | form.value.recorderId = userStore.id; |
| | | |
| | | if (type === "add") { |
| | | // æ°å¢æ¶çæéè´ååå· |
| | |
| | | await nextTick(); |
| | | } |
| | | }; |
| | | |
| | | // ProductSelectDialog éä¸åè° |
| | | const handleProductSelect = (products) => { |
| | | if (!products || !products.length) return; |
| | | const p = products[0]; |
| | | productForm.value.productId = p.productId; |
| | | productForm.value.productCategory = p.productName || ''; |
| | | productForm.value.productModelId = p.id; |
| | | productForm.value.specificationModel = p.model || ''; |
| | | productForm.value.unit = p.unit || ''; |
| | | showProductSelectDialog.value = false; |
| | | }; |
| | | |
| | | const getProductOptions = () => { |
| | | return productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¹éæå°éè´å - å¤éåå¹¶é¢è§ |
| | | const handlePrint = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | proxy.$modal.loading("æ£å¨å è½½æå°æ°æ®..."); |
| | | try { |
| | | const orders = []; |
| | | let companyName = ''; |
| | | for (const row of selectedRows.value) { |
| | | const res = await getPrintData(row.id); |
| | | const data = res.data || res; |
| | | companyName = data.companyName || companyName; |
| | | orders.push({ ledger: data.ledger, products: data.products }); |
| | | } |
| | | printPurchaseMulti(companyName, orders); |
| | | } catch (e) { |
| | | console.error('æå°å¤±è´¥:', e); |
| | | proxy.$modal.msgError("æå°å¤±è´¥"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | |
| | | if (field === "taxInclusiveTotalPrice") { |
| | | // å·²ç¥å«ç¨æ»ä»·åæ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | productForm.value.taxInclusiveUnitPrice = formatDecimal( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | ); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveUnitPrice) < 0) { |
| | | productForm.value.taxInclusiveUnitPrice = "0"; |
| | |
| | | } |
| | | } |
| | | // åç®ä¸å«ç¨æ»ä»· |
| | | productForm.value.taxExclusiveTotalPrice = ( |
| | | productForm.value.taxExclusiveTotalPrice = formatDecimal( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | ); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxExclusiveTotalPrice) < 0) { |
| | | productForm.value.taxExclusiveTotalPrice = "0"; |
| | | } |
| | | } else if (field === "taxExclusiveTotalPrice") { |
| | | // åç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = ( |
| | | productForm.value.taxInclusiveTotalPrice = formatDecimal( |
| | | Number(productForm.value.taxExclusiveTotalPrice) * |
| | | (1 + taxRate / 100) |
| | | ).toFixed(2); |
| | | ); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveTotalPrice) < 0) { |
| | | productForm.value.taxInclusiveTotalPrice = "0"; |
| | | } |
| | | // å·²ç¥æ°éï¼åç®å«ç¨åä»· |
| | | if (productForm.value.quantity) { |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | productForm.value.taxInclusiveUnitPrice = formatDecimal( |
| | | Number(productForm.value.taxInclusiveTotalPrice) / |
| | | Number(productForm.value.quantity) |
| | | ).toFixed(2); |
| | | ); |
| | | // ç¡®ä¿ç»æä¸ä¸ºè´æ° |
| | | if (Number(productForm.value.taxInclusiveUnitPrice) < 0) { |
| | | productForm.value.taxInclusiveUnitPrice = "0"; |
| | |
| | | fileListDialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¸è½½éè´å |
| | | const downloadOrder = async (id) => { |
| | | try { |
| | | const res = await getPrintData(id); |
| | | const data = res.data || res; |
| | | downloadPurchaseJpg(data.companyName, data.ledger, data.products, 'éè´å_' + (data.ledger.purchaseContractNumber || id) + '.jpg'); |
| | | } catch (e) { |
| | | console.error('ä¸è½½å¤±è´¥:', e); |
| | | } |
| | | }; |
| | | |
| | | // æå°éè´å |
| | | const printOrder = async (id) => { |
| | | try { |
| | | const res = await getPrintData(id); |
| | | const data = res.data || res; |
| | | printPurchase(data.companyName, data.ledger, data.products); |
| | | } catch (e) { |
| | | console.error('æå°å¤±è´¥:', e); |
| | | } |
| | | }; |
| | | |
| | | // å 餿¨¡æ¿ |
| | | const handleDeleteTemplate = async item => { |
| | | if (!item.id) { |
| | |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { procurementBusinessSummaryListPage } from '@/api/procurementManagement/procurementReport' |
| | | import { productTreeList } from '@/api/basicData/product' |
| | | import { formatDecimal, formatCurrency } from '@/utils/numberFormat' |
| | | |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | |
| | | prop: 'returnAmount', |
| | | width: 120, |
| | | formatData: (val) => { |
| | | return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00' |
| | | return val ? formatCurrency(val) : 'Â¥0' |
| | | } |
| | | }, |
| | | { |
| | | label: '鿬¾å¨é', |
| | | prop: 'purchaseAmount', |
| | | formatData: (val) => { |
| | | return val ? `Â¥${parseFloat(val).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : 'Â¥0.00' |
| | | return val ? formatCurrency(val) : 'Â¥0' |
| | | } |
| | | }, |
| | | { |
| | |
| | | prop: 'averagePrice', |
| | | width: 120, |
| | | formatData: (val) => { |
| | | return val ? `Â¥${parseFloat(val).toFixed(2)}` : 'Â¥0.00' |
| | | return val ? formatCurrency(val) : 'Â¥0' |
| | | } |
| | | }, |
| | | { |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦æ½æ£ï¼" |
| | | prop="isSampling"> |
| | | <el-radio-group v-model="form.isSampling" |
| | | class="sampling-radio-group" |
| | | :disabled="isViewMode" |
| | | @change="handleSamplingChange"> |
| | | <el-radio-button :label="1">æ¯</el-radio-button> |
| | | <el-radio-button :label="0">å¦</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.isSampling === 1"> |
| | | <el-form-item label="æ½æ£æ°éï¼" |
| | | prop="samplingQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.samplingQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµåä½ï¼" prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" placeholder="请è¾å
¥" clearable :disabled="isViewMode"/> |
| | | </el-form-item> |
| | |
| | | qualifiedQuantity: "", |
| | | unqualifiedQuantity: "", |
| | | checkCompany: "", |
| | | isSampling: 0, |
| | | samplingQuantity: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | qualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unqualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | isSampling: [{ required: true, message: "è¯·éæ©æ¯å¦æ½æ£", trigger: "change" }], |
| | | samplingQuantity: [{ required: true, message: "请è¾å
¥æ½æ£æ°é", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | |
| | | ]); |
| | | userList.value = userListsRes.data; |
| | | |
| | | form.value = {} |
| | | form.value = { isSampling: 0, samplingQuantity: "" } |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | |
| | |
| | | tableData.value = res.data; |
| | | }); |
| | | } |
| | | // æ¯å¦æ½æ£åæ¢ |
| | | const handleSamplingChange = (value) => { |
| | | if (value !== 1) { |
| | | form.value.samplingQuantity = ""; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.("samplingQuantity"); |
| | | }); |
| | | } |
| | | } |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | .sampling-radio-group { |
| | | height: 32px; |
| | | } |
| | | </style> |
| | |
| | | <el-dialog |
| | | v-model="bindingDialogVisible" |
| | | title="æ·»å ç»å®" |
| | | width="520px" |
| | | width="640px" |
| | | @close="closeBindingDialog" |
| | | > |
| | | <el-form label-width="100px"> |
| | | <el-form-item label="产å"> |
| | | <el-tree-select |
| | | v-model="selectedProductIds" |
| | | multiple |
| | | collapse-tags |
| | | collapse-tags-tooltip |
| | | placeholder="è¯·éæ©äº§åï¼å¯å¤éï¼" |
| | | <div class="binding-dialog"> |
| | | <el-input |
| | | v-model="productSearchKeyword" |
| | | placeholder="æç´¢äº§å" |
| | | clearable |
| | | check-strictly |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" |
| | | prefix-icon="Search" |
| | | class="binding-search" |
| | | /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <el-tree |
| | | ref="productTreeRef" |
| | | :key="productTreeKey" |
| | | v-loading="productTreeLoading" |
| | | :data="productTreeData" |
| | | show-checkbox |
| | | check-strictly |
| | | node-key="id" |
| | | :props="{ label: 'label', children: 'children', disabled: 'disabled' }" |
| | | :filter-node-method="filterProductNode" |
| | | class="product-binding-tree" |
| | | @check="handleProductTreeCheck" |
| | | /> |
| | | <div v-if="selectedProductLabels.length" class="selected-products"> |
| | | <div class="selected-title">已鿩ï¼{{ selectedProductLabels.length }}ï¼</div> |
| | | <div class="selected-tags"> |
| | | <el-tag |
| | | v-for="item in selectedProductLabels" |
| | | :key="item.id" |
| | | closable |
| | | type="info" |
| | | @close="removeSelectedProduct(item.id)" |
| | | > |
| | | {{ item.label }} |
| | | </el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button @click="closeBindingDialog">åæ¶</el-button> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from '@element-plus/icons-vue' |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue' |
| | | import { ref, reactive, toRefs, computed, watch, nextTick, onMounted, getCurrentInstance } from 'vue' |
| | | import { ElMessageBox } from 'element-plus' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { productTreeList } from '@/api/basicData/product.js' |
| | |
| | | const bindingDialogVisible = ref(false) |
| | | |
| | | // äº§åæ ï¼ç¨äºç»å®éæ©ï¼ |
| | | const productOptions = ref([]) |
| | | const productTreeData = ref([]) |
| | | const productTreeLoading = ref(false) |
| | | const productTreeRef = ref(null) |
| | | const productTreeKey = ref(0) |
| | | const productSearchKeyword = ref('') |
| | | const selectedProductIds = ref([]) |
| | | |
| | | const getProductOptions = async () => { |
| | | // é¿å
éå¤è¯·æ± |
| | | if (productOptions.value?.length) return |
| | | const res = await productTreeList() |
| | | productOptions.value = convertIdToValue(Array.isArray(res) ? res : []) |
| | | const markParentNodesDisabled = (nodes) => { |
| | | return (nodes || []).map((node) => { |
| | | const children = node.children?.length ? markParentNodesDisabled(node.children) : [] |
| | | return { |
| | | ...node, |
| | | children, |
| | | disabled: children.length > 0 |
| | | } |
| | | }) |
| | | } |
| | | |
| | | function convertIdToValue(data) { |
| | | return (data || []).map((item) => { |
| | | const { id, children, ...rest } = item |
| | | const newItem = { |
| | | ...rest, |
| | | value: id |
| | | const buildProductLabelMap = (nodes, map = {}) => { |
| | | ;(nodes || []).forEach((node) => { |
| | | if (node.id != null) { |
| | | map[node.id] = node.label |
| | | } |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children) |
| | | if (node.children?.length) { |
| | | buildProductLabelMap(node.children, map) |
| | | } |
| | | return newItem |
| | | }) |
| | | return map |
| | | } |
| | | |
| | | const productLabelMap = computed(() => buildProductLabelMap(productTreeData.value)) |
| | | |
| | | const selectedProductLabels = computed(() => |
| | | selectedProductIds.value.map((id) => ({ |
| | | id, |
| | | label: productLabelMap.value[id] || id |
| | | })) |
| | | ) |
| | | |
| | | const filterProductNode = (value, data) => { |
| | | if (!value) return true |
| | | return String(data.label || '').includes(value) |
| | | } |
| | | |
| | | watch(productSearchKeyword, (val) => { |
| | | productTreeRef.value?.filter(val) |
| | | }) |
| | | |
| | | const getProductTreeData = async () => { |
| | | if (productTreeData.value?.length) return |
| | | productTreeLoading.value = true |
| | | try { |
| | | const res = await productTreeList() |
| | | productTreeData.value = markParentNodesDisabled(Array.isArray(res) ? res : []) |
| | | } catch (error) { |
| | | console.error('è·åäº§åæ å¤±è´¥:', error) |
| | | } finally { |
| | | productTreeLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const handleProductTreeCheck = () => { |
| | | selectedProductIds.value = productTreeRef.value?.getCheckedKeys(true) || [] |
| | | } |
| | | |
| | | const removeSelectedProduct = (id) => { |
| | | selectedProductIds.value = selectedProductIds.value.filter((item) => item !== id) |
| | | productTreeRef.value?.setChecked(id, false, false) |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | |
| | | const openBindingDialog = () => { |
| | | if (!currentStandard.value?.id) return |
| | | selectedProductIds.value = [] |
| | | getProductOptions() |
| | | productSearchKeyword.value = '' |
| | | productTreeKey.value += 1 |
| | | bindingDialogVisible.value = true |
| | | nextTick(() => { |
| | | productTreeRef.value?.setCheckedKeys([]) |
| | | productTreeRef.value?.filter('') |
| | | }) |
| | | } |
| | | |
| | | const closeBindingDialog = () => { |
| | | bindingDialogVisible.value = false |
| | | selectedProductIds.value = [] |
| | | productSearchKeyword.value = '' |
| | | productTreeRef.value?.setCheckedKeys([]) |
| | | } |
| | | |
| | | const submitBinding = async () => { |
| | |
| | | onMounted(() => { |
| | | getStandardList() |
| | | getProcessList() |
| | | getProductTreeData() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | width: 100%; |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .binding-dialog { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .binding-search { |
| | | width: 100%; |
| | | } |
| | | |
| | | .product-binding-tree { |
| | | max-height: 360px; |
| | | overflow-y: auto; |
| | | border: 1px solid #ebeef5; |
| | | border-radius: 4px; |
| | | padding: 8px; |
| | | } |
| | | |
| | | .selected-products { |
| | | border-top: 1px solid #ebeef5; |
| | | padding-top: 12px; |
| | | } |
| | | |
| | | .selected-title { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .selected-tags { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | </style> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦æ½æ£ï¼" |
| | | prop="isSampling"> |
| | | <el-radio-group v-model="form.isSampling" |
| | | class="sampling-radio-group" |
| | | :disabled="isViewMode" |
| | | @change="handleSamplingChange"> |
| | | <el-radio-button :label="1">æ¯</el-radio-button> |
| | | <el-radio-button :label="0">å¦</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" |
| | | v-if="form.isSampling === 1"> |
| | | <el-form-item label="æ½æ£æ°éï¼" |
| | | prop="samplingQuantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.samplingQuantity" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | :precision="2" |
| | | :disabled="isViewMode" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ£æµåä½ï¼" |
| | | prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" |
| | |
| | | qualifiedQuantity: "", |
| | | unqualifiedQuantity: "", |
| | | checkCompany: "", |
| | | isSampling: 0, |
| | | samplingQuantity: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | qualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | unqualifiedQuantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | isSampling: [{ required: true, message: "è¯·éæ©æ¯å¦æ½æ£", trigger: "change" }], |
| | | samplingQuantity: [{ required: true, message: "请è¾å
¥æ½æ£æ°é", trigger: "blur" }], |
| | | }, |
| | | }); |
| | | const userList = ref([]); |
| | |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | isSampling: 0, |
| | | samplingQuantity: "", |
| | | }; |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | |
| | | tableData.value = res.data; |
| | | }); |
| | | }; |
| | | // æ¯å¦æ½æ£åæ¢ |
| | | const handleSamplingChange = (value) => { |
| | | if (value !== 1) { |
| | | form.value.samplingQuantity = ""; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.("samplingQuantity"); |
| | | }); |
| | | } |
| | | }; |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .sampling-radio-group { |
| | | height: 32px; |
| | | } |
| | | </style> |
| | |
| | | v-model="form.supplier" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | :disabled="isViewMode || supplierQuantityDisabled" |
| | | > |
| | | <el-option |
| | |
| | | <el-input-number :step="0.01" :min="0" :max="form.quantity || 0" style="width: 100%" |
| | | v-model="form.unqualifiedQuantity" placeholder="请è¾å
¥" :precision="2" |
| | | @change="onUnqualifiedChange" :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦æ½æ£ï¼" prop="isSampling"> |
| | | <el-radio-group v-model="form.isSampling" class="sampling-radio-group" :disabled="isViewMode" @change="handleSamplingChange"> |
| | | <el-radio-button :label="1">æ¯</el-radio-button> |
| | | <el-radio-button :label="0">å¦</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12" v-if="form.isSampling === 1"> |
| | | <el-form-item label="æ½æ£æ°éï¼" prop="samplingQuantity"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.samplingQuantity" |
| | | placeholder="请è¾å
¥" clearable :precision="2" :disabled="isViewMode"/> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | isSampling: 0, |
| | | samplingQuantity: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{required: true, message: "请è¾å
¥", trigger: "blur"},], |
| | |
| | | qualifiedQuantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | unqualifiedQuantity: [{required: true, message: "请è¾å
¥", trigger: "blur"}], |
| | | checkCompany: [{required: false, message: "请è¾å
¥", trigger: "blur"}], |
| | | isSampling: [{required: true, message: "è¯·éæ©æ¯å¦æ½æ£", trigger: "change"}], |
| | | samplingQuantity: [{required: true, message: "请è¾å
¥æ½æ£æ°é", trigger: "blur"}], |
| | | }, |
| | | }); |
| | | const tableColumn = ref([ |
| | |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | isSampling: 0, |
| | | samplingQuantity: "", |
| | | } |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | |
| | | } |
| | | }; |
| | | |
| | | // æ¯å¦æ½æ£åæ¢ |
| | | const handleSamplingChange = (value) => { |
| | | if (value !== 1) { |
| | | form.value.samplingQuantity = ""; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.("samplingQuantity"); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å
³éå¼¹æ¡ |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | .sampling-radio-group { |
| | | height: 32px; |
| | | } |
| | | </style> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="typeNum"> |
| | | <div class="typeNum" v-if="showSemiFinished"> |
| | | <div class="typeNum-left"> |
| | | <img src="~@/assets/images/chartCard2.svg" alt="å¾è¡¨" |
| | | style="width: 40px; height: 40px; object-fit: contain;"> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-card green-card"> |
| | | <div class="quality-card green-card" v-if="showSemiFinished"> |
| | | <div class="quality-card-title"> |
| | | <img src="~@/assets/images/chartCard2.svg" alt="åæå" |
| | | style="width: 24px; height: 24px; margin-right: 8px;"> |
| | |
| | | <div class="chart-box-title">åæææ»æ°</div> |
| | | <div class="chart-box-num">{{ getYearlyStatValue(0, 'totalCount') }}</div> |
| | | </div> |
| | | <div class="inspection-chart-box"> |
| | | <div class="inspection-chart-box" v-if="showSemiFinished"> |
| | | <div class="chart-box-title">åæåæ»æ°</div> |
| | | <div class="chart-box-num">{{ getYearlyStatValue(1, 'totalCount') }}</div> |
| | | </div> |
| | |
| | | <!-- Tab éæ©å¨ --> |
| | | <div class="tab-selector"> |
| | | <div class="tab-item" :class="{ active: activeTab === 'raw' }" @click="activeTab = 'raw'">åææ</div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'semi' }" @click="activeTab = 'semi'">åæå</div> |
| | | <div v-if="showSemiFinished" class="tab-item" :class="{ active: activeTab === 'semi' }" @click="activeTab = 'semi'">åæå</div> |
| | | <div class="tab-item" :class="{ active: activeTab === 'final' }" @click="activeTab = 'final'">æå</div> |
| | | </div> |
| | | </el-card> |
| | |
| | | const monthlyCompletionDetailsData = ref([]); |
| | | const topParametersData = ref({ totalCount: 0, list: [] }); |
| | | const activeTab = ref("raw"); |
| | | |
| | | // æ¯å¦æ¾ç¤ºåæåç¸å
³å
容ãæçä¼ä¸æ²¡æåæåï¼è®¾ä¸º false å³å¯éèææåæåç¸å
³å±ç¤º |
| | | const showSemiFinished = ref(false); |
| | | |
| | | const getParameterColor = (index) => { |
| | | const colors = ['#165DFF', '#14C9C9', '#F7BA1E', '#722ED1', '#3491FA', '#FF7D00', '#F53F3F']; |
| | |
| | | containLabel: true, |
| | | }, |
| | | legend: { |
| | | data: ["åææ", "åæå", "æå"], // å¾ä¾æ°æ® |
| | | data: showSemiFinished.value ? ["åææ", "åæå", "æå"] : ["åææ", "æå"], // å¾ä¾æ°æ® |
| | | icon: ["circle", "circle", "circle"], |
| | | itemWidth: 10, // è®¾ç½®å¾æ 宽度 |
| | | itemHeight: 10, |
| | |
| | | color: "#409EFF", |
| | | }, |
| | | }, |
| | | { |
| | | ...(showSemiFinished.value ? [{ |
| | | name: "åæå", |
| | | type: "bar", |
| | | barWidth: "15%", |
| | |
| | | itemStyle: { |
| | | color: "#67C23A", |
| | | }, |
| | | }, |
| | | }] : []), |
| | | { |
| | | name: "æå", |
| | | type: "bar", |
| | |
| | | radius: "70%", |
| | | data: [ |
| | | { value: getYearlyStatValue(0, 'totalCount'), name: "åææ", itemStyle: { color: "#1890FF" } }, |
| | | { value: getYearlyStatValue(1, 'totalCount'), name: "åæå", itemStyle: { color: "#F7BA1E" } }, |
| | | ...(showSemiFinished.value ? [{ value: getYearlyStatValue(1, 'totalCount'), name: "åæå", itemStyle: { color: "#F7BA1E" } }] : []), |
| | | { value: getYearlyStatValue(2, 'totalCount'), name: "æå", itemStyle: { color: "#14C9C9" } }, |
| | | ], |
| | | label: { |
| | |
| | | }, |
| | | // å¾ä¾é
ç½® |
| | | legend: { |
| | | data: ["åææ", "åæå", "æå"], // å¾ä¾æ°æ® |
| | | data: showSemiFinished.value ? ["åææ", "åæå", "æå"] : ["åææ", "æå"], // å¾ä¾æ°æ® |
| | | icon: ["circle", "circle", "circle"], |
| | | itemWidth: 10, // è®¾ç½®å¾æ 宽度 |
| | | itemHeight: 10, |
| | |
| | | }, |
| | | data: monthlyPassRateData.value.map(item => item.rawMaterial.passRate), |
| | | }, |
| | | { |
| | | ...(showSemiFinished.value ? [{ |
| | | name: "åæå", // ç³»ååç§° |
| | | type: "line", // å¾è¡¨ç±»å为æçº¿å¾ |
| | | // stack: "Total", // å å åç§° |
| | |
| | | color: "#F7BA1E", // è®¾ç½®è¿æ¡çº¿çé¢è² |
| | | }, |
| | | data: monthlyPassRateData.value.map(item => item.process.passRate), |
| | | }, |
| | | }] : []), |
| | | { |
| | | name: "æå", // ç³»ååç§° |
| | | type: "line", // å¾è¡¨ç±»å为æçº¿å¾ |
| | |
| | | "#52c41a", |
| | | getPassRateStatValue(0, 'passRate') |
| | | ); |
| | | if (showSemiFinished.value) { |
| | | semiCompletionChartInstance = initQualityChart( |
| | | semiCompletionChart, |
| | | "#1890ff", |
| | |
| | | "#52c41a", |
| | | getPassRateStatValue(1, 'passRate') |
| | | ); |
| | | } |
| | | finalCompletionChartInstance = initQualityChart( |
| | | finalCompletionChart, |
| | | "#1890ff", |
| | |
| | | |
| | | .typeNum { |
| | | height: 100%; |
| | | width: 33.33%; |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | |
| | | |
| | | .flex-center { |
| | | justify-content: space-evenly; |
| | | gap: 15px; |
| | | } |
| | | |
| | | .quality-card { |
| | | /* flex: 1; */ |
| | | width: 32%; |
| | | flex: 1; |
| | | min-width: 0; |
| | | /* height: 100px; */ |
| | | border-radius: 8px; |
| | | padding: 12px; |
| | |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item label="å¿«éåå·ï¼" v-if="false"> |
| | | <el-form-item label="å¿«éåå·ï¼" > |
| | | <el-input |
| | | v-model="searchForm.expressNumber" |
| | | placeholder="请è¾å
¥" |
| | |
| | | label="å货车çå·" |
| | | prop="shippingCarNumber" |
| | | show-overflow-tooltip |
| | | v-if="false" |
| | | /> |
| | | <el-table-column |
| | | label="å¿«éå
¬å¸" |
| | | prop="expressCompany" |
| | | v-if="false" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="å¿«éåå·" |
| | | prop="expressNumber" |
| | | v-if="false" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | |
| | | style="width: 100%" |
| | | @change="handleShippingTypeChange" |
| | | > |
| | | <el-option label="货车" value="货车" /> |
| | | <el-option label="å¿«é" value="å¿«é" v-if="false" /> |
| | | <el-option label="å¿«é" value="å¿«é" /> |
| | | <el-option label="货车" value="货车" v-if="false"/> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24" v-if="form.type === '货车'"> |
| | | <el-form-item label="å货车çå·ï¼" prop="shippingCarNumber"> |
| | | <el-form-item label="å货车çå·ï¼" prop="shippingCarNumber" v-if="false"> |
| | | <el-input |
| | | v-model="form.shippingCarNumber" |
| | | placeholder="请è¾å
¥å货车çå·" |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" v-if="false"> |
| | | <el-row :gutter="30" > |
| | | <el-col :span="24"> |
| | | <el-form-item label="å¿«éåå·ï¼" prop="expressNumber"> |
| | | <el-input |
| | |
| | | <el-descriptions-item label="å®¡æ ¸ç¶æ">{{ |
| | | getApprovalStatusText(detailRow.status) |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="å货车çå·">{{ |
| | | <el-descriptions-item label="å货车çå·" v-if="false">{{ |
| | | detailRow.shippingCarNumber || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¿«éå
¬å¸" v-if="false">{{ |
| | | <el-descriptions-item label="å¿«éå
¬å¸" >{{ |
| | | detailRow.expressCompany || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¿«éåå·" :span="2" v-if="false">{{ |
| | | <el-descriptions-item label="å¿«éåå·" :span="2" >{{ |
| | | detailRow.expressNumber || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="åºåºåå·" :span="2">{{ |
| | |
| | | customerName: "", |
| | | specificationModel: "", |
| | | productName: "", |
| | | type: "货车", // 货车, å¿«é |
| | | type: "å¿«é", // 货车, å¿«é |
| | | shippingDate: "", |
| | | shippingCarNumber: "", |
| | | expressCompany: "", |
| | |
| | | id: row.id ?? null, |
| | | salesContractNo: row.salesContractNo ?? "", |
| | | customerName: row.customerName ?? "", |
| | | type: row.type || "货车", |
| | | type: row.type || "å¿«é", |
| | | shippingDate: row.shippingDate || getCurrentDate(), |
| | | shippingCarNumber: row.shippingCarNumber ?? "", |
| | | expressCompany: row.expressCompany ?? "", |
| | |
| | | customewTransactionsDetails, |
| | | } from "@/api/salesManagement/indicatorStats.js"; |
| | | import Pagination from "../../../components/PIMTable/Pagination.vue"; |
| | | import { tableAmountFormatter, buildAmountSummaryFormat } from "@/utils/numberFormat"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const tableData = ref([]); |
| | | const receiptRecord = ref([]); |
| | |
| | | } |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return cellValue ? parseFloat(cellValue).toFixed(2) : "0.00"; |
| | | }; |
| | | const formattedNumber = tableAmountFormatter; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["contractAmounts", "shippedAmount", "unshippedAmount"], |
| | | buildAmountSummaryFormat( |
| | | ["contractAmounts", "shippedAmount", "unshippedAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | ticketsNum: { noDecimal: true }, |
| | | futureTickets: { noDecimal: true }, |
| | | } |
| | | ) |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | |
| | | var summarizeTable = proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount", "shippedAmount", "unshippedAmount"], |
| | | buildAmountSummaryFormat( |
| | | ["contractAmount", "shippedAmount", "unshippedAmount"], |
| | | { |
| | | ticketsNum: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | futureTickets: { noDecimal: true }, // ä¸ä¿çå°æ° |
| | | ticketsNum: { noDecimal: true }, |
| | | futureTickets: { noDecimal: true }, |
| | | } |
| | | ) |
| | | ); |
| | | return summarizeTable; |
| | | }; |
| | |
| | | </el-table-column> |
| | | <el-table-column label="å¿«éå
¬å¸" |
| | | prop="expressCompany" |
| | | v-if="false" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å¿«éåå·" |
| | | prop="expressNumber" |
| | | v-if="false" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="å货车ç" |
| | | minWidth="100px" |
| | | align="center"> |
| | | align="center" |
| | | v-if="false"> |
| | | <template #default="scope"> |
| | | <div> |
| | | <el-tag type="success" |
| | |
| | | </el-button> |
| | | <el-button link |
| | | type="primary" |
| | | @click="openFileDialog(scope.row)">éä»¶ |
| | | @click="downloadOrder(scope.row.id)">ä¸è½½ |
| | | </el-button> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | :reserve-keyword="false" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | :disabled="operationType === 'view'" |
| | | disabled |
| | | @change="changs"> |
| | | <el-option v-for="item in userList" |
| | | :key="item.userId" |
| | |
| | | width="160" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | {{ Number(scope.row.totalAmount ?? 0).toFixed(2) }} |
| | | {{ formatDecimal(scope.row.totalAmount) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | |
| | | :rules="productRules" |
| | | ref="productFormRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="产å大类ï¼" |
| | | prop="productCategory"> |
| | | <el-tree-select v-model="productForm.productCategory" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | filterable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | style="width: 100%" /> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产å大类ï¼" prop="productCategory"> |
| | | <el-input v-model="productForm.productCategory" |
| | | placeholder="ç¹å»éæ©" |
| | | readonly |
| | | @click="showProductSelectDialog = true" |
| | | style="cursor:pointer" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" |
| | | prop="productModelId"> |
| | | <el-select v-model="productForm.productModelId" |
| | | placeholder="è¯·éæ©" |
| | | clearable |
| | | @change="getProductModel" |
| | | filterable> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è§æ ¼åå·ï¼" prop="productModelId"> |
| | | <el-input v-model="productForm.specificationModel" |
| | | placeholder="ç¹å»éæ©" |
| | | readonly |
| | | @click="showProductSelectDialog = true" |
| | | style="cursor:pointer" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="å«ç¨åä»·(å
)ï¼" |
| | | prop="taxInclusiveUnitPrice"> |
| | | <el-input-number :step="0.01" |
| | | <el-input-number :step="0.000001" |
| | | :min="0" |
| | | v-model="productForm.taxInclusiveUnitPrice" |
| | | style="width: 100%" |
| | | :precision="2" |
| | | :precision="6" |
| | | placeholder="请è¾å
¥" |
| | | clearable |
| | | @change="calculateFromUnitPrice" /> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <!-- <el-col :span="12"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¯å¦ç产ï¼" |
| | | prop="isProduction"> |
| | | <div style="display: flex; align-items: center; width: 100%;"> |
| | | <el-radio-group v-model="productForm.isProduction"> |
| | | <el-radio label="æ¯" |
| | | :value="true" /> |
| | | <el-radio label="å¦" |
| | | :value="false" /> |
| | | </el-radio-group> |
| | | <div |
| | | v-if="currentStock !== null" |
| | | :style="{ |
| | | marginLeft: '20px', |
| | | padding: '0 12px', |
| | | height: '28px', |
| | | lineHeight: '28px', |
| | | borderRadius: '4px', |
| | | fontSize: '13px', |
| | | fontWeight: '500', |
| | | display: 'inline-block', |
| | | whiteSpace: 'nowrap', |
| | | backgroundColor: (productForm.quantity || 0) > currentStock ? '#fff0f0' : '#f0f9eb', |
| | | border: (productForm.quantity || 0) > currentStock ? '1px solid #ffcccc' : '1px solid #e1f3d8', |
| | | color: (productForm.quantity || 0) > currentStock ? '#f56c6c' : '#67c23a' |
| | | }" |
| | | > |
| | | <span>å½ååºåï¼{{ currentStock }} {{ productForm.unit || '' }}</span> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col>--> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </FormDialog> |
| | | <ProductSelectDialog v-model="showProductSelectDialog" |
| | | @confirm="handleProductSelect" |
| | | single /> |
| | | <!-- 导å
¥å¼¹çª --> |
| | | <FormDialog v-model="importUpload.open" |
| | | :title="importUpload.title" |
| | |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">{{ formatDate(item.createTime) }}</span> |
| | | </div> |
| | | <div> |
| | | <div v-if="false"> |
| | | <span class="label">å货车çå·ï¼</span> |
| | | <span class="value">{{ item.shippingCarNumber }}</span> |
| | | </div> |
| | |
| | | placeholder="è¯·éæ©åè´§ç±»å" |
| | | style="width: 100%" |
| | | @change="handleDeliveryTypeChange"> |
| | | <el-option label="货车" |
| | | value="货车" /> |
| | | <el-option label="å¿«é" |
| | | v-if="false" |
| | | value="å¿«é" /> |
| | | <el-option v-if="false" |
| | | label="货车" |
| | | value="货车" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30" |
| | | v-if="false"> |
| | | > |
| | | <el-col :span="24"> |
| | | <el-form-item label="å¿«éåå·ï¼" |
| | | prop="expressNumber"> |
| | |
| | | delProduct, |
| | | delLedgerFile, |
| | | getProductInventory, |
| | | getPrintData, |
| | | } from "@/api/salesManagement/salesLedger.js"; |
| | | import { downloadSalesJpg, printSalesMulti } from "@/utils/documentPrint.js"; |
| | | import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useFormData from "@/hooks/useFormData.js"; |
| | | import dayjs from "dayjs"; |
| | | import FileUpload from "@/components/AttachmentUpload/file/index.vue"; |
| | | import { |
| | | tableAmountFormatter, |
| | | formatDecimal, |
| | | buildAmountSummaryFormat, |
| | | } from "@/utils/numberFormat"; |
| | | import ImageUpload from "@/components/AttachmentUpload/image/index.vue"; |
| | | import ProductSelectDialog from "@/views/basicData/product/ProductSelectDialog.vue"; |
| | | import { getCurrentDate } from "@/utils/index.js"; |
| | | import { listCustomer } from "@/api/basicData/customer.js"; |
| | | |
| | |
| | | const total = ref(0); |
| | | const fileList = ref([]); |
| | | const deliveryFileList = ref([]); |
| | | const showProductSelectDialog = ref(false); |
| | | |
| | | // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® |
| | | const operationType = ref(""); |
| | |
| | | const currentId = ref(""); |
| | | const productFormData = reactive({ |
| | | productForm: { |
| | | productId: "", |
| | | productCategory: "", |
| | | productModelId: "", |
| | | specificationModel: "", |
| | | unit: "", |
| | | quantity: "", |
| | |
| | | isProduction: false, |
| | | }, |
| | | productRules: { |
| | | productCategory: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©", trigger: "change" }], |
| | | specificationModel: [ |
| | | { required: true, message: "è¯·éæ©", trigger: "change" }, |
| | | ], |
| | | productCategory: [{ required: true, message: "è¯·éæ©äº§å大类", trigger: "change" }], |
| | | productModelId: [{ required: true, message: "è¯·éæ©è§æ ¼åå·", trigger: "change" }], |
| | | unit: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | |
| | | item?.qualifiedQuantity ?? |
| | | item?.stockQuantity; |
| | | return quantity ?? 0; |
| | | }; |
| | | const currentStock = ref(null); |
| | | const fetchCurrentStock = async (productModelId) => { |
| | | if (!productModelId) { |
| | | currentStock.value = null; |
| | | return; |
| | | } |
| | | try { |
| | | const res = await getStockInventoryByModelId(productModelId); |
| | | const rawList = Array.isArray(res?.data) |
| | | ? res.data |
| | | : res?.data?.records || res?.data?.rows || []; |
| | | let total = 0; |
| | | rawList.forEach(item => { |
| | | total += Number(getDeliveryBatchQuantity(item) || 0); |
| | | }); |
| | | currentStock.value = total; |
| | | } catch (e) { |
| | | console.error(e); |
| | | currentStock.value = 0; |
| | | } |
| | | }; |
| | | const getCurrentDeliveryRowQuantity = () => { |
| | | return Number(currentDeliveryRow.value?.noQuantity || 0); |
| | |
| | | shippingCarNumber: "", |
| | | expressCompany: "", |
| | | expressNumber: "", |
| | | type: "货车", // 货车, å¿«é |
| | | type: "å¿«é", // 货车, å¿«é |
| | | }, |
| | | deliveryRules: { |
| | | shippingCarNumber: [ |
| | |
| | | return productOptions.value; |
| | | }); |
| | | }; |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | if (cellValue === undefined || cellValue === null || cellValue === "") { |
| | | return "0.00"; |
| | | } |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | const formattedNumber = tableAmountFormatter; |
| | | const findLedgerRecordByRow = row => { |
| | | if (!row) return null; |
| | | if ( |
| | |
| | | } |
| | | return formattedNumber(row, column, cellValue); |
| | | }; |
| | | // ProductSelectDialog éä¸åè° |
| | | const handleProductSelect = (products) => { |
| | | if (!products || !products.length) return; |
| | | const p = products[0]; |
| | | productForm.value.productId = p.productId; |
| | | productForm.value.productCategory = p.productName || ''; |
| | | productForm.value.productModelId = p.id; |
| | | productForm.value.specificationModel = p.model || ''; |
| | | productForm.value.unit = p.unit || ''; |
| | | showProductSelectDialog.value = false; |
| | | }; |
| | | // è·åtreeåæ°æ® |
| | | const getModels = value => { |
| | | productForm.value.productCategory = findNodeById(productOptions.value, value); |
| | | currentStock.value = null; |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res; |
| | | }); |
| | |
| | | if (index !== -1) { |
| | | productForm.value.specificationModel = modelOptions.value[index].model; |
| | | productForm.value.unit = modelOptions.value[index].unit; |
| | | fetchCurrentStock(value); |
| | | } else { |
| | | productForm.value.specificationModel = null; |
| | | productForm.value.unit = null; |
| | | currentStock.value = null; |
| | | } |
| | | }; |
| | | const findNodeById = (nodes, productId) => { |
| | |
| | | }; |
| | | // 主表åè®¡æ¹æ³ |
| | | const summarizeMainTable = param => { |
| | | return proxy.summarizeTable(param, [ |
| | | return proxy.summarizeTable( |
| | | param, |
| | | ["contractAmount", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice"], |
| | | buildAmountSummaryFormat([ |
| | | "contractAmount", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | ]) |
| | | ); |
| | | }; |
| | | // å表åè®¡æ¹æ³ |
| | | const summarizeChildrenTable = (param, parentRow) => { |
| | |
| | | return ""; |
| | | }); |
| | | } |
| | | return proxy.summarizeTable(param, [ |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]); |
| | | ], |
| | | buildAmountSummaryFormat([ |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ]) |
| | | ); |
| | | }; |
| | | // æå¼å¼¹æ¡ |
| | | const openForm = async (type, row) => { |
| | |
| | | const quantity = Number(p.quantity ?? 0) || 0; |
| | | const unitPrice = Number(p.unitPrice ?? 0) || 0; |
| | | const taxRate = "13"; // é»è®¤ 13%ï¼ä¾¿äºç´æ¥æäº¤ï¼å¦éå¯å¨äº§åä¸èªè¡ä¿®æ¹ï¼ |
| | | const taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | const taxInclusiveTotalPrice = formatDecimal(unitPrice * quantity); |
| | | const taxExclusiveTotalPrice = proxy.calculateTaxExclusiveTotalPrice( |
| | | taxInclusiveTotalPrice, |
| | | taxRate |
| | |
| | | unit: p.unit || "", |
| | | quantity: quantity, |
| | | taxRate: taxRate, |
| | | taxInclusiveUnitPrice: unitPrice.toFixed(2), |
| | | taxInclusiveUnitPrice: formatDecimal(unitPrice), |
| | | taxInclusiveTotalPrice: taxInclusiveTotalPrice, |
| | | taxExclusiveTotalPrice: taxExclusiveTotalPrice, |
| | | invoiceType: "墿®ç¥¨", |
| | |
| | | |
| | | productOperationType.value = type; |
| | | productForm.value = {}; |
| | | currentStock.value = null; |
| | | if (type === "add") { |
| | | productForm.value.isProduction = true; |
| | | } |
| | |
| | | ); |
| | | if (currentModel) { |
| | | productForm.value.productModelId = currentModel.id; |
| | | fetchCurrentStock(currentModel.id); |
| | | } |
| | | } |
| | | } catch (e) { |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æå°åè½ |
| | | // æå°åè½ - å¤éåå¹¶é¢è§ |
| | | const handlePrint = async () => { |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©è¦æå°çæ°æ®"); |
| | | return; |
| | | } |
| | | |
| | | // æ¾ç¤ºå è½½ç¶æ |
| | | proxy.$modal.loading("æ£å¨è·åäº§åæ°æ®ï¼è¯·ç¨å..."); |
| | | |
| | | proxy.$modal.loading("æ£å¨å è½½æå°æ°æ®..."); |
| | | try { |
| | | // 为æ¯ä¸ªéä¸çéå®å°è´¦è®°å½æ¥è¯¢å¯¹åºçäº§åæ°æ® |
| | | const printDataWithProducts = []; |
| | | |
| | | const orders = []; |
| | | let companyName = ''; |
| | | for (const row of selectedRows.value) { |
| | | try { |
| | | // è°ç¨productListæ¥å£æ¥è¯¢äº§åæ°æ® |
| | | const productRes = await productList({ |
| | | salesLedgerId: row.id, |
| | | type: 1, |
| | | }); |
| | | |
| | | // å°äº§åæ°æ®æ´åå°éå®å°è´¦è®°å½ä¸ |
| | | const rowWithProducts = { |
| | | ...row, |
| | | products: productRes.data || [], |
| | | }; |
| | | |
| | | printDataWithProducts.push(rowWithProducts); |
| | | } catch (error) { |
| | | console.error(`è·åéå®å°è´¦ ${row.id} çäº§åæ°æ®å¤±è´¥:`, error); |
| | | // å³ä½¿æä¸ªè®°å½çäº§åæ°æ®è·å失败ï¼ä¹è¦å
å«è¯¥è®°å½ |
| | | printDataWithProducts.push({ |
| | | ...row, |
| | | products: [], |
| | | }); |
| | | const res = await getPrintData(row.id); |
| | | const data = res.data || res; |
| | | companyName = data.companyName || companyName; |
| | | orders.push(data.ledger); |
| | | } |
| | | } |
| | | |
| | | printData.value = printDataWithProducts; |
| | | console.log("æå°æ°æ®ï¼å
å«äº§åï¼:", printData.value); |
| | | printPreviewVisible.value = true; |
| | | } catch (error) { |
| | | console.error("è·åäº§åæ°æ®å¤±è´¥:", error); |
| | | proxy.$modal.msgError("è·åäº§åæ°æ®å¤±è´¥ï¼è¯·éè¯"); |
| | | printSalesMulti(companyName, orders); |
| | | } catch (e) { |
| | | console.error('æå°å¤±è´¥:', e); |
| | | proxy.$modal.msgError("æå°å¤±è´¥"); |
| | | } finally { |
| | | proxy.$modal.closeLoading(); |
| | | } |
| | | }; |
| | | // æ§è¡æå° |
| | | const executePrint = () => { |
| | | console.log("å¼å§æ§è¡æå°ï¼æ°æ®æ¡æ°:", printData.value.length); |
| | | console.log("æå°æ°æ®:", printData.value); |
| | | |
| | | // å建ä¸ä¸ªæ°çæå°çªå£ |
| | | const printWindow = window.open("", "_blank", "width=800,height=600"); |
| | | |
| | | // æå»ºæå°å
容 |
| | | let printContent = ` |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <title>æå°é¢è§</title> |
| | | <style> |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | font-family: "SimSun", serif; |
| | | background: white; |
| | | } |
| | | .print-page { |
| | | width: 200mm; |
| | | height: 75mm; |
| | | padding: 10mm; |
| | | padding-left: 20mm; |
| | | background: white; |
| | | box-sizing: border-box; |
| | | page-break-after: always; |
| | | page-break-inside: avoid; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | .delivery-note { |
| | | width: 100%; |
| | | height: 100%; |
| | | font-size: 12px; |
| | | line-height: 1.2; |
| | | display: flex; |
| | | flex-direction: column; |
| | | color: #000; |
| | | } |
| | | .header { |
| | | text-align: center; |
| | | margin-bottom: 8px; |
| | | } |
| | | .company-name { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 4px; |
| | | } |
| | | .document-title { |
| | | font-size: 16px; |
| | | font-weight: bold; |
| | | } |
| | | .info-section { |
| | | margin-bottom: 8px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .info-row { |
| | | line-height: 20px; |
| | | } |
| | | .label { |
| | | font-weight: bold; |
| | | width: 60px; |
| | | font-size: 12px; |
| | | } |
| | | .value { |
| | | margin-right: 20px; |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .table-section { |
| | | margin-bottom: 40px; |
| | | // flex: 0.6; |
| | | } |
| | | .product-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #000; |
| | | } |
| | | .product-table th, .product-table td { |
| | | border: 1px solid #000; |
| | | padding: 6px; |
| | | text-align: center; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | .product-table th { |
| | | font-weight: bold; |
| | | } |
| | | .total-value { |
| | | font-weight: bold; |
| | | } |
| | | .footer-section { |
| | | margin-top: auto; |
| | | } |
| | | .footer-row { |
| | | display: flex; |
| | | margin-bottom: 3px; |
| | | line-height: 22px; |
| | | justify-content: space-between; |
| | | } |
| | | .footer-item { |
| | | display: flex; |
| | | margin-right: 20px; |
| | | } |
| | | .footer-item .label { |
| | | font-weight: bold; |
| | | width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .footer-item .value { |
| | | min-width: 80px; |
| | | font-size: 12px; |
| | | } |
| | | .address-item .address-value { |
| | | min-width: 200px; |
| | | } |
| | | @media print { |
| | | body { |
| | | margin: 0; |
| | | padding: 0; |
| | | } |
| | | .print-page { |
| | | margin: 0; |
| | | padding: 10mm; |
| | | /* padding-left: 20mm; */ |
| | | page-break-inside: avoid; |
| | | page-break-after: always; |
| | | } |
| | | .print-page:last-child { |
| | | page-break-after: avoid; |
| | | } |
| | | } |
| | | </style> |
| | | </head> |
| | | <body> |
| | | `; |
| | | |
| | | // ä¸ºæ¯æ¡æ°æ®çææå°é¡µé¢ |
| | | printData.value.forEach((item, index) => { |
| | | printContent += ` |
| | | <div class="print-page"> |
| | | <div class="delivery-note"> |
| | | <div class="header"> |
| | | <div class="document-title">é¶å®åè´§å</div> |
| | | </div> |
| | | |
| | | <div class="info-section"> |
| | | <div class="info-row"> |
| | | <div> |
| | | <span class="label">åè´§æ¥æï¼</span> |
| | | <span class="value">${formatDate( |
| | | item.createTime |
| | | )}</span> |
| | | </div> |
| | | <div> |
| | | <span class="label">客æ·åç§°ï¼</span> |
| | | <span class="value">${ |
| | | item.customerName |
| | | }</span> |
| | | </div> |
| | | </div> |
| | | <div class="info-row"> |
| | | <span class="label">åå·ï¼</span> |
| | | <span class="value">${ |
| | | item.salesContractNo || |
| | | "" |
| | | }</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table-section"> |
| | | <table class="product-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>产ååç§°</th> |
| | | <th>è§æ ¼åå·</th> |
| | | <th>åä½</th> |
| | | <th>åä»·</th> |
| | | <th>é¶å®æ°é</th> |
| | | <th>é¶å®éé¢</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | ${ |
| | | item.products && |
| | | item |
| | | .products |
| | | .length > |
| | | 0 |
| | | ? item.products |
| | | .map( |
| | | product => ` |
| | | <tr> |
| | | <td>${ |
| | | product.productCategory || |
| | | "" |
| | | }</td> |
| | | <td>${ |
| | | product.specificationModel || |
| | | "" |
| | | }</td> |
| | | <td>${ |
| | | product.unit || |
| | | "" |
| | | }</td> |
| | | <td>${ |
| | | product.taxInclusiveUnitPrice || |
| | | "0" |
| | | }</td> |
| | | <td>${ |
| | | product.quantity || |
| | | "0" |
| | | }</td> |
| | | <td>${ |
| | | product.taxInclusiveTotalPrice || |
| | | "0" |
| | | }</td> |
| | | </tr> |
| | | ` |
| | | ) |
| | | .join( |
| | | "" |
| | | ) |
| | | : '<tr><td colspan="6" style="text-align: center; color: #999;">ææ äº§åæ°æ®</td></tr>' |
| | | } |
| | | </tbody> |
| | | <tfoot> |
| | | <tr> |
| | | <td class="label">å计</td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value"></td> |
| | | <td class="total-value">${getTotalQuantityForPrint( |
| | | item.products |
| | | )}</td> |
| | | <td class="total-value">${getTotalAmountForPrint( |
| | | item.products |
| | | )}</td> |
| | | </tr> |
| | | </tfoot> |
| | | </table> |
| | | </div> |
| | | |
| | | <div class="footer-section"> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§çµè¯ï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æ¶è´§äººï¼</span> |
| | | <span class="value"></span> |
| | | </div> |
| | | <div class="footer-item address-item"> |
| | | <span class="label">æ¶è´§å°åï¼</span> |
| | | <span class="value address-value"></span> |
| | | </div> |
| | | </div> |
| | | <div class="footer-row"> |
| | | <div class="footer-item"> |
| | | <span class="label">æä½åï¼</span> |
| | | <span class="value">${ |
| | | userStore.nickName || |
| | | "æå¼å" |
| | | }</span> |
| | | </div> |
| | | <div class="footer-item"> |
| | | <span class="label">æå°æ¥æï¼</span> |
| | | <span class="value">${formatDateTime( |
| | | new Date() |
| | | )}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | `; |
| | | }); |
| | | |
| | | printContent += ` |
| | | </body> |
| | | </html> |
| | | `; |
| | | |
| | | // åå
¥å
容尿°çªå£ |
| | | printWindow.document.write(printContent); |
| | | printWindow.document.close(); |
| | | |
| | | // çå¾
å
容å è½½å®æåæå° |
| | | printWindow.onload = () => { |
| | | setTimeout(() => { |
| | | printWindow.print(); |
| | | printWindow.close(); |
| | | printPreviewVisible.value = false; |
| | | }, 500); |
| | | }; |
| | | }; |
| | | // æ ¼å¼åæ¥æ |
| | | const formatDate = dateString => { |
| | | if (!dateString) return getCurrentDate(); |
| | |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | return formatDecimal(total); |
| | | }; |
| | | |
| | | // ç¨äºæå°ç计ç®å½æ° |
| | |
| | | const total = products.reduce((sum, product) => { |
| | | return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0); |
| | | }, 0); |
| | | return total.toFixed(2); |
| | | return formatDecimal(total); |
| | | }; |
| | | |
| | | const mathNum = () => { |
| | |
| | | isCalculating.value = true; |
| | | |
| | | // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é |
| | | productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); |
| | | productForm.value.taxInclusiveUnitPrice = formatDecimal(totalPrice / quantity); |
| | | |
| | | // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· |
| | | if (productForm.value.taxRate) { |
| | |
| | | // å
计ç®å«ç¨æ»ä»· = ä¸å«ç¨æ»ä»· / (1 - ç¨ç/100) |
| | | const taxRateDecimal = taxRate / 100; |
| | | const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); |
| | | productForm.value.taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); |
| | | productForm.value.taxInclusiveTotalPrice = formatDecimal(inclusiveTotalPrice); |
| | | |
| | | // 计ç®å«ç¨åä»· = å«ç¨æ»ä»· / æ°é |
| | | productForm.value.taxInclusiveUnitPrice = ( |
| | | productForm.value.taxInclusiveUnitPrice = formatDecimal( |
| | | inclusiveTotalPrice / quantity |
| | | ).toFixed(2); |
| | | ); |
| | | |
| | | isCalculating.value = false; |
| | | }; |
| | |
| | | isCalculating.value = true; |
| | | |
| | | // 计ç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | productForm.value.taxInclusiveTotalPrice = formatDecimal(unitPrice * quantity); |
| | | |
| | | // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· |
| | | if (productForm.value.taxRate) { |
| | |
| | | isCalculating.value = true; |
| | | |
| | | // 计ç®å«ç¨æ»ä»· |
| | | productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | productForm.value.taxInclusiveTotalPrice = formatDecimal(unitPrice * quantity); |
| | | |
| | | // 妿æç¨çï¼è®¡ç®ä¸å«ç¨æ»ä»· |
| | | if (productForm.value.taxRate) { |
| | |
| | | fileDialogVisible.value = true; |
| | | }; |
| | | |
| | | // ä¸è½½éå®å |
| | | const downloadOrder = async (id) => { |
| | | try { |
| | | const res = await getPrintData(id); |
| | | const data = res.data || res; |
| | | downloadSalesJpg(data.companyName, data.ledger, 'éå®å_' + (data.ledger.salesContractNo || id) + '.jpg'); |
| | | } catch (e) { |
| | | console.error('ä¸è½½å¤±è´¥:', e); |
| | | } |
| | | }; |
| | | |
| | | // æå°éå®å |
| | | const printOrder = async (id) => { |
| | | try { |
| | | const res = await getPrintData(id); |
| | | const data = res.data || res; |
| | | printSales(data.companyName, data.ledger); |
| | | } catch (e) { |
| | | console.error('æå°å¤±è´¥:', e); |
| | | } |
| | | }; |
| | | |
| | | // æå¼åè´§å¼¹æ¡ |
| | | const openDeliveryForm = async row => { |
| | | // æ£æ¥æ¯å¦å¯ä»¥åè´§ |
| | |
| | | shippingCarNumber: "", |
| | | expressCompany: "", |
| | | expressNumber: "", |
| | | type: "货车", |
| | | type: "å¿«é", |
| | | batchNo: [], |
| | | batchNoList, |
| | | }; |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="totalAmount" label="æ¥ä»·éé¢" width="120"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.totalAmount.toFixed(2) }} |
| | | ¥{{ formatDecimal(scope.row.totalAmount) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æä½" width="200" fixed="right" align="center"> |
| | |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | <el-form-item :prop="`products.${scope.$index}.unitPrice`" class="product-table-form-item"> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="2" style="width: 100%" /> |
| | | <el-input-number v-model="scope.row.unitPrice" :min="0" :precision="6" :step="0.000001" style="width: 100%" /> |
| | | </el-form-item> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | <!-- <el-tag :type="getStatusType(currentQuotation.status)">{{ currentQuotation.status }}</el-tag>--> |
| | | <!-- </el-descriptions-item>--> |
| | | <el-descriptions-item label="æ¥ä»·æ»é¢" :span="2"> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ currentQuotation.totalAmount?.toFixed(2) }}</span> |
| | | <span style="font-size: 18px; color: #e6a23c; font-weight: bold;">Â¥{{ formatDecimal(currentQuotation.totalAmount) }}</span> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | |
| | | <el-table-column prop="unit" label="åä½" /> |
| | | <el-table-column prop="unitPrice" label="åä»·"> |
| | | <template #default="scope"> |
| | | ¥{{ scope.row.unitPrice.toFixed(2) }} |
| | | ¥{{ formatDecimal(scope.row.unitPrice) }} |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {listCustomer} from "@/api/basicData/customer.js"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { formatDecimal, roundAmount } from '@/utils/numberFormat'; |
| | | |
| | | // ååºå¼æ°æ® |
| | | const loading = ref(false) |
| | |
| | | |
| | | const baseRules = { |
| | | customer: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | customerId: [{ required: true, message: 'è¯·éæ©å®¢æ·', trigger: 'change' }], |
| | | salesperson: [{ required: true, message: 'è¯·éæ©ä¸å¡å', trigger: 'change' }], |
| | | quotationDate: [{ required: true, message: 'è¯·éæ©æ¥ä»·æ¥æ', trigger: 'change' }], |
| | | validDate: [{ required: true, message: 'è¯·éæ©æææ', trigger: 'change' }], |
| | |
| | | return |
| | | } |
| | | |
| | | // è®¡ç®ææäº§åçåä»·æ»å |
| | | form.totalAmount = form.products.reduce((sum, product) => { |
| | | // è®¡ç®ææäº§åçåä»·æ»åï¼ä¿çå
ä½å°æ° |
| | | form.totalAmount = roundAmount(form.products.reduce((sum, product) => { |
| | | const price = Number(product.unitPrice) || 0 |
| | | return sum + price |
| | | }, 0) |
| | | }, 0)) |
| | | |
| | | form.customer = customerOption.value.find(item => item.id === form.customerId)?.customerName || '' |
| | | if (isEdit.value) { |