This commit is contained in:
zlwu committed 2026-07-26 21:06:09 +08:00
1 parent 70bdd820ce
commit bede140d12
988 files changed
+692310 -775910

No files matched your search

@@ -0,0 +1,39 @@
/** 主体危化品能力:产品是否允许选择 */
export function productAllowedByHazard(
isToxic: number | undefined,
isExplosive: number | undefined,
allowToxic: number,
allowExplosive: number
): boolean {
const toxic = Number(isToxic || 0) === 1
const explosive = Number(isExplosive || 0) === 1
if (!toxic && !explosive) return true
if (toxic && explosive) return allowToxic === 1 && allowExplosive === 1
if (toxic) return allowToxic === 1
return allowExplosive === 1
}
export function removeProductsNotAllowedByHazard<
T extends {
productId?: string | number
productName?: string
isToxic?: number
isExplosive?: number
}
>(
products: T[],
allowToxic: number,
allowExplosive: number
): { kept: T[]; removedNames: string[] } {
const kept: T[] = []
const removedNames: string[] = []
for (const p of products) {
if (productAllowedByHazard(p.isToxic, p.isExplosive, allowToxic, allowExplosive)) {
kept.push(p)
} else {
removedNames.push(p.productName || String(p.productId || ''))
}
}
return { kept, removedNames }
}
+181 -98
View File
@@ -4,12 +4,14 @@ import { applyPdfStableTableCellBorders } from '@/utils/pl-slip-pdf-render'
/** 入库单打印默认公司信息(可在富文本中修改) */
export const INBOUND_SLIP_DEFAULTS = {
companyName: '安庆市山峡化工有限公司',
receivingUnit: '安庆市山峡化工有限公司',
companyAddress: '安庆市高新区霞虹路5号',
companyPhone: '0556-5999710',
contactPhone: '方总13395560066'
} as const
/** 旧模板曾把送货单位写死为本公司名,保存还原时一并识别 */
const LEGACY_RECEIVING_UNIT = '安庆市山峡化工有限公司'
const DOC_FONT = "SimSun, 'Songti SC', serif"
const FONT_TITLE = 'font-size:26px;font-weight:bold;'
const FONT_BODY = 'font-size:20px;'
@@ -29,17 +31,18 @@ const INFO_COLGROUP = `<colgroup>
<col span="7" style="width:14.28%;" />
</colgroup>`
/**
* 物料表列宽:加宽编号/名称,收窄规格/单位;前 5 列合计供条款区使用。
* 物料编号 / 物料名称 / 规格 / 单位 / 数量 / 产品规格 / 实收数量
* 物料表列宽:单据编号 + 加宽编号/名称,收窄规格/单位。
* 单据编号 / 物料编号 / 物料名称 / 规格 / 单位 / 数量 / 产品规格 / 实收数量
*/
const PRODUCT_COLGROUP = `<colgroup>
<col style="width:18%;" />
<col style="width:24%;" />
<col style="width:14%;" />
<col style="width:14%;" />
<col style="width:20%;" />
<col style="width:8%;" />
<col style="width:6%;" />
<col style="width:12%;" />
<col style="width:16%;" />
<col style="width:16%;" />
<col style="width:10%;" />
<col style="width:14%;" />
<col style="width:14%;" />
</colgroup>`
/** 物料网格 padding(编辑器预览) */
@@ -62,16 +65,12 @@ const PRODUCT_GRID_CELL = `padding:${PRODUCT_GRID_CELL_PADDING};box-sizing:borde
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,定位在地址单元格右上角(相对原位置向上 66px) */
/** 入库单印章:public/sxshzyz.png(兼容已保存模板的红章开关) */
const INBOUND_SLIP_SEAL_IMAGE_SRC = '/sxshzyz.png'
const INBOUND_SLIP_SEAL_TOP = -66
const INBOUND_SLIP_SEAL_IMAGE_STYLE = `position:absolute;right:4px;top:${INBOUND_SLIP_SEAL_TOP}px;width:140px;height:140px;opacity:0.85;z-index:10;`
const HANDWRITE_BLANK_INNER = '&nbsp;&nbsp;&nbsp;&nbsp;'
const HANDWRITE_BLANK = `<span class="inbound-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;`
@@ -87,9 +86,9 @@ const INFO_VALUE_INLINE_LEFT = `flex:1;min-width:0;box-sizing:border-box;border-
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 = 24
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 = 24
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
@@ -99,7 +98,7 @@ function buildInboundSlipSignItem(label: string, valueContent: string, minWidth:
function buildInboundSlipSignRowHtml(): string {
return `<tr class="inbound-slip-sign-row">
<td colspan="7" class="inbound-slip-sign-row-cell" style="border:none !important;padding:${TABLE_ROW_PADDING};text-align:center;${FONT_SIGN}background:transparent !important;">
<td colspan="8" class="inbound-slip-sign-row-cell" style="border:none !important;padding:${TABLE_ROW_PADDING};text-align:center;${FONT_SIGN}background:transparent !important;">
<div class="inbound-slip-sign-inner">
${buildInboundSlipSignItem('仓库:', '&nbsp;', '80px')}
${buildInboundSlipSignItem('审核:', '&nbsp;', '80px')}
@@ -149,6 +148,10 @@ function formatQty(value?: number | string | null): string {
return num.toFixed(3)
}
function buildDeliveryUnit(plan: Plan): string {
return plan.customerName?.trim() || plan.destinationName?.trim() || ''
}
function formatInboundSlipProductGridCellContent(content: string): string {
const plain = content
.trim()
@@ -161,15 +164,22 @@ function formatInboundSlipProductGridCellContent(content: string): string {
return plain || '&nbsp;'
}
function buildProductRows(products: PlanProduct[] = [], ctx?: InboundSlipRenderContext): string {
function buildProductRows(
products: PlanProduct[] = [],
planNo = '',
ctx?: InboundSlipRenderContext
): string {
if (!products.length) {
return `<tr><td class="inbound-slip-product-cell" colspan="7" style="${PRODUCT_CELL}">暂无产品信息</td></tr>`
return `<tr><td class="inbound-slip-product-cell" colspan="8" style="${PRODUCT_CELL}">暂无产品信息</td></tr>`
}
const planNoText = formatInboundSlipProductGridCellContent(planNo)
const dataRows = products
.map((product) => {
const qty = product.inboundQty ?? product.inboundEstimateQty ?? product.quantity ?? ''
return `<tr>
<td class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${planNoText}</td>
<td class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatInboundSlipProductGridCellContent(resolveSlipProductCode(product, ctx))}</td>
<td class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatInboundSlipProductGridCellContent(product.productName || '')}</td>
<td class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">${formatInboundSlipProductGridCellContent(product.model || '')}</td>
@@ -451,25 +461,8 @@ export function buildInboundSlipCompanySealHtml(): string {
return `<img src="${INBOUND_SLIP_SEAL_IMAGE_SRC}" alt="印章" style="${INBOUND_SLIP_SEAL_IMAGE_STYLE}" />`
}
const SEAL_BLOCK = `<div class="inbound-slip-seal-block" style="position:relative;padding:0;${FONT_BODY}${SEAL_BLOCK_LINE_STYLE}">
<!-- 印章图片 -->
{{companySeal}}
<div class="inbound-slip-seal-lines" style="position:relative;z-index:1;${SEAL_BLOCK_LINE_STYLE}margin:0;padding:0;">
地址:{{companyAddress}}<br />
送货单位固话:{{companyPhone}}<br />
联系人电话:{{contactPhone}}
</div>
</div>`
const SIDE_LABEL_CELL = `border:none !important;padding:0 !important;vertical-align:top;text-align:center;${FONT_SIDE}line-height:1.35;background:transparent !important;`
function buildInboundSlipTermsHtml(): string {
return `<div class="inbound-slip-terms-text" style="${TERMS_TEXT_STYLE}">
1.本送货单如购销双方未签订合同,视为有效;<br />
2.本次送货含桶${HANDWRITE_BLANK}个,回收${HANDWRITE_BLANK}个,结存${HANDWRITE_BLANK}个;
</div>`
}
/** 右侧联次:单列纵向,带圈序号 + 每字一行依次向下排列 */
export function buildInboundSlipSideLabelsHtml(): string {
return `<table class="inbound-slip-side-labels" style="border:none !important;border-collapse:collapse;margin:0 auto;width:auto;">
@@ -502,24 +495,10 @@ export function generateInboundSlipTemplate(_plan?: Plan): string {
<tr>
<td colspan="4" style="${INFO_CELL_LEFT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">收货单位:</span>
<span class="inbound-slip-info-label" style="${INFO_LABEL}">送货单位:</span>
<span class="inbound-slip-info-value-left" style="${INFO_VALUE_INLINE_LEFT}">{{receivingUnit}}&nbsp;</span>
</div>
</td>
<td colspan="3" style="${INFO_CELL_RIGHT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">单据编号:</span>
<span class="inbound-slip-info-value-center" style="${INFO_VALUE_INLINE_CENTER}">{{planNo}}&nbsp;</span>
</div>
</td>
</tr>
<tr>
<td colspan="4" style="${INFO_CELL_LEFT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">收货联系方式:</span>
<span class="inbound-slip-info-value-left" style="${INFO_VALUE_INLINE_LEFT}">{{receiveContact}}&nbsp;</span>
</div>
</td>
<td colspan="3" style="${INFO_CELL_RIGHT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">日&nbsp;期:</span>
@@ -531,10 +510,11 @@ export function generateInboundSlipTemplate(_plan?: Plan): string {
<div class="inbound-slip-section-gap inbound-slip-gap-before-product" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</div>
<!-- 2. 物料明细表(含备注、条款+印章;可拖表头/明细列宽行高) -->
<!-- 2. 物料明细表(含备注;可拖表头/明细列宽行高) -->
<table class="inbound-slip-product-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
${PRODUCT_COLGROUP}
<tr class="inbound-slip-product-header">
<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">单据编号</th>
<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">物料编号</th>
<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">物料名称</th>
<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">规格</th>
@@ -545,18 +525,10 @@ export function generateInboundSlipTemplate(_plan?: Plan): string {
</tr>
{{productTable}}
<tr>
<td colspan="7" class="inbound-slip-product-cell" style="${PRODUCT_TABLE_CELL_LEFT}">
<td colspan="8" class="inbound-slip-product-cell" style="${PRODUCT_TABLE_CELL_LEFT}">
备注:{{remark}}
</td>
</tr>
<tr>
<td colspan="5" class="inbound-slip-product-cell inbound-slip-terms-cell" style="${PRODUCT_TERMS_CELL_LEFT}">
${buildInboundSlipTermsHtml()}
</td>
<td colspan="2" class="inbound-slip-product-cell" style="${PRODUCT_TABLE_CELL_LEFT}">
${SEAL_BLOCK}
</td>
</tr>
</table>
<div class="inbound-slip-section-gap inbound-slip-gap-before-sign" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</div>
@@ -655,7 +627,7 @@ function patchInboundSlipProductGridCellAttrs(attrs: string): string {
function normalizeInboundSlipProductGridCell(html: string): string {
if (!html.includes('inbound-slip-doc') || !html.includes('inbound-slip-product-cell')) return html
let result = html.replace(/<tr([^>]*)>\s*(<th[\s\S]*?<\/th>\s*){7}\s*<\/tr>/g, (rowMatch) => {
let result = html.replace(/<tr([^>]*)>\s*(<th[\s\S]*?<\/th>\s*){8}\s*<\/tr>/g, (rowMatch) => {
if (!rowMatch.includes('物料编号') || rowMatch.includes('colspan')) return rowMatch
let row = rowMatch.includes('inbound-slip-product-header')
? rowMatch
@@ -665,7 +637,7 @@ function normalizeInboundSlipProductGridCell(html: string): string {
})
})
result = result.replace(/<tr([^>]*)>\s*(<td(?![^>]*colspan)[^>]*>[\s\S]*?<\/td>\s*){7}\s*<\/tr>/g, (rowMatch) => {
result = result.replace(/<tr([^>]*)>\s*(<td(?![^>]*colspan)[^>]*>[\s\S]*?<\/td>\s*){8}\s*<\/tr>/g, (rowMatch) => {
if (
rowMatch.includes('备注') ||
rowMatch.includes('1.本送货单') ||
@@ -862,9 +834,10 @@ function normalizeInboundSlipProductTableFooter(html: string): string {
let result = html
result = result.replace(/<td([^>]*colspan="7"[^>]*)>(\s*)备注:/g, (_match, attrs, space) => {
result = result.replace(/<td([^>]*colspan="[78]"[^>]*)>(\s*)备注:/g, (_match, attrs, space) => {
const withSpan = String(attrs).replace(/colspan="[78]"/, 'colspan="8"')
const patched = patchProductFooterCellAttrs(
attrs,
withSpan,
'inbound-slip-product-cell',
PRODUCT_TABLE_CELL_LEFT
)
@@ -949,33 +922,44 @@ function normalizeInboundSlipProductColWidths(html: string): string {
)
}
/** 条款区两行间距与括号内手写空白(兼容已保存模板) */
/** 移除条款+地址印章行(兼容已保存模板) */
function normalizeInboundSlipTermsText(html: string): string {
if (!html.includes('inbound-slip-doc') || !html.includes('1.本送货单')) {
if (!html.includes('inbound-slip-doc')) return html
if (
!html.includes('1.本送货单') &&
!html.includes('inbound-slip-terms-text') &&
!html.includes('inbound-slip-seal-block')
) {
return html
}
const termsHtml = buildInboundSlipTermsHtml()
if (html.includes('inbound-slip-terms-text')) {
return html.replace(/<div class="inbound-slip-terms-text"[\s\S]*?<\/div>/, termsHtml.trim())
}
return html.replace(
/1\.本送货单如购销双方未签订合同,视为有效;[\s\S]*?结存\([^)]*\)个;/,
termsHtml.trim()
// 条款+印章整行删除(同一 tr 内左右两格)
let result = html.replace(
/<tr[^>]*>\s*<td[^>]*colspan="[45]"[^>]*>[\s\S]*?(?:inbound-slip-terms-text|1\.本送货单)[\s\S]*?<\/tr>\s*/gi,
''
)
// 兜底:独立印章行
result = result.replace(
/<tr[^>]*>\s*<td[^>]*>[\s\S]*?inbound-slip-seal-block[\s\S]*?<\/tr>\s*/gi,
''
)
return result
}
/** 刷新已有间距行的 inline 样式(TinyMCE 保存后可能丢失) */
function refreshInboundSlipGapBeforeProduct(html: string): string {
return html.replace(
/<tr class="inbound-slip-gap-before-product">[\s\S]*?<\/tr>/,
`<tr class="inbound-slip-gap-before-product">\n <td colspan="7" class="inbound-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</td>\n </tr>`
)
return html
.replace(
/<tr class="inbound-slip-gap-before-product">[\s\S]*?<\/tr>/,
`<tr class="inbound-slip-gap-before-product">\n <td colspan="8" class="inbound-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</td>\n </tr>`
)
.replace(
/<div class="inbound-slip-section-gap inbound-slip-gap-before-product"[^>]*>[\s\S]*?<\/div>/,
`<div class="inbound-slip-section-gap inbound-slip-gap-before-product" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</div>`
)
}
/** 在收货联系方式行与物料表之间插入间距行(兼容已保存模板) */
/** 在信息区与物料表之间插入间距(兼容已保存模板) */
function normalizeInboundSlipGapBeforeProduct(html: string): string {
if (!html.includes('inbound-slip-doc')) {
return html
@@ -985,16 +969,21 @@ function normalizeInboundSlipGapBeforeProduct(html: string): string {
return refreshInboundSlipGapBeforeProduct(html)
}
const gapRow = `<tr class="inbound-slip-gap-before-product"><td colspan="7" class="inbound-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</td></tr>\n `
const gapRow = `<tr class="inbound-slip-gap-before-product"><td colspan="8" class="inbound-slip-gap-before-product-cell" style="${GAP_BEFORE_PRODUCT_CELL}">&nbsp;</td></tr>\n `
const productHeader = `(?:单据编号|物料编号)`
const withComment = html.replace(
/(<span class="inbound-slip-info-label"[^>]*>日(?:&nbsp;)?期:<\/span>[\s\S]*?<\/tr>\s*)(<!--\s*中:产品明细表[\s\S]*?<tr>\s*<th[^>]*>物料编号)/,
new RegExp(
`(<span class="inbound-slip-info-label"[^>]*>日(?:&nbsp;)?期:</span>[\\s\\S]*?</tr>\\s*)(<!--\\s*中:产品明细表[\\s\\S]*?<tr>\\s*<th[^>]*>${productHeader})`
),
`$1${gapRow}$2`
)
if (withComment !== html) return withComment
const withHeader = html.replace(
/(<span class="inbound-slip-info-label"[^>]*>日(?:&nbsp;)?期:<\/span>[\s\S]*?<\/tr>\s*)(<tr>\s*<th[^>]*>物料编号)/,
new RegExp(
`(<span class="inbound-slip-info-label"[^>]*>日(?:&nbsp;)?期:</span>[\\s\\S]*?</tr>\\s*)(<tr>\\s*<th[^>]*>${productHeader})`
),
`$1${gapRow}$2`
)
if (withHeader !== html) return withHeader
@@ -1007,10 +996,15 @@ function normalizeInboundSlipGapBeforeProduct(html: string): string {
/** 刷新签字区上方间距行 inline 样式 */
function refreshInboundSlipGapBeforeSign(html: string): string {
return html.replace(
/<tr class="inbound-slip-gap-before-sign">[\s\S]*?<\/tr>/,
`<tr class="inbound-slip-gap-before-sign">\n <td colspan="7" class="inbound-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</td>\n </tr>`
)
return html
.replace(
/<tr class="inbound-slip-gap-before-sign">[\s\S]*?<\/tr>/,
`<tr class="inbound-slip-gap-before-sign">\n <td colspan="8" class="inbound-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</td>\n </tr>`
)
.replace(
/<div class="inbound-slip-section-gap inbound-slip-gap-before-sign"[^>]*>[\s\S]*?<\/div>/,
`<div class="inbound-slip-section-gap inbound-slip-gap-before-sign" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</div>`
)
}
/** 在物料表与签字区之间插入间距行(兼容已保存模板) */
@@ -1021,16 +1015,16 @@ function normalizeInboundSlipGapBeforeSign(html: string): string {
return refreshInboundSlipGapBeforeSign(html)
}
const gapRow = `<tr class="inbound-slip-gap-before-sign"><td colspan="7" class="inbound-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</td></tr>\n\n `
const gapRow = `<tr class="inbound-slip-gap-before-sign"><td colspan="8" class="inbound-slip-gap-before-sign-cell" style="${GAP_BEFORE_SIGN_CELL}">&nbsp;</td></tr>\n\n `
const withComment = html.replace(
/(<\/tr>\s*)(<!--\s*下:签字区[\s\S]*?<tr>\s*<td colspan="7"[^>]*>\s*仓库:)/,
/(<\/tr>\s*)(<!--\s*下:签字区[\s\S]*?<tr>\s*<td colspan="[78]"[^>]*>\s*仓库:)/,
`$1${gapRow}$2`
)
if (withComment !== html) return withComment
return html.replace(
/(inbound-slip-seal-block[\s\S]*?<\/tr>\s*)(<tr>\s*<td colspan="7"[^>]*>\s*仓库:)/,
/(inbound-slip-seal-block[\s\S]*?<\/tr>\s*)(<tr>\s*<td colspan="[78]"[^>]*>\s*仓库:)/,
`$1${gapRow}$2`
)
}
@@ -1065,7 +1059,7 @@ function normalizeInboundSlipSignRow(html: string): string {
if (!html.includes('inbound-slip-doc') || !html.includes('仓库:')) return html
let result = html.replace(
/<tr([^>]*)>\s*<td colspan="7"([^>]*)>([\s\S]*?仓库:[\s\S]*?司机\/送货人\(附车牌\)[^<]*[\s\S]*?)<\/td>\s*<\/tr>/g,
/<tr([^>]*)>\s*<td colspan="[78]"([^>]*)>([\s\S]*?仓库:[\s\S]*?司机\/送货人\(附车牌\)[^<]*[\s\S]*?)<\/td>\s*<\/tr>/g,
(match, trAttrs, tdAttrs, content) => {
if (!content.includes('inbound-slip-sign-value') || content.includes('inbound-slip-sign-inner')) {
return match
@@ -1097,7 +1091,7 @@ function normalizeInboundSlipSignRow(html: string): string {
nextTdAttrs = `${nextTdAttrs} class="inbound-slip-sign-row-cell"`
}
return `<tr${trClassAttr}>\n <td colspan="7"${nextTdAttrs}>\n <div class="inbound-slip-sign-inner">${wrappedContent.trim()}</div>\n </td>\n </tr>`
return `<tr${trClassAttr}>\n <td colspan="8"${nextTdAttrs}>\n <div class="inbound-slip-sign-inner">${wrappedContent.trim()}</div>\n </td>\n </tr>`
}
)
@@ -1109,6 +1103,82 @@ function normalizeInboundSlipSignRow(html: string): string {
return normalizeInboundSlipSignLabelMarkup(result)
}
/** 信息区:送货单位 + 日期(兼容旧「收货单位/单据编号/收货联系方式」布局) */
function normalizeInboundSlipInfoLayout(html: string): string {
if (!html.includes('inbound-slip-info-table')) return html
const infoMatch = html.match(/<table class="inbound-slip-info-table"[^>]*>[\s\S]*?<\/table>/i)
if (!infoMatch) return html
const infoHtml = infoMatch[0]
const needsMigrate =
infoHtml.includes('收货单位:') ||
infoHtml.includes('收货联系方式:') ||
infoHtml.includes('单据编号:') ||
!infoHtml.includes('送货单位:') ||
(infoHtml.match(/<tr[\s>]/g) || []).length > 1
if (!needsMigrate) return html
const newInfoTable = `<table class="inbound-slip-info-table" style="width:100%;border-collapse:collapse;table-layout:fixed;border:none !important;">
${INFO_COLGROUP}
<tr>
<td colspan="4" style="${INFO_CELL_LEFT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">送货单位:</span>
<span class="inbound-slip-info-value-left" style="${INFO_VALUE_INLINE_LEFT}">{{receivingUnit}}&nbsp;</span>
</div>
</td>
<td colspan="3" style="${INFO_CELL_RIGHT}">
<div class="inbound-slip-info-row" style="${INFO_ROW}">
<span class="inbound-slip-info-label" style="${INFO_LABEL}">日&nbsp;期:</span>
<span class="inbound-slip-info-value-center" style="${INFO_VALUE_INLINE_CENTER}">{{planDateChinese}}&nbsp;</span>
</div>
</td>
</tr>
</table>`
return html.replace(infoMatch[0], newInfoTable)
}
/** 物料表:表头补「单据编号」列,并把旧 7 列数据行升为 8 列(兼容已保存模板) */
function normalizeInboundSlipProductPlanNoColumn(html: string): string {
if (!html.includes('inbound-slip-product-table')) return html
let result = html
if (!/>单据编号<\/th>/.test(result)) {
result = result.replace(
/(<tr[^>]*class="[^"]*inbound-slip-product-header[^"]*"[^>]*>\s*)(<th[^>]*>物料编号<\/th>)/,
`$1<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">单据编号</th>\n $2`
)
result = result.replace(
/(<tr>\s*)(<th[^>]*>物料编号<\/th>)/,
`$1<th class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_TH}">单据编号</th>\n $2`
)
}
// 旧 7 列明细行:在首格前插入单据编号占位
result = result.replace(
/<tr([^>]*)>\s*((?:<td(?![^>]*colspan)[^>]*>[\s\S]*?<\/td>\s*){7})<\/tr>/g,
(rowMatch, trAttrs, cells) => {
if (
rowMatch.includes('备注') ||
rowMatch.includes('1.本送货单') ||
rowMatch.includes('仓库:') ||
rowMatch.includes('暂无产品信息') ||
rowMatch.includes('单据编号')
) {
return rowMatch
}
return `<tr${trAttrs}>\n <td class="inbound-slip-product-cell inbound-slip-product-grid-cell" style="${PRODUCT_GRID_CELL}">{{planNo}}</td>\n ${cells}</tr>`
}
)
result = result.replace(/colspan="7"/g, 'colspan="8"')
return result
}
/**
* 旧版一体 layout-table / 曾拆出的 footer-table → 方案A:
* 信息表 + 物料表(含备注条款) + 签字表。
@@ -1143,6 +1213,7 @@ function migrateInboundSlipSplitTables(html: string): string {
(_full, inner: string) => {
const headerMatch =
inner.match(/<tr[^>]*class="[^"]*inbound-slip-product-header[^"]*"[\s\S]*?<\/tr>/i) ||
inner.match(/<tr>\s*<th[^>]*>单据编号[\s\S]*?<\/tr>/i) ||
inner.match(/<tr>\s*<th[^>]*>物料编号[\s\S]*?<\/tr>/i)
if (!headerMatch) return _full
@@ -1287,12 +1358,12 @@ export function replaceInboundSlipPlaceholders(
const replacements: Record<string, string> = {
'{{companyName}}': INBOUND_SLIP_DEFAULTS.companyName,
'{{receivingUnit}}': INBOUND_SLIP_DEFAULTS.receivingUnit,
'{{receivingUnit}}': buildDeliveryUnit(plan),
'{{planNo}}': plan.planNo || '',
'{{receiveContact}}': '',
'{{driverInfo}}': '',
'{{planDateChinese}}': formatPlanDateChinese(plan.planDate),
'{{productTable}}': buildProductRows(plan.products, ctx),
'{{productTable}}': buildProductRows(plan.products, plan.planNo || '', ctx),
'{{remark}}': plan.remark || '',
'{{companyAddress}}': INBOUND_SLIP_DEFAULTS.companyAddress,
'{{companyPhone}}': INBOUND_SLIP_DEFAULTS.companyPhone,
@@ -1310,7 +1381,13 @@ export function replaceInboundSlipPlaceholders(
normalizeInboundSlipProductTableFooter(
normalizeInboundSlipProductColWidths(
normalizeInboundSlipProductGridCell(
normalizeInboundSlipGapBeforeProduct(normalizeInboundSlipSideLabelsPlaceholder(htmlContent))
normalizeInboundSlipProductPlanNoColumn(
normalizeInboundSlipInfoLayout(
normalizeInboundSlipGapBeforeProduct(
normalizeInboundSlipSideLabelsPlaceholder(htmlContent)
)
)
)
)
)
)
@@ -1330,9 +1407,10 @@ export function replaceInboundSlipPlaceholders(
export function restoreInboundSlipPlaceholders(template: string, plan: Plan): string {
let result = template
const deliveryUnit = buildDeliveryUnit(plan)
const dynamicReplacements: Record<string, string> = {
'{{companyName}}': INBOUND_SLIP_DEFAULTS.companyName,
'{{receivingUnit}}': INBOUND_SLIP_DEFAULTS.receivingUnit,
'{{receivingUnit}}': deliveryUnit,
'{{planNo}}': plan.planNo || '',
'{{planDateChinese}}': formatPlanDateChinese(plan.planDate),
'{{remark}}': plan.remark || '',
@@ -1347,9 +1425,14 @@ export function restoreInboundSlipPlaceholders(template: string, plan: Plan): st
result = result.replace(new RegExp(escaped, 'g'), placeholder)
}
// 旧模板写死的本公司名 → 送货单位占位符
if (deliveryUnit !== LEGACY_RECEIVING_UNIT) {
result = result.replace(new RegExp(LEGACY_RECEIVING_UNIT.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '{{receivingUnit}}')
}
// 产品明细行还原为占位符(表头「实收数量」与「备注」行之间)
result = result.replace(
/(<th[^>]*>实收数量<\/th>\s*<\/tr>\s*)([\s\S]*?)(\s*<tr>\s*<td colspan="7"[^>]*>\s*备注:)/,
/(<th[^>]*>实收数量<\/th>\s*<\/tr>\s*)([\s\S]*?)(\s*<tr>\s*<td colspan="[78]"[^>]*>\s*备注:)/,
'$1{{productTable}}$3'
)
+39
View File
@@ -0,0 +1,39 @@
/** 运输单据打印弹窗:红章显隐(本次弹窗有效) */
export const SLIP_SEAL_IMG_RE = /<img[^>]*alt=["']印章["'][^>]*>/gi
export function stripSlipSealImages(html: string): string {
return html.replace(SLIP_SEAL_IMG_RE, '')
}
/**
* 将公司章插回单据 HTML。
* 优先落在「印章图片」注释后;否则插入指定 seal-block 容器开头。
*/
export function injectSlipSealImages(
html: string,
sealHtml: string,
sealBlockClass: string
): string {
let result = stripSlipSealImages(html)
if (!sealHtml) return result
if (/<!--\s*印章图片[^>]*-->/.test(result)) {
let sealCommentIndex = 0
return result.replace(/<!--\s*印章图片[^>]*-->/g, (match) => {
sealCommentIndex += 1
if (sealCommentIndex === 1) return `${match}\n ${sealHtml}`
return match
})
}
if (sealBlockClass && result.includes(sealBlockClass)) {
const escaped = sealBlockClass.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return result.replace(
new RegExp(`(<div class="${escaped}"[^>]*>)`),
`$1\n <!-- 印章图片 -->\n ${sealHtml}\n `
)
}
return result
}
+20 -10
View File
@@ -87,9 +87,9 @@ const INFO_VALUE_INLINE_LEFT = `flex:1;min-width:0;box-sizing:border-box;border-
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 = 24
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 = 24
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
@@ -1434,10 +1434,15 @@ function normalizeSubpackSlipRemoveTermsRow(html: string): string {
/** 刷新已有间距行的 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}">&nbsp;</td>\n </tr>`
)
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}">&nbsp;</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}">&nbsp;</div>`
)
}
/** 在收货联系方式行与物料表之间插入间距行(兼容已保存模板) */
@@ -1472,10 +1477,15 @@ function normalizeSubpackSlipGapBeforeProduct(html: string): string {
/** 刷新签字区上方间距行 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}">&nbsp;</td>\n </tr>`
)
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}">&nbsp;</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}">&nbsp;</div>`
)
}
/** 在物料表与签字区之间插入间距行(兼容已保存模板) */