提交
This commit is contained in:
1 parent
506cc7991a
commit
1dfcc66d2c
269 files changed
+1413333
-55990
No files matched your search
@@ -2,7 +2,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="isPreviewMode ? '预览送(提)货单' : '编辑送(提)货单'"
|
||||
width="1500px"
|
||||
align-center
|
||||
append-to-body
|
||||
@@ -12,6 +11,24 @@
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<template #header>
|
||||
<div class="pl-slip-print-dialog-header">
|
||||
<span class="pl-slip-print-dialog-title">{{ isPreviewMode ? '预览送(提)货单' : '编辑送(提)货单' }}</span>
|
||||
<el-radio-group
|
||||
v-model="selectedCompanyKey"
|
||||
class="pl-slip-company-radio"
|
||||
@change="handleCompanyChange"
|
||||
>
|
||||
<el-radio
|
||||
v-for="item in PL_SLIP_COMPANY_OPTIONS"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<TinyMceEditor
|
||||
v-model="slipContent"
|
||||
:height="editorHeight"
|
||||
@@ -76,6 +93,11 @@ import {
|
||||
type DeliverySlipRenderContext
|
||||
} from '@/utils/pl-delivery-slip-template'
|
||||
import { buildBatchPrintPlanView, normalizePlanIds } from '@/utils/pl-batch-print'
|
||||
import {
|
||||
applyPlSlipCompanyToHtml,
|
||||
PL_SLIP_COMPANY_OPTIONS,
|
||||
type PlSlipCompanyKey
|
||||
} from '@/utils/pl-slip-company'
|
||||
import { injectSlipSealImages, stripSlipSealImages } from '@/utils/pl-slip-red-seal'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
@@ -85,6 +107,7 @@ const isPreviewMode = ref(false)
|
||||
const isBlankMode = ref(false)
|
||||
/** 打印弹窗内红章显隐(仅本次弹窗有效,打开时默认显示) */
|
||||
const showRedSeal = ref(true)
|
||||
const selectedCompanyKey = ref<PlSlipCompanyKey>('shanxia')
|
||||
const saveLoading = ref(false)
|
||||
const pdfGenerating = ref(false)
|
||||
const currentPlanId = ref('')
|
||||
@@ -95,6 +118,10 @@ const editorHeight = computed(() => {
|
||||
return Math.min(860, Math.max(720, window.innerHeight - 200))
|
||||
})
|
||||
|
||||
function handleCompanyChange(key: PlSlipCompanyKey) {
|
||||
slipContent.value = applyPlSlipCompanyToHtml(slipContent.value, key, { swapSeal: true })
|
||||
}
|
||||
|
||||
async function waitForImagesLoaded(container: HTMLElement) {
|
||||
const imgs = Array.from(container.querySelectorAll('img'))
|
||||
await Promise.all(
|
||||
@@ -150,6 +177,7 @@ function openBlank() {
|
||||
isBlankMode.value = true
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = false
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
slipContent.value = renderBlankDeliverySlipHtml()
|
||||
visible.value = true
|
||||
}
|
||||
@@ -164,6 +192,7 @@ async function open(planIdOrIds: string | string[], options?: { customerId?: str
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
try {
|
||||
ElMessage.info({
|
||||
message: ids.length > 1 ? '正在生成合打送(提)货单...' : '正在生成送(提)货单...',
|
||||
@@ -212,6 +241,7 @@ function handleClosed() {
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
}
|
||||
|
||||
/** 切换红章显示/隐藏(同步影响编辑器、打印、PDF) */
|
||||
@@ -223,7 +253,7 @@ function toggleRedSeal() {
|
||||
}
|
||||
slipContent.value = injectSlipSealImages(
|
||||
slipContent.value,
|
||||
buildDeliverySlipCompanySealHtml(),
|
||||
buildDeliverySlipCompanySealHtml(selectedCompanyKey.value),
|
||||
'delivery-slip-seal-block'
|
||||
)
|
||||
showRedSeal.value = true
|
||||
@@ -246,7 +276,9 @@ async function handleSaveTemplate() {
|
||||
try {
|
||||
const plan = (await refreshPlan()) || currentPlan.value
|
||||
if (!plan) throw new Error('计划数据无效')
|
||||
const finalTemplate = restoreDeliverySlipPlaceholders(slipContent.value, plan)
|
||||
// 保存前归一为山峡抬头与山峡红章,避免把云栋切换态写入模板
|
||||
const normalized = applyPlSlipCompanyToHtml(slipContent.value, 'shanxia', { swapSeal: true })
|
||||
const finalTemplate = restoreDeliverySlipPlaceholders(normalized, plan)
|
||||
await planApi.saveDeliverySlipTemplate(currentPlanId.value, finalTemplate)
|
||||
ElMessage.success('模板保存成功')
|
||||
} catch (error) {
|
||||
@@ -305,6 +337,7 @@ async function handleGeneratePdf() {
|
||||
if (plan) {
|
||||
const renderCtx = await buildSlipRenderContext(plan)
|
||||
htmlContent = replaceDeliverySlipPlaceholders(htmlContent, plan, renderCtx)
|
||||
htmlContent = applyPlSlipCompanyToHtml(htmlContent, selectedCompanyKey.value, { swapSeal: true })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +410,38 @@ defineExpose({ open, openBlank })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pl-slip-print-dialog-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
padding-right: 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pl-slip-print-dialog-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.pl-slip-company-radio {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
justify-content: center;
|
||||
max-width: calc(100% - 280px);
|
||||
}
|
||||
|
||||
.delivery-slip-print-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="isPreviewMode ? '预览入库单' : '编辑入库单'"
|
||||
width="1500px"
|
||||
align-center
|
||||
append-to-body
|
||||
@@ -12,6 +11,24 @@
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<template #header>
|
||||
<div class="pl-slip-print-dialog-header">
|
||||
<span class="pl-slip-print-dialog-title">{{ isPreviewMode ? '预览入库单' : '编辑入库单' }}</span>
|
||||
<el-radio-group
|
||||
v-model="selectedCompanyKey"
|
||||
class="pl-slip-company-radio"
|
||||
@change="handleCompanyChange"
|
||||
>
|
||||
<el-radio
|
||||
v-for="item in PL_SLIP_COMPANY_OPTIONS"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<TinyMceEditor
|
||||
v-model="slipContent"
|
||||
:height="editorHeight"
|
||||
@@ -74,6 +91,11 @@ import {
|
||||
type InboundSlipRenderContext
|
||||
} from '@/utils/pl-inbound-slip-template'
|
||||
import { buildBatchPrintPlanView, normalizePlanIds } from '@/utils/pl-batch-print'
|
||||
import {
|
||||
applyPlSlipCompanyToHtml,
|
||||
PL_SLIP_COMPANY_OPTIONS,
|
||||
type PlSlipCompanyKey
|
||||
} from '@/utils/pl-slip-company'
|
||||
import { injectSlipSealImages, stripSlipSealImages } from '@/utils/pl-slip-red-seal'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
@@ -83,6 +105,7 @@ const isPreviewMode = ref(false)
|
||||
const isBlankMode = ref(false)
|
||||
/** 打印弹窗内红章显隐(仅本次弹窗有效,打开时默认显示) */
|
||||
const showRedSeal = ref(true)
|
||||
const selectedCompanyKey = ref<PlSlipCompanyKey>('shanxia')
|
||||
const saveLoading = ref(false)
|
||||
const pdfGenerating = ref(false)
|
||||
const currentPlanId = ref('')
|
||||
@@ -93,6 +116,10 @@ const editorHeight = computed(() => {
|
||||
return Math.min(860, Math.max(720, window.innerHeight - 200))
|
||||
})
|
||||
|
||||
function handleCompanyChange(key: PlSlipCompanyKey) {
|
||||
slipContent.value = applyPlSlipCompanyToHtml(slipContent.value, key)
|
||||
}
|
||||
|
||||
async function waitForImagesLoaded(container: HTMLElement) {
|
||||
const imgs = Array.from(container.querySelectorAll('img'))
|
||||
await Promise.all(
|
||||
@@ -148,6 +175,7 @@ function openBlank() {
|
||||
isBlankMode.value = true
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
slipContent.value = renderBlankInboundSlipHtml()
|
||||
visible.value = true
|
||||
}
|
||||
@@ -162,6 +190,7 @@ async function open(planIdOrIds: string | string[]) {
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
try {
|
||||
ElMessage.info({ message: ids.length > 1 ? '正在生成合打入库单...' : '正在生成入库单...', duration: 800 })
|
||||
const plans = await Promise.all(
|
||||
@@ -202,6 +231,7 @@ function handleClosed() {
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
}
|
||||
|
||||
/** 切换红章显示/隐藏(同步影响编辑器、打印、PDF) */
|
||||
@@ -236,7 +266,9 @@ async function handleSaveTemplate() {
|
||||
try {
|
||||
const plan = (await refreshPlan()) || currentPlan.value
|
||||
if (!plan) throw new Error('计划数据无效')
|
||||
const finalTemplate = restoreInboundSlipPlaceholders(slipContent.value, plan)
|
||||
// 保存前归一为山峡抬头,避免把云栋切换态写入模板
|
||||
const normalized = applyPlSlipCompanyToHtml(slipContent.value, 'shanxia')
|
||||
const finalTemplate = restoreInboundSlipPlaceholders(normalized, plan)
|
||||
await planApi.saveInboundSlipTemplate(currentPlanId.value, finalTemplate)
|
||||
ElMessage.success('模板保存成功')
|
||||
} catch (error) {
|
||||
@@ -295,6 +327,7 @@ async function handleGeneratePdf() {
|
||||
if (plan) {
|
||||
const renderCtx = await buildSlipRenderContext(plan)
|
||||
htmlContent = replaceInboundSlipPlaceholders(htmlContent, plan, renderCtx)
|
||||
htmlContent = applyPlSlipCompanyToHtml(htmlContent, selectedCompanyKey.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,6 +400,38 @@ defineExpose({ open, openBlank })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pl-slip-print-dialog-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
padding-right: 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pl-slip-print-dialog-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.pl-slip-company-radio {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
justify-content: center;
|
||||
max-width: calc(100% - 280px);
|
||||
}
|
||||
|
||||
.inbound-slip-print-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="isPreviewMode ? '预览分装单' : '编辑分装单'"
|
||||
width="1500px"
|
||||
align-center
|
||||
append-to-body
|
||||
@@ -12,6 +11,24 @@
|
||||
:close-on-click-modal="false"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<template #header>
|
||||
<div class="pl-slip-print-dialog-header">
|
||||
<span class="pl-slip-print-dialog-title">{{ isPreviewMode ? '预览分装单' : '编辑分装单' }}</span>
|
||||
<el-radio-group
|
||||
v-model="selectedCompanyKey"
|
||||
class="pl-slip-company-radio"
|
||||
@change="handleCompanyChange"
|
||||
>
|
||||
<el-radio
|
||||
v-for="item in PL_SLIP_COMPANY_OPTIONS"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</template>
|
||||
<TinyMceEditor
|
||||
v-model="slipContent"
|
||||
:height="editorHeight"
|
||||
@@ -77,6 +94,11 @@ import {
|
||||
type SubpackSlipRenderContext
|
||||
} from '@/utils/pl-subpack-slip-template'
|
||||
import { buildBatchPrintPlanView, normalizePlanIds } from '@/utils/pl-batch-print'
|
||||
import {
|
||||
applyPlSlipCompanyToHtml,
|
||||
PL_SLIP_COMPANY_OPTIONS,
|
||||
type PlSlipCompanyKey
|
||||
} from '@/utils/pl-slip-company'
|
||||
import { injectSlipSealImages, stripSlipSealImages } from '@/utils/pl-slip-red-seal'
|
||||
import { getPlSlipPdfCanvasScale } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
@@ -86,6 +108,7 @@ const isPreviewMode = ref(false)
|
||||
const isBlankMode = ref(false)
|
||||
/** 打印弹窗内红章显隐(仅本次弹窗有效,打开时默认显示) */
|
||||
const showRedSeal = ref(true)
|
||||
const selectedCompanyKey = ref<PlSlipCompanyKey>('shanxia')
|
||||
const saveLoading = ref(false)
|
||||
const pdfGenerating = ref(false)
|
||||
const currentPlanId = ref('')
|
||||
@@ -97,6 +120,10 @@ const editorHeight = computed(() => {
|
||||
return Math.min(860, Math.max(720, window.innerHeight - 200))
|
||||
})
|
||||
|
||||
function handleCompanyChange(key: PlSlipCompanyKey) {
|
||||
slipContent.value = applyPlSlipCompanyToHtml(slipContent.value, key)
|
||||
}
|
||||
|
||||
async function waitForImagesLoaded(container: HTMLElement) {
|
||||
const imgs = Array.from(container.querySelectorAll('img'))
|
||||
await Promise.all(
|
||||
@@ -142,6 +169,7 @@ function openBlank() {
|
||||
isBlankMode.value = true
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = false
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
slipContent.value = renderBlankSubpackSlipHtml()
|
||||
visible.value = true
|
||||
}
|
||||
@@ -156,6 +184,7 @@ async function open(planIdOrIds: string | string[]) {
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
try {
|
||||
ElMessage.info({ message: ids.length > 1 ? '正在生成合打分装单...' : '正在生成分装单...', duration: 800 })
|
||||
const plans = await Promise.all(
|
||||
@@ -202,6 +231,7 @@ function handleClosed() {
|
||||
isBlankMode.value = false
|
||||
isPreviewMode.value = false
|
||||
showRedSeal.value = true
|
||||
selectedCompanyKey.value = 'shanxia'
|
||||
}
|
||||
|
||||
/** 切换红章显示/隐藏(同步影响编辑器、打印、PDF) */
|
||||
@@ -238,7 +268,9 @@ async function handleSaveTemplate() {
|
||||
try {
|
||||
const plan = (await refreshPlan()) || currentPlan.value
|
||||
if (!plan) throw new Error('计划数据无效')
|
||||
const finalTemplate = restoreSubpackSlipPlaceholders(slipContent.value, plan)
|
||||
// 保存前归一为山峡抬头,避免把云栋切换态写入模板
|
||||
const normalized = applyPlSlipCompanyToHtml(slipContent.value, 'shanxia')
|
||||
const finalTemplate = restoreSubpackSlipPlaceholders(normalized, plan)
|
||||
await planApi.saveSubpackSlipTemplate(currentPlanId.value, finalTemplate)
|
||||
ElMessage.success('模板保存成功')
|
||||
} catch (error) {
|
||||
@@ -297,6 +329,7 @@ async function handleGeneratePdf() {
|
||||
if (plan) {
|
||||
const renderCtx = await buildSlipRenderContext(plan)
|
||||
htmlContent = replaceSubpackSlipPlaceholders(htmlContent, plan, renderCtx)
|
||||
htmlContent = applyPlSlipCompanyToHtml(htmlContent, selectedCompanyKey.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +402,38 @@ defineExpose({ open, openBlank })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.pl-slip-print-dialog-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-height: 24px;
|
||||
padding-right: 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pl-slip-print-dialog-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-shrink: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.pl-slip-company-radio {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 20px;
|
||||
justify-content: center;
|
||||
max-width: calc(100% - 280px);
|
||||
}
|
||||
|
||||
.subpack-slip-print-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Plan, PlanProduct } from '@/api/pl'
|
||||
import type { SlipPlanProduct } from '@/utils/pl-batch-print'
|
||||
import { formatRemarkForSlipHtml } from '@/utils/pl-batch-print'
|
||||
import { getPlSlipSealSrc, type PlSlipCompanyKey } from '@/utils/pl-slip-company'
|
||||
import { applyPdfStableTableCellBorders } from '@/utils/pl-slip-pdf-render'
|
||||
|
||||
/** 送(提)货单打印默认公司信息(可在富文本中修改) */
|
||||
@@ -597,9 +598,9 @@ export const DELIVERY_SLIP_PRINT_CSS = `
|
||||
|
||||
export const DELIVERY_SLIP_STYLE_BLOCK = `<style type="text/css">${DELIVERY_SLIP_PRINT_CSS}</style>`
|
||||
|
||||
/** 生成公司印章 HTML(地址/电话单元格右上角) */
|
||||
export function buildDeliverySlipCompanySealHtml(): string {
|
||||
return `<img class="delivery-slip-seal-img" src="${DELIVERY_SLIP_SEAL_IMAGE_SRC}" alt="印章" style="${DELIVERY_SLIP_SEAL_IMAGE_STYLE}" />`
|
||||
/** 生成公司印章 HTML(地址/电话单元格右上角;云栋用合同同款 yd.png) */
|
||||
export function buildDeliverySlipCompanySealHtml(companyKey: PlSlipCompanyKey = 'shanxia'): string {
|
||||
return `<img class="delivery-slip-seal-img" src="${getPlSlipSealSrc(companyKey)}" alt="印章" style="${DELIVERY_SLIP_SEAL_IMAGE_STYLE}" />`
|
||||
}
|
||||
|
||||
const SEAL_BLOCK = `<div class="delivery-slip-seal-block" style="position:relative;padding:0;${FONT_BODY}${SEAL_BLOCK_LINE_STYLE}">
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/** 运输单据打印:抬头公司(山峡 / 云栋)切换 */
|
||||
|
||||
import ydSealImg from '@/assets/images/yd.png'
|
||||
|
||||
export const PL_SLIP_COMPANY_SHANXIA = '安庆市山峡化工有限公司'
|
||||
export const PL_SLIP_COMPANY_YUNDONG = '安徽云栋化工有限公司'
|
||||
|
||||
export type PlSlipCompanyKey = 'shanxia' | 'yundong'
|
||||
|
||||
export const PL_SLIP_COMPANY_OPTIONS: { label: string; value: PlSlipCompanyKey }[] = [
|
||||
{ label: PL_SLIP_COMPANY_SHANXIA, value: 'shanxia' },
|
||||
{ label: PL_SLIP_COMPANY_YUNDONG, value: 'yundong' }
|
||||
]
|
||||
|
||||
/** 山峡红章(与现有单据 public 路径一致) */
|
||||
export const PL_SLIP_SHANXIA_SEAL_SRC = '/sxshzyz.png'
|
||||
/** 云栋红章(与合同管理 yd.png 一致) */
|
||||
export const PL_SLIP_YUNDONG_SEAL_SRC = ydSealImg as string
|
||||
|
||||
const TITLE_HEADING_SELECTOR =
|
||||
'.inbound-slip-title-heading, .delivery-slip-title-heading, .subpack-slip-title-heading'
|
||||
|
||||
export function getPlSlipCompanyName(key: PlSlipCompanyKey): string {
|
||||
return key === 'yundong' ? PL_SLIP_COMPANY_YUNDONG : PL_SLIP_COMPANY_SHANXIA
|
||||
}
|
||||
|
||||
export function getPlSlipSealSrc(key: PlSlipCompanyKey): string {
|
||||
return key === 'yundong' ? PL_SLIP_YUNDONG_SEAL_SRC : PL_SLIP_SHANXIA_SEAL_SRC
|
||||
}
|
||||
|
||||
function isSlipLogoImg(img: HTMLImageElement): boolean {
|
||||
const alt = (img.getAttribute('alt') || '').trim()
|
||||
if (alt === '印章') return false
|
||||
if (alt === 'logo') return true
|
||||
const src = img.getAttribute('src') || ''
|
||||
if (src.includes('watermask')) return false
|
||||
return src.includes('logo.gif') || /\/logo(\.|$)/.test(src) || src.includes('/logo')
|
||||
}
|
||||
|
||||
function isCompanyTitleText(text: string): boolean {
|
||||
const normalized = text.replace(/\u00a0/g, ' ').replace(/\s+/g, '').trim()
|
||||
if (!normalized) return false
|
||||
if (normalized.includes('入库') || normalized.includes('货单') || normalized.includes('分装')) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
normalized === PL_SLIP_COMPANY_SHANXIA.replace(/\s+/g, '') ||
|
||||
normalized === PL_SLIP_COMPANY_YUNDONG.replace(/\s+/g, '') ||
|
||||
normalized.includes('山峡化工') ||
|
||||
normalized.includes('云栋化工')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按抬头公司更新单据 HTML:标题公司名、山峡 logo 显隐;可选替换红章图。
|
||||
* 仅本次弹窗预览有效,不改后端默认模板生成逻辑。
|
||||
*/
|
||||
export function applyPlSlipCompanyToHtml(
|
||||
html: string,
|
||||
companyKey: PlSlipCompanyKey,
|
||||
options?: { swapSeal?: boolean }
|
||||
): string {
|
||||
if (!html || typeof document === 'undefined') return html
|
||||
|
||||
const wrap = document.createElement('div')
|
||||
wrap.innerHTML = html
|
||||
const companyName = getPlSlipCompanyName(companyKey)
|
||||
const isYundong = companyKey === 'yundong'
|
||||
|
||||
wrap.querySelectorAll('img').forEach((img) => {
|
||||
if (!isSlipLogoImg(img as HTMLImageElement)) return
|
||||
;(img as HTMLImageElement).style.display = isYundong ? 'none' : ''
|
||||
})
|
||||
|
||||
wrap.querySelectorAll(TITLE_HEADING_SELECTOR).forEach((el) => {
|
||||
const text = (el.textContent || '').trim()
|
||||
if (!isCompanyTitleText(text)) return
|
||||
el.textContent = companyName
|
||||
})
|
||||
|
||||
if (options?.swapSeal) {
|
||||
const sealSrc = getPlSlipSealSrc(companyKey)
|
||||
wrap.querySelectorAll('img[alt="印章"]').forEach((img) => {
|
||||
img.setAttribute('src', sealSrc)
|
||||
})
|
||||
}
|
||||
|
||||
return wrap.innerHTML
|
||||
}
|
||||
@@ -186,10 +186,11 @@
|
||||
</ListTable>
|
||||
</div>
|
||||
|
||||
<div ref="expenseItemPagerRef" class="pagination list-pager-bar">
|
||||
<div ref="expenseItemPagerRef" class="pagination list-pager-bar">
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -397,6 +397,8 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { advanceApi, paymentApi, reimburseApi } from '@/api/bx'
|
||||
import { pickBxPageResult } from '@/utils/http/bx'
|
||||
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
|
||||
import {
|
||||
bizTypeTagType,
|
||||
statusLabel,
|
||||
@@ -728,19 +730,17 @@ const selectedRows = ref<any[]>([])
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const listParams = { page: 1, pageSize: 9999, ...buildApiQuery() }
|
||||
const apiQuery = buildApiQuery()
|
||||
const [advRes, reimRes] = await Promise.allSettled([
|
||||
advanceApi.list(listParams),
|
||||
reimburseApi.list(listParams)
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await advanceApi.list({ page, pageSize, ...apiQuery }))
|
||||
),
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await reimburseApi.list({ page, pageSize, ...apiQuery }))
|
||||
)
|
||||
])
|
||||
const advRows =
|
||||
advRes.status === 'fulfilled'
|
||||
? ((advRes.value as any).data?.rows || []).map(mapAdvanceRow)
|
||||
: []
|
||||
const reimRows =
|
||||
reimRes.status === 'fulfilled'
|
||||
? ((reimRes.value as any).data?.rows || []).map(mapReimburseRow)
|
||||
: []
|
||||
const advRows = advRes.status === 'fulfilled' ? advRes.value.map(mapAdvanceRow) : []
|
||||
const reimRows = reimRes.status === 'fulfilled' ? reimRes.value.map(mapReimburseRow) : []
|
||||
mergedRows.value = [...advRows, ...reimRows]
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
|
||||
@@ -385,6 +385,8 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { advanceApi, paymentApi, reimburseApi } from '@/api/bx'
|
||||
import { pickBxPageResult } from '@/utils/http/bx'
|
||||
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
|
||||
import {
|
||||
bizTypeTagType,
|
||||
statusLabel,
|
||||
@@ -678,17 +680,18 @@ const selectedRows = ref<any[]>([])
|
||||
tableLoading.value = true
|
||||
try {
|
||||
const apiQuery = buildApiQuery()
|
||||
const listParams = { page: 1, pageSize: 9999, ...apiQuery }
|
||||
const [advRes, reimRes] = await Promise.allSettled([
|
||||
advanceApi.my(listParams),
|
||||
reimburseApi.my(listParams)
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await advanceApi.my({ page, pageSize, ...apiQuery }))
|
||||
),
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await reimburseApi.my({ page, pageSize, ...apiQuery }))
|
||||
)
|
||||
])
|
||||
const advRows =
|
||||
advRes.status === 'fulfilled' ? (advRes.value.data?.rows || []).map(mapAdvanceMineRow) : []
|
||||
advRes.status === 'fulfilled' ? advRes.value.map(mapAdvanceMineRow) : []
|
||||
const reimRows =
|
||||
reimRes.status === 'fulfilled'
|
||||
? (reimRes.value.data?.rows || []).map(mapReimburseMineRow)
|
||||
: []
|
||||
reimRes.status === 'fulfilled' ? reimRes.value.map(mapReimburseMineRow) : []
|
||||
mergedRows.value = sortMergedRows([...advRows, ...reimRows])
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
|
||||
@@ -463,6 +463,8 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Check, Close, Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import { advanceApi, paymentApi, reimburseApi } from '@/api/bx'
|
||||
import { pickBxPageResult } from '@/utils/http/bx'
|
||||
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
|
||||
import {
|
||||
bizTypeTagType,
|
||||
formatMoneyYuan,
|
||||
@@ -729,17 +731,19 @@ import ListExportDropdown from '@/components/business/list-export-dropdown/index
|
||||
if (options.buttonLoading) searchLoading.value = true
|
||||
tableLoading.value = true
|
||||
try {
|
||||
const listParams = { page: 1, pageSize: 9999, ...buildApiQuery() }
|
||||
const apiQuery = buildApiQuery()
|
||||
const [advRes, reimRes] = await Promise.allSettled([
|
||||
advanceApi.pendingApproval(listParams),
|
||||
reimburseApi.pendingApproval(listParams)
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await advanceApi.pendingApproval({ page, pageSize, ...apiQuery }))
|
||||
),
|
||||
fetchAllPagedRecords(async (page, pageSize) =>
|
||||
pickBxPageResult(await reimburseApi.pendingApproval({ page, pageSize, ...apiQuery }))
|
||||
)
|
||||
])
|
||||
const advRows =
|
||||
advRes.status === 'fulfilled' ? (advRes.value.data?.rows || []).map(mapAdvanceTodoRow) : []
|
||||
advRes.status === 'fulfilled' ? advRes.value.map(mapAdvanceTodoRow) : []
|
||||
const reimRows =
|
||||
reimRes.status === 'fulfilled'
|
||||
? (reimRes.value.data?.rows || []).map(mapReimburseTodoRow)
|
||||
: []
|
||||
reimRes.status === 'fulfilled' ? reimRes.value.map(mapReimburseTodoRow) : []
|
||||
mergedRows.value = [...advRows, ...reimRows]
|
||||
} catch {
|
||||
// error handled by interceptor
|
||||
|
||||
@@ -119,10 +119,11 @@
|
||||
</ListTable>
|
||||
</div>
|
||||
|
||||
<div ref="wfAdvancePagerRef" class="pagination list-pager-bar">
|
||||
<div ref="wfAdvancePagerRef" class="pagination list-pager-bar">
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<el-dialog
|
||||
|
||||
@@ -1345,7 +1345,7 @@ const handleDetailCopy = () => {
|
||||
const handleDetailEdit = async () => {
|
||||
if (!detailContract.value?.id) return
|
||||
if (!isContractEditableStatus(detailContract.value.status)) {
|
||||
$modal.msgWarning('仅草稿、审批中或已审批(未执行)的合同才能修改')
|
||||
$modal.msgWarning('已完成的合同不能修改')
|
||||
return
|
||||
}
|
||||
const confirmMsg = getContractReeditConfirmMessage(detailContract.value.status)
|
||||
@@ -2042,18 +2042,19 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
}
|
||||
|
||||
|
||||
// 合计区(12 列):第 1 行左右对半运费/桶押金;第 2 行合计;第 3 行提示
|
||||
// 合计区(12 列):运费/桶押金 → 不含税/税额 → 含税金额合计 → 提示
|
||||
const freightRaw = Number(contractData.freight);
|
||||
const freightAmount = Number.isFinite(freightRaw) && freightRaw > 0 ? freightRaw : 0;
|
||||
const depositRaw = Number(contractData.barrelDeposit);
|
||||
const barrelDepositAmount = Number.isFinite(depositRaw) && depositRaw > 0 ? depositRaw : 0;
|
||||
const totalWithExtras = productTotal + freightAmount + barrelDepositAmount;
|
||||
const totalWithExtrasChinese = numberToChinese(totalWithExtras);
|
||||
const freightText = freightAmount > 0 ? `¥${formatNumber(freightAmount)}` : '-';
|
||||
const barrelDepositText = barrelDepositAmount > 0 ? `¥${formatNumber(barrelDepositAmount)}` : '-';
|
||||
const totalWithExtrasText = isFrameworkContract
|
||||
? '随行就市'
|
||||
: `${totalWithExtrasChinese}(¥${formatNumber(totalWithExtras)})`;
|
||||
const formatRmbAmountText = (amount: number) =>
|
||||
isFrameworkContract ? '随行就市' : `${numberToChinese(amount)}(¥${formatNumber(amount)})`;
|
||||
const beforeTaxTotalText = formatRmbAmountText(productTotalBeforeTax);
|
||||
const taxTotalText = formatRmbAmountText(totalTax);
|
||||
const totalWithExtrasText = formatRmbAmountText(totalWithExtras);
|
||||
const tipRowText = hasSelfPickupProduct(contractData.products)
|
||||
? '含税、客户自提结算以实际到货吨位为准'
|
||||
: '含税、含运费、装卸费(一票制) 结算以实际到货吨位为准';
|
||||
@@ -2065,7 +2066,13 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
<td colspan="4" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${barrelDepositText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">合计(人民币)</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">不含税金额合计(人民币)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${beforeTaxTotalText}</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">税额合计(人民币)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${taxTotalText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">含税金额合计(人民币)</td>
|
||||
<td colspan="10" style="padding: 5px 6px; border: 0.3px solid #000; text-align: center; font-size: 12px;">${totalWithExtrasText}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -2074,7 +2081,16 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
result = result.replace(/\{\{productTotalRow\}\}/gi, productTotalRow);
|
||||
// 优先替换占位符;否则替换已展开的「运费~提示」合计块(兼容旧模板,打印/PDF 共用)
|
||||
if (/\{\{productTotalRow\}\}/i.test(result)) {
|
||||
result = result.replace(/\{\{productTotalRow\}\}/gi, productTotalRow);
|
||||
} else {
|
||||
const summaryBlockPattern =
|
||||
/(?:<tr[^>]*font-weight:\s*bold[^>]*>[\s\S]*?<\/tr>\s*){1,3}<tr>[\s\S]*?结算以实际到货吨位为准[\s\S]*?<\/tr>/i;
|
||||
if (summaryBlockPattern.test(result)) {
|
||||
result = result.replace(summaryBlockPattern, productTotalRow.trim());
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理:印章图片
|
||||
// 从合同编号中提取供方编码
|
||||
@@ -2421,7 +2437,7 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
|
||||
</thead>
|
||||
<tbody>
|
||||
{{productTable}}
|
||||
<!-- 合计行+提示行(由 {{productTotalRow}} 一并输出,此处不再重复) -->
|
||||
<!-- 运费/桶押金 + 不含税/税额 + 含税合计 + 提示(由 {{productTotalRow}} 一并输出) -->
|
||||
{{productTotalRow}}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -2562,9 +2578,9 @@ const handleSaveTemplate = async () => {
|
||||
const productTableMatch = templateWithPlaceholders.match(/<tbody>[\s\S]*?<\/tbody>/);
|
||||
const productTableHtml = productTableMatch ? productTableMatch[0] : '';
|
||||
|
||||
// 2. 提取运费/桶押金行 + 合计行(连续两行加粗)
|
||||
// 2. 提取运费/桶押金 + 不含税/税额 + 含税合计(连续 2~3 行加粗,兼容旧版)
|
||||
const totalRowMatch = templateWithPlaceholders.match(
|
||||
/(?:<tr style="background-color: #fff; font-weight: bold;">[\s\S]*?<\/tr>\s*){2}/
|
||||
/(?:<tr style="background-color: #fff; font-weight: bold;">[\s\S]*?<\/tr>\s*){2,3}/
|
||||
);
|
||||
const totalRowHtml = totalRowMatch ? totalRowMatch[0] : '';
|
||||
|
||||
@@ -2583,10 +2599,10 @@ const handleSaveTemplate = async () => {
|
||||
);
|
||||
}
|
||||
|
||||
// 替换运费/桶押金行 + 合计行(兼容旧版单行合计)
|
||||
// 替换运费/桶押金行 + 合计相关行(兼容旧版 1~2 行、新版 3 行)
|
||||
if (totalRowHtml) {
|
||||
const totalRowPattern =
|
||||
/(?:<tr style="background-color: #fff; font-weight: bold;">[\s\S]*?<\/tr>\s*){1,2}/;
|
||||
/(?:<tr style="background-color: #fff; font-weight: bold;">[\s\S]*?<\/tr>\s*){1,3}/;
|
||||
if (totalRowPattern.test(finalTemplate)) {
|
||||
finalTemplate = finalTemplate.replace(totalRowPattern, totalRowHtml);
|
||||
}
|
||||
@@ -2956,13 +2972,12 @@ const handleGeneratePdfFromDialog = async () => {
|
||||
// 判断是否为SX供方编码
|
||||
const isSXSupplier = supplierCode === 'SX';
|
||||
|
||||
// 获取合同内容并替换占位符(使用最新的合同数据)
|
||||
// 获取合同内容并用最新数据刷新合计区(打印弹窗与 PDF 同源)
|
||||
let htmlContent = contractContent.value;
|
||||
// 如果内容中包含占位符,需要替换
|
||||
if (htmlContent.includes('{{') || htmlContent.includes('SETTLEMENT_METHOD_CONTENT')) {
|
||||
// 重新获取最新合同数据来替换占位符
|
||||
const res = await getContractDetail(detailContract.value.id);
|
||||
const res = await getContractDetail(detailContract.value.id);
|
||||
if (res?.data) {
|
||||
htmlContent = replacePlaceholders(htmlContent, res.data);
|
||||
contractContent.value = htmlContent;
|
||||
}
|
||||
|
||||
// 1. 创建临时容器(border-box:794 含 padding)
|
||||
|
||||
@@ -20,24 +20,35 @@ export function isPurchaseLikeContractType(type?: string | null): boolean {
|
||||
return PURCHASE_LIKE_CONTRACT_TYPES.includes((type || '').trim() as PurchaseLikeContractType)
|
||||
}
|
||||
|
||||
/** 允许打开编辑的合同状态(保存后回草稿) */
|
||||
export const CONTRACT_EDITABLE_STATUSES = ['draft', 'approving', 'approved'] as const
|
||||
/** 禁止再编辑的合同状态:仅已完成 */
|
||||
export const CONTRACT_NON_EDITABLE_STATUSES = ['finished'] as const
|
||||
|
||||
export type ContractNonEditableStatus = (typeof CONTRACT_NON_EDITABLE_STATUSES)[number]
|
||||
|
||||
/** 允许打开编辑的合同状态(保存后回草稿);除已完成外均可 */
|
||||
export const CONTRACT_EDITABLE_STATUSES = [
|
||||
'draft',
|
||||
'approving',
|
||||
'approved',
|
||||
'executing',
|
||||
'invalid'
|
||||
] as const
|
||||
|
||||
export type ContractEditableStatus = (typeof CONTRACT_EDITABLE_STATUSES)[number]
|
||||
|
||||
export function isContractEditableStatus(status?: string | null): boolean {
|
||||
return CONTRACT_EDITABLE_STATUSES.includes(
|
||||
(status || '').trim() as ContractEditableStatus
|
||||
)
|
||||
const s = (status || '').trim()
|
||||
if (!s) return true
|
||||
return !CONTRACT_NON_EDITABLE_STATUSES.includes(s as ContractNonEditableStatus)
|
||||
}
|
||||
|
||||
/** 审批中 / 已审批打开编辑前的确认文案;草稿返回 null(无需确认) */
|
||||
/** 非草稿打开编辑前的确认文案;草稿返回 null(无需确认) */
|
||||
export function getContractReeditConfirmMessage(status?: string | null): string | null {
|
||||
const s = (status || '').trim()
|
||||
if (s === 'approving') {
|
||||
return '编辑保存后将作废当前审批流,合同变回草稿,需重新提交审批'
|
||||
}
|
||||
if (s === 'approved') {
|
||||
if (s === 'approved' || s === 'executing' || s === 'invalid') {
|
||||
return '编辑保存后合同将变回草稿,需重新提交审批'
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -1418,7 +1418,7 @@ const goBack = () => {
|
||||
/** 编辑 */
|
||||
const handleEdit = async () => {
|
||||
if (!isContractEditableStatus(contract.value?.status)) {
|
||||
$modal.msgWarning('仅草稿、审批中或已审批(未执行)的合同才能修改')
|
||||
$modal.msgWarning('已完成的合同不能修改')
|
||||
return
|
||||
}
|
||||
const confirmMsg = getContractReeditConfirmMessage(contract.value?.status)
|
||||
@@ -1741,18 +1741,19 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
)
|
||||
}
|
||||
|
||||
// 合计区(12 列):第 1 行左右对半运费/桶押金;第 2 行合计;第 3 行提示
|
||||
// 合计区(12 列):运费/桶押金 → 不含税/税额 → 含税金额合计 → 提示
|
||||
const freightRaw = Number(contractData.freight);
|
||||
const freightAmount = Number.isFinite(freightRaw) && freightRaw > 0 ? freightRaw : 0;
|
||||
const depositRaw = Number(contractData.barrelDeposit);
|
||||
const barrelDepositAmount = Number.isFinite(depositRaw) && depositRaw > 0 ? depositRaw : 0;
|
||||
const totalWithExtras = productTotal + freightAmount + barrelDepositAmount;
|
||||
const totalWithExtrasChinese = numberToChinese(totalWithExtras);
|
||||
const freightText = freightAmount > 0 ? `¥${formatNumber(freightAmount)}` : '-';
|
||||
const barrelDepositText = barrelDepositAmount > 0 ? `¥${formatNumber(barrelDepositAmount)}` : '-';
|
||||
const totalWithExtrasText = isFrameworkContract
|
||||
? '随行就市'
|
||||
: `${totalWithExtrasChinese}(¥${formatNumber(totalWithExtras)})`;
|
||||
const formatRmbAmountText = (amount: number) =>
|
||||
isFrameworkContract ? '随行就市' : `${numberToChinese(amount)}(¥${formatNumber(amount)})`;
|
||||
const beforeTaxTotalText = formatRmbAmountText(productTotalBeforeTax);
|
||||
const taxTotalText = formatRmbAmountText(totalTax);
|
||||
const totalWithExtrasText = formatRmbAmountText(totalWithExtras);
|
||||
const tipRowText = hasSelfPickupProduct(contractData.products)
|
||||
? '含税、客户自提结算以实际到货吨位为准'
|
||||
: '含税、含运费、装卸费(一票制) 结算以实际到货吨位为准';
|
||||
@@ -1764,7 +1765,13 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
<td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${barrelDepositText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">合计(人民币)</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">不含税金额合计(人民币)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${beforeTaxTotalText}</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">税额合计(人民币)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${taxTotalText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">含税金额合计(人民币)</td>
|
||||
<td colspan="10" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${totalWithExtrasText}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1773,7 +1780,16 @@ const replacePlaceholders = (htmlContent: string, contractData: ContractVO | und
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
result = result.replace(/\{\{productTotalRow\}\}/gi, productTotalRow);
|
||||
// 优先替换占位符;否则替换已展开的「运费~提示」合计块(兼容旧模板,打印/PDF 共用)
|
||||
if (/\{\{productTotalRow\}\}/i.test(result)) {
|
||||
result = result.replace(/\{\{productTotalRow\}\}/gi, productTotalRow);
|
||||
} else {
|
||||
const summaryBlockPattern =
|
||||
/(?:<tr[^>]*font-weight:\s*bold[^>]*>[\s\S]*?<\/tr>\s*){1,3}<tr>[\s\S]*?结算以实际到货吨位为准[\s\S]*?<\/tr>/i;
|
||||
if (summaryBlockPattern.test(result)) {
|
||||
result = result.replace(summaryBlockPattern, productTotalRow.trim());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -1989,13 +2005,14 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
|
||||
const depositRaw = Number(contractData.barrelDeposit);
|
||||
const barrelDepositAmount = Number.isFinite(depositRaw) && depositRaw > 0 ? depositRaw : 0;
|
||||
const totalWithExtras = productTotal + freightAmount + barrelDepositAmount;
|
||||
const totalWithExtrasChinese = numberToChinese(totalWithExtras);
|
||||
const freightText = freightAmount > 0 ? `¥${formatNumber(freightAmount)}` : '-';
|
||||
const barrelDepositText = barrelDepositAmount > 0 ? `¥${formatNumber(barrelDepositAmount)}` : '-';
|
||||
const isFrameworkContract = isFrameworkContractType(contractData.contractType);
|
||||
const totalWithExtrasText = isFrameworkContract
|
||||
? '随行就市'
|
||||
: `${totalWithExtrasChinese}(¥${formatNumber(totalWithExtras)})`;
|
||||
const formatRmbAmountText = (amount: number) =>
|
||||
isFrameworkContract ? '随行就市' : `${numberToChinese(amount)}(¥${formatNumber(amount)})`;
|
||||
const beforeTaxTotalText = formatRmbAmountText(productTotalBeforeTax);
|
||||
const taxTotalText = formatRmbAmountText(totalTax);
|
||||
const totalWithExtrasText = formatRmbAmountText(totalWithExtras);
|
||||
|
||||
// 构建产品明细表格行
|
||||
const productRows =
|
||||
@@ -2078,7 +2095,7 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
|
||||
</thead>
|
||||
<tbody>
|
||||
${productRows}
|
||||
<!-- 运费/桶押金一行(11 列对半),合计单独一行 -->
|
||||
<!-- 运费/桶押金 → 不含税/税额 → 含税金额合计 → 提示 -->
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">运费(元)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${freightText}</td>
|
||||
@@ -2086,7 +2103,13 @@ const generateContractTemplate = (contractData: ContractVO | undefined): string
|
||||
<td colspan="3" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${barrelDepositText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">合计(人民币)</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">不含税金额合计(人民币)</td>
|
||||
<td colspan="4" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${beforeTaxTotalText}</td>
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">税额合计(人民币)</td>
|
||||
<td colspan="3" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${taxTotalText}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #fff; font-weight: bold;">
|
||||
<td colspan="2" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">含税金额合计(人民币)</td>
|
||||
<td colspan="9" style="padding: 5px 6px; border: 1px solid #000; text-align: center; font-size: 12px;">${totalWithExtrasText}</td>
|
||||
</tr>
|
||||
<!-- 提示行 -->
|
||||
@@ -2816,6 +2839,14 @@ const handleGeneratePdfFile = async () => {
|
||||
pdfGenerating.value = true;
|
||||
$modal.loading('正在生成PDF,请稍候...');
|
||||
|
||||
// 用最新合同数据刷新合计区(与打印弹窗同源)
|
||||
if (contract.value?.id) {
|
||||
const res = await getContract(contract.value.id);
|
||||
if (res?.data) {
|
||||
contractContent.value = replacePlaceholders(contractContent.value, res.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个临时div来渲染HTML内容
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.style.position = 'absolute';
|
||||
|
||||
@@ -1159,6 +1159,8 @@
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
// keepAlive:false 时不会触发 onActivated,须在挂载时处理计划跳转 query
|
||||
openAddFromPlanQuery()
|
||||
nextTick(() => recalcTableHeight())
|
||||
})
|
||||
|
||||
|
||||
@@ -1202,6 +1202,8 @@
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
// keepAlive:false 时不会触发 onActivated,须在挂载时处理出入库跳转 query
|
||||
openAddFromInventoryQuery()
|
||||
nextTick(() => recalcTableHeight())
|
||||
})
|
||||
|
||||
|
||||
@@ -91,19 +91,19 @@
|
||||
<div ref="sysDeptTableWrapRef" class="table-scroll bx-list-table-wrap">
|
||||
<ListTable
|
||||
ref="sysDeptTableRef"
|
||||
:height="tableHeight" table-key="system-dept"
|
||||
ref="tableRef"
|
||||
:key="listTableKey"
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
table-key="system-dept"
|
||||
:key="listTableKey"
|
||||
class="list-table"
|
||||
:height="tableHeight"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
@@ -171,9 +171,10 @@
|
||||
</div>
|
||||
<div ref="sysDeptBelowRef">
|
||||
<div class="pagination list-pager-bar">
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -401,7 +402,6 @@ const dictStore = useDictStore()
|
||||
const selectedRows = ref<Api.Dept.DeptItem[]>([])
|
||||
const loading = ref(false)
|
||||
const isExpandAll = ref(true)
|
||||
const tableRef = ref()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('新增组织')
|
||||
@@ -643,7 +643,7 @@ const dictStore = useDictStore()
|
||||
|
||||
/** 树表格的 default-expand-all 只在初次挂载生效,展开态需用表格 API 同步 */
|
||||
function applyTreeExpand(expand: boolean) {
|
||||
const table = tableRef.value
|
||||
const table = sysDeptTableRef.value
|
||||
if (!table?.toggleRowExpansion) return
|
||||
const preorder = (items: Api.Dept.DeptItem[]) => {
|
||||
for (const row of items) {
|
||||
|
||||
@@ -90,18 +90,18 @@
|
||||
<div ref="sysMenuTableWrapRef" class="table-scroll bx-list-table-wrap">
|
||||
<ListTable
|
||||
ref="sysMenuTableRef"
|
||||
:height="tableHeight" table-key="system-menu"
|
||||
ref="tableRef"
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
table-key="system-menu"
|
||||
class="list-table"
|
||||
:height="tableHeight"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
:default-expand-all="isExpandAll"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
@@ -205,9 +205,10 @@
|
||||
</div>
|
||||
<div ref="sysMenuBelowRef">
|
||||
<div class="pagination list-pager-bar">
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
<div class="list-pager-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -385,7 +386,6 @@ import { useListTableViewportHeight } from '@/composables/useListTableViewportHe
|
||||
const selectedRows = ref<Api.Menu.MenuItem[]>([])
|
||||
const loading = ref(false)
|
||||
const isExpandAll = ref(true)
|
||||
const tableRef = ref()
|
||||
|
||||
function getMenuExportFilterParams() {
|
||||
const quickKeyword = quickSearchKeyword.value?.trim()
|
||||
@@ -494,7 +494,7 @@ import { useListTableViewportHeight } from '@/composables/useListTableViewportHe
|
||||
|
||||
/** 树表格的 default-expand-all 只在初次挂载生效,展开态需用表格 API 同步 */
|
||||
function applyTreeExpand(expand: boolean) {
|
||||
const table = tableRef.value
|
||||
const table = sysMenuTableRef.value
|
||||
if (!table?.toggleRowExpansion) return
|
||||
const preorder = (items: Api.Menu.MenuItem[]) => {
|
||||
for (const row of items) {
|
||||
|
||||
Reference in new issue
Block a user