|
|
|
|
@ -1,7 +1,13 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
|
|
<xy-dialog title="打印预览" :is-show.sync="isShow" :width="90" ok-text="打印" @on-ok="printHtml">
|
|
|
|
|
<xy-dialog
|
|
|
|
|
:title="editMode ? '编辑付款登记' : '打印预览'"
|
|
|
|
|
:is-show.sync="isShow"
|
|
|
|
|
:width="90"
|
|
|
|
|
ok-text="打印"
|
|
|
|
|
@on-ok="printHtml"
|
|
|
|
|
>
|
|
|
|
|
<template v-slot:normalContent>
|
|
|
|
|
<div style="position: relative; margin-bottom: 16px; min-height: 40px;">
|
|
|
|
|
<div style="width: fit-content; margin: 0 auto;">
|
|
|
|
|
@ -62,30 +68,48 @@
|
|
|
|
|
<td colspan="2">{{ fundLog && fundLog.remark || '-' }}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</table>
|
|
|
|
|
<div v-if="getForms" v-html="getForms" />
|
|
|
|
|
<div v-if="getForms" :key="formsRenderKey" v-html="getForms" />
|
|
|
|
|
<div v-else class="no-form-message">暂无事后支付表格内容</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-if="editMode" v-slot:footerContent>
|
|
|
|
|
<Button ghost type="primary" @click="handleCancel">取消</Button>
|
|
|
|
|
<Button type="primary" :loading="saving" style="margin-left: 8px" @click="handleSave">保存</Button>
|
|
|
|
|
<Button type="primary" :loading="saving" style="margin-left: 8px" @click="handleSaveAndPrint">保存并打印</Button>
|
|
|
|
|
</template>
|
|
|
|
|
</xy-dialog>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { detailFundLog } from '@/api/paymentRegistration/fundLog'
|
|
|
|
|
import { detailFundLog, editorFundLog } from '@/api/paymentRegistration/fundLog'
|
|
|
|
|
import html2canvas from 'html2canvas'
|
|
|
|
|
import * as printJS from 'print-js'
|
|
|
|
|
import { numberToChinese } from '@/utils'
|
|
|
|
|
|
|
|
|
|
/** 资金划拨等表单:金额字段 -> 大写字段 */
|
|
|
|
|
const AMOUNT_REMARK_PAIRS = [
|
|
|
|
|
['contractAmount', 'contractRemark'],
|
|
|
|
|
['auditAmount', 'auditRemark'],
|
|
|
|
|
['previousPayment', 'previousPaymentRemark'],
|
|
|
|
|
['totalPaid', 'totalPaidRemark'],
|
|
|
|
|
['totalPlanned', 'totalPlannedRemark']
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'PrintPaymentForm',
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
isShow: false,
|
|
|
|
|
editMode: false,
|
|
|
|
|
saving: false,
|
|
|
|
|
currentForm: 'post',
|
|
|
|
|
fundLog: null,
|
|
|
|
|
printOrientation: 'portrait' // 默认纵向
|
|
|
|
|
formsRenderKey: 0,
|
|
|
|
|
printOrientation: 'portrait', // 默认纵向
|
|
|
|
|
_uppercaseDomCleanups: null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
@ -131,25 +155,241 @@ export default {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
this.editMode = false
|
|
|
|
|
this.detachConfigurableUppercase(this.$refs.printtable)
|
|
|
|
|
// 关闭时清空,避免打印预览中的未保存修改残留到下次打开
|
|
|
|
|
this.fundLog = null
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
immediate: true
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
async openForPrint(id) {
|
|
|
|
|
this.editMode = false
|
|
|
|
|
await this.getDetailFundLog(id)
|
|
|
|
|
this.currentForm = 'post'
|
|
|
|
|
this.isShow = true
|
|
|
|
|
},
|
|
|
|
|
async openForEdit(id) {
|
|
|
|
|
this.editMode = true
|
|
|
|
|
await this.getDetailFundLog(id)
|
|
|
|
|
this.currentForm = 'post'
|
|
|
|
|
this.isShow = true
|
|
|
|
|
},
|
|
|
|
|
async getDetailFundLog(id) {
|
|
|
|
|
try {
|
|
|
|
|
this.fundLog = null
|
|
|
|
|
const res = await detailFundLog({ id })
|
|
|
|
|
this.fundLog = res
|
|
|
|
|
this.formsRenderKey += 1
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取付款详情失败:', error)
|
|
|
|
|
this.$Message.error('获取付款详情失败')
|
|
|
|
|
throw error
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
handleCancel() {
|
|
|
|
|
this.isShow = false
|
|
|
|
|
},
|
|
|
|
|
async handleSave() {
|
|
|
|
|
try {
|
|
|
|
|
await this.syncAndSave()
|
|
|
|
|
this.$Message.success('保存成功')
|
|
|
|
|
this.$emit('saved')
|
|
|
|
|
this.isShow = false
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('保存失败:', error)
|
|
|
|
|
this.$Message.error('保存失败,请重试')
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
async handleSaveAndPrint() {
|
|
|
|
|
try {
|
|
|
|
|
await this.syncAndSave()
|
|
|
|
|
this.$Message.success('保存成功')
|
|
|
|
|
this.$emit('saved')
|
|
|
|
|
this.printHtml()
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('保存失败:', error)
|
|
|
|
|
this.$Message.error('保存失败,请重试')
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
parseMoneyForUppercase(raw) {
|
|
|
|
|
if (raw == null) return { kind: 'empty' }
|
|
|
|
|
const s = String(raw).trim()
|
|
|
|
|
if (s === '') return { kind: 'empty' }
|
|
|
|
|
const cleaned = s.replace(/[¥¥\s,,]/g, '')
|
|
|
|
|
if (cleaned === '') return { kind: 'empty' }
|
|
|
|
|
const n = parseFloat(cleaned)
|
|
|
|
|
if (!Number.isFinite(n)) return { kind: 'invalid' }
|
|
|
|
|
return { kind: 'ok', value: n }
|
|
|
|
|
},
|
|
|
|
|
getSourceFieldRawValue(dom, fieldName) {
|
|
|
|
|
if (!dom || !fieldName) return ''
|
|
|
|
|
const radios = dom.querySelectorAll(`input[type="radio"][data-field="${fieldName}"]`)
|
|
|
|
|
if (radios.length) {
|
|
|
|
|
const c = dom.querySelector(`input[type="radio"][data-field="${fieldName}"]:checked`)
|
|
|
|
|
return c ? String(c.value) : ''
|
|
|
|
|
}
|
|
|
|
|
const el = dom.querySelector(`input[data-field="${fieldName}"],select[data-field="${fieldName}"],textarea[data-field="${fieldName}"]`)
|
|
|
|
|
if (!el) return ''
|
|
|
|
|
if (el.type === 'checkbox') {
|
|
|
|
|
const c = dom.querySelector(`input[type="checkbox"][data-field="${fieldName}"]:checked`)
|
|
|
|
|
return c ? String(c.value) : ''
|
|
|
|
|
}
|
|
|
|
|
return el.value != null ? String(el.value) : ''
|
|
|
|
|
},
|
|
|
|
|
setFieldValue(dom, fieldName, text) {
|
|
|
|
|
if (!dom || !fieldName) return
|
|
|
|
|
const els = dom.querySelectorAll(`input[data-field="${fieldName}"],textarea[data-field="${fieldName}"],select[data-field="${fieldName}"]`)
|
|
|
|
|
els.forEach((el) => {
|
|
|
|
|
if (el.type === 'radio' || el.type === 'checkbox') return
|
|
|
|
|
el.value = text
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
applyConfigurableUppercase(dom) {
|
|
|
|
|
const templateFields = this.fundLog && this.fundLog.other_data
|
|
|
|
|
if (!dom || !templateFields || !templateFields.length) return
|
|
|
|
|
templateFields.forEach((meta) => {
|
|
|
|
|
const conf = meta.uppercase_source
|
|
|
|
|
if (!conf || conf.mode !== 'field' || !conf.source_field) return
|
|
|
|
|
const src = conf.source_field
|
|
|
|
|
if (src === meta.field) return
|
|
|
|
|
const raw = this.getSourceFieldRawValue(dom, src)
|
|
|
|
|
const parsed = this.parseMoneyForUppercase(raw)
|
|
|
|
|
const val = parsed.kind === 'ok' ? numberToChinese(parsed.value) : ''
|
|
|
|
|
this.setFieldValue(dom, meta.field, val)
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
syncAmountRemarkUppercase(dom) {
|
|
|
|
|
if (!dom) return
|
|
|
|
|
AMOUNT_REMARK_PAIRS.forEach(([amountField, remarkField]) => {
|
|
|
|
|
const amountInput = dom.querySelector(`input[data-field="${amountField}"]`)
|
|
|
|
|
const remarkInput = dom.querySelector(`input[data-field="${remarkField}"]`)
|
|
|
|
|
if (!amountInput || !remarkInput) return
|
|
|
|
|
const parsed = this.parseMoneyForUppercase(amountInput.value)
|
|
|
|
|
remarkInput.value = parsed.kind === 'ok' ? numberToChinese(parsed.value) : ''
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
syncAllUppercase(dom) {
|
|
|
|
|
if (!dom) return
|
|
|
|
|
this.calculateTotal()
|
|
|
|
|
this.applyConfigurableUppercase(dom)
|
|
|
|
|
this.syncAmountRemarkUppercase(dom)
|
|
|
|
|
},
|
|
|
|
|
detachConfigurableUppercase(dom) {
|
|
|
|
|
if (!this._uppercaseDomCleanups || !dom) return
|
|
|
|
|
const cleanup = this._uppercaseDomCleanups.get(dom)
|
|
|
|
|
if (cleanup) {
|
|
|
|
|
cleanup()
|
|
|
|
|
this._uppercaseDomCleanups.delete(dom)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
attachConfigurableUppercase(dom) {
|
|
|
|
|
if (!dom) return
|
|
|
|
|
if (!this._uppercaseDomCleanups) {
|
|
|
|
|
this._uppercaseDomCleanups = new Map()
|
|
|
|
|
}
|
|
|
|
|
const sourceFields = new Set()
|
|
|
|
|
const templateFields = this.fundLog && this.fundLog.other_data
|
|
|
|
|
if (templateFields && templateFields.length) {
|
|
|
|
|
templateFields.forEach(m => {
|
|
|
|
|
const cfg = m.uppercase_source
|
|
|
|
|
const s = cfg && cfg.mode === 'field' && cfg.source_field
|
|
|
|
|
if (s) sourceFields.add(s)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
AMOUNT_REMARK_PAIRS.forEach(([amountField]) => sourceFields.add(amountField))
|
|
|
|
|
|
|
|
|
|
if (sourceFields.size > 0) {
|
|
|
|
|
const handler = () => {
|
|
|
|
|
this.syncAllUppercase(dom)
|
|
|
|
|
}
|
|
|
|
|
const types = ['input', 'change', 'blur']
|
|
|
|
|
types.forEach(type => dom.addEventListener(type, handler, true))
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
types.forEach(type => dom.removeEventListener(type, handler, true))
|
|
|
|
|
}
|
|
|
|
|
this._uppercaseDomCleanups.set(dom, cleanup)
|
|
|
|
|
}
|
|
|
|
|
this.syncAllUppercase(dom)
|
|
|
|
|
},
|
|
|
|
|
syncFormDomToHtml() {
|
|
|
|
|
const dom = this.$refs.printtable
|
|
|
|
|
if (!dom || !this.fundLog) return
|
|
|
|
|
const templateFields = this.fundLog.other_data
|
|
|
|
|
const inputs = dom.querySelectorAll('input, select, textarea')
|
|
|
|
|
inputs.forEach(input => {
|
|
|
|
|
const fieldName = input.getAttribute('data-field')
|
|
|
|
|
if (!fieldName) return
|
|
|
|
|
if (templateFields && Array.isArray(templateFields)) {
|
|
|
|
|
const field = templateFields.find(f => f.field === fieldName)
|
|
|
|
|
if (field) {
|
|
|
|
|
if (input.type === 'checkbox' || input.type === 'radio') {
|
|
|
|
|
const checkedInput = dom.querySelector(`[data-field="${fieldName}"]:checked`)
|
|
|
|
|
field.value = checkedInput ? checkedInput.value : ''
|
|
|
|
|
if (checkedInput) {
|
|
|
|
|
checkedInput.setAttribute('checked', 'checked')
|
|
|
|
|
}
|
|
|
|
|
} else if (input.tagName.toLowerCase() === 'textarea') {
|
|
|
|
|
field.value = input.value || input.textContent || ''
|
|
|
|
|
input.textContent = field.value
|
|
|
|
|
} else {
|
|
|
|
|
field.value = input.value
|
|
|
|
|
input.setAttribute('value', input.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (input.type === 'checkbox' || input.type === 'radio') {
|
|
|
|
|
const checkedInput = dom.querySelector(`[data-field="${fieldName}"]:checked`)
|
|
|
|
|
if (checkedInput) {
|
|
|
|
|
checkedInput.setAttribute('checked', 'checked')
|
|
|
|
|
}
|
|
|
|
|
} else if (input.tagName.toLowerCase() === 'textarea') {
|
|
|
|
|
input.textContent = input.value || ''
|
|
|
|
|
} else {
|
|
|
|
|
input.setAttribute('value', input.value)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.fundLog.forms = dom.innerHTML
|
|
|
|
|
},
|
|
|
|
|
async syncAndSave() {
|
|
|
|
|
if (!this.fundLog || !this.fundLog.id) {
|
|
|
|
|
throw new Error('缺少付款登记数据')
|
|
|
|
|
}
|
|
|
|
|
const dom = this.$refs.printtable
|
|
|
|
|
if (dom) {
|
|
|
|
|
this.syncAllUppercase(dom)
|
|
|
|
|
this.syncFormDomToHtml()
|
|
|
|
|
}
|
|
|
|
|
this.saving = true
|
|
|
|
|
try {
|
|
|
|
|
await editorFundLog({
|
|
|
|
|
id: this.fundLog.id,
|
|
|
|
|
contract_id: this.fundLog.contract_id,
|
|
|
|
|
apply_money: this.fundLog.apply_money,
|
|
|
|
|
act_money: this.fundLog.act_money,
|
|
|
|
|
discount_money: this.fundLog.discount_money,
|
|
|
|
|
audit_money: this.fundLog.audit_money,
|
|
|
|
|
type: this.fundLog.type,
|
|
|
|
|
is_end: this.fundLog.is_end,
|
|
|
|
|
remark: this.fundLog.remark,
|
|
|
|
|
act_date: this.fundLog.act_date,
|
|
|
|
|
forms: this.fundLog.forms,
|
|
|
|
|
other_data: this.fundLog.other_data,
|
|
|
|
|
before_forms: this.fundLog.before_forms,
|
|
|
|
|
before_other_data: this.fundLog.before_other_data
|
|
|
|
|
})
|
|
|
|
|
} finally {
|
|
|
|
|
this.saving = false
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
setupAmountListeners() {
|
|
|
|
|
const dom = this.$refs.printtable
|
|
|
|
|
if (!dom) return
|
|
|
|
|
|
|
|
|
|
this.detachConfigurableUppercase(dom)
|
|
|
|
|
|
|
|
|
|
const sdateAmountInputs = dom.querySelectorAll('input[data-field^="sdate"]')
|
|
|
|
|
// 移除旧的监听器
|
|
|
|
|
sdateAmountInputs.forEach(input => {
|
|
|
|
|
@ -321,8 +561,7 @@ export default {
|
|
|
|
|
totalInput.addEventListener('blur', this.updateUpperCaseFromTotal)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 初始计算一次
|
|
|
|
|
this.calculateTotal()
|
|
|
|
|
this.attachConfigurableUppercase(dom)
|
|
|
|
|
},
|
|
|
|
|
caculateRoadDay() {
|
|
|
|
|
const sdateInput = this.$refs.printtable.querySelector('input[data-field^="sdate"]')
|
|
|
|
|
@ -528,9 +767,12 @@ export default {
|
|
|
|
|
console.log('更新大写金额:', upperCaseInput.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (otherTotal !== 0) {
|
|
|
|
|
upperCaseInput.value = numberToChinese(otherTotal)
|
|
|
|
|
if (upperCaseInput && otherTotal !== 0) {
|
|
|
|
|
upperCaseInput.value = numberToChinese(otherTotal)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.applyConfigurableUppercase(dom)
|
|
|
|
|
this.syncAmountRemarkUppercase(dom)
|
|
|
|
|
},
|
|
|
|
|
updateUpperCaseFromTotal() {
|
|
|
|
|
const dom = this.$refs.printtable
|
|
|
|
|
@ -544,6 +786,8 @@ export default {
|
|
|
|
|
upperCaseInput.value = numberToChinese(total)
|
|
|
|
|
console.log('从总金额更新大写金额:', total, upperCaseInput.value)
|
|
|
|
|
}
|
|
|
|
|
this.applyConfigurableUppercase(dom)
|
|
|
|
|
this.syncAmountRemarkUppercase(dom)
|
|
|
|
|
},
|
|
|
|
|
replaceControls(element) {
|
|
|
|
|
const inputs = element.getElementsByTagName('input')
|
|
|
|
|
@ -717,6 +961,7 @@ export default {
|
|
|
|
|
// 先同步用户输入到HTML
|
|
|
|
|
const dom = this.$refs.printtable
|
|
|
|
|
if (dom) {
|
|
|
|
|
this.syncAllUppercase(dom)
|
|
|
|
|
// 获取所有输入控件
|
|
|
|
|
const inputs = dom.querySelectorAll('input, select, textarea')
|
|
|
|
|
inputs.forEach(input => {
|
|
|
|
|
@ -788,26 +1033,6 @@ export default {
|
|
|
|
|
this.replaceControls(printNode)
|
|
|
|
|
console.log('printNode', printNode.innerHTML)
|
|
|
|
|
|
|
|
|
|
// 计算并更新总金额
|
|
|
|
|
const amountInputs = printNode.querySelectorAll('input[data-field^="amount"]')
|
|
|
|
|
let total = 0
|
|
|
|
|
amountInputs.forEach(input => {
|
|
|
|
|
const value = parseFloat(input.value) || 0
|
|
|
|
|
total += value
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 更新总金额显示
|
|
|
|
|
const totalInput = printNode.querySelector('input[data-field="total"]')
|
|
|
|
|
if (totalInput) {
|
|
|
|
|
totalInput.value = total.toFixed(2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新大写金额
|
|
|
|
|
const upperCaseInput = printNode.querySelector('input[data-field="upperCaseAmount"]')
|
|
|
|
|
if (upperCaseInput) {
|
|
|
|
|
upperCaseInput.value = numberToChinese(total)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const orientation = this.printOrientation || 'portrait';
|
|
|
|
|
const margin = orientation === 'portrait'
|
|
|
|
|
? '5mm 5mm 10mm 2mm' // 减少左右边距
|
|
|
|
|
|