ContractManageForm.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. <script setup lang="ts">
  2. import { reactive, ref, computed, nextTick } from 'vue'
  3. import ContractBody from './components/ContractBody.vue'
  4. import AttachmentPage from './components/AttachmentPage.vue'
  5. import AttachmentSelector from './components/AttachmentSelector.vue'
  6. import RiskDisclosureBody from './components/RiskDisclosureBody.vue'
  7. import CheckInRegistrationBody from './components/CheckInRegistrationBody.vue'
  8. import CheckInInformationBody from './components/CheckInInformationBody.vue'
  9. import ElderSafetyCommitmentBody from './components/ElderSafetyCommitmentBody.vue'
  10. import OutingConfirmationBody from './components/OutingConfirmationBody.vue'
  11. import SignatureProxyApplicationBody from './components/SignatureProxyApplicationBody.vue'
  12. import PowerOfAttorneyBody from './components/PowerOfAttorneyBody.vue'
  13. import DelegationAgentConfirmationBody from './components/DelegationAgentConfirmationBody.vue'
  14. import ServiceChargingStandardsBody from './components/ServiceChargingStandardsBody.vue'
  15. import RoomFacilitiesEquipmentBody from './components/RoomFacilitiesEquipmentBody.vue'
  16. import ExplanationExaminationReportBody from './components/ExplanationExaminationReportBody.vue'
  17. import DocumentAttachmentBody from './components/DocumentAttachmentBody.vue'
  18. import FirstServiceProjectConfirmationBody from './components/FirstServiceProjectConfirmationBody.vue'
  19. import ChangeConfirmationFormBody from './components/ChangeConfirmationFormBody.vue'
  20. import {
  21. riskDisclosure_getById,
  22. checkInRegistration_getById,
  23. elderSafetyCommitment_getById,
  24. outingConfirmation_getById,
  25. signatureProxyApplication_getById,
  26. powerOfAttorney_getById,
  27. delegationAgentConfirmation_getById,
  28. serviceChargingStandards_getById,
  29. roomFacilitiesEquipment_getById,
  30. explanationExaminationReport_getById,
  31. partyBIdentificationDocument_getById,
  32. guardianPartyB_getById,
  33. registrationCertificateCivilAffairs_getById,
  34. comprehensiveAssessmentCapabilities_getById,
  35. firstServiceProjectConfirmation_getById,
  36. changeConfirmationForm_getById
  37. } from '@/api/elderly/contracts'
  38. const attachmentDetailMap: Record<string, (id: number | string) => Promise<any>> = {
  39. attach_1: riskDisclosure_getById,
  40. attach_2: checkInRegistration_getById,
  41. attach_4: elderSafetyCommitment_getById,
  42. attach_5: outingConfirmation_getById,
  43. attach_6: signatureProxyApplication_getById,
  44. attach_7: powerOfAttorney_getById,
  45. attach_8: delegationAgentConfirmation_getById,
  46. attach_9: serviceChargingStandards_getById,
  47. attach_10: roomFacilitiesEquipment_getById,
  48. attach_11: explanationExaminationReport_getById,
  49. attach_12: partyBIdentificationDocument_getById,
  50. attach_13: guardianPartyB_getById,
  51. attach_14: registrationCertificateCivilAffairs_getById,
  52. attach_15: comprehensiveAssessmentCapabilities_getById,
  53. attach_16: firstServiceProjectConfirmation_getById,
  54. attach_17: changeConfirmationForm_getById
  55. }
  56. const message = useMessage()
  57. const visible = ref(false)
  58. const formLoading = ref(false)
  59. const isTextMode = ref(false)
  60. const isPrint = ref(false)
  61. const contractBodyRef = ref<any>(null)
  62. const recordRow = ref<any>(null)
  63. const attachmentList = [
  64. { no: 1, title: '知情同意书' },
  65. { no: 2, title: '入住登记表' },
  66. { no: 3, title: '入住须知' },
  67. { no: 4, title: '长者安全承诺书' },
  68. { no: 5, title: '长者外出情况确认书' },
  69. { no: 6, title: '签名代理申请' },
  70. { no: 7, title: '授权委托书' },
  71. { no: 8, title: '委托代理人确认表' },
  72. { no: 9, title: '机构服务范围及收费标准' },
  73. { no: 10, title: '房间设施设备清单' },
  74. { no: 11, title: '体检报告项目说明' },
  75. { no: 12, title: '乙方有效证件身份证、户口本复印件' },
  76. { no: 13, title: '乙方监护人(丙方)身份证、户口本复印件' },
  77. { no: 14, title: '甲方登记证书及民政部门备案回执' },
  78. { no: 15, title: '老年人能力综合评估结果' },
  79. { no: 16, title: '首次服务项目确认表' },
  80. { no: 17, title: '变更事项确认表' }
  81. ]
  82. const attachments = reactive<Record<string, any>>(
  83. attachmentList.reduce(
  84. (acc: any, item) => {
  85. // 固定内容附件(3=入住须知)默认展示,不需要从列表选择
  86. if (item.no === 3) {
  87. acc[`attach_${item.no}`] = { id: '', title: item.title }
  88. } else {
  89. acc[`attach_${item.no}`] = null
  90. }
  91. return acc
  92. },
  93. {} as Record<string, any>
  94. )
  95. )
  96. const riskDisclosureForm = reactive<Record<string, any>>({})
  97. const safetyCommitmentForm = reactive<Record<string, any>>({
  98. elderSign: '',
  99. guardianSign: '',
  100. signDate: ''
  101. })
  102. const outingConfirmationForm = reactive<Record<string, any>>({
  103. elderName: '',
  104. outingAbility: '',
  105. elderSign: '',
  106. guardianSign: '',
  107. signDate: ''
  108. })
  109. const signatureProxyForm = reactive<Record<string, any>>({
  110. elderName: '',
  111. idCardNo: '',
  112. agentName: '',
  113. agentIdCardNo: '',
  114. applicantSign: '',
  115. agentSign: '',
  116. signDate: '',
  117. handlerSign: ''
  118. })
  119. const powerOfAttorneyForm = reactive<Record<string, any>>({
  120. principalName: '',
  121. principalGender: '',
  122. principalIdCardNo: '',
  123. institutionName: '',
  124. agentName: '',
  125. agentGender: '',
  126. agentIdCardNo: '',
  127. agentPhone: '',
  128. elderName: '',
  129. relationship: '',
  130. principalSign: '',
  131. agentSign: '',
  132. signDate: ''
  133. })
  134. const delegationAgentForm = reactive<Record<string, any>>({
  135. elderName: '',
  136. elderGender: '',
  137. elderAge: '',
  138. elderIdCardNo: '',
  139. elderAddress: '',
  140. agentName: '',
  141. agentGender: '',
  142. agentIdCardNo: '',
  143. agentAddress: '',
  144. declarerName: '',
  145. representedName: '',
  146. relationship: '',
  147. elderSign: '',
  148. agentSign: '',
  149. signDate: ''
  150. })
  151. const serviceChargingForm = reactive<Record<string, any>>({
  152. signatorySign: '',
  153. signDate: ''
  154. })
  155. const roomFacilitiesForm = reactive<Record<string, any>>({
  156. elderName: '',
  157. roomNo: '',
  158. leftItems: [],
  159. rightItems: [],
  160. blankItems: [],
  161. elderSign: '',
  162. guardianSign: '',
  163. signDate: ''
  164. })
  165. const examinationReportForm = reactive<Record<string, any>>({
  166. images: []
  167. })
  168. const identificationPartyBForm = reactive<Record<string, any>>({
  169. images: []
  170. })
  171. const guardianPartyBForm = reactive<Record<string, any>>({
  172. images: []
  173. })
  174. const registrationCertificateForm = reactive<Record<string, any>>({
  175. images: []
  176. })
  177. const comprehensiveAssessmentForm = reactive<Record<string, any>>({
  178. images: []
  179. })
  180. const firstServiceForm = reactive<Record<string, any>>({})
  181. const changeConfirmForm = reactive<Record<string, any>>({})
  182. const checkInRegistrationForm = reactive<Record<string, any>>({
  183. checkInNo: '',
  184. formDate: '',
  185. elderName: '',
  186. gender: '',
  187. age: '',
  188. photo: '',
  189. nationality: '',
  190. birthDate: '',
  191. nativePlace: '',
  192. householdPlace: '',
  193. nation: '',
  194. nationOther: '',
  195. politicalStatus: '',
  196. maritalStatus: '',
  197. idCardType: '',
  198. idCardNo: '',
  199. educationLevel: '',
  200. residentialAddress: '',
  201. guarantorName: '',
  202. guarantorRelation: '',
  203. guarantorPhone: '',
  204. guarantorIdCardType: '',
  205. guarantorIdCardNo: '',
  206. guarantorAddress: '',
  207. guarantorEmail: '',
  208. secondContactName: '',
  209. secondContactPhone: '',
  210. medicalPaymentMethod: '',
  211. medicalPaymentOther: '',
  212. incomeSource: [],
  213. incomeSourceOther: '',
  214. preResidence: '',
  215. preResidenceHomeType: '',
  216. preResidenceHomeOther: '',
  217. checkInReason: '',
  218. pastHistory: {
  219. hasDisease: false,
  220. diseases: [
  221. { name: '', confirmTime: '' },
  222. { name: '', confirmTime: '' }
  223. ],
  224. hasSurgery: false,
  225. surgeries: [
  226. { name: '', time: '' },
  227. { name: '', time: '' }
  228. ],
  229. hasTrauma: false,
  230. traumas: [
  231. { part: '', time: '' },
  232. { part: '', time: '' }
  233. ]
  234. },
  235. currentDiseases: [
  236. { name: '', confirmTime: '', currentStatus: '' },
  237. { name: '', confirmTime: '', currentStatus: '' }
  238. ],
  239. medicalSituation: {
  240. regularVisit: { none: true, reason: '', frequency: '', frequencyUnit: '' },
  241. hospitalization: {
  242. none: true,
  243. count: '',
  244. records: [
  245. { inTime: '', outTime: '', reason: '', hospital: '' },
  246. { inTime: '', outTime: '', reason: '', hospital: '' }
  247. ]
  248. },
  249. emergency: {
  250. none: true,
  251. count: '',
  252. records: [{ time: '', reason: '', hospital: '' }]
  253. }
  254. },
  255. discomfortSymptoms: {
  256. none: true,
  257. items: [],
  258. other: ''
  259. },
  260. allergyHistory: {
  261. drug: { none: true, detail: '' },
  262. food: { none: true, detail: '' },
  263. environment: { none: true, detail: '' }
  264. },
  265. geriatricSyndrome: {
  266. fall: { none: true, detail: '' },
  267. memoryDecline: { none: true, detail: '' },
  268. weightLoss: { none: true, weight: '' },
  269. urinaryIncontinence: { none: true, count: '' },
  270. sleepDisorder: { none: true, types: [], other: '' },
  271. pain: { none: true, part: '' },
  272. visionAbnormal: { none: true, types: [], degree: '' },
  273. hearingLoss: { none: true, detail: '' }
  274. },
  275. mentalStatus: {
  276. none: true,
  277. items: [],
  278. other: ''
  279. },
  280. languageExpression: [],
  281. languageExpressionOther: '',
  282. otherNotes: '',
  283. partyBSign: '',
  284. partyCSign: '',
  285. signDate: ''
  286. })
  287. const checkInInformationForm = ref({})
  288. const contractSignatureData = ref<Record<string, any>>({
  289. partyASign: '',
  290. partyALegalSign: '',
  291. partyADate: '',
  292. partyBSign: '',
  293. partyBDate: '',
  294. partyCSign: '',
  295. partyCDate: ''
  296. })
  297. const handleContractDataChange = (data: any) => {
  298. if (!data) return
  299. contractSignatureData.value.partyASign = data.partyASign || ''
  300. contractSignatureData.value.partyALegalSign = data.partyALegalSign || ''
  301. contractSignatureData.value.partyADate = data.partyADate || ''
  302. contractSignatureData.value.partyBSign = data.partyBSign || ''
  303. contractSignatureData.value.partyBDate = data.partyBDate || ''
  304. contractSignatureData.value.partyCSign = data.partyCSign || ''
  305. contractSignatureData.value.partyCDate = data.partyCDate || ''
  306. }
  307. const handleAttachmentsLoad = (data: Record<string, any>) => {
  308. console.log('收到', data)
  309. if (!data) return
  310. Object.keys(data).forEach((key) => {
  311. if (attachments.hasOwnProperty(key)) {
  312. // console.log('AAAAAAA', data[key])
  313. attachments[key] = data[key]
  314. if (key == 'attach_1') {
  315. Object.assign(riskDisclosureForm, data[key])
  316. } else if (key == 'attach_2') {
  317. Object.assign(checkInRegistrationForm, data[key])
  318. } else if (key == 'attach_4') {
  319. Object.assign(safetyCommitmentForm, data[key])
  320. } else if (key == 'attach_5') {
  321. Object.assign(outingConfirmationForm, data[key])
  322. } else if (key == 'attach_6') {
  323. Object.assign(signatureProxyForm, data[key])
  324. } else if (key == 'attach_7') {
  325. Object.assign(powerOfAttorneyForm, data[key])
  326. } else if (key == 'attach_8') {
  327. Object.assign(delegationAgentForm, data[key])
  328. } else if (key == 'attach_9') {
  329. Object.assign(serviceChargingForm, data[key])
  330. } else if (key == 'attach_10') {
  331. Object.assign(roomFacilitiesForm, data[key])
  332. } else if (key == 'attach_11') {
  333. Object.assign(examinationReportForm, data[key])
  334. } else if (key == 'attach_12') {
  335. Object.assign(identificationPartyBForm, data[key])
  336. } else if (key == 'attach_13') {
  337. Object.assign(guardianPartyBForm, data[key])
  338. } else if (key == 'attach_14') {
  339. Object.assign(registrationCertificateForm, data[key])
  340. } else if (key == 'attach_15') {
  341. Object.assign(comprehensiveAssessmentForm, data[key])
  342. } else if (key == 'attach_16') {
  343. Object.assign(firstServiceForm, data[key])
  344. } else if (key == 'attach_17') {
  345. Object.assign(changeConfirmForm, data[key])
  346. }
  347. }
  348. })
  349. }
  350. const selectedAttachments = computed(() => {
  351. return attachmentList.filter((a) => {
  352. const data = attachments[`attach_${a.no}`]
  353. if (!data) return false
  354. if (Array.isArray(data)) return data.length > 0
  355. if (typeof data === 'object') return Object.keys(data).length > 0
  356. return !!data
  357. })
  358. })
  359. const selectorVisible = ref<{
  360. show: boolean
  361. current: string
  362. title: string
  363. list: any[]
  364. }>({
  365. show: false,
  366. current: '',
  367. title: '',
  368. list: []
  369. })
  370. const type_c = ref(1)
  371. const open = (row?: any, type = 1) => {
  372. recordRow.value = row || null
  373. type_c.value = type
  374. isTextMode.value = type_c.value != 1
  375. formLoading.value = true
  376. isPrint.value = false
  377. attachmentList.forEach((it) => {
  378. // 固定内容附件(3=入住须知)默认展示,不需要从列表选择
  379. if (it.no === 3) {
  380. attachments[`attach_${it.no}`] = { title: it.title }
  381. } else {
  382. attachments[`attach_${it.no}`] = null
  383. }
  384. })
  385. visible.value = true
  386. setTimeout(() => {
  387. formLoading.value = false
  388. }, 100)
  389. }
  390. const close = () => {
  391. visible.value = false
  392. }
  393. defineExpose({ open, close })
  394. const emit = defineEmits(['refreshTree'])
  395. const handleSave = async () => {
  396. formLoading.value = true
  397. try {
  398. if (contractBodyRef.value) {
  399. await contractBodyRef.value.saveContract()
  400. }
  401. message.success('保存成功')
  402. visible.value = false
  403. emit('refreshTree')
  404. } finally {
  405. setTimeout(() => {
  406. formLoading.value = false
  407. }, 500)
  408. }
  409. }
  410. const handleClose = () => {
  411. visible.value = false
  412. }
  413. const handlePrint = async () => {
  414. isPrint.value = true
  415. isTextMode.value = true
  416. await nextTick()
  417. await new Promise((resolve) => setTimeout(resolve, 1500))
  418. window.print()
  419. setTimeout(() => {
  420. isPrint.value = false
  421. isTextMode.value = false
  422. }, 500)
  423. }
  424. const toggleTextMode = () => {
  425. isTextMode.value = !isTextMode.value
  426. }
  427. const openAttachmentSelector = (attach: any) => {
  428. selectorVisible.value = {
  429. show: true,
  430. current: `attach_${attach.no}`,
  431. title: attach.title,
  432. list: []
  433. }
  434. }
  435. const handleAttachmentDelete = (attach: any) => {
  436. const key = `attach_${attach.no}`
  437. // 固定内容附件(3=入住须知)删除后恢复默认展示
  438. if (attach.no === 3) {
  439. attachments[key] = { title: attach.title }
  440. } else {
  441. attachments[key] = null
  442. }
  443. message.success(`已删除:${attach.title}`)
  444. }
  445. const handleAttachmentSelect = async (row: any) => {
  446. const key = selectorVisible.value.current
  447. console.log('KEY值:', key, row, attachments)
  448. if (key) {
  449. const getDetail = attachmentDetailMap[key]
  450. let fullData = row
  451. if (getDetail && row.id) {
  452. try {
  453. const detail = await getDetail(row.id)
  454. if (detail && detail.content) {
  455. fullData = detail.content
  456. const dataJson = JSON.parse(fullData)
  457. // 将id添加到数据中,方便后续操作
  458. dataJson.id = row.id
  459. fullData = JSON.stringify(dataJson)
  460. }
  461. } catch (error) {
  462. console.error(`获取附件详情失败: ${key}`, error)
  463. }
  464. }
  465. let data = undefined
  466. try {
  467. attachments[key] = fullData
  468. data = JSON.parse(fullData)
  469. } catch (error) {}
  470. if (key === 'attach_1' && data) {
  471. Object.assign(riskDisclosureForm, data)
  472. } else if (key === 'attach_2') {
  473. Object.assign(checkInRegistrationForm, data)
  474. } else if (key === 'attach_3') {
  475. Object.assign(safetyCommitmentForm, data)
  476. } else if (key === 'attach_5') {
  477. Object.assign(outingConfirmationForm, data)
  478. } else if (key == 'attach_6') {
  479. Object.assign(signatureProxyForm, data)
  480. } else if (key == 'attach_7') {
  481. Object.assign(powerOfAttorneyForm, data)
  482. } else if (key == 'attach_8') {
  483. Object.assign(delegationAgentForm, data)
  484. } else if (key == 'attach_9') {
  485. Object.assign(serviceChargingForm, data)
  486. } else if (key == 'attach_10') {
  487. Object.assign(roomFacilitiesForm, data)
  488. } else if (key == 'attach_11') {
  489. Object.assign(examinationReportForm, data)
  490. } else if (key == 'attach_12') {
  491. Object.assign(identificationPartyBForm, data)
  492. } else if (key == 'attach_13') {
  493. Object.assign(guardianPartyBForm, data)
  494. } else if (key == 'attach_14') {
  495. Object.assign(registrationCertificateForm, data)
  496. } else if (key == 'attach_15') {
  497. Object.assign(comprehensiveAssessmentForm, data)
  498. } else if (key == 'attach_16') {
  499. Object.assign(firstServiceForm, data)
  500. } else if (key == 'attach_17') {
  501. Object.assign(changeConfirmForm, data)
  502. }
  503. message.success(
  504. `已选择:${fullData.elderName || row[activeTitleField(key)] || attachTitle(key)}`
  505. )
  506. }
  507. selectorVisible.value.show = false
  508. }
  509. const attachTitle = (key: string) => {
  510. const no = Number(key.replace('attach_', ''))
  511. const item = attachmentList.find((it) => it.no === no)
  512. return item?.title || '附件数据'
  513. }
  514. const activeTitleField = (key: string) => {
  515. const map: Record<string, string> = {
  516. attach_2: 'elderName'
  517. }
  518. return map[key] || 'title'
  519. }
  520. const handleAttachmentSelectorClose = () => {
  521. selectorVisible.value.show = false
  522. }
  523. const handFormData = (data: any) => {
  524. console.log('收到:', data)
  525. }
  526. const renderAttachmentContent = (key: string) => {
  527. const data = attachments[key]
  528. if (!data) return null
  529. return data.title || '已选择数据'
  530. }
  531. </script>
  532. <template>
  533. <el-drawer
  534. v-model="visible"
  535. title="电子合同"
  536. size="70vw"
  537. direction="rtl"
  538. class="contract-manage-drawer"
  539. >
  540. <div class="drawer-content">
  541. <div class="content-toolbar" v-if="!isPrint">
  542. <el-tag type="info" size="large">颐年集团养老服务合同</el-tag>
  543. <div class="toolbar-right">
  544. <el-tooltip
  545. :content="isTextMode ? '切换为编辑模式(输入框)' : '切换为文本模式(打印预览)'"
  546. placement="top"
  547. >
  548. <el-button
  549. v-show="type_c == 1"
  550. :type="isTextMode ? 'success' : 'default'"
  551. size="small"
  552. @click="toggleTextMode"
  553. >
  554. {{ isTextMode ? '文本模式' : '编辑模式' }}
  555. </el-button>
  556. </el-tooltip>
  557. <el-button
  558. v-show="type_c == 1"
  559. type="primary"
  560. :loading="formLoading"
  561. size="small"
  562. @click="handleSave"
  563. >保存</el-button
  564. >
  565. <el-button size="small" @click="handleClose">关闭</el-button>
  566. <el-tooltip
  567. placement="bottom"
  568. content="打印时目标打印机选择「Microsoft Print to PDF」;导出时目标打印机选择「另存为 PDF」"
  569. >
  570. <el-button type="success" size="small" @click="handlePrint">打印/导出</el-button>
  571. </el-tooltip>
  572. </div>
  573. </div>
  574. <div class="content-body">
  575. <!-- ============ 合同主体 ============ -->
  576. <div class="section-divider">
  577. <span>合同主体</span>
  578. </div>
  579. <ContractBody
  580. ref="contractBodyRef"
  581. :record-row="recordRow"
  582. :is-text-mode="isTextMode"
  583. v-model="contractSignatureData"
  584. :attachment-ids="attachments"
  585. @update:form-data="handFormData"
  586. @update:data="handleContractDataChange"
  587. @update:attachments="handleAttachmentsLoad"
  588. />
  589. <!-- ============ 16个附件 ============ -->
  590. <div class="section-divider" style="margin-top: 30px">
  591. <span>附件清单</span>
  592. </div>
  593. <AttachmentPage
  594. v-for="attach in attachmentList"
  595. :key="attach.no"
  596. :attachment-no="attach.no"
  597. :title="attach.title"
  598. :selected-data="attachments[`attach_${attach.no}`]"
  599. :is-text-mode="isTextMode"
  600. @select="openAttachmentSelector(attach)"
  601. @delete="handleAttachmentDelete(attach)"
  602. >
  603. <template #default="{ data }">
  604. <div v-if="attach.no === 1" class="attachment-content">
  605. <RiskDisclosureBody v-model="riskDisclosureForm" :is-text-mode="isTextMode" />
  606. </div>
  607. <div v-else-if="attach.no === 2" class="attachment-content">
  608. <CheckInRegistrationBody
  609. v-model="checkInRegistrationForm"
  610. :is-text-mode="true"
  611. :elder-id="recordRow?.elderId"
  612. />
  613. </div>
  614. <div v-else-if="attach.no === 3" class="attachment-content">
  615. <CheckInInformationBody
  616. v-model="checkInInformationForm"
  617. :is-text-mode="true"
  618. :elder-id="recordRow?.elderId"
  619. />
  620. </div>
  621. <div v-else-if="attach.no === 4" class="attachment-content">
  622. <ElderSafetyCommitmentBody v-model="safetyCommitmentForm" :is-text-mode="true" />
  623. </div>
  624. <div v-else-if="attach.no === 5" class="attachment-content">
  625. <OutingConfirmationBody v-model="outingConfirmationForm" :is-text-mode="true" />
  626. </div>
  627. <div v-else-if="attach.no === 6" class="attachment-content">
  628. <SignatureProxyApplicationBody v-model="signatureProxyForm" :is-text-mode="true" />
  629. </div>
  630. <div v-else-if="attach.no === 7" class="attachment-content">
  631. <PowerOfAttorneyBody v-model="powerOfAttorneyForm" :is-text-mode="true" />
  632. </div>
  633. <div v-else-if="attach.no === 8" class="attachment-content">
  634. <DelegationAgentConfirmationBody v-model="delegationAgentForm" :is-text-mode="true" />
  635. </div>
  636. <div v-else-if="attach.no === 9" class="attachment-content">
  637. <ServiceChargingStandardsBody v-model="serviceChargingForm" :is-text-mode="true" />
  638. </div>
  639. <div v-else-if="attach.no === 10" class="attachment-content">
  640. <RoomFacilitiesEquipmentBody v-model="roomFacilitiesForm" :is-text-mode="true" />
  641. </div>
  642. <div v-else-if="attach.no === 11" class="attachment-content">
  643. <ExplanationExaminationReportBody
  644. v-model="examinationReportForm"
  645. :is-text-mode="true"
  646. />
  647. </div>
  648. <div v-else-if="attach.no === 12" class="attachment-content">
  649. <DocumentAttachmentBody
  650. v-model="identificationPartyBForm"
  651. :is-text-mode="true"
  652. title="乙方有效证件"
  653. description="乙方:有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页)"
  654. />
  655. </div>
  656. <div v-else-if="attach.no === 13" class="attachment-content">
  657. <DocumentAttachmentBody
  658. v-model="guardianPartyBForm"
  659. :is-text-mode="true"
  660. title="乙方监护人(丙方)身份证、户口本复印件"
  661. description="丙方:(系自然人的)有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页);(系单位的)的有效注册登登记材料复印件(加盖公章)、法定代表人/负责人及联系人的身份证件复印件(身份证件需正反面复印)"
  662. />
  663. </div>
  664. <div v-else-if="attach.no === 14" class="attachment-content">
  665. <DocumentAttachmentBody
  666. v-model="registrationCertificateForm"
  667. :is-text-mode="true"
  668. description="甲方登记证书及民政部门备案回执"
  669. />
  670. </div>
  671. <div v-else-if="attach.no === 15" class="attachment-content">
  672. <DocumentAttachmentBody
  673. v-model="comprehensiveAssessmentForm"
  674. :is-text-mode="true"
  675. title="老年人能力综合评估结果"
  676. />
  677. </div>
  678. <div v-else-if="attach.no === 16" class="attachment-content">
  679. <FirstServiceProjectConfirmationBody
  680. v-model="firstServiceForm"
  681. :is-text-mode="true"
  682. />
  683. </div>
  684. <div v-else-if="attach.no === 17" class="attachment-content">
  685. <ChangeConfirmationFormBody v-model="changeConfirmForm" :is-text-mode="true" />
  686. </div>
  687. <div v-else class="attachment-body">
  688. <p><strong>日期:</strong>{{ data?.createTime || '-' }}</p>
  689. <p style="color: #999; font-size: 13px">
  690. (此处为附件内容占位,实际各附件将由对应列表页提供完整内容排版)
  691. </p>
  692. </div>
  693. </template>
  694. </AttachmentPage>
  695. </div>
  696. </div>
  697. </el-drawer>
  698. <AttachmentSelector
  699. v-model:visible="selectorVisible.show"
  700. :title="selectorVisible.title"
  701. :current="selectorVisible.current"
  702. :elder-id="recordRow?.elderId"
  703. :list="selectorVisible.list"
  704. @select="handleAttachmentSelect"
  705. @update:visible="handleAttachmentSelectorClose"
  706. />
  707. <!-- ========== 打印容器:Teleport 到 body,确保是 body 直接子元素 ========== -->
  708. <Teleport to="body">
  709. <div v-if="isPrint" class="print-container">
  710. <!-- 合同主体 -->
  711. <ContractBody
  712. :record-row="recordRow"
  713. :is-text-mode="true"
  714. v-model="contractSignatureData"
  715. :attachment-ids="attachments"
  716. />
  717. <!-- 已选的附件 -->
  718. <template v-if="selectedAttachments.length > 0">
  719. <div v-for="attach in selectedAttachments" :key="attach.no" class="print-attachment">
  720. <!-- 附件1:知情同意书,使用 RiskDisclosureBody -->
  721. <RiskDisclosureBody
  722. v-if="attach.no === 1"
  723. v-model="riskDisclosureForm"
  724. class="mgb"
  725. :is-text-mode="true"
  726. />
  727. <!-- 附件2:入住登记表,使用 CheckInRegistrationBody -->
  728. <CheckInRegistrationBody
  729. v-else-if="attach.no === 2"
  730. v-model="checkInRegistrationForm"
  731. class="mgb"
  732. :is-text-mode="true"
  733. :elder-id="recordRow?.elderId"
  734. />
  735. <!-- 附件3:入住须知,使用 CheckInInformationBody -->
  736. <CheckInInformationBody v-else-if="attach.no === 3" class="mgb" :is-text-mode="true" />
  737. <!-- 附件4:长者安全承诺书,使用 ElderSafetyCommitmentBody -->
  738. <ElderSafetyCommitmentBody
  739. v-else-if="attach.no === 4"
  740. class="mgb"
  741. v-model="safetyCommitmentForm"
  742. :is-text-mode="true"
  743. />
  744. <!-- 附件5:长者外出情况确认书,使用 OutingConfirmationBody -->
  745. <OutingConfirmationBody
  746. v-else-if="attach.no === 5"
  747. class="mgb"
  748. v-model="outingConfirmationForm"
  749. :is-text-mode="true"
  750. />
  751. <!-- 附件6:签名代理申请,使用 SignatureProxyApplicationBody -->
  752. <SignatureProxyApplicationBody
  753. v-else-if="attach.no === 6"
  754. class="mgb"
  755. v-model="signatureProxyForm"
  756. :is-text-mode="true"
  757. />
  758. <!-- 附件7:授权委托书,使用 PowerOfAttorneyBody -->
  759. <PowerOfAttorneyBody
  760. v-else-if="attach.no === 7"
  761. v-model="powerOfAttorneyForm"
  762. class="mgb"
  763. :is-text-mode="true"
  764. />
  765. <!-- 附件8:委托代理人确认表,使用 DelegationAgentConfirmationBody -->
  766. <DelegationAgentConfirmationBody
  767. v-else-if="attach.no === 8"
  768. class="mgb"
  769. v-model="delegationAgentForm"
  770. :is-text-mode="true"
  771. />
  772. <!-- 附件9:机构服务范围及收费标准,使用 ServiceChargingStandardsBody -->
  773. <ServiceChargingStandardsBody
  774. v-else-if="attach.no === 9"
  775. v-model="serviceChargingForm"
  776. class="mgb"
  777. :is-text-mode="true"
  778. />
  779. <!-- 附件10:标准房间物品配备,使用 RoomFacilitiesEquipmentBody -->
  780. <RoomFacilitiesEquipmentBody
  781. v-else-if="attach.no === 10"
  782. v-model="roomFacilitiesForm"
  783. class="mgb"
  784. :is-text-mode="true"
  785. />
  786. <!-- 附件11:体检报告项目说明,使用 ExplanationExaminationReportBody -->
  787. <ExplanationExaminationReportBody
  788. v-else-if="attach.no === 11"
  789. v-model="examinationReportForm"
  790. class="mgb"
  791. :is-text-mode="true"
  792. />
  793. <!-- 附件12:乙方有效证件,使用 DocumentAttachmentBody -->
  794. <DocumentAttachmentBody
  795. v-else-if="attach.no === 12"
  796. v-model="identificationPartyBForm"
  797. class="mgb"
  798. :is-text-mode="true"
  799. title="乙方有效证件"
  800. description="乙方:有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页)"
  801. />
  802. <!-- 附件13:乙方监护人(丙方)身份证、户口本复印件,使用 DocumentAttachmentBody -->
  803. <DocumentAttachmentBody
  804. v-else-if="attach.no === 13"
  805. v-model="guardianPartyBForm"
  806. class="mgb"
  807. :is-text-mode="true"
  808. title="乙方监护人(丙方)身份证、户口本复印件"
  809. description="丙方:(系自然人的)有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页);(系单位的)的有效注册登登记材料复印件(加盖公章)、法定代表人/负责人及联系人的身份证件复印件(身份证件需正反面复印)"
  810. />
  811. <!-- 附件14:甲方登记证书及民政部门备案回执,使用 DocumentAttachmentBody -->
  812. <DocumentAttachmentBody
  813. v-else-if="attach.no === 14"
  814. v-model="registrationCertificateForm"
  815. class="mgb"
  816. :is-text-mode="true"
  817. description="甲方登记证书及民政部门备案回执"
  818. />
  819. <!-- 附件15:老年人能力综合评估结果,使用 DocumentAttachmentBody -->
  820. <DocumentAttachmentBody
  821. v-else-if="attach.no === 15"
  822. v-model="comprehensiveAssessmentForm"
  823. class="mgb"
  824. :is-text-mode="true"
  825. title="老年人能力综合评估结果"
  826. />
  827. <!-- 附件16:首次服务项目确认表,使用 FirstServiceProjectConfirmationBody -->
  828. <FirstServiceProjectConfirmationBody
  829. v-else-if="attach.no === 16"
  830. v-model="firstServiceForm"
  831. class="mgb"
  832. :is-text-mode="true"
  833. />
  834. <!-- 附件17:变更事项确认表,使用 ChangeConfirmationFormBody -->
  835. <ChangeConfirmationFormBody
  836. v-else-if="attach.no === 17"
  837. v-model="changeConfirmForm"
  838. class="mgb"
  839. :is-text-mode="true"
  840. />
  841. <!-- 其他附件:展示标题和信息 -->
  842. <div v-else class="a4-page">
  843. <div class="print-attachment-header">
  844. <div class="print-attachment-title">附件{{ attach.no }}:{{ attach.title }}</div>
  845. </div>
  846. <div class="print-attachment-info">
  847. <p><strong>名称:</strong>{{ renderAttachmentContent(`attach_${attach.no}`) }}</p>
  848. <p><strong>版本:</strong>{{ attachments[`attach_${attach.no}`]?.version || '-' }}</p>
  849. <p
  850. ><strong>日期:</strong
  851. >{{ attachments[`attach_${attach.no}`]?.createTime || '-' }}</p
  852. >
  853. </div>
  854. </div>
  855. </div>
  856. </template>
  857. </div>
  858. </Teleport>
  859. </template>
  860. <style lang="scss">
  861. .contract-manage-drawer {
  862. .el-drawer__header {
  863. margin-bottom: 0 !important;
  864. padding: 14px 20px;
  865. }
  866. .el-drawer__body {
  867. padding: 0 !important;
  868. display: flex;
  869. flex-direction: column;
  870. height: 100%;
  871. overflow: hidden;
  872. }
  873. }
  874. /* ========== 打印容器(屏幕上隐藏,仅在 html2pdf 导出时临时显示,或在 @media print 时显示) ========== */
  875. .print-container {
  876. display: none;
  877. }
  878. /* ========== 打印样式核心逻辑:只显示 Teleport 到 body 的 print-container ========== */
  879. @media print {
  880. html,
  881. body {
  882. background: #fff !important;
  883. margin: 0 !important;
  884. padding: 0 !important;
  885. overflow: visible !important;
  886. }
  887. /* 1. body 的所有直接子元素都隐藏,除了 print-container */
  888. body > *:not(.print-container) {
  889. display: none !important;
  890. }
  891. /* 2. 打印容器本身显示出来 */
  892. body > .print-container {
  893. display: block !important;
  894. }
  895. /* 3. A4 页:保留屏幕内边距(10mm 14mm),由 @page margin 控制物理边距
  896. 各组件打印样式已统一为 width:100%,不会再溢出 */
  897. .print-container .a4-page {
  898. width: 100% !important;
  899. margin: 0 !important;
  900. padding: 10mm 14mm !important;
  901. background: #fff !important;
  902. box-shadow: none !important;
  903. box-sizing: border-box !important;
  904. page-break-after: auto;
  905. break-after: auto;
  906. }
  907. .print-container .print-attachment .mgb {
  908. padding-bottom: 30px !important;
  909. }
  910. /* 封面页保留完整 A4 高度并强制分页(可打印区域 265mm,@page margin 16mm) */
  911. .print-container .a4-page.cover-a4 {
  912. min-height: 265mm !important;
  913. page-break-after: always;
  914. break-after: page;
  915. }
  916. /* 其他页自适应高度,内容连续流动 */
  917. .print-container .a4-page:not(.cover-a4) {
  918. min-height: auto !important;
  919. page-break-after: auto;
  920. break-after: auto;
  921. }
  922. /* 4. 防止标题、列表项、签名区被分页切断 */
  923. .print-container .section-title,
  924. .print-container .sub-title {
  925. break-after: avoid-page;
  926. page-break-after: avoid;
  927. }
  928. .print-container .num-list li,
  929. .print-container .signature-group {
  930. break-inside: avoid-page;
  931. page-break-inside: avoid;
  932. }
  933. @page {
  934. size: 210mm 297mm;
  935. margin: 16mm;
  936. }
  937. }
  938. </style>
  939. <style scoped lang="scss">
  940. .drawer-content {
  941. display: flex;
  942. flex-direction: column;
  943. height: 100%;
  944. min-height: 0;
  945. }
  946. .content-toolbar {
  947. flex-shrink: 0;
  948. display: flex;
  949. justify-content: space-between;
  950. align-items: center;
  951. padding: 10px 16px;
  952. background: #f8f9fb;
  953. border-bottom: 1px solid #e4e7ed;
  954. }
  955. .toolbar-right {
  956. display: flex;
  957. gap: 8px;
  958. }
  959. .section-divider {
  960. width: 210mm;
  961. margin: 0 auto 8px;
  962. text-align: center;
  963. padding: 8px 0;
  964. font-size: 15px;
  965. font-weight: bold;
  966. color: #666;
  967. border-top: 1px dashed #ccc;
  968. position: relative;
  969. }
  970. .section-divider span {
  971. display: inline-block;
  972. padding: 4px 14px;
  973. background: #fff;
  974. border-radius: 4px;
  975. border: 1px solid #e4e7ed;
  976. }
  977. .content-body {
  978. flex: 1;
  979. overflow-y: auto;
  980. padding: 16px 0;
  981. background: #f5f5f5;
  982. }
  983. .content-footer {
  984. flex-shrink: 0;
  985. display: flex;
  986. justify-content: flex-end;
  987. gap: 12px;
  988. padding: 12px 20px;
  989. border-top: 1px solid #ebeef5;
  990. background: #fafafa;
  991. }
  992. .attachment-body {
  993. padding: 10px 0;
  994. }
  995. .attachment-body p {
  996. margin: 4px 0;
  997. line-height: 1.8;
  998. }
  999. /* 打印模式下其他附件标题样式 */
  1000. .print-attachment-header {
  1001. padding-bottom: 10px;
  1002. border-bottom: 1.5px solid #333;
  1003. margin-bottom: 16px;
  1004. }
  1005. .print-attachment-title {
  1006. font-size: 18px;
  1007. font-weight: bold;
  1008. color: #222;
  1009. }
  1010. .print-attachment-info {
  1011. font-size: 14px;
  1012. line-height: 1.9;
  1013. color: #333;
  1014. }
  1015. .print-attachment-info p {
  1016. margin: 6px 0;
  1017. }
  1018. </style>