import html2canvas from 'html2canvas' function esc(s) { if (!s) return '' return String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') } function fmtDate(v) { if (!v) return '' const d = new Date(v) const y = d.getFullYear() const m = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') return y + '-' + m + '-' + day } function fmtMoney(v) { if (v == null) return '0.00' return Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } function digitUppercase(n) { const digits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'] const radices = ['', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿'] let num = Math.abs(n) let result = '' const intPart = Math.floor(num) const intStr = String(intPart) const len = intStr.length if (intPart === 0) { result = '零' } else { let zeroFlag = false for (let i = 0; i < len; i++) { const d = parseInt(intStr[i]) const pos = len - 1 - i if (d === 0) { zeroFlag = true if (pos % 4 === 0) { result += radices[pos] zeroFlag = false } } else { if (zeroFlag) { result += '零' zeroFlag = false } result += digits[d] + radices[pos] } } if (result.endsWith('零')) result = result.slice(0, -1) } result += '元' const decPart = Math.round((num - intPart) * 100) if (decPart === 0) { result += '整' } else { const jiao = Math.floor(decPart / 10) const fen = decPart % 10 if (jiao > 0) result += digits[jiao] + '角' if (fen > 0) result += digits[fen] + '分' } return result } function coreCss() { return `*{margin:0;padding:0;box-sizing:border-box}body{font-family:"Microsoft YaHei","SimSun",sans-serif;padding:30px 36px;color:#333;background:#fff}.title{text-align:center;font-size:18px;font-weight:bold;letter-spacing:6px;margin-bottom:4px}.sub{text-align:center;font-size:12px;color:#999;margin-bottom:18px}.info-top{display:flex;justify-content:space-between;font-size:13px;margin-bottom:14px;padding-bottom:10px;border-bottom:1px dashed #ccc}.info-top .left,.info-top .right{line-height:2}.info-grid{display:grid;grid-template-columns:1fr 1fr;gap:4px 36px;font-size:13px;margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid #ccc}.info-grid .l{color:#666}.info-grid .v{font-weight:500}table{width:100%;border-collapse:collapse;font-size:13px;margin-bottom:14px}th,td{border:1px solid #333;padding:5px 6px;text-align:center}th{background:#eee;font-weight:500}td.r{text-align:right}.summary{text-align:right;font-size:13px;margin-bottom:16px;line-height:2.2}.summary .cn{font-weight:bold;color:#cf1322}` } function productRows(products) { if (!products || !products.length) return '' let html = '' products.forEach((p, i) => { const name = p.productCategory || '' const spec = p.specificationModel || '' html += '