|
|
|
|
@ -104,6 +104,8 @@
|
|
|
|
|
<MeetingMinutesField
|
|
|
|
|
:model-value="payment?.fields?.[el.id]"
|
|
|
|
|
readonly
|
|
|
|
|
:title-only="getPaymentMeetingMinutesUsageState(el).isDuplicate"
|
|
|
|
|
:repeat-notice="getPaymentMeetingMinutesUsageState(el).isDuplicate ? REPEATED_MEETING_MINUTES_MESSAGE : ''"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@ -187,7 +189,7 @@
|
|
|
|
|
|
|
|
|
|
<!-- 会议纪要:若唯一附件为 PDF,则在本行后追加一个通栏行展示 PDF(尽量拉宽) -->
|
|
|
|
|
<tr
|
|
|
|
|
v-if="el.type === 'meeting_minutes' && getMeetingMinutesPdfUrl(payment?.fields?.[el.id])"
|
|
|
|
|
v-if="el.type === 'meeting_minutes' && !getPaymentMeetingMinutesUsageState(el).isDuplicate && getMeetingMinutesPdfUrl(payment?.fields?.[el.id])"
|
|
|
|
|
class="meeting-minutes-pdf-row"
|
|
|
|
|
>
|
|
|
|
|
<td class="value meeting-minutes-pdf-td" colspan="4">
|
|
|
|
|
@ -214,6 +216,8 @@
|
|
|
|
|
:contract-id="payment.contract_id"
|
|
|
|
|
:current-payment-id="payment?.id"
|
|
|
|
|
:embed-planned-expenditures="true"
|
|
|
|
|
:meeting-minutes-context-prefix="`print:contract:${payment.contract_id}`"
|
|
|
|
|
:resolve-meeting-minutes-usage="resolveMeetingMinutesUsage"
|
|
|
|
|
@collect-flow-id="collectFlowId"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
@ -223,6 +227,8 @@
|
|
|
|
|
:contract-id="payment?.related_id"
|
|
|
|
|
:current-payment-id="payment?.id"
|
|
|
|
|
:embed-planned-expenditures="true"
|
|
|
|
|
:meeting-minutes-context-prefix="`print:related-contract:${payment?.related_id}`"
|
|
|
|
|
:resolve-meeting-minutes-usage="resolveMeetingMinutesUsage"
|
|
|
|
|
@collect-flow-id="collectFlowId"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
@ -232,6 +238,8 @@
|
|
|
|
|
v-if="relatedPlannedExpenditure"
|
|
|
|
|
:expenditure="relatedPlannedExpenditure"
|
|
|
|
|
:template="relatedPlannedExpenditureTemplate"
|
|
|
|
|
:meeting-minutes-context-prefix="`print:planned-expenditure:${relatedPlannedExpenditure?.id || payment?.related_id}`"
|
|
|
|
|
:resolve-meeting-minutes-usage="resolveMeetingMinutesUsage"
|
|
|
|
|
@collect-flow-id="collectFlowId"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
@ -241,6 +249,8 @@
|
|
|
|
|
:contract-id="payment.contract_id"
|
|
|
|
|
:current-payment-id="payment?.id"
|
|
|
|
|
:embed-planned-expenditures="false"
|
|
|
|
|
:meeting-minutes-context-prefix="`print:contract:${payment.contract_id}`"
|
|
|
|
|
:resolve-meeting-minutes-usage="resolveMeetingMinutesUsage"
|
|
|
|
|
@collect-flow-id="collectFlowId"
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
@ -364,6 +374,97 @@ let flowIframeLoadTimeout = null
|
|
|
|
|
// 收集所有相关流程ID(用于打印模版渲染)
|
|
|
|
|
const collectedFlowIds = ref(new Set())
|
|
|
|
|
const renderedPrintTemplates = ref([]) // 存储渲染后的打印模版HTML
|
|
|
|
|
const REPEATED_MEETING_MINUTES_MESSAGE = '会议纪要再次使用,仅展示标题,不重复打印'
|
|
|
|
|
|
|
|
|
|
let meetingMinutesUsageStack = []
|
|
|
|
|
let meetingMinutesUsageCache = {}
|
|
|
|
|
let meetingMinutesSeenMap = new Map()
|
|
|
|
|
|
|
|
|
|
const resetMeetingMinutesUsage = () => {
|
|
|
|
|
meetingMinutesUsageStack = []
|
|
|
|
|
meetingMinutesUsageCache = {}
|
|
|
|
|
meetingMinutesSeenMap = new Map()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const parseMeetingMinutesValue = (rawVal) => {
|
|
|
|
|
if (rawVal === null || rawVal === undefined || rawVal === '') return null
|
|
|
|
|
if (typeof rawVal === 'string') {
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(rawVal)
|
|
|
|
|
if (parsed && typeof parsed === 'object') return parsed
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
return rawVal
|
|
|
|
|
}
|
|
|
|
|
return rawVal
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getMeetingMinutesDisplayTitle = (rawVal) => {
|
|
|
|
|
const parsed = parseMeetingMinutesValue(rawVal)
|
|
|
|
|
if (parsed && typeof parsed === 'object') {
|
|
|
|
|
return parsed.title || parsed.name || parsed.display_name || '会议纪要'
|
|
|
|
|
}
|
|
|
|
|
if (typeof parsed === 'string' && parsed.trim()) {
|
|
|
|
|
return parsed.trim()
|
|
|
|
|
}
|
|
|
|
|
return '会议纪要'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const extractMeetingMinuteId = (rawVal) => {
|
|
|
|
|
const parsed = parseMeetingMinutesValue(rawVal)
|
|
|
|
|
if (!parsed || typeof parsed !== 'object') return null
|
|
|
|
|
const id = parsed.meeting_minute_id
|
|
|
|
|
if (id === null || id === undefined || id === '') return null
|
|
|
|
|
const normalized = Number(id)
|
|
|
|
|
if (!Number.isFinite(normalized) || normalized <= 0) return null
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const buildMeetingMinutesFingerprint = (rawVal) => {
|
|
|
|
|
const meetingMinuteId = extractMeetingMinuteId(rawVal)
|
|
|
|
|
if (meetingMinuteId) {
|
|
|
|
|
return `meeting_minute_id:${meetingMinuteId}`
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const resolveMeetingMinutesUsage = (occurrenceKey, rawVal) => {
|
|
|
|
|
if (!occurrenceKey) {
|
|
|
|
|
return { isDuplicate: false, title: getMeetingMinutesDisplayTitle(rawVal), fingerprint: null }
|
|
|
|
|
}
|
|
|
|
|
if (meetingMinutesUsageCache[occurrenceKey]) {
|
|
|
|
|
return meetingMinutesUsageCache[occurrenceKey]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fingerprint = buildMeetingMinutesFingerprint(rawVal)
|
|
|
|
|
if (!fingerprint) {
|
|
|
|
|
const emptyState = { isDuplicate: false, title: getMeetingMinutesDisplayTitle(rawVal), fingerprint: null }
|
|
|
|
|
meetingMinutesUsageCache[occurrenceKey] = emptyState
|
|
|
|
|
return emptyState
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const firstOccurrenceKey = meetingMinutesSeenMap.get(fingerprint) || null
|
|
|
|
|
const state = {
|
|
|
|
|
isDuplicate: !!firstOccurrenceKey,
|
|
|
|
|
title: getMeetingMinutesDisplayTitle(rawVal),
|
|
|
|
|
fingerprint,
|
|
|
|
|
firstOccurrenceKey
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!firstOccurrenceKey) {
|
|
|
|
|
meetingMinutesSeenMap.set(fingerprint, occurrenceKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
meetingMinutesUsageStack.push({
|
|
|
|
|
occurrenceKey,
|
|
|
|
|
fingerprint,
|
|
|
|
|
title: state.title,
|
|
|
|
|
isDuplicate: state.isDuplicate
|
|
|
|
|
})
|
|
|
|
|
meetingMinutesUsageCache[occurrenceKey] = state
|
|
|
|
|
return state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 收集流程ID(去重)
|
|
|
|
|
const collectFlowId = (flowId) => {
|
|
|
|
|
@ -645,6 +746,11 @@ const visiblePaymentTemplateElements = computed(() => {
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const getPaymentMeetingMinutesUsageState = (element) => {
|
|
|
|
|
const occurrenceKey = `payment:${payment.value?.id || 'unknown'}:element:${element?.id || 'unknown'}`
|
|
|
|
|
return resolveMeetingMinutesUsage(occurrenceKey, payment.value?.fields?.[element?.id])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 当 Payment 关联对象为 planned_expenditure 时,按 related_id 获取事前流程实体并加载对应模板
|
|
|
|
|
const loadRelatedPlannedExpenditure = async () => {
|
|
|
|
|
relatedPlannedExpenditure.value = null
|
|
|
|
|
@ -679,6 +785,7 @@ const loadPaymentDetail = async () => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
resetMeetingMinutesUsage()
|
|
|
|
|
// 清空之前收集的流程ID
|
|
|
|
|
collectedFlowIds.value.clear()
|
|
|
|
|
|
|
|
|
|
@ -1803,5 +1910,3 @@ onMounted(async () => {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|