Merge remote-tracking branch 'origin/dev_pro_河南鹤壁' into dev_pro_河南鹤壁
已添加6个文件
已修改29个文件
1908 ■■■■■ 文件已修改
multiple/assets/favicon/JHJMfavicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/TDDQfavicon.ico 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/JHJMLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/logo/TDDQLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
multiple/config.json 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/procurementManagement/procurementLedger.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/salesLedger.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PIMTable/PIMTable.vue 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/documentPrint.js 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/numberFormat.js 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/summarizeTable.js 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/BlacklistTab.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/supplierManage/components/HomeTab.vue 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/spareParts/index.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/dispatchLog/Record.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/receiptManagement/Record.vue 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Record.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/paymentLedger/index.vue 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/detail.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 199 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementReport/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/finalInspection/components/formDia.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/metricBinding/index.vue 179 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/processInspection/components/formDia.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/reportManagement/index.vue 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/deliveryLedger/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/receiptPaymentLedger/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesLedger/index.vue 598 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/salesQuotation/index.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
multiple/assets/favicon/JHJMfavicon.ico
multiple/assets/favicon/TDDQfavicon.ico
multiple/assets/logo/JHJMLogo.png
multiple/assets/logo/TDDQLogo.png
multiple/config.json
@@ -132,6 +132,24 @@
    "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"
}
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
@@ -122,4 +122,11 @@
        method: "get",
        params: id,
    });
}
// èŽ·å–é‡‡è´­å•æ‰“å°æ•°æ®
export function getPrintData(id) {
    return request({
        url: '/purchase/ledger/printData/' + id,
        method: 'get',
    });
}
src/api/salesManagement/salesLedger.js
@@ -116,4 +116,11 @@
        method: "get",
        params: query,
    });
}
// èŽ·å–é”€å”®å•æ‰“å°æ•°æ®
export function getPrintData(id) {
    return request({
        url: '/sales/ledger/printData/' + id,
        method: 'get',
    });
}
src/components/PIMTable/PIMTable.vue
@@ -8,6 +8,7 @@
            :highlight-current-row="highlightCurrentRow"
            :row-class-name="rowClassName"
            :row-style="rowStyle"
            :cell-style="cellStyle"
            :row-key="rowKey"
            :style="tableStyle"
            tooltip-effect="dark"
@@ -293,6 +294,10 @@
      type: [Object, Function],
      default: () => ({}),
    },
    cellStyle: {
      type: [Object, Function],
      default: () => ({}),
    },
    tableData: {
      type: Array,
      default: () => [],
src/utils/documentPrint.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,331 @@
import html2canvas from 'html2canvas'
function esc(s) {
  if (!s) return ''
  return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
}
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/utils/numberFormat.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
/** é‡‘额类字段默认最大小数位数 */
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
}
src/utils/summarizeTable.js
@@ -1,3 +1,5 @@
import { formatDecimal, AMOUNT_DECIMAL_SCALE } from './numberFormat'
/**
 * é€šç”¨çš„表格合计方法
 * @param {Object} param - åŒ…含表格列配置和数据源的对象
@@ -26,28 +28,35 @@
          // å¦‚果指定了不需要保留小数,则直接转换为整数
          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 {
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/basicData/product/index.vue
@@ -335,7 +335,7 @@
    rules: {
      productName: [
        { required: true, message: "请输入", trigger: "blur" },
        { max: 20, message: "产品名称不能超过20个字符", trigger: "blur" },
        { max: 50, message: "产品名称不能超过50个字符", trigger: "blur" },
      ],
    },
    modelForm: {
src/views/basicData/supplierManage/components/BlacklistTab.vue
@@ -96,7 +96,7 @@
          </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"
@@ -112,7 +112,7 @@
                           value="丁" />
              </el-select>
            </el-form-item>
          </el-col>
          </el-col> -->
          <el-col :span="12">
            <el-form-item label="是否白名单:"
                          prop="isWhite">
@@ -203,22 +203,22 @@
    {
      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: "维护人",
@@ -228,7 +228,7 @@
    {
      label: "维护时间",
      prop: "maintainTime",
      width: 100,
      // width: 100,
    },
    {
      dataType: "action",
@@ -244,14 +244,14 @@
            openForm("edit", row);
          },
        },
        {
          //资质附件
          name: "资质文件",
          type: "text",
          clickFun: row => {
            openFileDialog(row);
          },
        },
        // {
        //   //资质附件
        //   name: "资质文件",
        //   type: "text",
        //   clickFun: row => {
        //     openFileDialog(row);
        //   },
        // },
      ],
    },
  ]);
@@ -290,9 +290,9 @@
      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);
src/views/basicData/supplierManage/components/HomeTab.vue
@@ -102,7 +102,7 @@
          </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"
@@ -118,7 +118,7 @@
                           value="丁" />
              </el-select>
            </el-form-item>
          </el-col>
          </el-col> -->
          <el-col :span="12">
            <el-form-item label="是否白名单:"
                          prop="isWhite">
@@ -209,26 +209,27 @@
    {
      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,
    },
    {
@@ -250,14 +251,14 @@
            openForm("edit", row);
          },
        },
        {
          //资质附件
          name: "资质文件",
          type: "text",
          clickFun: row => {
            openFileDialog(row);
          },
        },
        // {
        //   //资质附件
        //   name: "资质文件",
        //   type: "text",
        //   clickFun: row => {
        //     openFileDialog(row);
        //   },
        // },
      ],
    },
  ]);
@@ -296,9 +297,9 @@
      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);
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -22,14 +22,10 @@
        <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>
@@ -42,6 +38,18 @@
                        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>
@@ -75,7 +83,7 @@
                <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>
@@ -92,7 +100,7 @@
                                   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>
@@ -135,7 +143,7 @@
                <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>
@@ -154,11 +162,11 @@
                                   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>
@@ -332,6 +340,7 @@
  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();
src/views/collaborativeApproval/rulesRegulationsManagement/index.vue
@@ -318,7 +318,7 @@
      formatData: (v) => (v === "active" ? "生效中" : "已废止"),
      formatType: (v) => (v === "active" ? "success" : "info"),
    },
    { label: "已读人数", prop: "readCount", width: 100 },
    // { label: "已读人数", prop: "readCount", width: 100 },
    {
      dataType: "action",
      label: "操作",
src/views/equipmentManagement/spareParts/index.vue
@@ -29,6 +29,7 @@
                        :tableLoading="loading"
                        :page="pagination"
                        :isShowPagination="true"
                        :row-class-name="rowClassName"
                        @pagination="handleSizeChange"
                    >
                        <template #status="{ row }">
@@ -95,7 +96,7 @@
        </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="备件名称">
@@ -521,9 +522,17 @@
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;
}
@@ -547,8 +556,17 @@
  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;
}
/* æŒ‰é’®ç»„样式 */
@@ -567,4 +585,5 @@
  font-size: 12px;
  margin-right: 4px;
}
</style>
src/views/inventoryManagement/dispatchLog/Record.vue
@@ -102,9 +102,13 @@
        <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"
@@ -167,6 +171,7 @@
  findAllQualifiedStockOutRecordTypeOptions,
  findAllUnQualifiedStockOutRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
src/views/inventoryManagement/receiptManagement/Record.vue
@@ -106,8 +106,12 @@
                         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/>
@@ -185,6 +189,7 @@
  findAllQualifiedStockInRecordTypeOptions, 
  // findAllUnQualifiedStockInRecordTypeOptions,
} from "@/api/basicData/enum.js";
import { parseTime } from "@/utils/ruoyi";
const {proxy} = getCurrentInstance();
src/views/inventoryManagement/stockManagement/Record.vue
@@ -104,11 +104,11 @@
          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="最近更新时间"
src/views/procurementManagement/paymentLedger/index.vue
@@ -79,7 +79,7 @@
                    @pagination="sonPaginationSearch">
            <template #payableAmountSlot="{ row }">
              <el-text type="danger">
                {{ parseFloat(row.unshippedAmount).toFixed(2) }}
                {{ formatDecimal(row.unshippedAmount) }}
              </el-text>
            </template>
          </PIMTable>
@@ -90,13 +90,14 @@
</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);
@@ -140,17 +141,13 @@
      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: "未入库金额(元)",
@@ -171,10 +168,13 @@
    return proxy.summarizeTable(
      param,
      ["contractAmounts", "shippedAmount", "unshippedAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
      buildAmountSummaryFormat(
        ["contractAmounts", "shippedAmount", "unshippedAmount"],
        {
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
        }
      )
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
@@ -182,10 +182,13 @@
    let summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "shippedAmount", "unshippedAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
      buildAmountSummaryFormat(
        ["contractAmount", "shippedAmount", "unshippedAmount"],
        {
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
        }
      )
    );
    return summarizeTable;
  };
@@ -257,13 +260,7 @@
    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>
src/views/procurementManagement/procurementLedger/detail.vue
@@ -127,6 +127,7 @@
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)
// æ˜¯å¦æ˜¾ç¤ºé”€å”®åˆåŒå·ç»‘定
@@ -157,12 +158,7 @@
  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) => {
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>
@@ -161,7 +162,7 @@
        <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>
@@ -208,7 +209,7 @@
          <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" />
@@ -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>
@@ -310,7 +313,7 @@
        <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>
@@ -324,13 +327,13 @@
        <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>
@@ -344,14 +347,12 @@
              </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">
@@ -361,8 +362,21 @@
            </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" />
@@ -403,6 +417,7 @@
  productList,
  getPurchaseById,
  getOptions,
  getPrintData,
  getPurchaseTemplateList,
  delPurchaseTemplate,
} from "@/api/procurementManagement/procurementLedger.js";
@@ -435,8 +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();
@@ -636,6 +654,7 @@
// äº§å“è¡¨å•弹框数据
const productFormVisible = ref(false);
const showProductSelectDialog = ref(false);
const productOperationType = ref("");
const productOperationIndex = ref("");
const currentId = ref("");
@@ -656,15 +675,15 @@
    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" },
    ],
@@ -763,9 +782,7 @@
  handleQuery();
};
const formattedNumber = (row, column, cellValue) => {
  return parseFloat(cellValue).toFixed(2);
};
const formattedNumber = tableAmountFormatter;
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
@@ -883,10 +900,19 @@
      "futureTickets",
      "futureTicketsAmount",
    ],
    {
      ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
    }
    buildAmountSummaryFormat(
      [
        "taxInclusiveUnitPrice",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
        "ticketsAmount",
        "futureTicketsAmount",
      ],
      {
        ticketsNum: { noDecimal: true },
        futureTickets: { noDecimal: true },
      }
    )
  );
};
const paginationChange = obj => {
@@ -949,15 +975,27 @@
};
// ä¸»è¡¨åˆè®¡æ–¹æ³•
const summarizeMainTable = param => {
  return proxy.summarizeTable(param, ["contractAmount"]);
  return proxy.summarizeTable(
    param,
    ["contractAmount"],
    buildAmountSummaryFormat(["contractAmount"])
  );
};
// å­è¡¨åˆè®¡æ–¹æ³•
const summarizeProTable = param => {
  return proxy.summarizeTable(param, [
    "taxInclusiveUnitPrice",
    "taxInclusiveTotalPrice",
    "taxExclusiveTotalPrice",
  ]);
  return proxy.summarizeTable(
    param,
    [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ],
    buildAmountSummaryFormat([
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ])
  );
};
// æ‰“开弹框
const openForm = async (type, row) => {
@@ -998,6 +1036,7 @@
    );
    form.value.entryDate = getCurrentDate();
    form.value.recorderId = userStore.id;
    if (type === "add") {
      // æ–°å¢žæ—¶ç”Ÿæˆé‡‡è´­åˆåŒå·
@@ -1225,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);
@@ -1407,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();
@@ -1466,10 +1543,10 @@
  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";
@@ -1487,30 +1564,30 @@
      }
    }
    // åç®—不含税总价
    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";
@@ -1560,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/procurementManagement/procurementReport/index.vue
@@ -83,6 +83,7 @@
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()
@@ -135,14 +136,14 @@
    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'
    }
  },
  {
@@ -155,7 +156,7 @@
    prop: 'averagePrice',
    width: 120,
    formatData: (val) => {
      return val ? `Â¥${parseFloat(val).toFixed(2)}` : 'Â¥0.00'
      return val ? formatCurrency(val) : 'Â¥0'
    }
  },
  {
src/views/qualityManagement/finalInspection/components/formDia.vue
@@ -97,6 +97,34 @@
        </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>
@@ -184,6 +212,8 @@
    qualifiedQuantity: "",
    unqualifiedQuantity: "",
    checkCompany: "",
    isSampling: 0,
    samplingQuantity: "",
  },
  rules: {
    checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -197,6 +227,8 @@
    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);
@@ -289,7 +321,7 @@
  ]);
  userList.value = userListsRes.data;
  form.value = {}
  form.value = { isSampling: 0, samplingQuantity: "" }
  testStandardOptions.value = [];
  tableData.value = [];
@@ -502,6 +534,15 @@
    tableData.value = res.data;
  });
}
// æ˜¯å¦æŠ½æ£€åˆ‡æ¢
const handleSamplingChange = (value) => {
  if (value !== 1) {
    form.value.samplingQuantity = "";
    nextTick(() => {
      proxy.$refs?.formRef?.clearValidate?.("samplingQuantity");
    });
  }
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
@@ -517,5 +558,7 @@
</script>
<style scoped>
.sampling-radio-group {
  height: 32px;
}
</style>
src/views/qualityManagement/metricBinding/index.vue
@@ -120,25 +120,45 @@
    <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="请选择产品(可多选)"
            clearable
            check-strictly
            :data="productOptions"
            :render-after-expand="false"
            style="width: 100%"
          />
        </el-form-item>
      </el-form>
      <div class="binding-dialog">
        <el-input
          v-model="productSearchKeyword"
          placeholder="搜索产品"
          clearable
          prefix-icon="Search"
          class="binding-search"
        />
        <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>
@@ -150,8 +170,7 @@
</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'
@@ -261,28 +280,74 @@
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 = () => {
@@ -354,12 +419,20 @@
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 () => {
@@ -428,6 +501,7 @@
onMounted(() => {
  getStandardList()
  getProcessList()
  getProductTreeData()
})
</script>
@@ -533,4 +607,39 @@
  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>
src/views/qualityManagement/processInspection/components/formDia.vue
@@ -130,6 +130,34 @@
        </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"
@@ -242,6 +270,8 @@
      qualifiedQuantity: "",
      unqualifiedQuantity: "",
      checkCompany: "",
      isSampling: 0,
      samplingQuantity: "",
    },
    rules: {
      checkTime: [{ required: true, message: "请输入", trigger: "blur" }],
@@ -255,6 +285,8 @@
      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([]);
@@ -359,6 +391,8 @@
      unit: "",
      quantity: "",
      checkCompany: "",
      isSampling: 0,
      samplingQuantity: "",
    };
    testStandardOptions.value = [];
    tableData.value = [];
@@ -607,6 +641,15 @@
      tableData.value = res.data;
    });
  };
  // æ˜¯å¦æŠ½æ£€åˆ‡æ¢
  const handleSamplingChange = (value) => {
    if (value !== 1) {
      form.value.samplingQuantity = "";
      nextTick(() => {
        proxy.$refs?.formRef?.clearValidate?.("samplingQuantity");
      });
    }
  };
  // å…³é—­å¼¹æ¡†
  const closeDia = () => {
    proxy.resetForm("formRef");
@@ -622,4 +665,7 @@
</script>
<style scoped>
.sampling-radio-group {
  height: 32px;
}
</style>
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -14,6 +14,7 @@
                  v-model="form.supplier"
                  placeholder="请选择"
                  clearable
                  filterable
                  :disabled="isViewMode || supplierQuantityDisabled"
              >
                <el-option
@@ -96,6 +97,23 @@
              <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>
@@ -190,6 +208,8 @@
    unit: "",
    quantity: "",
    checkCompany: "",
    isSampling: 0,
    samplingQuantity: "",
  },
  rules: {
    checkTime: [{required: true, message: "请输入", trigger: "blur"},],
@@ -203,6 +223,8 @@
    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([
@@ -307,6 +329,8 @@
    unit: "",
    quantity: "",
    checkCompany: "",
    isSampling: 0,
    samplingQuantity: "",
  }
  testStandardOptions.value = [];
  tableData.value = [];
@@ -531,6 +555,16 @@
  }
};
// æ˜¯å¦æŠ½æ£€åˆ‡æ¢
const handleSamplingChange = (value) => {
  if (value !== 1) {
    form.value.samplingQuantity = "";
    nextTick(() => {
      proxy.$refs?.formRef?.clearValidate?.("samplingQuantity");
    });
  }
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
@@ -546,5 +580,7 @@
</script>
<style scoped>
.sampling-radio-group {
  height: 32px;
}
</style>
src/views/reportAnalysis/reportManagement/index.vue
@@ -37,7 +37,7 @@
                  </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;">
@@ -123,7 +123,7 @@
                  </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;">
@@ -202,7 +202,7 @@
                    <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>
@@ -266,7 +266,7 @@
            <!-- 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>
@@ -295,6 +295,9 @@
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'];
@@ -526,7 +529,7 @@
        containLabel: true,
      },
      legend: {
        data: ["原材料", "半成品", "成品"], // å›¾ä¾‹æ•°æ®
        data: showSemiFinished.value ? ["原材料", "半成品", "成品"] : ["原材料", "成品"], // å›¾ä¾‹æ•°æ®
        icon: ["circle", "circle", "circle"],
        itemWidth: 10, // è®¾ç½®å›¾æ ‡å®½åº¦
        itemHeight: 10,
@@ -564,7 +567,7 @@
            color: "#409EFF",
          },
        },
        {
        ...(showSemiFinished.value ? [{
          name: "半成品",
          type: "bar",
          barWidth: "15%",
@@ -573,7 +576,7 @@
          itemStyle: {
            color: "#67C23A",
          },
        },
        }] : []),
        {
          name: "成品",
          type: "bar",
@@ -607,7 +610,7 @@
          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: {
@@ -655,7 +658,7 @@
      },
      // å›¾ä¾‹é…ç½®
      legend: {
        data: ["原材料", "半成品", "成品"], // å›¾ä¾‹æ•°æ®
        data: showSemiFinished.value ? ["原材料", "半成品", "成品"] : ["原材料", "成品"], // å›¾ä¾‹æ•°æ®
        icon: ["circle", "circle", "circle"],
        itemWidth: 10, // è®¾ç½®å›¾æ ‡å®½åº¦
        itemHeight: 10,
@@ -697,7 +700,7 @@
          },
          data: monthlyPassRateData.value.map(item => item.rawMaterial.passRate),
        },
        {
        ...(showSemiFinished.value ? [{
          name: "半成品", // ç³»åˆ—名称
          type: "line", // å›¾è¡¨ç±»åž‹ä¸ºæŠ˜çº¿å›¾
          // stack: "Total", // å †å åç§°
@@ -706,7 +709,7 @@
            color: "#F7BA1E", // è®¾ç½®è¿™æ¡çº¿çš„颜色
          },
          data: monthlyPassRateData.value.map(item => item.process.passRate),
        },
        }] : []),
        {
          name: "成品", // ç³»åˆ—名称
          type: "line", // å›¾è¡¨ç±»åž‹ä¸ºæŠ˜çº¿å›¾
@@ -769,16 +772,18 @@
    "#52c41a",
    getPassRateStatValue(0, 'passRate')
  );
  semiCompletionChartInstance = initQualityChart(
    semiCompletionChart,
    "#1890ff",
    getPassRateStatValue(1, 'completionRate')
  );
  semiQualityChartInstance = initQualityChart(
    semiQualityChart,
    "#52c41a",
    getPassRateStatValue(1, 'passRate')
  );
  if (showSemiFinished.value) {
    semiCompletionChartInstance = initQualityChart(
      semiCompletionChart,
      "#1890ff",
      getPassRateStatValue(1, 'completionRate')
    );
    semiQualityChartInstance = initQualityChart(
      semiQualityChart,
      "#52c41a",
      getPassRateStatValue(1, 'passRate')
    );
  }
  finalCompletionChartInstance = initQualityChart(
    finalCompletionChart,
    "#1890ff",
@@ -1161,7 +1166,7 @@
.typeNum {
  height: 100%;
  width: 33.33%;
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
@@ -1297,11 +1302,12 @@
.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;
src/views/salesManagement/deliveryLedger/index.vue
@@ -22,7 +22,7 @@
            @change="handleQuery"
          />
        </el-form-item>
        <el-form-item label="快递单号:" v-if="false">
        <el-form-item label="快递单号:" >
          <el-input
            v-model="searchForm.expressNumber"
            placeholder="请输入"
@@ -95,17 +95,16 @@
          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
@@ -182,8 +181,8 @@
                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>
@@ -205,7 +204,7 @@
        </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="请输入发货车牌号"
@@ -223,7 +222,7 @@
            </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
@@ -283,13 +282,13 @@
          <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">{{
@@ -385,7 +384,7 @@
    customerName: "",
    specificationModel: "",
    productName: "",
    type: "货车", // è´§è½¦, å¿«é€’
    type: "快递", // è´§è½¦, å¿«é€’
    shippingDate: "",
    shippingCarNumber: "",
    expressCompany: "",
@@ -476,7 +475,7 @@
      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 ?? "",
src/views/salesManagement/receiptPaymentLedger/index.vue
@@ -126,6 +126,7 @@
    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([]);
@@ -173,18 +174,19 @@
      }
    });
  };
  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"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
      buildAmountSummaryFormat(
        ["contractAmounts", "shippedAmount", "unshippedAmount"],
        {
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
        }
      )
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
@@ -192,10 +194,13 @@
    var summarizeTable = proxy.summarizeTable(
      param,
      ["contractAmount", "shippedAmount", "unshippedAmount"],
      {
        ticketsNum: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
        futureTickets: { noDecimal: true }, // ä¸ä¿ç•™å°æ•°
      }
      buildAmountSummaryFormat(
        ["contractAmount", "shippedAmount", "unshippedAmount"],
        {
          ticketsNum: { noDecimal: true },
          futureTickets: { noDecimal: true },
        }
      )
    );
    return summarizeTable;
  };
src/views/salesManagement/salesLedger/index.vue
@@ -123,15 +123,14 @@
              </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"
@@ -250,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>
@@ -380,7 +379,7 @@
                         :reserve-keyword="false"
                         placeholder="请选择"
                         clearable
                         :disabled="operationType === 'view'"
                         disabled
                         @change="changs">
                <el-option v-for="item in userList"
                           :key="item.userId"
@@ -568,7 +567,7 @@
                         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"
@@ -606,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>
@@ -666,11 +652,11 @@
          <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" />
@@ -724,20 +710,44 @@
              </el-select>
            </el-form-item>
          </el-col>
<!--          <el-col :span="12">
          <el-col :span="12">
            <el-form-item label="是否生产:"
                          prop="isProduction">
              <el-radio-group v-model="productForm.isProduction">
                <el-radio label="是"
                          :value="true" />
                <el-radio label="否"
                          :value="false" />
              </el-radio-group>
              <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"
@@ -813,7 +823,7 @@
                    <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>
@@ -916,11 +926,11 @@
                         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>
@@ -952,7 +962,7 @@
          </el-col>
        </el-row>
        <el-row :gutter="30"
                v-if="false">
                >
          <el-col :span="24">
            <el-form-item label="快递单号:"
                          prop="expressNumber">
@@ -1051,13 +1061,21 @@
    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";
@@ -1086,6 +1104,7 @@
  const total = ref(0);
  const fileList = ref([]);
  const deliveryFileList = ref([]);
  const showProductSelectDialog = ref(false);
  // ç”¨æˆ·ä¿¡æ¯è¡¨å•弹框数据
  const operationType = ref("");
@@ -1128,7 +1147,9 @@
  const currentId = ref("");
  const productFormData = reactive({
    productForm: {
      productId: "",
      productCategory: "",
      productModelId: "",
      specificationModel: "",
      unit: "",
      quantity: "",
@@ -1140,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: [
@@ -1197,6 +1215,27 @@
      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);
@@ -1283,7 +1322,7 @@
      shippingCarNumber: "",
      expressCompany: "",
      expressNumber: "",
      type: "货车", // è´§è½¦, å¿«é€’
      type: "快递", // è´§è½¦, å¿«é€’
    },
    deliveryRules: {
      shippingCarNumber: [
@@ -1401,12 +1440,7 @@
      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 (
@@ -1460,9 +1494,21 @@
    }
    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;
    });
@@ -1472,9 +1518,11 @@
    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) => {
@@ -1569,11 +1617,15 @@
  };
  // ä¸»è¡¨åˆè®¡æ–¹æ³•
  const summarizeMainTable = param => {
    return proxy.summarizeTable(param, [
      "contractAmount",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
    return proxy.summarizeTable(
      param,
      ["contractAmount", "taxInclusiveTotalPrice", "taxExclusiveTotalPrice"],
      buildAmountSummaryFormat([
        "contractAmount",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
      ])
    );
  };
  // å­è¡¨åˆè®¡æ–¹æ³•
  const summarizeChildrenTable = (param, parentRow) => {
@@ -1595,11 +1647,19 @@
        return "";
      });
    }
    return proxy.summarizeTable(param, [
      "taxInclusiveUnitPrice",
      "taxInclusiveTotalPrice",
      "taxExclusiveTotalPrice",
    ]);
    return proxy.summarizeTable(
      param,
      [
        "taxInclusiveUnitPrice",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
      ],
      buildAmountSummaryFormat([
        "taxInclusiveUnitPrice",
        "taxInclusiveTotalPrice",
        "taxExclusiveTotalPrice",
      ])
    );
  };
  // æ‰“开弹框
  const openForm = async (type, row) => {
@@ -1727,7 +1787,7 @@
      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
@@ -1739,7 +1799,7 @@
        unit: p.unit || "",
        quantity: quantity,
        taxRate: taxRate,
        taxInclusiveUnitPrice: unitPrice.toFixed(2),
        taxInclusiveUnitPrice: formatDecimal(unitPrice),
        taxInclusiveTotalPrice: taxInclusiveTotalPrice,
        taxExclusiveTotalPrice: taxExclusiveTotalPrice,
        invoiceType: "增普票",
@@ -1798,6 +1858,7 @@
    productOperationType.value = type;
    productForm.value = {};
    currentStock.value = null;
    if (type === "add") {
      productForm.value.isProduction = true;
    }
@@ -1824,6 +1885,7 @@
          );
          if (currentModel) {
            productForm.value.productModelId = currentModel.id;
            fetchCurrentStock(currentModel.id);
          }
        }
      } catch (e) {
@@ -2026,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();
@@ -2413,7 +2147,7 @@
    const total = products.reduce((sum, product) => {
      return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
    }, 0);
    return total.toFixed(2);
    return formatDecimal(total);
  };
  // ç”¨äºŽæ‰“印的计算函数
@@ -2430,7 +2164,7 @@
    const total = products.reduce((sum, product) => {
      return sum + (parseFloat(product.taxInclusiveTotalPrice) || 0);
    }, 0);
    return total.toFixed(2);
    return formatDecimal(total);
  };
  const mathNum = () => {
@@ -2471,7 +2205,7 @@
    isCalculating.value = true;
    // è®¡ç®—含税单价 = å«ç¨Žæ€»ä»· / æ•°é‡
    productForm.value.taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2);
    productForm.value.taxInclusiveUnitPrice = formatDecimal(totalPrice / quantity);
    // å¦‚果有税率,计算不含税总价
    if (productForm.value.taxRate) {
@@ -2508,12 +2242,12 @@
    // å…ˆè®¡ç®—含税总价 = ä¸å«ç¨Žæ€»ä»· / (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;
  };
@@ -2536,7 +2270,7 @@
    isCalculating.value = true;
    // è®¡ç®—含税总价
    productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    productForm.value.taxInclusiveTotalPrice = formatDecimal(unitPrice * quantity);
    // å¦‚果有税率,计算不含税总价
    if (productForm.value.taxRate) {
@@ -2568,7 +2302,7 @@
    isCalculating.value = true;
    // è®¡ç®—含税总价
    productForm.value.taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2);
    productForm.value.taxInclusiveTotalPrice = formatDecimal(unitPrice * quantity);
    // å¦‚果有税率,计算不含税总价
    if (productForm.value.taxRate) {
@@ -2709,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 => {
    // æ£€æŸ¥æ˜¯å¦å¯ä»¥å‘è´§
@@ -2727,7 +2483,7 @@
      shippingCarNumber: "",
      expressCompany: "",
      expressNumber: "",
      type: "货车",
      type: "快递",
      batchNo: [],
      batchNoList,
    };
src/views/salesManagement/salesQuotation/index.vue
@@ -65,7 +65,7 @@
        </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">
@@ -215,7 +215,7 @@
            <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>
@@ -267,7 +267,7 @@
<!--          <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>
@@ -279,7 +279,7 @@
          <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>
@@ -303,6 +303,7 @@
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)
@@ -346,6 +347,7 @@
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' }],
@@ -686,11 +688,11 @@
        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) {