| package.json | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/procurementManagement/procurementLedger.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/api/salesManagement/salesLedger.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/utils/documentPrint.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/basicData/product/ProductSelectDialog.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/procurementManagement/procurementLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/views/salesManagement/salesLedger/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
package.json
@@ -29,6 +29,7 @@ "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", src/api/procurementManagement/procurementLedger.js
@@ -123,3 +123,10 @@ params: id, }); } // è·åéè´åæå°æ°æ® export function getPrintData(id) { return request({ url: '/purchase/ledger/printData/' + id, method: 'get', }); } src/api/salesManagement/salesLedger.js
@@ -117,3 +117,10 @@ params: query, }); } // è·åéå®åæå°æ°æ® export function getPrintData(id) { return request({ url: '/sales/ledger/printData/' + id, method: 'get', }); } src/utils/documentPrint.js
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,331 @@ 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) } src/views/basicData/product/ProductSelectDialog.vue
@@ -2,11 +2,11 @@ <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> @@ -20,9 +20,10 @@ @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"> src/views/procurementManagement/procurementLedger/index.vue
@@ -45,6 +45,7 @@ <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" @@ -100,14 +101,14 @@ <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> @@ -276,20 +277,22 @@ @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> @@ -371,6 +374,9 @@ </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" /> @@ -411,6 +417,7 @@ productList, getPurchaseById, getOptions, getPrintData, getPurchaseTemplateList, delPurchaseTemplate, } from "@/api/procurementManagement/procurementLedger.js"; @@ -443,9 +450,11 @@ 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(); @@ -645,6 +654,7 @@ // 产å表åå¼¹æ¡æ°æ® const productFormVisible = ref(false); const showProductSelectDialog = ref(false); const productOperationType = ref(""); const productOperationIndex = ref(""); const currentId = ref(""); @@ -665,8 +675,8 @@ 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: [ @@ -1254,6 +1264,19 @@ 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); @@ -1436,6 +1459,31 @@ }); }; // æ¹éæå°éè´å - å¤éåå¹¶é¢è§ 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(); @@ -1589,6 +1637,28 @@ 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) { src/views/salesManagement/salesLedger/index.vue
@@ -249,7 +249,7 @@ </el-button> <el-button link type="primary" @click="openFileDialog(scope.row)">éä»¶ @click="downloadOrder(scope.row.id)">ä¸è½½ </el-button> </template> </el-table-column> @@ -605,35 +605,22 @@ :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> @@ -758,6 +745,9 @@ </el-row> </el-form> </FormDialog> <ProductSelectDialog v-model="showProductSelectDialog" @confirm="handleProductSelect" single /> <!-- å¯¼å ¥å¼¹çª --> <FormDialog v-model="importUpload.open" :title="importUpload.title" @@ -1071,7 +1061,9 @@ 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"; @@ -1083,6 +1075,7 @@ 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"; @@ -1111,6 +1104,7 @@ const total = ref(0); const fileList = ref([]); const deliveryFileList = ref([]); const showProductSelectDialog = ref(false); // ç¨æ·ä¿¡æ¯è¡¨åå¼¹æ¡æ°æ® const operationType = ref(""); @@ -1153,7 +1147,9 @@ const currentId = ref(""); const productFormData = reactive({ productForm: { productId: "", productCategory: "", productModelId: "", specificationModel: "", unit: "", quantity: "", @@ -1165,11 +1161,8 @@ 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: [ @@ -1500,6 +1493,17 @@ return "*****"; } 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 => { @@ -2084,359 +2088,31 @@ }); }; // æå°åè½ // æå°åè½ - å¤éåå¹¶é¢è§ 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(); @@ -2767,6 +2443,28 @@ 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 => { // æ£æ¥æ¯å¦å¯ä»¥åè´§