2257 lines
96 KiB
TypeScript
2257 lines
96 KiB
TypeScript
import type { Plan, PlanProduct } from '@/api/pl'
|
||
import { formatRemarkForSlipHtml } from '@/utils/pl-batch-print'
|
||
import { applyPdfStableTableCellBorders } from '@/utils/pl-slip-pdf-render'
|
||
|
||
/** 分装单打印默认公司信息(可在富文本中修改) */
|
||
export const SUBPACK_SLIP_DEFAULTS = {
|
||
companyName: '安庆市山峡化工有限公司',
|
||
receivingUnit: '安庆市山峡化工有限公司',
|
||
companyAddress: '安庆市高新区霞虹路5号',
|
||
companyPhone: '0556-5999710',
|
||
contactPhone: '方总13395560066'
|
||
} as const
|
||
|
||
const DOC_FONT = "SimSun, 'Songti SC', serif"
|
||
const FONT_TITLE = 'font-size:26px;font-weight:bold;'
|
||
const FONT_BODY = 'font-size:20px;'
|
||
const FONT_SIGN = 'font-size:14px;'
|
||
const FONT_SIDE = 'font-size:18px;'
|
||
const DOC_PADDING = '8px 10px 24px'
|
||
/** 单据内容区最大宽度(编辑预览居中,略窄于 A4 像素宽) */
|
||
export const SUBPACK_SLIP_DOC_MAX_WIDTH = '960px'
|
||
const DOC_WRAP_EXTRA = `max-width:${SUBPACK_SLIP_DOC_MAX_WIDTH};margin:0 auto;box-sizing:border-box;`
|
||
const LOGO_HEIGHT = 36
|
||
const LOGO_LEFT = 50
|
||
const TITLE_STYLE = `${FONT_TITLE}font-weight:700 !important;letter-spacing:6px;line-height:1.35;`
|
||
const TITLE_SUB_STYLE = `${TITLE_STYLE}margin-top:2px;letter-spacing:10px;`
|
||
const TABLE_ROW_PADDING = '4px'
|
||
/** 信息表等宽列 */
|
||
const INFO_COLGROUP = `<colgroup>
|
||
<col span="7" style="width:14.28%;" />
|
||
</colgroup>`
|
||
/**
|
||
* 物料表列宽:加宽名称/型号,收窄包装规格/单位。
|
||
* 序号 / 产品名称 / 产品型号 / 包装规格 / 单位 / 数量 / 是否完成
|
||
*/
|
||
const PRODUCT_COLGROUP = `<colgroup>
|
||
<col style="width:8%;" />
|
||
<col style="width:26%;" />
|
||
<col style="width:18%;" />
|
||
<col style="width:10%;" />
|
||
<col style="width:7%;" />
|
||
<col style="width:15%;" />
|
||
<col style="width:16%;" />
|
||
</colgroup>`
|
||
|
||
/** 物料网格 padding(编辑器预览) */
|
||
const PRODUCT_GRID_CELL_PADDING = '8px 6px'
|
||
const PRODUCT_GRID_TH_PADDING = '10px 6px'
|
||
/** PDF 专用:padding 直接写在 td/th(html2canvas 对 flex/vertical-align 支持差,底部多留白) */
|
||
const PRODUCT_GRID_PDF_TD_PADDING = '8px 6px 12px 6px'
|
||
const PRODUCT_GRID_PDF_TH_PADDING = '10px 6px 16px 6px'
|
||
const PRODUCT_GRID_PDF_LINE_HEIGHT = '24px'
|
||
/** PDF 专用:备注/条款/地址等左对齐合并单元格 */
|
||
const PRODUCT_FOOTER_PDF_PADDING = '10px 6px 16px 6px'
|
||
const PRODUCT_FOOTER_PDF_LINE_HEIGHT = '1.35'
|
||
/** 物料表格边框线宽(备注/条款等合并单元格,与网格一致便于 PDF 衔接) */
|
||
const PRODUCT_TABLE_BORDER = '0.3px solid #000'
|
||
/** 物料明细网格线(与合同 PDF 一致:全边框 inline + 0.3px,html2canvas 渲染稳定) */
|
||
const PRODUCT_GRID_BORDER = '0.3px solid #000'
|
||
/** 物料表格单元格:长文本自动换行 */
|
||
const PRODUCT_CELL_WRAP = 'white-space:normal;word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;'
|
||
const PRODUCT_GRID_CELL = `padding:${PRODUCT_GRID_CELL_PADDING};box-sizing:border-box;border:${PRODUCT_GRID_BORDER} !important;text-align:center;${FONT_BODY}line-height:normal;background-color:#fff !important;${PRODUCT_CELL_WRAP}`
|
||
const PRODUCT_CELL = `padding:4px 6px;border:${PRODUCT_TABLE_BORDER} !important;text-align:center;${FONT_BODY}line-height:1.35;background-color:#fff !important;${PRODUCT_CELL_WRAP}`
|
||
const PRODUCT_TH = `padding:${PRODUCT_GRID_TH_PADDING};box-sizing:border-box;border:${PRODUCT_GRID_BORDER} !important;text-align:center;${FONT_BODY}line-height:normal;font-weight:bold;background-color:#fff !important;${PRODUCT_CELL_WRAP}`
|
||
const PRODUCT_TABLE_CELL_LEFT = `padding:10px 6px 14px 6px;border:${PRODUCT_TABLE_BORDER} !important;text-align:left;${FONT_BODY}line-height:1.35;vertical-align:top;background-color:#fff !important;`
|
||
/** 条款单元格:行高随内容,避免被右侧印章区 min-height 撑出底部空白 */
|
||
const PRODUCT_TERMS_CELL_LEFT = `padding:10px 6px 14px 6px;border:${PRODUCT_TABLE_BORDER} !important;text-align:left;${FONT_BODY}line-height:1.35;vertical-align:top;background-color:#fff !important;`
|
||
const TERMS_TEXT_STYLE = 'line-height:1.35;'
|
||
const SEAL_BLOCK_LINE_STYLE = 'line-height:1.35;'
|
||
/** 分装单印章:public/sxshzyz.png,定位在地址单元格右上角(相对原位置向上 100px) */
|
||
const SUBPACK_SLIP_SEAL_IMAGE_SRC = '/sxshzyz.png'
|
||
const SUBPACK_SLIP_SEAL_TOP = -96
|
||
const SUBPACK_SLIP_SEAL_IMAGE_STYLE = `position:absolute;right:4px;top:${SUBPACK_SLIP_SEAL_TOP}px;width:140px;height:140px;opacity:0.85;z-index:10;`
|
||
const HANDWRITE_BLANK_INNER = ' '
|
||
const HANDWRITE_BLANK = `<span class="subpack-slip-handwrite-blank">(${HANDWRITE_BLANK_INNER})</span>`
|
||
const PLAIN_CELL = `border:none !important;padding:${TABLE_ROW_PADDING};${FONT_BODY}line-height:1.8;vertical-align:top;background:transparent !important;`
|
||
const NO_BORDER = `${PLAIN_CELL} vertical-align:bottom;`
|
||
|
||
/** 基本信息:label 与值同一行,值区域占满剩余宽度并带下划线 */
|
||
const INFO_ROW = `display:flex;align-items:flex-end;width:100%;box-sizing:border-box;`
|
||
const INFO_LABEL = `white-space:nowrap;flex-shrink:0;padding-right:2px;`
|
||
const INFO_UNDERLINE = '1px solid #000'
|
||
const SIGN_UNDERLINE = '1px solid #000'
|
||
/** 签字值下划线与文字间距(底部 padding 把 border-bottom 推离字模) */
|
||
const SIGN_VALUE_PADDING = '0 2px 6px 2px'
|
||
const SIGN_VALUE_PDF_PADDING = '0 2px 8px 2px'
|
||
const INFO_VALUE_INLINE_LEFT = `flex:1;min-width:0;box-sizing:border-box;border-bottom:${INFO_UNDERLINE} !important;padding:0 2px 2px;text-align:left;min-height:1.3em;`
|
||
const INFO_VALUE_INLINE_CENTER = `flex:1;min-width:0;box-sizing:border-box;border-bottom:${INFO_UNDERLINE} !important;padding:0 2px 2px;text-align:center;min-height:1.3em;`
|
||
const INFO_CELL_LEFT = `${NO_BORDER}text-align:left;`
|
||
const INFO_CELL_RIGHT = `${NO_BORDER}text-align:left;`
|
||
const INFO_GAP_BEFORE_PRODUCT = 12
|
||
const GAP_BEFORE_PRODUCT_CELL = `border:none !important;padding:0 !important;line-height:0 !important;font-size:0 !important;background:transparent !important;height:${INFO_GAP_BEFORE_PRODUCT}px !important;min-height:${INFO_GAP_BEFORE_PRODUCT}px !important;`
|
||
const INFO_GAP_BEFORE_SIGN = 12
|
||
const GAP_BEFORE_SIGN_CELL = `border:none !important;padding:0 !important;line-height:0 !important;font-size:0 !important;background:transparent !important;height:${INFO_GAP_BEFORE_SIGN}px !important;min-height:${INFO_GAP_BEFORE_SIGN}px !important;`
|
||
const SIDE_LABELS_COL_WIDTH = 36
|
||
|
||
/** 签字区下划线最小宽度 */
|
||
const SUBPACK_SLIP_SIGN_VALUE_MIN_WIDTH = '120px'
|
||
|
||
function buildSubpackSlipSignItem(label: string, valueContent: string, minWidth: string = SUBPACK_SLIP_SIGN_VALUE_MIN_WIDTH): string {
|
||
return `<span class="subpack-slip-sign-item"><span class="subpack-slip-sign-label">${label}</span><span class="subpack-slip-sign-value" style="min-width:${minWidth};">${valueContent}</span></span>`
|
||
}
|
||
|
||
function buildSubpackSlipSignRowHtml(): string {
|
||
return `<tr class="subpack-slip-sign-row">
|
||
<td colspan="7" class="subpack-slip-sign-row-cell" style="border:none !important;padding:${TABLE_ROW_PADDING};text-align:center;${FONT_SIGN}background:transparent !important;">
|
||
<div class="subpack-slip-sign-inner">
|
||
${buildSubpackSlipSignItem('制单人:', '{{preparerUser}} ')}
|
||
${buildSubpackSlipSignItem('审核:', ' ')}
|
||
${buildSubpackSlipSignItem('生产主管:', ' ')}
|
||
</div>
|
||
</td>
|
||
</tr>`
|
||
}
|
||
|
||
export interface SubpackSlipRenderContext {
|
||
/** 单位字典 code -> 展示文案 */
|
||
getUnitLabel?: (unitCode: string) => string
|
||
/** 分装单号 NO:SXFZYYYYMMDD-### */
|
||
subpackSlipNo?: string
|
||
/** 下单日期 YYYY-MM-DD */
|
||
orderDate?: string
|
||
/** 下单人(当前登录用户姓名) */
|
||
orderUser?: string
|
||
/** 制单人(默认当前登录用户姓名) */
|
||
preparerUser?: string
|
||
}
|
||
|
||
function resolveSlipUnitLabel(unitCode: string | undefined, ctx?: SubpackSlipRenderContext): string {
|
||
const code = String(unitCode || '').trim()
|
||
if (!code) return ''
|
||
return ctx?.getUnitLabel?.(code) || code
|
||
}
|
||
|
||
export function canPrintSubpackSlip(plan?: Pick<Plan, 'planType' | 'transportStatus'> | null): boolean {
|
||
return plan?.planType !== 'inbound'
|
||
}
|
||
|
||
function formatPlanDateChinese(planDate?: string): string {
|
||
if (!planDate) return ''
|
||
const date = new Date(planDate)
|
||
if (Number.isNaN(date.getTime())) return planDate
|
||
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
|
||
}
|
||
|
||
function formatQty(value?: number | string | null): string {
|
||
const num = parseFloat(String(value ?? ''))
|
||
if (Number.isNaN(num)) return ''
|
||
return num.toFixed(3)
|
||
}
|
||
|
||
function formatSubpackSlipProductGridCellContent(content: string): string {
|
||
const plain = content
|
||
.trim()
|
||
.replace(/<div class="subpack-slip-product-cell-inner"[^>]*>([\s\S]*?)<\/div>/gi, '$1')
|
||
.replace(/<div class="subpack-slip-pdf-grid-inner"[^>]*>([\s\S]*?)<\/div>/gi, '$1')
|
||
.replace(/<span class="subpack-slip-pdf-grid-text"[^>]*>([\s\S]*?)<\/span>/gi, '$1')
|
||
.replace(/<span class="subpack-slip-product-grid-align"[^>]*>([\s\S]*?)<\/span>/gi, '$1')
|
||
.replace(/^<p[^>]*>([\s\S]*?)<\/p>$/i, '$1')
|
||
.trim()
|
||
return plain || ' '
|
||
}
|
||
|
||
function formatDateIso(value?: string | Date | null): string {
|
||
const date = value instanceof Date ? value : value ? new Date(value) : new Date()
|
||
if (Number.isNaN(date.getTime())) return ''
|
||
const y = date.getFullYear()
|
||
const m = String(date.getMonth() + 1).padStart(2, '0')
|
||
const d = String(date.getDate()).padStart(2, '0')
|
||
return `${y}-${m}-${d}`
|
||
}
|
||
|
||
/** 分装单默认下单日期(当天 YYYY-MM-DD) */
|
||
export function formatSubpackSlipDateIso(value?: string | Date | null): string {
|
||
return formatDateIso(value)
|
||
}
|
||
|
||
function buildSubpackSlipCompleteCheckHtml(): string {
|
||
return `<label class="subpack-slip-check-item subpack-slip-complete-check-item" contenteditable="false"><input type="checkbox" class="subpack-slip-check-input" />完成</label>`
|
||
}
|
||
|
||
const SUBPACK_SLIP_PRODUCT_MIN_ROWS = 5
|
||
|
||
function buildSubpackSlipProductRow(
|
||
rowIndex: number,
|
||
product: PlanProduct | undefined,
|
||
ctx?: SubpackSlipRenderContext
|
||
): string {
|
||
if (product) {
|
||
const qty = product.outboundQty ?? product.outboundEstimateQty ?? product.quantity ?? ''
|
||
return `<tr>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(String(rowIndex))}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(product.productName || '')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(product.model || '')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(product.packageSpec || '')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(resolveSlipUnitLabel(product.unit, ctx))}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(formatQty(qty))}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell subpack-slip-complete-cell" style="${PRODUCT_GRID_CELL}">${buildSubpackSlipCompleteCheckHtml()}</td>
|
||
</tr>`
|
||
}
|
||
|
||
return `<tr>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent(String(rowIndex))}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent('')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent('')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent('')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent('')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatSubpackSlipProductGridCellContent('')}</td>
|
||
<td class="subpack-slip-product-cell subpack-slip-product-grid-cell subpack-slip-complete-cell" style="${PRODUCT_GRID_CELL}"> </td>
|
||
</tr>`
|
||
}
|
||
|
||
function buildProductRows(products: PlanProduct[] = [], ctx?: SubpackSlipRenderContext): string {
|
||
const rowCount = Math.max(products.length, SUBPACK_SLIP_PRODUCT_MIN_ROWS)
|
||
const rows: string[] = []
|
||
|
||
for (let index = 0; index < rowCount; index += 1) {
|
||
rows.push(buildSubpackSlipProductRow(index + 1, products[index], ctx))
|
||
}
|
||
|
||
return rows.join('')
|
||
}
|
||
|
||
export const SUBPACK_SLIP_PRINT_CSS = `
|
||
.subpack-slip-doc {
|
||
padding: ${DOC_PADDING} !important;
|
||
max-width: ${SUBPACK_SLIP_DOC_MAX_WIDTH} !important;
|
||
margin-left: auto !important;
|
||
margin-right: auto !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-title-heading {
|
||
font-weight: 700 !important;
|
||
line-height: 1.35 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-title-heading + .subpack-slip-title-heading {
|
||
margin-top: 2px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-info-row {
|
||
display: flex !important;
|
||
align-items: flex-end !important;
|
||
width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-slip-no-display {
|
||
font-size: 20px !important;
|
||
line-height: 1.35 !important;
|
||
letter-spacing: 0.5px !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-slip-no-anchor {
|
||
position: relative !important;
|
||
flex-shrink: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-slip-no-anchor-text {
|
||
visibility: hidden !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-slip-no-anchor .subpack-slip-slip-no-display {
|
||
position: absolute !important;
|
||
right: 0 !important;
|
||
top: 0 !important;
|
||
text-align: right !important;
|
||
margin-top: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-slip-no-field .subpack-slip-info-value-left {
|
||
visibility: hidden !important;
|
||
border-bottom: none !important;
|
||
min-height: 0 !important;
|
||
padding-bottom: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-basic-info-spacer {
|
||
visibility: hidden !important;
|
||
min-height: 0 !important;
|
||
height: 0 !important;
|
||
overflow: hidden !important;
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-basic-info-inner {
|
||
display: flex !important;
|
||
flex-wrap: wrap !important;
|
||
justify-content: space-between !important;
|
||
align-items: flex-end !important;
|
||
gap: 8px 12px !important;
|
||
width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-basic-info-item {
|
||
flex: 1 !important;
|
||
min-width: 0 !important;
|
||
}
|
||
/* 下单/交货日期略宽;下单人与经手人等宽(单号行 spacer 同步,保持右缘对齐) */
|
||
.subpack-slip-doc .subpack-slip-basic-info-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(1),
|
||
.subpack-slip-doc .subpack-slip-basic-info-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(2),
|
||
.subpack-slip-doc .subpack-slip-slip-no-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(1),
|
||
.subpack-slip-doc .subpack-slip-slip-no-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(2) {
|
||
flex: 1.1 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-basic-info-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(3),
|
||
.subpack-slip-doc .subpack-slip-basic-info-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(4),
|
||
.subpack-slip-doc .subpack-slip-slip-no-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(3),
|
||
.subpack-slip-doc .subpack-slip-slip-no-row .subpack-slip-basic-info-inner > .subpack-slip-basic-info-item:nth-child(4) {
|
||
flex: 1 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-remark-row-inner {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
gap: 12px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-remark-label {
|
||
flex-shrink: 0 !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
/* 备注正文 + 补充输入共用一条连续下划线(底部 padding 把线推离字模,避免 PDF/html2canvas 遮字) */
|
||
.subpack-slip-doc .subpack-slip-remark-underline-group {
|
||
display: inline-flex !important;
|
||
align-items: baseline !important;
|
||
flex: 1 !important;
|
||
min-width: 200px !important;
|
||
max-width: 420px !important;
|
||
box-sizing: border-box !important;
|
||
border-bottom: ${INFO_UNDERLINE} !important;
|
||
padding: ${SIGN_VALUE_PADDING} !important;
|
||
gap: 0 !important;
|
||
line-height: 1.35 !important;
|
||
overflow: visible !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-remark-value {
|
||
display: inline-block !important;
|
||
flex: 0 1 auto !important;
|
||
min-width: 0 !important;
|
||
max-width: 260px !important;
|
||
box-sizing: border-box !important;
|
||
border-bottom: none !important;
|
||
padding: 0 2px !important;
|
||
min-height: 1.3em !important;
|
||
line-height: 1.35 !important;
|
||
overflow: visible !important;
|
||
white-space: nowrap !important;
|
||
vertical-align: baseline !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-remark-extra-wrap {
|
||
flex: 1 !important;
|
||
min-width: 80px !important;
|
||
display: inline-block !important;
|
||
vertical-align: bottom !important;
|
||
-webkit-user-modify: read-only !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-remark-extra-input {
|
||
width: 100% !important;
|
||
min-width: 80px !important;
|
||
height: 28px !important;
|
||
padding: 0 2px 0 !important;
|
||
margin: 0 !important;
|
||
border: none !important;
|
||
border-bottom: none !important;
|
||
border-radius: 0 !important;
|
||
background: transparent !important;
|
||
font-family: inherit !important;
|
||
font-size: 20px !important;
|
||
line-height: 1.35 !important;
|
||
box-sizing: border-box !important;
|
||
outline: none !important;
|
||
color: #000 !important;
|
||
pointer-events: auto !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-bucket-check-row {
|
||
flex: 1 !important;
|
||
min-width: 0 !important;
|
||
display: flex !important;
|
||
flex-wrap: wrap !important;
|
||
justify-content: center !important;
|
||
align-items: center !important;
|
||
gap: 14px 18px !important;
|
||
box-sizing: border-box !important;
|
||
font-size: 20px !important;
|
||
line-height: 1.35 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-complete-cell {
|
||
text-align: center !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-complete-check-item {
|
||
justify-content: center !important;
|
||
margin: 0 auto !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-item {
|
||
display: inline-flex !important;
|
||
align-items: center !important;
|
||
gap: 4px !important;
|
||
white-space: nowrap !important;
|
||
cursor: pointer !important;
|
||
user-select: none !important;
|
||
-webkit-user-modify: read-only !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-input {
|
||
width: 16px !important;
|
||
height: 16px !important;
|
||
margin: 0 !important;
|
||
flex-shrink: 0 !important;
|
||
vertical-align: middle !important;
|
||
cursor: pointer !important;
|
||
pointer-events: auto !important;
|
||
appearance: none !important;
|
||
-webkit-appearance: none !important;
|
||
background: #fff !important;
|
||
border: 1px solid #000 !important;
|
||
border-radius: 0 !important;
|
||
box-sizing: border-box !important;
|
||
print-color-adjust: exact !important;
|
||
-webkit-print-color-adjust: exact !important;
|
||
position: relative !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-input:checked::after {
|
||
content: '' !important;
|
||
position: absolute !important;
|
||
left: 4px !important;
|
||
top: 1px !important;
|
||
width: 5px !important;
|
||
height: 10px !important;
|
||
border: solid #000 !important;
|
||
border-width: 0 2.5px 2.5px 0 !important;
|
||
transform: rotate(45deg) !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-item-pdf {
|
||
display: inline-block !important;
|
||
white-space: nowrap !important;
|
||
font-family: SimSun, 'Songti SC', serif !important;
|
||
font-size: 20px !important;
|
||
line-height: 20px !important;
|
||
height: 20px !important;
|
||
vertical-align: middle !important;
|
||
margin-right: 18px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-item-pdf:last-child {
|
||
margin-right: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-img-pdf {
|
||
display: inline-block !important;
|
||
width: 16px !important;
|
||
height: 16px !important;
|
||
margin: 0 4px 0 0 !important;
|
||
padding: 0 !important;
|
||
border: none !important;
|
||
vertical-align: middle !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-text-pdf {
|
||
display: inline-block !important;
|
||
font-size: 20px !important;
|
||
line-height: 20px !important;
|
||
vertical-align: middle !important;
|
||
position: relative !important;
|
||
top: -12px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-check-item-pdf.subpack-slip-complete-check-item-pdf {
|
||
margin-right: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-info-label {
|
||
white-space: nowrap !important;
|
||
flex-shrink: 0 !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-layout-table tr.subpack-slip-gap-before-product td.subpack-slip-gap-before-product-cell,
|
||
.subpack-slip-doc .subpack-slip-section-gap.subpack-slip-gap-before-product {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
padding: 0 !important;
|
||
line-height: 0 !important;
|
||
font-size: 0 !important;
|
||
height: ${INFO_GAP_BEFORE_PRODUCT}px !important;
|
||
min-height: ${INFO_GAP_BEFORE_PRODUCT}px !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-layout-table tr.subpack-slip-gap-before-sign td.subpack-slip-gap-before-sign-cell,
|
||
.subpack-slip-doc .subpack-slip-section-gap.subpack-slip-gap-before-sign {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
padding: 0 !important;
|
||
line-height: 0 !important;
|
||
font-size: 0 !important;
|
||
height: ${INFO_GAP_BEFORE_SIGN}px !important;
|
||
min-height: ${INFO_GAP_BEFORE_SIGN}px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-info-value-left {
|
||
flex: 1 !important;
|
||
min-width: 0 !important;
|
||
box-sizing: border-box !important;
|
||
text-align: left !important;
|
||
border-bottom: ${INFO_UNDERLINE} !important;
|
||
min-height: 1.3em !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-info-value-center {
|
||
flex: 1 !important;
|
||
min-width: 0 !important;
|
||
box-sizing: border-box !important;
|
||
text-align: center !important;
|
||
border-bottom: ${INFO_UNDERLINE} !important;
|
||
min-height: 1.3em !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-value {
|
||
display: inline-block !important;
|
||
min-width: ${SUBPACK_SLIP_SIGN_VALUE_MIN_WIDTH} !important;
|
||
line-height: 1.35 !important;
|
||
padding: ${SIGN_VALUE_PADDING} !important;
|
||
margin: 0 !important;
|
||
vertical-align: baseline !important;
|
||
border: none !important;
|
||
border-bottom: ${SIGN_UNDERLINE} !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-label {
|
||
line-height: 1.35 !important;
|
||
vertical-align: baseline !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-outer-table {
|
||
table-layout: fixed !important;
|
||
width: 100% !important;
|
||
border-collapse: collapse !important;
|
||
}
|
||
.subpack-slip-doc td.subpack-slip-main-col {
|
||
overflow: hidden !important;
|
||
box-sizing: border-box !important;
|
||
vertical-align: top !important;
|
||
}
|
||
.subpack-slip-doc td.subpack-slip-side-labels-col {
|
||
width: ${SIDE_LABELS_COL_WIDTH}px !important;
|
||
min-width: ${SIDE_LABELS_COL_WIDTH}px !important;
|
||
max-width: ${SIDE_LABELS_COL_WIDTH}px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-row-cell,
|
||
.subpack-slip-doc .subpack-slip-sign-inner,
|
||
.subpack-slip-doc .subpack-slip-sign-item {
|
||
font-size: 14px !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-row-cell {
|
||
overflow: hidden !important;
|
||
max-width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-inner {
|
||
display: flex !important;
|
||
flex-wrap: wrap !important;
|
||
justify-content: center !important;
|
||
align-items: baseline !important;
|
||
column-gap: 10px !important;
|
||
row-gap: 6px !important;
|
||
max-width: 100% !important;
|
||
box-sizing: border-box !important;
|
||
margin: 0 auto !important;
|
||
white-space: normal !important;
|
||
line-height: 1.35 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-sign-item {
|
||
display: inline !important;
|
||
white-space: nowrap !important;
|
||
line-height: 1.35 !important;
|
||
vertical-align: baseline !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-terms-text {
|
||
line-height: 1.35 !important;
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
.subpack-slip-doc td.subpack-slip-terms-cell {
|
||
padding: 10px 6px 14px 6px !important;
|
||
line-height: 1.35 !important;
|
||
vertical-align: top !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-handwrite-blank {
|
||
display: inline-block !important;
|
||
min-width: 3.6em !important;
|
||
text-align: center !important;
|
||
letter-spacing: 0.08em !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-seal-block {
|
||
position: relative !important;
|
||
line-height: 1.35 !important;
|
||
min-height: 0 !important;
|
||
height: auto !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-seal-block img[alt="印章"] {
|
||
position: absolute !important;
|
||
right: 4px !important;
|
||
top: ${SUBPACK_SLIP_SEAL_TOP}px !important;
|
||
left: auto !important;
|
||
transform: none !important;
|
||
width: 140px !important;
|
||
height: 140px !important;
|
||
opacity: 0.85 !important;
|
||
z-index: 10 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-seal-block .subpack-slip-seal-lines {
|
||
position: relative !important;
|
||
z-index: 1 !important;
|
||
line-height: 1.35 !important;
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-outer-table > tbody > tr > td,
|
||
.subpack-slip-doc table.subpack-slip-outer-table > tbody > tr > th,
|
||
.subpack-slip-doc table.subpack-slip-layout-table td:not(.subpack-slip-product-cell):not(.subpack-slip-gap-before-product-cell):not(.subpack-slip-gap-before-sign-cell),
|
||
.subpack-slip-doc table.subpack-slip-layout-table th:not(.subpack-slip-product-cell),
|
||
.subpack-slip-doc table.subpack-slip-info-table td,
|
||
.subpack-slip-doc table.subpack-slip-info-table th,
|
||
.subpack-slip-doc table.subpack-slip-sign-table td,
|
||
.subpack-slip-doc table.subpack-slip-sign-table th {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
padding: ${TABLE_ROW_PADDING} !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-layout-table,
|
||
.subpack-slip-doc table.subpack-slip-info-table,
|
||
.subpack-slip-doc table.subpack-slip-product-table,
|
||
.subpack-slip-doc table.subpack-slip-footer-table,
|
||
.subpack-slip-doc table.subpack-slip-sign-table {
|
||
border-collapse: collapse !important;
|
||
border-spacing: 0 !important;
|
||
table-layout: fixed !important;
|
||
width: 100% !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-product-grid-cell {
|
||
border: ${PRODUCT_GRID_BORDER} !important;
|
||
text-align: center !important;
|
||
vertical-align: middle !important;
|
||
/* padding/line-height 勿 !important:与合同一致,允许 tr 行高拖拽生效 */
|
||
padding: ${PRODUCT_GRID_CELL_PADDING};
|
||
box-sizing: border-box !important;
|
||
line-height: normal;
|
||
overflow: visible !important;
|
||
white-space: normal !important;
|
||
word-wrap: break-word !important;
|
||
overflow-wrap: break-word !important;
|
||
word-break: break-word !important;
|
||
background-color: #fff !important;
|
||
}
|
||
.subpack-slip-doc th.subpack-slip-product-grid-cell {
|
||
padding: ${PRODUCT_GRID_TH_PADDING};
|
||
font-weight: bold !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-product-grid-cell > p {
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
text-align: center !important;
|
||
line-height: 1.35;
|
||
}
|
||
.subpack-slip-doc .sx-excel-row-spacer {
|
||
display: block !important;
|
||
float: left !important;
|
||
width: 0 !important;
|
||
max-width: 0 !important;
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
border: 0 !important;
|
||
overflow: hidden !important;
|
||
visibility: hidden !important;
|
||
pointer-events: none !important;
|
||
line-height: 0 !important;
|
||
font-size: 0 !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-product-cell:not(.subpack-slip-product-grid-cell) {
|
||
border: ${PRODUCT_GRID_BORDER} !important;
|
||
background-color: #fff !important;
|
||
padding: 10px 6px 14px 6px !important;
|
||
line-height: 1.35 !important;
|
||
vertical-align: top !important;
|
||
white-space: normal !important;
|
||
word-wrap: break-word !important;
|
||
overflow-wrap: break-word !important;
|
||
word-break: break-word !important;
|
||
}
|
||
.subpack-slip-doc .subpack-slip-seal-block,
|
||
.subpack-slip-doc .subpack-slip-seal-lines,
|
||
.subpack-slip-doc .subpack-slip-terms-text {
|
||
margin: 0 !important;
|
||
padding: 0 !important;
|
||
line-height: 1.35 !important;
|
||
}
|
||
.subpack-slip-doc table.subpack-slip-side-labels {
|
||
border: none !important;
|
||
border-collapse: collapse !important;
|
||
width: auto !important;
|
||
margin: 0 auto !important;
|
||
}
|
||
.subpack-slip-doc td.subpack-slip-side-labels-col,
|
||
.subpack-slip-doc table.subpack-slip-side-labels td,
|
||
.subpack-slip-doc table.subpack-slip-side-labels th {
|
||
border: none !important;
|
||
background: transparent !important;
|
||
padding: 0 !important;
|
||
vertical-align: top !important;
|
||
text-align: center !important;
|
||
font-size: 18px !important;
|
||
line-height: 1.35 !important;
|
||
letter-spacing: 0 !important;
|
||
white-space: normal !important;
|
||
}
|
||
`
|
||
|
||
export const SUBPACK_SLIP_STYLE_BLOCK = `<style type="text/css">${SUBPACK_SLIP_PRINT_CSS}</style>`
|
||
|
||
/** 生成公司印章 HTML(地址/电话单元格右上角) */
|
||
export function buildSubpackSlipCompanySealHtml(): string {
|
||
return `<img src="${SUBPACK_SLIP_SEAL_IMAGE_SRC}" alt="印章" style="${SUBPACK_SLIP_SEAL_IMAGE_STYLE}" />`
|
||
}
|
||
|
||
const SUBPACK_SLIP_BUCKET_CHECK_INNER_STYLE =
|
||
'flex:1;min-width:0;display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:14px 18px;box-sizing:border-box;font-size:20px;line-height:1.35;'
|
||
|
||
function buildSubpackSlipProductHeaderRowHtml(): string {
|
||
return `<tr class="subpack-slip-product-header">
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">序号</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">产品名称</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">产品型号</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">包装规格</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">单位</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">数量</th>
|
||
<th class="subpack-slip-product-cell subpack-slip-product-grid-cell" style="${PRODUCT_TH}">是否完成</th>
|
||
</tr>`
|
||
}
|
||
|
||
function buildSubpackSlipBucketCheckItem(label: string): string {
|
||
return `<label class="subpack-slip-check-item subpack-slip-bucket-check-item" contenteditable="false"><input type="checkbox" class="subpack-slip-check-input" />${label}</label>`
|
||
}
|
||
|
||
function buildSubpackSlipBucketCheckboxesHtml(): string {
|
||
return `${buildSubpackSlipBucketCheckItem('全新桶')}${buildSubpackSlipBucketCheckItem('是否回收桶')}${buildSubpackSlipBucketCheckItem('桶循环')}`
|
||
}
|
||
|
||
function buildSubpackSlipSlipNoDisplayHtml(): string {
|
||
return `<span class="subpack-slip-slip-no-display">{{subpackSlipNo}} </span>`
|
||
}
|
||
|
||
/** 基本信息列宽:日期略宽;下单人/经手人等宽(与 content_style nth-child 一致) */
|
||
const BASIC_INFO_FLEX_DATE = '1.1'
|
||
const BASIC_INFO_FLEX_PERSON = '1'
|
||
|
||
function buildSubpackSlipSlipNoRowHtml(): string {
|
||
const spacer = (flex: string) =>
|
||
`<div class="subpack-slip-basic-info-item subpack-slip-basic-info-spacer" style="flex:${flex};min-width:0;"> </div>`
|
||
return `<tr class="subpack-slip-slip-no-row">
|
||
<td colspan="7" style="${INFO_CELL_LEFT}">
|
||
<div class="subpack-slip-basic-info-inner" style="display:flex;flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:8px 12px;width:100%;box-sizing:border-box;">
|
||
${spacer(BASIC_INFO_FLEX_DATE)}${spacer(BASIC_INFO_FLEX_DATE)}${spacer(BASIC_INFO_FLEX_PERSON)}
|
||
<div class="subpack-slip-info-row subpack-slip-basic-info-item subpack-slip-slip-no-field" style="${INFO_ROW}flex:${BASIC_INFO_FLEX_PERSON};min-width:0;">
|
||
<span class="subpack-slip-info-label subpack-slip-slip-no-anchor" style="${INFO_LABEL}">
|
||
<span class="subpack-slip-slip-no-anchor-text">经手人:</span>
|
||
${buildSubpackSlipSlipNoDisplayHtml()}
|
||
</span>
|
||
<span class="subpack-slip-info-value-left" style="${INFO_VALUE_INLINE_LEFT}"> </span>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>`
|
||
}
|
||
|
||
function buildSubpackSlipBasicInfoRowHtml(): string {
|
||
const field = (label: string, placeholder: string, flex: string = BASIC_INFO_FLEX_PERSON) =>
|
||
`<div class="subpack-slip-info-row subpack-slip-basic-info-item" style="${INFO_ROW}flex:${flex};min-width:0;">
|
||
<span class="subpack-slip-info-label" style="${INFO_LABEL}">${label}</span>
|
||
<span class="subpack-slip-info-value-left" style="${INFO_VALUE_INLINE_LEFT}">${placeholder} </span>
|
||
</div>`
|
||
return `<tr class="subpack-slip-basic-info-row">
|
||
<td colspan="7" style="${INFO_CELL_LEFT}">
|
||
<div class="subpack-slip-basic-info-inner" style="display:flex;flex-wrap:wrap;justify-content:space-between;align-items:flex-end;gap:8px 12px;width:100%;box-sizing:border-box;">
|
||
${field('下单日期:', '{{orderDate}}', BASIC_INFO_FLEX_DATE)}
|
||
${field('交货日期:', '{{deliveryDate}}', BASIC_INFO_FLEX_DATE)}
|
||
${field('下单人:', '{{orderUser}}', BASIC_INFO_FLEX_PERSON)}
|
||
${field('经手人:', '{{handlerName}}', BASIC_INFO_FLEX_PERSON)}
|
||
</div>
|
||
</td>
|
||
</tr>`
|
||
}
|
||
|
||
function buildSubpackSlipRemarkExtraInputHtml(): string {
|
||
return `<span class="subpack-slip-remark-extra-wrap" contenteditable="false"><input type="text" class="subpack-slip-remark-extra-input" value="{{remarkExtra}}" /></span>`
|
||
}
|
||
|
||
function buildSubpackSlipRemarkUnderlineGroupHtml(remarkContent: string = '{{remark}}'): string {
|
||
return `<span class="subpack-slip-remark-underline-group"><span class="subpack-slip-remark-value">${remarkContent} </span>${buildSubpackSlipRemarkExtraInputHtml()}</span>`
|
||
}
|
||
|
||
function buildSubpackSlipRemarkRowHtml(): string {
|
||
return `<div class="subpack-slip-remark-row-inner">
|
||
<span class="subpack-slip-remark-label">备注:</span>
|
||
${buildSubpackSlipRemarkUnderlineGroupHtml()}
|
||
<div class="subpack-slip-bucket-check-row" contenteditable="false" style="${SUBPACK_SLIP_BUCKET_CHECK_INNER_STYLE}">${buildSubpackSlipBucketCheckboxesHtml()}</div>
|
||
</div>`
|
||
}
|
||
|
||
/** 修复旧模板中 label / 桶选项容器缺少 contenteditable="false" 导致 TinyMCE 内多选失效 */
|
||
function ensureSubpackSlipInteractiveMarkup(html: string): string {
|
||
let result = html.replace(/<label\b([^>]*\bsubpack-slip-check-item\b[^>]*)>/gi, (match, attrs: string) => {
|
||
if (/contenteditable=/i.test(attrs)) return match
|
||
return `<label${attrs} contenteditable="false">`
|
||
})
|
||
result = result.replace(/<div class="subpack-slip-bucket-check-row"([^>]*)>/g, (match, attrs: string) => {
|
||
if (/contenteditable=/i.test(attrs)) return match
|
||
return `<div class="subpack-slip-bucket-check-row"${attrs} contenteditable="false">`
|
||
})
|
||
return result
|
||
}
|
||
|
||
/** @deprecated 分装单已移除右侧联次,保留空实现兼容旧引用 */
|
||
export function buildSubpackSlipSideLabelsHtml(): string {
|
||
return ''
|
||
}
|
||
|
||
/** 生成带占位符的分装单 HTML 模板(方案A:信息 / 物料 / 页脚+签字 分表) */
|
||
export function generateSubpackSlipTemplate(_plan?: Plan): string {
|
||
return `${SUBPACK_SLIP_STYLE_BLOCK}<div class="subpack-slip-doc" style="font-family:${DOC_FONT};${FONT_BODY}color:#000;line-height:1.5;background:#fff;padding:${DOC_PADDING};${DOC_WRAP_EXTRA}">
|
||
|
||
<!-- 上部分:标题区 -->
|
||
<div class="subpack-slip-title-section" style="position:relative;min-height:56px;margin-bottom:14px;">
|
||
<img src="/logo.gif" alt="logo" style="position:absolute;left:${LOGO_LEFT}px;top:50%;transform:translateY(-50%);height:${LOGO_HEIGHT}px;width:auto;" />
|
||
<div style="text-align:center;padding:4px 60px 0 100px;">
|
||
<div class="subpack-slip-title-heading" style="${TITLE_STYLE}">{{companyName}}</div>
|
||
<div class="subpack-slip-title-heading" style="${TITLE_SUB_STYLE}">分 装 单</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 1. 基本信息(独立表,不可拖宽高) -->
|
||
<table class="subpack-slip-info-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${INFO_COLGROUP}
|
||
|
||
<!-- 分装单号(右缘与经手人标签右缘对齐) -->
|
||
${buildSubpackSlipSlipNoRowHtml()}
|
||
|
||
<!-- 基本信息一行四列 -->
|
||
${buildSubpackSlipBasicInfoRowHtml()}
|
||
</table>
|
||
|
||
<div class="subpack-slip-section-gap subpack-slip-gap-before-product" style="${GAP_BEFORE_PRODUCT_CELL}"> </div>
|
||
|
||
<!-- 2. 物料明细(含备注+桶选项) -->
|
||
<table class="subpack-slip-product-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${PRODUCT_COLGROUP}
|
||
${buildSubpackSlipProductHeaderRowHtml()}
|
||
{{productTable}}
|
||
<tr>
|
||
<td colspan="7" class="subpack-slip-product-cell subpack-slip-remark-cell" style="${PRODUCT_TABLE_CELL_LEFT}">
|
||
${buildSubpackSlipRemarkRowHtml()}
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
<div class="subpack-slip-section-gap subpack-slip-gap-before-sign" style="${GAP_BEFORE_SIGN_CELL}"> </div>
|
||
|
||
<!-- 3. 签字区(独立表) -->
|
||
<table class="subpack-slip-sign-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${buildSubpackSlipSignRowHtml()}
|
||
</table>
|
||
</div>`
|
||
}
|
||
|
||
/** 移除印章、地址/电话行(分装单不再展示) */
|
||
function normalizeSubpackSlipRemoveAddressAndSeal(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
let result = html.replace(/<img[^>]*alt="印章"[^>]*>/gi, '')
|
||
result = result.replace(/\{\{companySeal\}\}/g, '')
|
||
result = result.replace(/<!--\s*印章图片\s*-->\s*/g, '')
|
||
result = result.replace(/<!--\s*地址\/电话[\s\S]*?-->\s*/g, '')
|
||
result = result.replace(/<!--\s*地址\/印章[\s\S]*?-->\s*/g, '')
|
||
result = result.replace(/<div class="subpack-slip-address-block"[\s\S]*?<\/div>/g, '')
|
||
result = result.replace(/<div class="subpack-slip-seal-block"[\s\S]*?<\/div>/g, '')
|
||
result = result.replace(
|
||
/<tr>\s*<td colspan="7"[^>]*>\s*<div class="subpack-slip-address-block"[\s\S]*?<\/tr>\s*/g,
|
||
''
|
||
)
|
||
result = result.replace(
|
||
/<tr>\s*<td colspan="7"[^>]*>\s*<div class="subpack-slip-seal-block"[\s\S]*?<\/tr>\s*/g,
|
||
''
|
||
)
|
||
result = result.replace(
|
||
/<tr>\s*<td colspan="4"[^>]*>\s* \s*<\/td>\s*<td colspan="3"[^>]*>\s*<div class="subpack-slip-seal-block"[\s\S]*?<\/tr>\s*/g,
|
||
''
|
||
)
|
||
result = result.replace(
|
||
/<tr>\s*<!--\s*地址[\s\S]*?<\/tr>\s*/g,
|
||
''
|
||
)
|
||
result = result.replace(
|
||
/<tr>\s*<td colspan="7"[^>]*>[\s\S]*?地址:[\s\S]*?联系人电话:[\s\S]*?<\/tr>\s*/g,
|
||
''
|
||
)
|
||
|
||
return result.replace(/min-height:76px;/g, 'min-height:0;')
|
||
}
|
||
|
||
/** @deprecated 请使用 normalizeSubpackSlipRemoveAddressAndSeal */
|
||
function normalizeSubpackSlipSealPlaceholder(html: string): string {
|
||
return normalizeSubpackSlipRemoveAddressAndSeal(html)
|
||
}
|
||
|
||
function applyProductGridBorderToStyle(style: string): string {
|
||
// 保留拖拽产生的 height / min-height / line-height,避免打开模板时被抹掉
|
||
const keptHeight = style.match(/height:\s*[^;]+;?/gi)?.join('') || ''
|
||
const keptMinHeight = style.match(/min-height:\s*[^;]+;?/gi)?.join('') || ''
|
||
const keptLineHeight = style.match(/line-height:\s*[^;]+;?/gi)?.join('') || ''
|
||
let next = style
|
||
.replace(/border:[^;]*;?/gi, '')
|
||
.replace(/border-right:[^;]*;?/gi, '')
|
||
.replace(/border-bottom:[^;]*;?/gi, '')
|
||
.replace(/border-left:[^;]*;?/gi, '')
|
||
.replace(/border-top:[^;]*;?/gi, '')
|
||
.replace(/height:\s*[^;]+;?/gi, '')
|
||
.replace(/min-height:\s*[^;]+;?/gi, '')
|
||
.replace(/line-height:\s*[^;]+;?/gi, '')
|
||
.replace(/text-align:left;/g, 'text-align:center;')
|
||
.replace(/vertical-align:top;/g, '')
|
||
.trim()
|
||
const lineHeight = keptLineHeight || 'line-height:normal;'
|
||
return `border:${PRODUCT_GRID_BORDER} !important;text-align:center;padding:${PRODUCT_GRID_CELL_PADDING};box-sizing:border-box;${lineHeight}${keptHeight}${keptMinHeight}${next}`
|
||
}
|
||
|
||
function patchProductFooterCellAttrs(attrs: string, classes: string, style: string): string {
|
||
let next = attrs
|
||
if (next.includes('class="')) {
|
||
next = next.replace(/class="([^"]*)"/, (_, cls: string) => {
|
||
let updated = cls.trim()
|
||
for (const item of classes.split(/\s+/)) {
|
||
if (item && !updated.includes(item)) updated += ` ${item}`
|
||
}
|
||
return `class="${updated.trim()}"`
|
||
})
|
||
} else {
|
||
next = `${next} class="${classes.trim()}"`
|
||
}
|
||
if (next.includes('style="')) {
|
||
next = next.replace(/style="[^"]*"/, `style="${style}"`)
|
||
} else {
|
||
next = `${next} style="${style}"`
|
||
}
|
||
return next
|
||
}
|
||
|
||
/** 物料明细网格:全边框 inline + 原生居中(兼容已保存模板,对齐合同 PDF) */
|
||
function patchSubpackSlipProductGridCellAttrs(attrs: string): string {
|
||
let next = attrs.includes('class="')
|
||
? attrs.replace(/class="([^"]*)"/, (_, cls: string) => {
|
||
let updated = cls
|
||
if (!updated.includes('subpack-slip-product-cell')) updated += ' subpack-slip-product-cell'
|
||
if (!updated.includes('subpack-slip-product-grid-cell')) updated += ' subpack-slip-product-grid-cell'
|
||
return `class="${updated.trim()}"`
|
||
})
|
||
: `${attrs} class="subpack-slip-product-cell subpack-slip-product-grid-cell"`
|
||
if (next.includes('style="')) {
|
||
next = next.replace(/style="([^"]*)"/, (_, style: string) => `style="${applyProductGridBorderToStyle(style)}"`)
|
||
} else {
|
||
next = `${next} style="${PRODUCT_GRID_CELL}"`
|
||
}
|
||
return next
|
||
}
|
||
|
||
function normalizeSubpackSlipProductGridCell(html: string): string {
|
||
if (!html.includes('subpack-slip-doc') || !html.includes('subpack-slip-product-cell')) return html
|
||
|
||
let result = html.replace(/<tr([^>]*)>\s*(<th[\s\S]*?<\/th>\s*){7}\s*<\/tr>/g, (rowMatch) => {
|
||
if (
|
||
(!rowMatch.includes('物料编号') && !rowMatch.includes('序号')) ||
|
||
rowMatch.includes('colspan')
|
||
) {
|
||
return rowMatch
|
||
}
|
||
let row = rowMatch.includes('subpack-slip-product-header')
|
||
? rowMatch
|
||
: rowMatch.replace('<tr', '<tr class="subpack-slip-product-header"')
|
||
return row.replace(/<(th)([^>]*)>([\s\S]*?)<\/th>/g, (_m, tag, attrs, content) => {
|
||
return `<${tag}${patchSubpackSlipProductGridCellAttrs(attrs)}>${formatSubpackSlipProductGridCellContent(content)}</${tag}>`
|
||
})
|
||
})
|
||
|
||
result = result.replace(/<tr([^>]*)>\s*(<td(?![^>]*colspan)[^>]*>[\s\S]*?<\/td>\s*){7}\s*<\/tr>/g, (rowMatch) => {
|
||
if (
|
||
rowMatch.includes('备注') ||
|
||
rowMatch.includes('1.本送货单') ||
|
||
rowMatch.includes('制单人:') ||
|
||
rowMatch.includes('仓库:') ||
|
||
rowMatch.includes('subpack-slip-complete-check-item') ||
|
||
rowMatch.includes('暂无产品信息')
|
||
) {
|
||
return rowMatch
|
||
}
|
||
return rowMatch.replace(/<(td)([^>]*)>([\s\S]*?)<\/td>/g, (_m, tag, attrs, content) => {
|
||
if (attrs.includes('colspan')) return `<${tag}${attrs}>${content}</td>`
|
||
return `<${tag}${patchSubpackSlipProductGridCellAttrs(attrs)}>${formatSubpackSlipProductGridCellContent(content)}</${tag}>`
|
||
})
|
||
})
|
||
|
||
return normalizeSubpackSlipGridCellWrappers(result)
|
||
}
|
||
|
||
/** 去掉 TinyMCE / 历史包裹层,单元格内保留纯文本(对齐合同 PDF) */
|
||
function normalizeSubpackSlipGridCellWrappers(html: string): string {
|
||
if (!html.includes('subpack-slip-product-grid-cell')) return html
|
||
|
||
const unwrap = (tag: string, attrs: string, content: string) =>
|
||
`<${tag}${attrs}>${formatSubpackSlipProductGridCellContent(content)}</${tag}>`
|
||
|
||
let result = html.replace(
|
||
/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)>\s*<span class="subpack-slip-product-grid-align"[^>]*>([\s\S]*?)<\/span>\s*<\/\1>/gi,
|
||
(_m, tag, attrs, content) => unwrap(tag, attrs, content)
|
||
)
|
||
|
||
result = result.replace(
|
||
/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)>\s*<span class="subpack-slip-pdf-grid-text"[^>]*>([\s\S]*?)<\/span>\s*<\/\1>/gi,
|
||
(_m, tag, attrs, content) => unwrap(tag, attrs, content)
|
||
)
|
||
|
||
result = result.replace(
|
||
/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)>\s*<div class="subpack-slip-pdf-grid-inner"[^>]*>([\s\S]*?)<\/div>\s*<\/\1>/gi,
|
||
(_m, tag, attrs, content) => unwrap(tag, attrs, content)
|
||
)
|
||
|
||
result = result.replace(
|
||
/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)>\s*<p[^>]*>([\s\S]*?)<\/p>\s*<\/\1>/gi,
|
||
(_m, tag, attrs, content) => unwrap(tag, attrs, content)
|
||
)
|
||
|
||
result = result.replace(
|
||
/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)>\s*<div(?![^>]*class="subpack-slip)[^>]*>([\s\S]*?)<\/div>\s*<\/\1>/gi,
|
||
(_m, tag, attrs, content) => unwrap(tag, attrs, content)
|
||
)
|
||
|
||
result = result.replace(/<(td|th)([^>]*subpack-slip-product-grid-cell[^>]*)\s+height="[^"]*"/gi, '<$1$2')
|
||
result = result.replace(/<(tr)([^>]*class="[^"]*subpack-slip-product[^"]*"[^>]*)\s+height="[^"]*"/gi, '<$1$2')
|
||
|
||
return result
|
||
}
|
||
|
||
function extractSubpackSlipGridCellContent(cell: HTMLTableCellElement): string {
|
||
let content = cell.innerHTML.trim()
|
||
const onlyChild = cell.firstElementChild
|
||
if (onlyChild && cell.childElementCount === 1) {
|
||
const tag = onlyChild.tagName
|
||
if (
|
||
tag === 'P' ||
|
||
tag === 'DIV' ||
|
||
tag === 'SPAN' ||
|
||
onlyChild.classList.contains('subpack-slip-pdf-grid-inner') ||
|
||
onlyChild.classList.contains('subpack-slip-pdf-grid-text')
|
||
) {
|
||
content = onlyChild.innerHTML.trim()
|
||
}
|
||
}
|
||
return formatSubpackSlipProductGridCellContent(content)
|
||
}
|
||
|
||
function applySubpackSlipPdfGridCellStyles(cell: HTMLTableCellElement, isHeader: boolean): void {
|
||
const content = extractSubpackSlipGridCellContent(cell)
|
||
cell.innerHTML = content || ' '
|
||
cell.removeAttribute('height')
|
||
cell.style.removeProperty('height')
|
||
cell.style.removeProperty('min-height')
|
||
cell.style.setProperty('padding', isHeader ? PRODUCT_GRID_PDF_TH_PADDING : PRODUCT_GRID_PDF_TD_PADDING, 'important')
|
||
cell.style.setProperty('text-align', 'center', 'important')
|
||
cell.style.setProperty('font-size', '20px', 'important')
|
||
cell.style.setProperty('line-height', PRODUCT_GRID_PDF_LINE_HEIGHT, 'important')
|
||
cell.style.setProperty('box-sizing', 'border-box', 'important')
|
||
cell.style.setProperty('overflow', 'visible', 'important')
|
||
cell.style.setProperty('vertical-align', 'middle', 'important')
|
||
if (isHeader) {
|
||
cell.style.setProperty('font-weight', 'bold', 'important')
|
||
}
|
||
}
|
||
|
||
function applySubpackSlipPdfFooterCellStyles(cell: HTMLTableCellElement): void {
|
||
cell.removeAttribute('height')
|
||
cell.style.removeProperty('height')
|
||
cell.style.removeProperty('min-height')
|
||
cell.style.setProperty('padding', PRODUCT_FOOTER_PDF_PADDING, 'important')
|
||
cell.style.setProperty('font-size', '20px', 'important')
|
||
cell.style.setProperty('line-height', PRODUCT_FOOTER_PDF_LINE_HEIGHT, 'important')
|
||
cell.style.setProperty('box-sizing', 'border-box', 'important')
|
||
cell.style.setProperty('overflow', 'visible', 'important')
|
||
cell.style.setProperty('vertical-align', 'top', 'important')
|
||
cell.style.setProperty('text-align', 'left', 'important')
|
||
}
|
||
|
||
function unwrapSubpackSlipPdfCellParagraph(cell: HTMLTableCellElement): void {
|
||
const onlyChild = cell.firstElementChild
|
||
if (onlyChild?.tagName === 'P' && cell.childElementCount === 1) {
|
||
cell.innerHTML = onlyChild.innerHTML.trim() || ' '
|
||
}
|
||
}
|
||
|
||
function prepareSubpackSlipPdfRemarkCell(cell: HTMLTableCellElement): void {
|
||
unwrapSubpackSlipPdfCellParagraph(cell)
|
||
applySubpackSlipPdfFooterCellStyles(cell)
|
||
cell.querySelectorAll('.subpack-slip-remark-underline-group').forEach((node) => {
|
||
const group = node as HTMLElement
|
||
group.style.setProperty('display', 'inline-flex', 'important')
|
||
group.style.setProperty('align-items', 'baseline', 'important')
|
||
group.style.setProperty('flex', '1', 'important')
|
||
group.style.setProperty('min-width', '200px', 'important')
|
||
group.style.setProperty('max-width', '420px', 'important')
|
||
group.style.setProperty('border', 'none', 'important')
|
||
group.style.setProperty('border-bottom', INFO_UNDERLINE, 'important')
|
||
// 与签字区一致:加大底部 padding,避免 html2canvas 把下划线叠在文字上
|
||
group.style.setProperty('padding', SIGN_VALUE_PDF_PADDING, 'important')
|
||
group.style.setProperty('gap', '0', 'important')
|
||
group.style.setProperty('line-height', '1.35', 'important')
|
||
group.style.setProperty('overflow', 'visible', 'important')
|
||
group.style.setProperty('box-sizing', 'border-box', 'important')
|
||
})
|
||
cell.querySelectorAll('.subpack-slip-remark-value').forEach((node) => {
|
||
const el = node as HTMLElement
|
||
el.style.setProperty('display', 'inline-block', 'important')
|
||
el.style.setProperty('border-bottom', 'none', 'important')
|
||
el.style.setProperty('padding', '0 2px', 'important')
|
||
el.style.setProperty('line-height', '1.35', 'important')
|
||
el.style.setProperty('overflow', 'visible', 'important')
|
||
el.style.setProperty('vertical-align', 'baseline', 'important')
|
||
})
|
||
cell.querySelectorAll('input.subpack-slip-remark-extra-input').forEach((node) => {
|
||
const input = node as HTMLInputElement
|
||
const text = (input.value || '').trim()
|
||
const span = document.createElement('span')
|
||
span.className = 'subpack-slip-remark-extra-text-pdf'
|
||
span.textContent = text || '\u00a0'
|
||
span.style.setProperty('display', 'inline-block', 'important')
|
||
span.style.setProperty('min-width', '80px', 'important')
|
||
span.style.setProperty('flex', '1', 'important')
|
||
span.style.setProperty('padding', '0 2px', 'important')
|
||
span.style.setProperty('border-bottom', 'none', 'important')
|
||
span.style.setProperty('font-size', '20px', 'important')
|
||
span.style.setProperty('line-height', '1.35', 'important')
|
||
span.style.setProperty('vertical-align', 'baseline', 'important')
|
||
input.replaceWith(span)
|
||
})
|
||
}
|
||
|
||
function prepareSubpackSlipPdfTermsCell(cell: HTMLTableCellElement): void {
|
||
applySubpackSlipPdfFooterCellStyles(cell)
|
||
const terms = cell.querySelector('.subpack-slip-terms-text') as HTMLElement | null
|
||
if (terms) {
|
||
terms.style.setProperty('margin', '0', 'important')
|
||
terms.style.setProperty('padding', '0', 'important')
|
||
terms.style.setProperty('line-height', PRODUCT_FOOTER_PDF_LINE_HEIGHT, 'important')
|
||
}
|
||
}
|
||
|
||
function prepareSubpackSlipPdfSealCell(cell: HTMLTableCellElement): void {
|
||
applySubpackSlipPdfFooterCellStyles(cell)
|
||
const block =
|
||
(cell.querySelector('.subpack-slip-address-block') as HTMLElement | null) ||
|
||
(cell.querySelector('.subpack-slip-seal-block') as HTMLElement | null)
|
||
const lines = cell.querySelector('.subpack-slip-seal-lines') as HTMLElement | null
|
||
if (block) {
|
||
block.style.setProperty('padding', '0', 'important')
|
||
block.style.setProperty('margin', '0', 'important')
|
||
block.style.setProperty('position', 'relative', 'important')
|
||
block.style.setProperty('line-height', PRODUCT_FOOTER_PDF_LINE_HEIGHT, 'important')
|
||
}
|
||
if (lines) {
|
||
lines.style.setProperty('margin', '0', 'important')
|
||
lines.style.setProperty('padding', '0', 'important')
|
||
lines.style.setProperty('line-height', PRODUCT_FOOTER_PDF_LINE_HEIGHT, 'important')
|
||
}
|
||
}
|
||
|
||
function prepareSubpackSlipPdfSignValues(root: HTMLElement): void {
|
||
root.querySelectorAll('.subpack-slip-sign-value').forEach((node) => {
|
||
const el = node as HTMLElement
|
||
el.style.setProperty('display', 'inline-block', 'important')
|
||
el.style.setProperty('padding', SIGN_VALUE_PDF_PADDING, 'important')
|
||
el.style.setProperty('line-height', '1.35', 'important')
|
||
el.style.setProperty('vertical-align', 'baseline', 'important')
|
||
el.style.setProperty('border', 'none', 'important')
|
||
el.style.setProperty('border-bottom', SIGN_UNDERLINE, 'important')
|
||
el.style.setProperty('box-sizing', 'border-box', 'important')
|
||
el.style.setProperty('min-width', SUBPACK_SLIP_SIGN_VALUE_MIN_WIDTH, 'important')
|
||
})
|
||
}
|
||
|
||
/** PDF 截图前:将原生 checkbox 替换为 canvas 方框图 + 文字(html2canvas 对 flex/SVG 对齐不稳定) */
|
||
const subpackSlipPdfCheckboxImgCache: { empty?: string; checked?: string } = {}
|
||
|
||
function buildSubpackSlipPdfCheckboxImgDataUrl(checked: boolean): string {
|
||
const key = checked ? 'checked' : 'empty'
|
||
if (subpackSlipPdfCheckboxImgCache[key]) return subpackSlipPdfCheckboxImgCache[key]!
|
||
|
||
const size = 32
|
||
const canvas = document.createElement('canvas')
|
||
canvas.width = size
|
||
canvas.height = size
|
||
const ctx = canvas.getContext('2d')
|
||
if (!ctx) return ''
|
||
|
||
ctx.fillStyle = '#ffffff'
|
||
ctx.fillRect(0, 0, size, size)
|
||
ctx.strokeStyle = '#000000'
|
||
ctx.lineWidth = 2
|
||
ctx.strokeRect(1, 1, size - 2, size - 2)
|
||
if (checked) {
|
||
ctx.beginPath()
|
||
ctx.moveTo(8, 17)
|
||
ctx.lineTo(14, 23)
|
||
ctx.lineTo(24, 8)
|
||
ctx.strokeStyle = '#000000'
|
||
ctx.lineWidth = 3.5
|
||
ctx.lineCap = 'round'
|
||
ctx.lineJoin = 'round'
|
||
ctx.stroke()
|
||
}
|
||
|
||
const url = canvas.toDataURL('image/png')
|
||
subpackSlipPdfCheckboxImgCache[key] = url
|
||
return url
|
||
}
|
||
|
||
function buildSubpackSlipPdfCheckItemHtml(checked: boolean, labelText: string, extraClass = ''): string {
|
||
const imgSrc = buildSubpackSlipPdfCheckboxImgDataUrl(checked)
|
||
const className = `subpack-slip-check-item-pdf${extraClass ? ` ${extraClass}` : ''}`
|
||
if (!imgSrc) return `<span class="${className}">${labelText}</span>`
|
||
|
||
return `<span class="${className}"><img class="subpack-slip-check-img-pdf" src="${imgSrc}" width="16" height="16" alt="" /><span class="subpack-slip-check-text-pdf">${labelText}</span></span>`
|
||
}
|
||
|
||
function applySubpackSlipPdfCheckboxLayout(root: HTMLElement): void {
|
||
root.querySelectorAll('.subpack-slip-bucket-check-row').forEach((node) => {
|
||
const row = node as HTMLElement
|
||
row.style.setProperty('display', 'flex', 'important')
|
||
row.style.setProperty('flex', '1', 'important')
|
||
row.style.setProperty('justify-content', 'center', 'important')
|
||
row.style.setProperty('align-items', 'center', 'important')
|
||
row.style.setProperty('flex-wrap', 'wrap', 'important')
|
||
row.style.setProperty('gap', '14px 18px', 'important')
|
||
row.style.setProperty('line-height', '20px', 'important')
|
||
row.style.setProperty('font-size', '20px', 'important')
|
||
})
|
||
|
||
root.querySelectorAll('.subpack-slip-remark-row-inner').forEach((node) => {
|
||
const row = node as HTMLElement
|
||
row.style.setProperty('display', 'flex', 'important')
|
||
row.style.setProperty('align-items', 'center', 'important')
|
||
row.style.setProperty('width', '100%', 'important')
|
||
row.style.setProperty('gap', '12px', 'important')
|
||
})
|
||
|
||
root.querySelectorAll('.subpack-slip-check-item-pdf').forEach((node) => {
|
||
const item = node as HTMLElement
|
||
const isComplete = item.classList.contains('subpack-slip-complete-check-item-pdf')
|
||
item.style.setProperty('display', 'inline-block', 'important')
|
||
item.style.setProperty('white-space', 'nowrap', 'important')
|
||
item.style.setProperty('font-family', "SimSun, 'Songti SC', serif", 'important')
|
||
item.style.setProperty('font-size', '20px', 'important')
|
||
item.style.setProperty('line-height', '20px', 'important')
|
||
item.style.setProperty('height', '20px', 'important')
|
||
item.style.setProperty('margin-right', isComplete ? '0' : '18px', 'important')
|
||
item.style.setProperty('vertical-align', 'middle', 'important')
|
||
|
||
const img = item.querySelector('img.subpack-slip-check-img-pdf') as HTMLImageElement | null
|
||
if (img) {
|
||
img.style.setProperty('display', 'inline-block', 'important')
|
||
img.style.setProperty('width', '16px', 'important')
|
||
img.style.setProperty('height', '16px', 'important')
|
||
img.style.setProperty('margin', '0 4px 0 0', 'important')
|
||
img.style.setProperty('padding', '0', 'important')
|
||
img.style.setProperty('border', 'none', 'important')
|
||
img.style.setProperty('vertical-align', 'middle', 'important')
|
||
}
|
||
|
||
const text = item.querySelector('.subpack-slip-check-text-pdf') as HTMLElement | null
|
||
if (text) {
|
||
text.style.setProperty('display', 'inline-block', 'important')
|
||
text.style.setProperty('font-size', '20px', 'important')
|
||
text.style.setProperty('line-height', '20px', 'important')
|
||
text.style.setProperty('vertical-align', 'middle', 'important')
|
||
text.style.setProperty('position', 'relative', 'important')
|
||
text.style.setProperty('top', '-12px', 'important')
|
||
}
|
||
})
|
||
}
|
||
|
||
function prepareSubpackSlipPdfCheckboxes(root: HTMLElement): void {
|
||
root.querySelectorAll('label.subpack-slip-check-item').forEach((node) => {
|
||
const label = node as HTMLElement
|
||
const input = label.querySelector('input.subpack-slip-check-input') as HTMLInputElement | null
|
||
const checked = input?.checked ?? false
|
||
const labelText = (label.textContent || '').replace(/[✓☑□☐]/g, '').trim()
|
||
const extraClass = label.classList.contains('subpack-slip-complete-check-item')
|
||
? 'subpack-slip-complete-check-item-pdf'
|
||
: label.classList.contains('subpack-slip-bucket-check-item')
|
||
? 'subpack-slip-bucket-check-item-pdf'
|
||
: ''
|
||
|
||
const html = buildSubpackSlipPdfCheckItemHtml(checked, labelText, extraClass)
|
||
const wrapper = document.createElement('div')
|
||
wrapper.innerHTML = html.trim()
|
||
const replacement = wrapper.firstElementChild
|
||
if (replacement) {
|
||
label.replaceWith(replacement)
|
||
}
|
||
})
|
||
|
||
applySubpackSlipPdfCheckboxLayout(root)
|
||
}
|
||
|
||
/** PDF 截图前:物料网格 + 备注/条款/地址行 + 签字区统一样式 */
|
||
export function prepareSubpackSlipPdfGridCells(root: HTMLElement): void {
|
||
root.querySelectorAll('td.subpack-slip-product-grid-cell, th.subpack-slip-product-grid-cell').forEach((node) => {
|
||
applySubpackSlipPdfGridCellStyles(node as HTMLTableCellElement, node.tagName === 'TH')
|
||
})
|
||
|
||
root.querySelectorAll('td.subpack-slip-terms-cell').forEach((node) => {
|
||
prepareSubpackSlipPdfTermsCell(node as HTMLTableCellElement)
|
||
})
|
||
|
||
root.querySelectorAll('td.subpack-slip-product-cell:not(.subpack-slip-product-grid-cell)').forEach((node) => {
|
||
const cell = node as HTMLTableCellElement
|
||
if (cell.classList.contains('subpack-slip-terms-cell')) return
|
||
if (cell.querySelector('.subpack-slip-address-block, .subpack-slip-seal-block')) {
|
||
prepareSubpackSlipPdfSealCell(cell)
|
||
return
|
||
}
|
||
if (/备注\s*[::]/.test(cell.textContent || '')) {
|
||
prepareSubpackSlipPdfRemarkCell(cell)
|
||
}
|
||
})
|
||
|
||
root.querySelectorAll('tr').forEach((row) => {
|
||
if (!row.querySelector('.subpack-slip-product-cell')) return
|
||
row.removeAttribute('height')
|
||
row.style.removeProperty('height')
|
||
row.style.removeProperty('min-height')
|
||
})
|
||
|
||
prepareSubpackSlipPdfSignValues(root)
|
||
prepareSubpackSlipPdfCheckboxes(root)
|
||
|
||
// 亚像素 border 在 Windows DPI 下易变粗;截图前改为 1px 背景单线
|
||
applyPdfStableTableCellBorders(root.querySelectorAll('.subpack-slip-product-cell'))
|
||
}
|
||
|
||
/** 备注、条款、印章行:统一 0.3px 全边框(兼容已保存模板,与物料网格衔接) */
|
||
function normalizeSubpackSlipProductTableFooter(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
let result = html
|
||
|
||
result = result.replace(/<td([^>]*colspan="7"[^>]*)>(\s*)备注:/g, (_match, attrs, space) => {
|
||
const patched = patchProductFooterCellAttrs(
|
||
attrs,
|
||
'subpack-slip-product-cell subpack-slip-remark-cell',
|
||
PRODUCT_TABLE_CELL_LEFT
|
||
)
|
||
if (_match.includes('subpack-slip-remark-row-inner')) return _match
|
||
return `<td${patched}>${space}${buildSubpackSlipRemarkRowHtml()}`
|
||
})
|
||
|
||
result = result.replace(
|
||
/<td([^>]*colspan="4"[^>]*)>\s*(<div class="subpack-slip-terms-text"|\s*1\.本送货单)/g,
|
||
(_match, attrs, contentStart) => {
|
||
const patched = patchProductFooterCellAttrs(
|
||
attrs,
|
||
'subpack-slip-product-cell subpack-slip-terms-cell',
|
||
PRODUCT_TERMS_CELL_LEFT
|
||
)
|
||
return `<td${patched}>\n ${contentStart}`
|
||
}
|
||
)
|
||
|
||
result = result.replace(
|
||
/<td([^>]*colspan="3"[^>]*)>\s*<div class="subpack-slip-seal-block"/g,
|
||
(_match, attrs) => {
|
||
const patched = patchProductFooterCellAttrs(attrs, 'subpack-slip-product-cell', PRODUCT_TABLE_CELL_LEFT)
|
||
return `<td${patched}>\n <div class="subpack-slip-seal-block"`
|
||
}
|
||
)
|
||
|
||
return result
|
||
}
|
||
|
||
/** 条款单元格行高随内容(兼容已保存模板) */
|
||
function normalizeSubpackSlipTermsCell(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
const patchTermsTd = (attrs: string) => {
|
||
let nextAttrs = attrs
|
||
if (nextAttrs.includes('class="')) {
|
||
nextAttrs = nextAttrs.replace(/class="([^"]*)"/, (_, cls: string) =>
|
||
cls.includes('subpack-slip-terms-cell') ? `class="${cls}"` : `class="${cls} subpack-slip-terms-cell"`
|
||
)
|
||
} else {
|
||
nextAttrs = `${nextAttrs} class="subpack-slip-product-cell subpack-slip-terms-cell"`
|
||
}
|
||
if (nextAttrs.includes('style="')) {
|
||
nextAttrs = nextAttrs.replace(/style="[^"]*"/, `style="${PRODUCT_TERMS_CELL_LEFT}"`)
|
||
} else {
|
||
nextAttrs = `${nextAttrs} style="${PRODUCT_TERMS_CELL_LEFT}"`
|
||
}
|
||
return nextAttrs
|
||
}
|
||
|
||
let result = html.replace(
|
||
/<td colspan="4"([^>]*)>\s*<div class="subpack-slip-terms-text"/g,
|
||
(match, attrs) => {
|
||
if (attrs.includes('subpack-slip-terms-cell') && attrs.includes('border:0.3px solid #000')) return match
|
||
return `<td colspan="4"${patchTermsTd(attrs)}>\n <div class="subpack-slip-terms-text"`
|
||
}
|
||
)
|
||
|
||
result = result.replace(
|
||
/<div class="subpack-slip-seal-block" style="([^"]*)"/g,
|
||
(match, style) => {
|
||
if (style.includes('padding:0;') && !style.includes('min-height')) return match
|
||
let nextStyle = style.replace(/min-height:\d+px;/g, '').replace(/padding:[^;]*;/g, '')
|
||
nextStyle = `padding:0;${nextStyle}`
|
||
return `<div class="subpack-slip-seal-block" style="${nextStyle}"`
|
||
}
|
||
)
|
||
|
||
return result
|
||
}
|
||
|
||
/** 移除旧版送货条款行(兼容已保存模板) */
|
||
function normalizeSubpackSlipRemoveTermsRow(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
if (!html.includes('1.本送货单') && !html.includes('subpack-slip-terms-text')) return html
|
||
|
||
let result = html.replace(
|
||
/<td colspan="4"([^>]*class="[^"]*subpack-slip-terms-cell[^"]*"[^>]*)>[\s\S]*?<\/td>/g,
|
||
`<td colspan="4" class="subpack-slip-product-cell" style="${PRODUCT_TABLE_CELL_LEFT}"> </td>`
|
||
)
|
||
|
||
result = result.replace(
|
||
/<td colspan="4"([^>]*)>\s*(<div class="subpack-slip-terms-text"[\s\S]*?<\/div>|1\.本送货单[\s\S]*?)<\/td>/g,
|
||
`<td colspan="4" class="subpack-slip-product-cell" style="${PRODUCT_TABLE_CELL_LEFT}"> </td>`
|
||
)
|
||
|
||
return result
|
||
}
|
||
|
||
/** 刷新已有间距行的 inline 样式(TinyMCE 保存后可能丢失) */
|
||
function refreshSubpackSlipGapBeforeProduct(html: string): string {
|
||
return html
|
||
.replace(
|
||
/<tr class="subpack-slip-gap-before-product">[\s\S]*?<\/tr>/,
|
||
`<tr class="subpack-slip-gap-before-product">\n <td colspan="7" class="subpack-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}"> </td>\n </tr>`
|
||
)
|
||
.replace(
|
||
/<div class="subpack-slip-section-gap subpack-slip-gap-before-product"[^>]*>[\s\S]*?<\/div>/,
|
||
`<div class="subpack-slip-section-gap subpack-slip-gap-before-product" style="${GAP_BEFORE_PRODUCT_CELL}"> </div>`
|
||
)
|
||
}
|
||
|
||
/** 在收货联系方式行与物料表之间插入间距行(兼容已保存模板) */
|
||
function normalizeSubpackSlipGapBeforeProduct(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) {
|
||
return html
|
||
}
|
||
|
||
if (html.includes('subpack-slip-gap-before-product')) {
|
||
return refreshSubpackSlipGapBeforeProduct(html)
|
||
}
|
||
|
||
const gapRow = `<tr class="subpack-slip-gap-before-product"><td colspan="7" class="subpack-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}"> </td></tr>\n `
|
||
|
||
const withComment = html.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>经(?: )?手人:<\/span>[\s\S]*?<\/tr>\s*)(<!--\s*中:产品明细表[\s\S]*?<tr[^>]*>\s*<th[^>]*>(?:序号|物料编号))/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
if (withComment !== html) return withComment
|
||
|
||
const withHeader = html.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>经(?: )?手人:<\/span>[\s\S]*?<\/tr>\s*)(<tr[^>]*>\s*<th[^>]*>(?:序号|物料编号))/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
if (withHeader !== html) return withHeader
|
||
|
||
return html.replace(
|
||
/(下单人:[\s\S]*?<\/tr>\s*)(<!--\s*中:产品明细表[\s\S]*?<tr[^>]*>\s*<th[^>]*>(?:序号|物料编号)|<tr[^>]*>\s*<th[^>]*>(?:序号|物料编号))/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
}
|
||
|
||
/** 刷新签字区上方间距行 inline 样式 */
|
||
function refreshSubpackSlipGapBeforeSign(html: string): string {
|
||
return html
|
||
.replace(
|
||
/<tr class="subpack-slip-gap-before-sign">[\s\S]*?<\/tr>/,
|
||
`<tr class="subpack-slip-gap-before-sign">\n <td colspan="7" class="subpack-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}"> </td>\n </tr>`
|
||
)
|
||
.replace(
|
||
/<div class="subpack-slip-section-gap subpack-slip-gap-before-sign"[^>]*>[\s\S]*?<\/div>/,
|
||
`<div class="subpack-slip-section-gap subpack-slip-gap-before-sign" style="${GAP_BEFORE_SIGN_CELL}"> </div>`
|
||
)
|
||
}
|
||
|
||
/** 在物料表与签字区之间插入间距行(兼容已保存模板) */
|
||
function normalizeSubpackSlipGapBeforeSign(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
if (html.includes('subpack-slip-gap-before-sign')) {
|
||
return refreshSubpackSlipGapBeforeSign(html)
|
||
}
|
||
|
||
const gapRow = `<tr class="subpack-slip-gap-before-sign"><td colspan="7" class="subpack-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}"> </td></tr>\n\n `
|
||
|
||
const withComment = html.replace(
|
||
/(<\/tr>\s*)(<!--\s*下:签字区[\s\S]*?<tr[^>]*>\s*<td colspan="7"[^>]*>\s*(?:<div class="subpack-slip-sign-inner">\s*)?制单人:)/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
if (withComment !== html) return withComment
|
||
|
||
const withLegacy = html.replace(
|
||
/(<\/tr>\s*)(<!--\s*下:签字区[\s\S]*?<tr[^>]*>\s*<td colspan="7"[^>]*>\s*(?:<div class="subpack-slip-sign-inner">\s*)?仓库:)/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
if (withLegacy !== html) return withLegacy
|
||
|
||
return html.replace(
|
||
/(subpack-slip-remark-cell[\s\S]*?<\/tr>\s*)(<tr[^>]*>\s*<td colspan="7"[^>]*>\s*(?:<div class="subpack-slip-sign-inner">\s*)?(?:制单人:|仓库:))/,
|
||
`$1${gapRow}$2`
|
||
)
|
||
}
|
||
|
||
/** 签字区标签/下划线 baseline 对齐(兼容已保存模板) */
|
||
function normalizeSubpackSlipSignLabelMarkup(html: string): string {
|
||
if (!html.includes('subpack-slip-sign-value')) return html
|
||
|
||
let result = html.replace(
|
||
/<span class="subpack-slip-sign-item">(制单人:|审核:|生产主管:|仓库:|客户签收:|司机\/送货人\(附车牌\):)\s*(<span class="subpack-slip-sign-value")/g,
|
||
'<span class="subpack-slip-sign-item"><span class="subpack-slip-sign-label">$1</span>$2'
|
||
)
|
||
|
||
result = result.replace(/<span class="subpack-slip-sign-value" style="([^"]*)"/g, (match, style) => {
|
||
const nextStyle = style
|
||
.replace(/display:inline-block;?/g, '')
|
||
.replace(/padding:[^;]*;?/g, '')
|
||
.replace(/vertical-align:[^;]*;?/g, '')
|
||
.replace(/border:none !important;?/g, '')
|
||
.replace(/border-bottom:[^;]*;?/g, '')
|
||
.replace(/min-width:\s*[^;]*;?/g, '')
|
||
.replace(/;\s*;/g, ';')
|
||
.replace(/^;|;$/g, '')
|
||
.trim()
|
||
const minWidthStyle = `min-width:${SUBPACK_SLIP_SIGN_VALUE_MIN_WIDTH};`
|
||
const mergedStyle = nextStyle ? `${nextStyle};${minWidthStyle}` : minWidthStyle
|
||
return `<span class="subpack-slip-sign-value" style="${mergedStyle}"`
|
||
})
|
||
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-sign-label">制单人:<\/span>\s*<span class="subpack-slip-sign-value"[^>]*>)\s*(?: |\s)*<\/span>/g,
|
||
'$1{{preparerUser}} </span>'
|
||
)
|
||
|
||
return result
|
||
}
|
||
|
||
/** 签字区 flex 布局(兼容已保存模板,避免打印预览溢出到右侧联次列) */
|
||
function normalizeSubpackSlipSignRow(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
if (
|
||
!html.includes('制单人:') &&
|
||
!html.includes('仓库:') &&
|
||
!html.includes('客户签收:') &&
|
||
!html.includes('司机/送货人')
|
||
) {
|
||
return html
|
||
}
|
||
|
||
if (html.includes('制单人:') && html.includes('subpack-slip-sign-inner') && !html.includes('司机/送货人')) {
|
||
return normalizeSubpackSlipSignLabelMarkup(html)
|
||
}
|
||
|
||
let result = html
|
||
if (html.includes('仓库:') || html.includes('客户签收:') || html.includes('司机/送货人')) {
|
||
result = result.replace(
|
||
/<tr class="subpack-slip-sign-row"[\s\S]*?<\/tr>/,
|
||
buildSubpackSlipSignRowHtml()
|
||
)
|
||
result = result.replace(
|
||
/<tr>\s*<td colspan="7"[^>]*>[\s\S]*?(?:仓库:|制单人:)[\s\S]*?(?:司机\/送货人|生产主管)[\s\S]*?<\/td>\s*<\/tr>/,
|
||
buildSubpackSlipSignRowHtml()
|
||
)
|
||
}
|
||
|
||
result = result.replace(
|
||
/<tr([^>]*)>\s*<td colspan="7"([^>]*)>([\s\S]*?(?:制单人:|仓库:)[\s\S]*?)<\/td>\s*<\/tr>/g,
|
||
(match, trAttrs, tdAttrs, content) => {
|
||
if (!content.includes('subpack-slip-sign-value') || content.includes('subpack-slip-sign-inner')) {
|
||
return match
|
||
}
|
||
|
||
const wrappedContent = content
|
||
.replace(/ /g, '')
|
||
.replace(
|
||
/(制单人:|审核:|生产主管:|仓库:|客户签收:|司机\/送货人\(附车牌\):)\s*(<span class="subpack-slip-sign-value"[\s\S]*?<\/span>)/g,
|
||
'<span class="subpack-slip-sign-item"><span class="subpack-slip-sign-label">$1</span>$2</span>'
|
||
)
|
||
|
||
const trClassAttr = trAttrs.includes('class="')
|
||
? trAttrs.replace(/class="([^"]*)"/, (_, cls: string) =>
|
||
cls.includes('subpack-slip-sign-row') ? `class="${cls}"` : `class="${cls} subpack-slip-sign-row"`
|
||
)
|
||
: `${trAttrs} class="subpack-slip-sign-row"`
|
||
|
||
let nextTdAttrs = tdAttrs.replace(/white-space:nowrap;?/g, '').replace(/font-size:16px;?/g, 'font-size:14px;')
|
||
if (nextTdAttrs.includes('class="')) {
|
||
nextTdAttrs = nextTdAttrs.replace(/class="([^"]*)"/, (_, cls: string) =>
|
||
cls.includes('subpack-slip-sign-row-cell') ? `class="${cls}"` : `class="${cls} subpack-slip-sign-row-cell"`
|
||
)
|
||
} else {
|
||
nextTdAttrs = `${nextTdAttrs} class="subpack-slip-sign-row-cell"`
|
||
}
|
||
|
||
return `<tr${trClassAttr}>\n <td colspan="7"${nextTdAttrs}>\n <div class="subpack-slip-sign-inner">${wrappedContent.trim()}</div>\n </td>\n </tr>`
|
||
}
|
||
)
|
||
|
||
result = result.replace(
|
||
/<td(?![^>]*class="[^"]*subpack-slip-main-col)([^>]*style="[^"]*vertical-align:top;padding-right:6px)/g,
|
||
'<td class="subpack-slip-main-col"$1'
|
||
)
|
||
|
||
return normalizeSubpackSlipSignLabelMarkup(result)
|
||
}
|
||
|
||
/**
|
||
* 旧版一体 layout-table / 曾拆出的 footer-table → 信息表 + 物料表(含备注) + 签字表。
|
||
*/
|
||
function migrateSubpackSlipSplitTables(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
if (
|
||
html.includes('subpack-slip-product-table') &&
|
||
!html.includes('subpack-slip-footer-table') &&
|
||
/subpack-slip-product-table[\s\S]*?(?:subpack-slip-remark-cell|备注:)/.test(html)
|
||
) {
|
||
return html
|
||
}
|
||
|
||
if (html.includes('subpack-slip-footer-table') && html.includes('subpack-slip-product-table')) {
|
||
return html.replace(
|
||
/(<\/tr>\s*)<\/table>\s*<table class="subpack-slip-footer-table"[^>]*>\s*(?:<colgroup>[\s\S]*?<\/colgroup>\s*)?([\s\S]*?)<\/table>/i,
|
||
(_m, beforeClose, footerInner) => {
|
||
const rows = footerInner.replace(/^\s*<colgroup>[\s\S]*?<\/colgroup>\s*/i, '').trim()
|
||
return `${beforeClose}${rows}\n </table>`
|
||
}
|
||
)
|
||
}
|
||
|
||
if (!html.includes('subpack-slip-layout-table')) return html
|
||
|
||
return html.replace(
|
||
/<table class="subpack-slip-layout-table"[^>]*>([\s\S]*?)<\/table>/i,
|
||
(_full, inner: string) => {
|
||
const headerMatch =
|
||
inner.match(/<tr[^>]*class="[^"]*subpack-slip-product-header[^"]*"[\s\S]*?<\/tr>/i) ||
|
||
inner.match(/<tr>\s*<th[^>]*>(?:序号|物料编号)[\s\S]*?<\/tr>/i)
|
||
if (!headerMatch) return _full
|
||
|
||
const headerIdx = inner.indexOf(headerMatch[0])
|
||
let before = inner.slice(0, headerIdx)
|
||
before = before.replace(/<colgroup>[\s\S]*?<\/colgroup>/i, '')
|
||
before = before.replace(/<tr class="subpack-slip-gap-before-product">[\s\S]*?<\/tr>/i, '')
|
||
|
||
const afterHeader = inner.slice(headerIdx + headerMatch[0].length)
|
||
const signMatch =
|
||
afterHeader.match(/<tr[^>]*class="[^"]*subpack-slip-sign-row[^"]*"[\s\S]*?<\/tr>/i) ||
|
||
afterHeader.match(/<tr>\s*<td[^>]*>[\s\S]*?(?:制单人:|仓库:)[\s\S]*?<\/tr>/i)
|
||
|
||
let productAndFooter = afterHeader
|
||
let signPart = buildSubpackSlipSignRowHtml()
|
||
if (signMatch) {
|
||
const signAt = afterHeader.indexOf(signMatch[0])
|
||
productAndFooter = afterHeader.slice(0, signAt)
|
||
signPart = signMatch[0]
|
||
}
|
||
productAndFooter = productAndFooter.replace(
|
||
/<tr class="subpack-slip-gap-before-sign">[\s\S]*?<\/tr>/i,
|
||
''
|
||
)
|
||
|
||
return `<table class="subpack-slip-info-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${INFO_COLGROUP}
|
||
${before.trim()}
|
||
</table>
|
||
<div class="subpack-slip-section-gap subpack-slip-gap-before-product" style="${GAP_BEFORE_PRODUCT_CELL}"> </div>
|
||
<table class="subpack-slip-product-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${PRODUCT_COLGROUP}
|
||
${headerMatch[0]}
|
||
${productAndFooter.trim()}
|
||
</table>
|
||
<div class="subpack-slip-section-gap subpack-slip-gap-before-sign" style="${GAP_BEFORE_SIGN_CELL}"> </div>
|
||
<table class="subpack-slip-sign-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
|
||
${signPart.trim()}
|
||
</table>`
|
||
}
|
||
)
|
||
}
|
||
|
||
/** 物料表列宽(兼容已保存模板) */
|
||
function normalizeSubpackSlipProductColWidths(html: string): string {
|
||
if (!html.includes('subpack-slip-product-table')) return html
|
||
return html.replace(
|
||
/(<table class="subpack-slip-product-table"[^>]*>\s*)<colgroup>[\s\S]*?<\/colgroup>/i,
|
||
`$1${PRODUCT_COLGROUP}`
|
||
)
|
||
}
|
||
|
||
/** 统一单据左右 10px 间距、内容区宽度(兼容已保存模板) */
|
||
function normalizeSubpackSlipDocLayout(html: string): string {
|
||
let result = migrateSubpackSlipSplitTables(html)
|
||
.replace(/padding:8px 12px 24px 4px;/g, `padding:${DOC_PADDING};`)
|
||
.replace(/padding:8px 12px 24px;/g, `padding:${DOC_PADDING};`)
|
||
.replace(/padding-left: 4px !important;/g, `padding: ${DOC_PADDING} !important;`)
|
||
.replace(/left:0;top:50%/g, `left:${LOGO_LEFT}px;top:50%`)
|
||
.replace(/left:20px;/g, `left:${LOGO_LEFT}px;`)
|
||
.replace(/padding:4px 80px 0 120px;/g, 'padding:4px 60px 0 100px;')
|
||
.replace(/padding:4px 72px 0 88px;/g, 'padding:4px 60px 0 100px;')
|
||
.replace(/max-width:720px/g, `max-width:${SUBPACK_SLIP_DOC_MAX_WIDTH}`)
|
||
.replace(/max-width:760px/g, `max-width:${SUBPACK_SLIP_DOC_MAX_WIDTH}`)
|
||
.replace(/max-width:794px/g, `max-width:${SUBPACK_SLIP_DOC_MAX_WIDTH}`)
|
||
.replace(/height:46px;/g, `height:${LOGO_HEIGHT}px;`)
|
||
.replace(/height:23px;/g, `height:${LOGO_HEIGHT}px;`)
|
||
.replace(/min-height:72px;/g, 'min-height:56px;')
|
||
.replace(/min-height:48px;/g, 'min-height:56px;')
|
||
.replace(/padding:4px 60px 0 68px;/g, 'padding:4px 60px 0 100px;')
|
||
.replace(/padding:4px 60px 0 52px;/g, 'padding:4px 60px 0 100px;')
|
||
.replace(/padding:1px 6px;/g, `padding:${TABLE_ROW_PADDING};`)
|
||
.replace(/padding:4px 6px;/g, `padding:${TABLE_ROW_PADDING};`)
|
||
.replace(/padding-bottom:5px;/g, '')
|
||
.replace(/padding-bottom:10px;/g, '')
|
||
.replace(/padding:18px 6px 8px;/g, `padding:${TABLE_ROW_PADDING};`)
|
||
.replace(/margin-top:6px;/g, 'margin-top:2px;')
|
||
.replace(/line-height:1\.6;/g, 'line-height:1.35;')
|
||
.replace(/line-height:1\.9;/g, 'line-height:1.35;')
|
||
.replace(/min-height:88px;/g, 'min-height:0;')
|
||
.replace(/min-height:36px;/g, '')
|
||
.replace(/min-height:76px;/g, 'min-height:0;')
|
||
.replace(/min-height:120px;/g, 'min-height:0;')
|
||
.replace(/top:4px !important;/g, `top:${SUBPACK_SLIP_SEAL_TOP}px !important;`)
|
||
.replace(/top:4px;/g, `top:${SUBPACK_SLIP_SEAL_TOP}px;`)
|
||
.replace(/>日期:</g, '>日 期:<')
|
||
.replace(/border-bottom:2px solid #000 !important;/g, `border-bottom:${INFO_UNDERLINE} !important;`)
|
||
.replace(/border-bottom:1px solid #000 !important;/g, `border-bottom:${INFO_UNDERLINE} !important;`)
|
||
.replace(/border:2px solid #000 !important;/g, `border:${PRODUCT_GRID_BORDER} !important;`)
|
||
.replace(/border:1px solid #000 !important;/g, `border:${PRODUCT_GRID_BORDER} !important;`)
|
||
.replace(
|
||
/display:inline-block;(?:min-width:\d+px;)?padding:0 2px 1px;vertical-align:bottom;border:none !important;/g,
|
||
''
|
||
)
|
||
.replace(
|
||
/display:inline-block;padding:0 2px 2px;vertical-align:bottom;border:none !important;border-bottom:1px solid #000 !important;/g,
|
||
''
|
||
)
|
||
.replace(
|
||
/(制单人:|审核:|生产主管:|仓库:|客户签收:|司机\/送货人\(附车牌\):)<span(?![^>]*class="subpack-slip-sign-value") style="/g,
|
||
'$1<span class="subpack-slip-sign-value" style="'
|
||
)
|
||
.replace(
|
||
/(<span class="subpack-slip-sign-value" style="[^"]*)border-bottom:2px solid #000 !important;/g,
|
||
`$1border-bottom:${SIGN_UNDERLINE} !important;`
|
||
)
|
||
.replace(/class="subpack-slip-info-before-product"/g, '')
|
||
.replace(/padding-bottom:12px;/g, '')
|
||
.replace(/padding-bottom:24px;/g, '')
|
||
|
||
if (result.includes('subpack-slip-doc') && !result.includes('max-width:')) {
|
||
result = result.replace(
|
||
/(<div class="subpack-slip-doc" style=")([^"]*)"/,
|
||
`$1$2;${DOC_WRAP_EXTRA}"`
|
||
)
|
||
}
|
||
|
||
if (result.includes('subpack-slip-doc')) {
|
||
result = result.replace(/<style type="text\/css">[\s\S]*?<\/style>/, SUBPACK_SLIP_STYLE_BLOCK)
|
||
result = normalizeSubpackSlipSignLabelMarkup(result)
|
||
result = normalizeSubpackSlipProductGridCell(result)
|
||
result = normalizeSubpackSlipProductTableFooter(result)
|
||
result = normalizeSubpackSlipSlipNoRow(result)
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
/** 基本信息字段与标题(兼容从送(提)货单复制的旧模板) */
|
||
function normalizeSubpackSlipBasicInfoFields(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
let result = html
|
||
result = result.replace(/送 \(提\) 货 单/g, '分 装 单')
|
||
result = result.replace(/送\(提\)货单/g, '分装单')
|
||
result = result.replace(/收货单位:/g, '下单日期:')
|
||
result = result.replace(/单据编号:/g, '交货日期:')
|
||
result = result.replace(/收货联系方式:/g, '下单人:')
|
||
result = result.replace(/日 期:/g, '经手人:')
|
||
result = result.replace(/>日期:</g, '>经手人:<')
|
||
result = result.replace(/\{\{receivingUnit\}\}/g, '{{orderDate}}')
|
||
result = result.replace(/\{\{planNo\}\}/g, '{{deliveryDate}}')
|
||
result = result.replace(/\{\{receiveContact\}\}/g, '{{orderUser}}')
|
||
result = result.replace(/\{\{planDateChinese\}\}/g, '{{handlerName}}')
|
||
result = result.replace(/\{\{driverInfo\}\}/g, '{{handlerName}}')
|
||
|
||
result = result.replace(
|
||
/<div class="subpack-slip-info-row subpack-slip-type-check-wrap"[\s\S]*?<\/div>\s*/g,
|
||
''
|
||
)
|
||
result = result.replace(/<div class="subpack-slip-type-check-row"[\s\S]*?<\/div>\s*/g, '')
|
||
|
||
return result
|
||
}
|
||
|
||
/** 物料表头(兼容旧版 物料编号/实收数量 表头) */
|
||
function normalizeSubpackSlipProductHeader(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
if (html.includes('>序号<') && html.includes('>是否完成<')) return html
|
||
|
||
const headerHtml = buildSubpackSlipProductHeaderRowHtml()
|
||
const withClass = html.replace(/<tr class="subpack-slip-product-header"[\s\S]*?<\/tr>/, headerHtml)
|
||
if (withClass !== html) return withClass
|
||
|
||
return html.replace(
|
||
/<tr>\s*<th[^>]*>物料编号<\/th>[\s\S]*?<th[^>]*>实收数量<\/th>\s*<\/tr>/,
|
||
headerHtml
|
||
)
|
||
}
|
||
|
||
/** 备注行 + 桶选项多选框(兼容旧版纯文本备注行) */
|
||
function normalizeSubpackSlipRemarkRow(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
if (html.includes('subpack-slip-remark-row-inner')) {
|
||
let result = html
|
||
// 旧版:备注正文与补充输入分开,升级为同一条连续下划线
|
||
if (!result.includes('subpack-slip-remark-underline-group')) {
|
||
const extractRemark = (raw: string) =>
|
||
String(raw || '')
|
||
.replace(/<[^>]+>/g, '')
|
||
.replace(/备注:/g, '')
|
||
.replace(/ /g, '')
|
||
.trim() || '{{remark}}'
|
||
|
||
result = result.replace(
|
||
/<div class="subpack-slip-remark-row-inner">([\s\S]*?)<div class="subpack-slip-bucket-check-row"/,
|
||
(_m, middle: string) => {
|
||
const valueMatch = String(middle).match(
|
||
/class="subpack-slip-remark-value"[^>]*>([\s\S]*?)<\/span>/
|
||
)
|
||
const plainMatch = String(middle).match(/备注:([^<]*)/)
|
||
const content = extractRemark(valueMatch?.[1] ?? plainMatch?.[1] ?? '{{remark}}')
|
||
const extraMatch = String(middle).match(
|
||
/<input type="text" class="subpack-slip-remark-extra-input"([^>]*)\/?>/
|
||
)
|
||
const extraHtml = extraMatch
|
||
? `<span class="subpack-slip-remark-extra-wrap" contenteditable="false"><input type="text" class="subpack-slip-remark-extra-input"${extraMatch[1]} /></span>`
|
||
: buildSubpackSlipRemarkExtraInputHtml()
|
||
return `<div class="subpack-slip-remark-row-inner">
|
||
<span class="subpack-slip-remark-label">备注:</span>
|
||
<span class="subpack-slip-remark-underline-group"><span class="subpack-slip-remark-value">${content} </span>${extraHtml}</span>
|
||
<div class="subpack-slip-bucket-check-row"`
|
||
}
|
||
)
|
||
}
|
||
if (!result.includes('subpack-slip-remark-extra-input')) {
|
||
result = result.replace(
|
||
/(<div class="subpack-slip-remark-row-inner">[\s\S]*?)(\s*<div class="subpack-slip-bucket-check-row")/,
|
||
`$1\n ${buildSubpackSlipRemarkUnderlineGroupHtml()}$2`
|
||
)
|
||
} else if (!result.includes('subpack-slip-remark-extra-wrap')) {
|
||
result = result.replace(
|
||
/<input type="text" class="subpack-slip-remark-extra-input"([^>]*)\/?>/g,
|
||
`<span class="subpack-slip-remark-extra-wrap" contenteditable="false"><input type="text" class="subpack-slip-remark-extra-input"$1 /></span>`
|
||
)
|
||
}
|
||
return ensureSubpackSlipInteractiveMarkup(result)
|
||
}
|
||
|
||
return html.replace(
|
||
/(<td[^>]*colspan="7"[^>]*>\s*)备注:(\{\{remark\}\}|[^<]*?)(\s*<\/td>)/,
|
||
`$1${buildSubpackSlipRemarkRowHtml()}$3`
|
||
)
|
||
}
|
||
|
||
/** 移除右侧联次列,并解包旧版 outer-table 布局 */
|
||
function normalizeSubpackSlipRemoveSideLabels(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
let result = html
|
||
result = result.replace(/<table class="subpack-slip-side-labels"[\s\S]*?<\/table>/g, '')
|
||
result = result.replace(/\{\{sideLabels\}\}/g, '')
|
||
result = result.replace(
|
||
/<!--\s*最右侧纵向联次\s*-->\s*<td class="subpack-slip-side-labels-col"[\s\S]*?<\/td>/g,
|
||
''
|
||
)
|
||
result = result.replace(/<td class="subpack-slip-side-labels-col"[^>]*>[\s\S]*?<\/td>/g, '')
|
||
|
||
if (result.includes('subpack-slip-outer-table') && result.includes('subpack-slip-main-col')) {
|
||
result = result.replace(
|
||
/<table class="subpack-slip-outer-table" style="[^"]*">\s*<tr>\s*<td class="subpack-slip-main-col" style="[^"]*">/,
|
||
''
|
||
)
|
||
result = result.replace(/<\/td>\s*<\/tr>\s*<\/table>(?=\s*<\/div>)/, '')
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
/** 基本信息合并为一行四列(兼容旧版两行布局) */
|
||
function normalizeSubpackSlipBasicInfoOneRow(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
if (html.includes('subpack-slip-basic-info-row')) return html
|
||
|
||
const basicInfoRow = buildSubpackSlipBasicInfoRowHtml()
|
||
const replaced = html.replace(
|
||
/<!--\s*上:基本信息两行\s*-->\s*<tr>[\s\S]*?<\/tr>\s*<tr>[\s\S]*?<\/tr>/,
|
||
`<!-- 基本信息一行 -->\n ${basicInfoRow}`
|
||
)
|
||
if (replaced !== html) return replaced
|
||
|
||
const fallback = html.replace(
|
||
/<tr>\s*<td colspan="4"[^>]*>\s*<div class="subpack-slip-info-row"[^>]*>\s*<span class="subpack-slip-info-label"[^>]*>下单日期:[\s\S]*?<\/tr>\s*<tr>\s*<td colspan="4"[^>]*>\s*<div class="subpack-slip-info-row"[^>]*>\s*<span class="subpack-slip-info-label"[^>]*>下单人:[\s\S]*?<\/tr>/,
|
||
basicInfoRow
|
||
)
|
||
return fallback
|
||
}
|
||
|
||
/** 分装单号与经手人标签右缘对齐(兼容旧版标题区居中布局) */
|
||
function normalizeSubpackSlipSlipNoRow(html: string): string {
|
||
if (!html.includes('subpack-slip-doc')) return html
|
||
|
||
let result = html
|
||
result = result.replace(/<div class="subpack-slip-info-row subpack-slip-slip-no-wrap"[\s\S]*?<\/div>\s*/g, '')
|
||
result = result.replace(/<span class="subpack-slip-info-label"[^>]*>分装单号:<\/span>\s*/g, '')
|
||
|
||
result = result.replace(
|
||
/<div class="subpack-slip-slip-no-display"[^>]*>([\s\S]*?)<\/div>/g,
|
||
'<span class="subpack-slip-slip-no-display">$1</span>'
|
||
)
|
||
|
||
if (result.includes('subpack-slip-title-section') && result.includes('subpack-slip-slip-no-display')) {
|
||
result = result.replace(
|
||
/\s*<span class="subpack-slip-slip-no-display"[^>]*>[\s\S]*?<\/span>\s*(?=<\/div>\s*<\/div>\s*<table class="subpack-slip-(?:layout|info)-table")/,
|
||
'\n'
|
||
)
|
||
result = result.replace(
|
||
/<div class="subpack-slip-slip-no-display"[^>]*>[\s\S]*?<\/div>\s*(?=<\/div>\s*<\/div>\s*<table class="subpack-slip-(?:layout|info)-table")/,
|
||
''
|
||
)
|
||
}
|
||
|
||
if (!result.includes('subpack-slip-slip-no-row')) {
|
||
result = result.replace(
|
||
/(<table class="subpack-slip-(?:layout|info)-table"[\s\S]*?<colgroup>[\s\S]*?<\/colgroup>\s*)/,
|
||
`$1\n ${buildSubpackSlipSlipNoRowHtml()}\n\n `
|
||
)
|
||
}
|
||
|
||
if (result.includes('subpack-slip-slip-no-row') && !result.includes('subpack-slip-slip-no-anchor')) {
|
||
result = result.replace(
|
||
/<tr class="subpack-slip-slip-no-row"[\s\S]*?<\/tr>\s*/,
|
||
`${buildSubpackSlipSlipNoRowHtml()}\n`
|
||
)
|
||
}
|
||
|
||
return ensureSubpackSlipInteractiveMarkup(result)
|
||
}
|
||
|
||
const SUBPACK_SLIP_CHECK_DELEGATE_ATTR = 'data-subpack-slip-check-delegate'
|
||
const SUBPACK_SLIP_REMARK_EXTRA_BOUND_ATTR = 'data-subpack-slip-remark-extra-bound'
|
||
|
||
function syncSubpackSlipRemarkExtraInputValue(input: HTMLInputElement): void {
|
||
input.setAttribute('value', input.value)
|
||
}
|
||
|
||
/** 富文本编辑器内:允许备注补充输入框获得焦点并输入(TinyMCE 默认会拦截) */
|
||
export function bindSubpackSlipRemarkExtraInputInteraction(
|
||
root: HTMLElement | null | undefined,
|
||
onChange?: () => void
|
||
): void {
|
||
if (!root) return
|
||
|
||
root.querySelectorAll('input.subpack-slip-remark-extra-input').forEach((node) => {
|
||
const input = node as HTMLInputElement
|
||
const wrap = input.closest('.subpack-slip-remark-extra-wrap') as HTMLElement | null
|
||
if (wrap) {
|
||
wrap.setAttribute('contenteditable', 'false')
|
||
}
|
||
input.removeAttribute('readonly')
|
||
input.removeAttribute('disabled')
|
||
syncSubpackSlipRemarkExtraInputValue(input)
|
||
|
||
if (input.getAttribute(SUBPACK_SLIP_REMARK_EXTRA_BOUND_ATTR) === '1') return
|
||
input.setAttribute(SUBPACK_SLIP_REMARK_EXTRA_BOUND_ATTR, '1')
|
||
|
||
const stopBubble = (e: Event) => {
|
||
e.stopPropagation()
|
||
}
|
||
|
||
input.addEventListener('mousedown', stopBubble, true)
|
||
input.addEventListener('click', stopBubble, true)
|
||
input.addEventListener('keydown', stopBubble, true)
|
||
input.addEventListener('keyup', stopBubble, true)
|
||
input.addEventListener('keypress', stopBubble, true)
|
||
input.addEventListener('focus', stopBubble, true)
|
||
input.addEventListener('input', () => {
|
||
syncSubpackSlipRemarkExtraInputValue(input)
|
||
onChange?.()
|
||
})
|
||
input.addEventListener('blur', () => {
|
||
syncSubpackSlipRemarkExtraInputValue(input)
|
||
onChange?.()
|
||
})
|
||
})
|
||
}
|
||
|
||
function syncSubpackSlipCheckboxInput(input: HTMLInputElement): void {
|
||
if (input.checked) {
|
||
input.setAttribute('checked', 'checked')
|
||
} else {
|
||
input.removeAttribute('checked')
|
||
}
|
||
}
|
||
|
||
/** 富文本编辑器内:绑定多选框点击(TinyMCE 默认可编辑区会拦截原生 checkbox) */
|
||
export function bindSubpackSlipCheckboxInteraction(
|
||
root: HTMLElement | null | undefined,
|
||
onChange?: () => void
|
||
): void {
|
||
if (!root) return
|
||
|
||
root.querySelectorAll('label.subpack-slip-check-item').forEach((node) => {
|
||
const label = node as HTMLElement
|
||
label.setAttribute('contenteditable', 'false')
|
||
const input = label.querySelector('input.subpack-slip-check-input') as HTMLInputElement | null
|
||
if (input) syncSubpackSlipCheckboxInput(input)
|
||
})
|
||
|
||
if (root.getAttribute(SUBPACK_SLIP_CHECK_DELEGATE_ATTR) === '1') return
|
||
root.setAttribute(SUBPACK_SLIP_CHECK_DELEGATE_ATTR, '1')
|
||
|
||
root.addEventListener(
|
||
'mousedown',
|
||
(event) => {
|
||
if (root.isContentEditable === false) return
|
||
const target = event.target as HTMLElement | null
|
||
if (!target) return
|
||
const label = target.closest('label.subpack-slip-check-item')
|
||
if (!label || !root.contains(label)) return
|
||
const input = label.querySelector('input.subpack-slip-check-input') as HTMLInputElement | null
|
||
if (!input) return
|
||
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
input.checked = !input.checked
|
||
syncSubpackSlipCheckboxInput(input)
|
||
onChange?.()
|
||
},
|
||
true
|
||
)
|
||
}
|
||
|
||
/** 分装单 TinyMCE:初始化与内容变更时绑定多选项与备注输入框交互 */
|
||
export function setupSubpackSlipEditorCheckboxes(editor: {
|
||
getBody: () => HTMLElement
|
||
on: (event: string, callback: (...args: unknown[]) => void) => void
|
||
fire: (event: string) => void
|
||
}): void {
|
||
const bind = () => {
|
||
const body = editor.getBody()
|
||
bindSubpackSlipCheckboxInteraction(body, () => editor.fire('change'))
|
||
bindSubpackSlipRemarkExtraInputInteraction(body, () => editor.fire('change'))
|
||
}
|
||
|
||
const syncRemarkExtraValues = () => {
|
||
editor.getBody().querySelectorAll('input.subpack-slip-remark-extra-input').forEach((node) => {
|
||
syncSubpackSlipRemarkExtraInputValue(node as HTMLInputElement)
|
||
})
|
||
}
|
||
|
||
editor.on('init', () => {
|
||
window.setTimeout(bind, 0)
|
||
})
|
||
editor.on('SetContent', () => {
|
||
window.setTimeout(bind, 0)
|
||
})
|
||
editor.on('focus', () => {
|
||
window.setTimeout(bind, 0)
|
||
})
|
||
editor.on('BeforeGetContent', () => {
|
||
syncRemarkExtraValues()
|
||
})
|
||
}
|
||
|
||
/** @deprecated 兼容旧引用,请使用 setupSubpackSlipEditorCheckboxes */
|
||
export const setupSubpackSlipEditorTypeCheckboxes = setupSubpackSlipEditorCheckboxes
|
||
|
||
/** @deprecated 兼容旧引用,请使用 normalizeSubpackSlipRemoveSideLabels */
|
||
function normalizeSubpackSlipSideLabelsPlaceholder(html: string): string {
|
||
return normalizeSubpackSlipRemoveSideLabels(html)
|
||
}
|
||
|
||
/** 将占位符替换为计划最新数据 */
|
||
export function replaceSubpackSlipPlaceholders(
|
||
htmlContent: string,
|
||
plan: Plan,
|
||
ctx?: SubpackSlipRenderContext
|
||
): string {
|
||
if (!htmlContent || !plan) return htmlContent
|
||
|
||
const orderDate = ctx?.orderDate || formatDateIso()
|
||
const orderUser = ctx?.orderUser || ''
|
||
const preparerUser = ctx?.preparerUser || orderUser
|
||
|
||
const replacements: Record<string, string> = {
|
||
'{{companyName}}': SUBPACK_SLIP_DEFAULTS.companyName,
|
||
'{{subpackSlipNo}}': ctx?.subpackSlipNo || '',
|
||
'{{orderDate}}': orderDate,
|
||
'{{deliveryDate}}': '',
|
||
'{{orderUser}}': orderUser,
|
||
'{{preparerUser}}': preparerUser,
|
||
'{{handlerName}}': '',
|
||
'{{receivingUnit}}': orderDate,
|
||
'{{planNo}}': '',
|
||
'{{receiveContact}}': orderUser,
|
||
'{{driverInfo}}': '',
|
||
'{{planDateChinese}}': '',
|
||
'{{productTable}}': buildProductRows(plan.products, ctx),
|
||
'{{remark}}': formatRemarkForSlipHtml(plan.remark),
|
||
'{{remarkExtra}}': '',
|
||
'{{companyAddress}}': '',
|
||
'{{companyPhone}}': '',
|
||
'{{contactPhone}}': '',
|
||
'{{companySeal}}': '',
|
||
'{{sideLabels}}': ''
|
||
}
|
||
|
||
let result = normalizeSubpackSlipDocLayout(
|
||
normalizeSubpackSlipRemoveAddressAndSeal(
|
||
normalizeSubpackSlipSignRow(
|
||
normalizeSubpackSlipTermsCell(
|
||
normalizeSubpackSlipRemoveTermsRow(
|
||
normalizeSubpackSlipGapBeforeSign(
|
||
normalizeSubpackSlipProductTableFooter(
|
||
normalizeSubpackSlipProductColWidths(
|
||
normalizeSubpackSlipRemarkRow(
|
||
normalizeSubpackSlipProductGridCell(
|
||
normalizeSubpackSlipProductHeader(
|
||
normalizeSubpackSlipGapBeforeProduct(
|
||
normalizeSubpackSlipSlipNoRow(
|
||
normalizeSubpackSlipBasicInfoOneRow(
|
||
normalizeSubpackSlipBasicInfoFields(
|
||
normalizeSubpackSlipRemoveSideLabels(htmlContent)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
for (const [placeholder, value] of Object.entries(replacements)) {
|
||
result = result.split(placeholder).join(value)
|
||
}
|
||
|
||
if (ctx?.subpackSlipNo) {
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-slip-no-display"[^>]*>)[\s\S]*?( <\/span>)/g,
|
||
`$1${ctx.subpackSlipNo}$2`
|
||
)
|
||
}
|
||
|
||
if (preparerUser) {
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-sign-label">制单人:<\/span>\s*<span class="subpack-slip-sign-value"[^>]*>)[\s\S]*?(<\/span>)/g,
|
||
`$1${preparerUser}$2`
|
||
)
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
/** 保存模板前:将动态字段还原为占位符 */
|
||
export function restoreSubpackSlipPlaceholders(template: string, plan: Plan): string {
|
||
let result = template
|
||
|
||
const dynamicReplacements: Record<string, string> = {
|
||
'{{companyName}}': SUBPACK_SLIP_DEFAULTS.companyName,
|
||
'{{orderDate}}': formatDateIso(),
|
||
'{{remark}}': plan.remark || '',
|
||
'{{receivingUnit}}': formatDateIso(),
|
||
'{{planDateChinese}}': formatPlanDateChinese(plan.planDate)
|
||
}
|
||
|
||
for (const [placeholder, currentValue] of Object.entries(dynamicReplacements)) {
|
||
if (!currentValue) continue
|
||
const escaped = currentValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||
result = result.replace(new RegExp(escaped, 'g'), placeholder)
|
||
}
|
||
|
||
result = result.replace(/NO:SXFZ\d{8}-\d{3}/g, '{{subpackSlipNo}}')
|
||
result = result.replace(/SXFZ\d{8}-\d{3}/g, '{{subpackSlipNo}}')
|
||
result = result.replace(/SXFZ-\d{4}-\d{2}-\d{2}-\d{3}/g, '{{subpackSlipNo}}')
|
||
result = result.replace(/NO:SXFZ-\d{4}-\d{2}-\d{2}-\d{3}/g, '{{subpackSlipNo}}')
|
||
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-slip-no-display"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{subpackSlipNo}}'
|
||
)
|
||
result = result.replace(
|
||
/(<div class="subpack-slip-slip-no-display"[^>]*>)[^<]*(?= <\/div>)/,
|
||
'$1{{subpackSlipNo}}'
|
||
)
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-slip-no-value[^"]*"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{subpackSlipNo}}'
|
||
)
|
||
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>下单日期:<\/span>\s*<span class="subpack-slip-info-value-left"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{orderDate}}'
|
||
)
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>下单人:<\/span>\s*<span class="subpack-slip-info-value-left"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{orderUser}}'
|
||
)
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>交货日期:<\/span>\s*<span class="subpack-slip-info-value-center"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{deliveryDate}}'
|
||
)
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-info-label"[^>]*>经手人:<\/span>\s*<span class="subpack-slip-info-value-center"[^>]*>)[^<]*(?= <\/span>)/,
|
||
'$1{{handlerName}}'
|
||
)
|
||
result = result.replace(
|
||
/(<span class="subpack-slip-sign-label">制单人:<\/span>\s*<span class="subpack-slip-sign-value"[^>]*>)[^<]*(?=<\/span>)/,
|
||
'$1{{preparerUser}}'
|
||
)
|
||
|
||
// 产品明细行还原为占位符(表头之后、备注行之前)
|
||
result = result.replace(
|
||
/(<th[^>]*>是否完成<\/th>\s*<\/tr>\s*)([\s\S]*?)(\s*<tr>\s*<td colspan="7"[^>]*>\s*(?:<div class="subpack-slip-remark-row-inner">|备注:))/,
|
||
'$1{{productTable}}$3'
|
||
)
|
||
result = result.replace(
|
||
/(<th[^>]*>实收数量<\/th>\s*<\/tr>\s*)([\s\S]*?)(\s*<tr>\s*<td colspan="7"[^>]*>\s*(?:<div class="subpack-slip-remark-row-inner">|备注:))/,
|
||
'$1{{productTable}}$3'
|
||
)
|
||
|
||
// 移除右侧联次、印章与地址/电话行
|
||
result = normalizeSubpackSlipRemoveSideLabels(result)
|
||
result = normalizeSubpackSlipRemoveAddressAndSeal(result)
|
||
|
||
return result
|
||
}
|