Преглед на файлове

继续优化电子合同

unknown преди 2 седмици
родител
ревизия
aa1c77ac1d
променени са 36 файла, в които са добавени 8270 реда и са изтрити 152 реда
  1. 194 6
      src/views/elderly/contracts/ContractManageForm.vue
  2. 789 0
      src/views/elderly/contracts/change-confirmation-form/AddForm.vue
  3. 312 0
      src/views/elderly/contracts/change-confirmation-form/index.vue
  4. 5 7
      src/views/elderly/contracts/components/AttachmentPage.vue
  5. 351 0
      src/views/elderly/contracts/components/ChangeConfirmationFormBody.vue
  6. 9 4
      src/views/elderly/contracts/components/CheckInInformationBody.vue
  7. 13 10
      src/views/elderly/contracts/components/CheckInRegistrationBody.vue
  8. 7 10
      src/views/elderly/contracts/components/ContractBody.vue
  9. 334 0
      src/views/elderly/contracts/components/DelegationAgentConfirmationBody.vue
  10. 280 0
      src/views/elderly/contracts/components/DocumentAttachmentBody.vue
  11. 4 3
      src/views/elderly/contracts/components/ElderSafetyCommitmentBody.vue
  12. 270 0
      src/views/elderly/contracts/components/ExplanationExaminationReportBody.vue
  13. 639 0
      src/views/elderly/contracts/components/FirstServiceProjectConfirmationBody.vue
  14. 327 0
      src/views/elderly/contracts/components/OutingConfirmationBody.vue
  15. 263 0
      src/views/elderly/contracts/components/PowerOfAttorneyBody.vue
  16. 20 40
      src/views/elderly/contracts/components/RiskDisclosureBody.vue
  17. 458 0
      src/views/elderly/contracts/components/RoomFacilitiesEquipmentBody.vue
  18. 209 0
      src/views/elderly/contracts/components/ServiceChargingStandardsBody.vue
  19. 209 0
      src/views/elderly/contracts/components/SignatureProxyApplicationBody.vue
  20. 792 0
      src/views/elderly/contracts/comprehensive-assessment-capabilities/AddForm.vue
  21. 312 0
      src/views/elderly/contracts/comprehensive-assessment-capabilities/index.vue
  22. 37 6
      src/views/elderly/contracts/delegation-agent-confirmation-form/AddForm.vue
  23. 4 1
      src/views/elderly/contracts/explanation-examination-report-items copy/AddForm.vue
  24. 25 6
      src/views/elderly/contracts/explanation-examination-report-items/AddForm.vue
  25. 789 0
      src/views/elderly/contracts/first-service-project-confirmation/AddForm.vue
  26. 312 0
      src/views/elderly/contracts/first-service-project-confirmation/index.vue
  27. 26 6
      src/views/elderly/contracts/guardian-party-b/AddForm.vue
  28. 26 7
      src/views/elderly/contracts/identification-document-partyb/AddForm.vue
  29. 28 15
      src/views/elderly/contracts/outing-confirmation-form/AddForm.vue
  30. 35 6
      src/views/elderly/contracts/power-of-attorney/AddForm.vue
  31. 792 0
      src/views/elderly/contracts/registration-certificate-civil-affairs-department/AddForm.vue
  32. 312 0
      src/views/elderly/contracts/registration-certificate-civil-affairs-department/index.vue
  33. 30 6
      src/views/elderly/contracts/room-facilities-equipment/AddForm.vue
  34. 25 7
      src/views/elderly/contracts/service-charging-standards-institution/AddForm.vue
  35. 30 10
      src/views/elderly/contracts/signature-proxy-application/AddForm.vue
  36. 2 2
      src/views/elderly/elder/contract/index.vue

+ 194 - 6
src/views/elderly/contracts/ContractManageForm.vue

@@ -8,6 +8,16 @@ import RiskDisclosureBody from './components/RiskDisclosureBody.vue'
 import CheckInRegistrationBody from './components/CheckInRegistrationBody.vue'
 import CheckInInformationBody from './components/CheckInInformationBody.vue'
 import ElderSafetyCommitmentBody from './components/ElderSafetyCommitmentBody.vue'
+import OutingConfirmationBody from './components/OutingConfirmationBody.vue'
+import SignatureProxyApplicationBody from './components/SignatureProxyApplicationBody.vue'
+import PowerOfAttorneyBody from './components/PowerOfAttorneyBody.vue'
+import DelegationAgentConfirmationBody from './components/DelegationAgentConfirmationBody.vue'
+import ServiceChargingStandardsBody from './components/ServiceChargingStandardsBody.vue'
+import RoomFacilitiesEquipmentBody from './components/RoomFacilitiesEquipmentBody.vue'
+import ExplanationExaminationReportBody from './components/ExplanationExaminationReportBody.vue'
+import DocumentAttachmentBody from './components/DocumentAttachmentBody.vue'
+import FirstServiceProjectConfirmationBody from './components/FirstServiceProjectConfirmationBody.vue'
+import ChangeConfirmationFormBody from './components/ChangeConfirmationFormBody.vue'
 
 const message = useMessage()
 
@@ -68,6 +78,99 @@ const safetyCommitmentForm = reactive<Record<string, any>>({
   signDate: ''
 })
 
+const outingConfirmationForm = reactive<Record<string, any>>({
+  elderName: '',
+  outingAbility: '',
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+})
+
+const signatureProxyForm = reactive<Record<string, any>>({
+  elderName: '',
+  idCardNo: '',
+  agentName: '',
+  agentIdCardNo: '',
+  applicantSign: '',
+  agentSign: '',
+  signDate: '',
+  handlerSign: ''
+})
+
+const powerOfAttorneyForm = reactive<Record<string, any>>({
+  principalName: '',
+  principalGender: '',
+  principalIdCardNo: '',
+  institutionName: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentPhone: '',
+  elderName: '',
+  relationship: '',
+  principalSign: '',
+  agentSign: '',
+  signDate: ''
+})
+
+const delegationAgentForm = reactive<Record<string, any>>({
+  elderName: '',
+  elderGender: '',
+  elderAge: '',
+  elderIdCardNo: '',
+  elderAddress: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentAddress: '',
+  declarerName: '',
+  representedName: '',
+  relationship: '',
+  elderSign: '',
+  agentSign: '',
+  signDate: ''
+})
+
+const serviceChargingForm = reactive<Record<string, any>>({
+  signatorySign: '',
+  signDate: ''
+})
+
+const roomFacilitiesForm = reactive<Record<string, any>>({
+  elderName: '',
+  roomNo: '',
+  leftItems: [],
+  rightItems: [],
+  blankItems: [],
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+})
+
+const examinationReportForm = reactive<Record<string, any>>({
+  images: []
+})
+
+const identificationPartyBForm = reactive<Record<string, any>>({
+  images: []
+})
+
+const guardianPartyBForm = reactive<Record<string, any>>({
+  images: []
+})
+
+const registrationCertificateForm = reactive<Record<string, any>>({
+  images: []
+})
+
+const comprehensiveAssessmentForm = reactive<Record<string, any>>({
+  images: []
+})
+
+const firstServiceForm = reactive<Record<string, any>>({})
+
+const changeConfirmForm = reactive<Record<string, any>>({})
+
 const checkInRegistrationForm = reactive<Record<string, any>>({
   checkInNo: '',
   formDate: '',
@@ -236,6 +339,13 @@ const open = (row?: any) => {
   safetyCommitmentForm.guardianSign = ''
   safetyCommitmentForm.signDate = ''
 
+  // 重置长者外出情况确认书表单
+  outingConfirmationForm.elderName = ''
+  outingConfirmationForm.outingAbility = ''
+  outingConfirmationForm.elderSign = ''
+  outingConfirmationForm.guardianSign = ''
+  outingConfirmationForm.signDate = ''
+
   // 重置入住登记表表单
   Object.keys(checkInRegistrationForm).forEach((key) => {
     const val = (checkInRegistrationForm as any)[key]
@@ -409,6 +519,17 @@ const handleAttachmentSelect = (row: any) => {
         console.error('解析长者安全承诺书数据失败', error)
       }
     }
+    // 附件 5:长者外出情况确认书,解析 assessData 到表单
+    if (key === 'attach_5' && row.assessData) {
+      try {
+        const data = JSON.parse(row.assessData)
+        if (data.outingConfirmation) {
+          Object.assign(outingConfirmationForm, data.outingConfirmation)
+        }
+      } catch (error) {
+        console.error('解析长者外出情况确认书数据失败', error)
+      }
+    }
     message.success(`已选择:${row[activeTitleField(key)] || attachTitle(key)}`)
   }
   selectorVisible.value.show = false
@@ -505,6 +626,45 @@ const renderAttachmentContent = (key: string) => {
             <div v-else-if="attach.no === 4" class="attachment-content">
               <ElderSafetyCommitmentBody v-model="safetyCommitmentForm" :is-text-mode="isTextMode" />
             </div>
+            <div v-else-if="attach.no === 5" class="attachment-content">
+              <OutingConfirmationBody v-model="outingConfirmationForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 6" class="attachment-content">
+              <SignatureProxyApplicationBody v-model="signatureProxyForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 7" class="attachment-content">
+              <PowerOfAttorneyBody v-model="powerOfAttorneyForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 8" class="attachment-content">
+              <DelegationAgentConfirmationBody v-model="delegationAgentForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 9" class="attachment-content">
+              <ServiceChargingStandardsBody v-model="serviceChargingForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 10" class="attachment-content">
+              <RoomFacilitiesEquipmentBody v-model="roomFacilitiesForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 11" class="attachment-content">
+              <ExplanationExaminationReportBody v-model="examinationReportForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 12" class="attachment-content">
+              <DocumentAttachmentBody v-model="identificationPartyBForm" :is-text-mode="isTextMode" title="乙方有效证件" description="乙方:有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页)" />
+            </div>
+            <div v-else-if="attach.no === 13" class="attachment-content">
+              <DocumentAttachmentBody v-model="guardianPartyBForm" :is-text-mode="isTextMode" title="乙方监护人(丙方)身份证、户口本复印件" description="丙方:(系自然人的)有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页);(系单位的)的有效注册登登记材料复印件(加盖公章)、法定代表人/负责人及联系人的身份证件复印件(身份证件需正反面复印)" />
+            </div>
+            <div v-else-if="attach.no === 14" class="attachment-content">
+              <DocumentAttachmentBody v-model="registrationCertificateForm" :is-text-mode="isTextMode" description="甲方登记证书及民政部门备案回执" />
+            </div>
+            <div v-else-if="attach.no === 15" class="attachment-content">
+              <DocumentAttachmentBody v-model="comprehensiveAssessmentForm" :is-text-mode="isTextMode" title="老年人能力综合评估结果" />
+            </div>
+            <div v-else-if="attach.no === 16" class="attachment-content">
+              <FirstServiceProjectConfirmationBody v-model="firstServiceForm" :is-text-mode="isTextMode" />
+            </div>
+            <div v-else-if="attach.no === 17" class="attachment-content">
+              <ChangeConfirmationFormBody v-model="changeConfirmForm" :is-text-mode="isTextMode" />
+            </div>
             <div v-else class="attachment-body">           
               <p><strong>日期:</strong>{{ data?.createTime || '-' }}</p>
               <p style="color: #999; font-size: 13px;">
@@ -548,6 +708,33 @@ const renderAttachmentContent = (key: string) => {
           <CheckInInformationBody v-else-if="attach.no === 3" :is-text-mode="true" />
           <!-- 附件4:长者安全承诺书,使用 ElderSafetyCommitmentBody -->
           <ElderSafetyCommitmentBody v-else-if="attach.no === 4" v-model="safetyCommitmentForm" :is-text-mode="true" />
+          <!-- 附件5:长者外出情况确认书,使用 OutingConfirmationBody -->
+          <OutingConfirmationBody v-else-if="attach.no === 5" v-model="outingConfirmationForm" :is-text-mode="true" />
+          <!-- 附件6:签名代理申请,使用 SignatureProxyApplicationBody -->
+          <SignatureProxyApplicationBody v-else-if="attach.no === 6" v-model="signatureProxyForm" :is-text-mode="true" />
+          <!-- 附件7:授权委托书,使用 PowerOfAttorneyBody -->
+          <PowerOfAttorneyBody v-else-if="attach.no === 7" v-model="powerOfAttorneyForm" :is-text-mode="true" />
+          <!-- 附件8:委托代理人确认表,使用 DelegationAgentConfirmationBody -->
+          <DelegationAgentConfirmationBody v-else-if="attach.no === 8" v-model="delegationAgentForm" :is-text-mode="true" />
+          <!-- 附件9:机构服务范围及收费标准,使用 ServiceChargingStandardsBody -->
+          <ServiceChargingStandardsBody v-else-if="attach.no === 9" v-model="serviceChargingForm" :is-text-mode="true" />
+          <!-- 附件10:标准房间物品配备,使用 RoomFacilitiesEquipmentBody -->
+          <RoomFacilitiesEquipmentBody v-else-if="attach.no === 10" v-model="roomFacilitiesForm" :is-text-mode="true" />
+          <!-- 附件11:体检报告项目说明,使用 ExplanationExaminationReportBody -->
+          <ExplanationExaminationReportBody v-else-if="attach.no === 11" v-model="examinationReportForm" :is-text-mode="true" />
+          <!-- 附件12:乙方有效证件,使用 DocumentAttachmentBody -->
+          <DocumentAttachmentBody v-else-if="attach.no === 12" v-model="identificationPartyBForm" :is-text-mode="true" title="乙方有效证件" description="乙方:有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页)" />
+          <!-- 附件13:乙方监护人(丙方)身份证、户口本复印件,使用 DocumentAttachmentBody -->
+          <DocumentAttachmentBody v-else-if="attach.no === 13" v-model="guardianPartyBForm" :is-text-mode="true" title="乙方监护人(丙方)身份证、户口本复印件" description="丙方:(系自然人的)有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页);(系单位的)的有效注册登登记材料复印件(加盖公章)、法定代表人/负责人及联系人的身份证件复印件(身份证件需正反面复印)" />
+          <!-- 附件14:甲方登记证书及民政部门备案回执,使用 DocumentAttachmentBody -->
+          <DocumentAttachmentBody v-else-if="attach.no === 14" v-model="registrationCertificateForm" :is-text-mode="true" description="甲方登记证书及民政部门备案回执" />
+          <!-- 附件15:老年人能力综合评估结果,使用 DocumentAttachmentBody -->
+          <DocumentAttachmentBody v-else-if="attach.no === 15" v-model="comprehensiveAssessmentForm" :is-text-mode="true" title="老年人能力综合评估结果" />
+          <!-- 附件16:首次服务项目确认表,使用 FirstServiceProjectConfirmationBody -->
+          <FirstServiceProjectConfirmationBody v-else-if="attach.no === 16" v-model="firstServiceForm" :is-text-mode="true" />
+          <!-- 附件17:变更事项确认表,使用 ChangeConfirmationFormBody -->
+          <ChangeConfirmationFormBody v-else-if="attach.no === 17" v-model="changeConfirmForm" :is-text-mode="true" />
+
           <!-- 其他附件:展示标题和信息 -->
           <div v-else class="a4-page">
             <div class="print-attachment-header">
@@ -606,11 +793,12 @@ const renderAttachmentContent = (key: string) => {
     display: block !important;
   }
 
-  /* 3. A4 页:去除容器 padding,改用 @page margin 控制每页四周间距 */
+  /* 3. A4 页:保留屏幕内边距(10mm 14mm),由 @page margin 控制物理边距
+     各组件打印样式已统一为 width:100%,不会再溢出 */
   .print-container .a4-page {
     width: 100% !important;
     margin: 0 !important;
-    padding: 0 !important;
+    padding: 10mm 14mm !important;
     background: #fff !important;
     box-shadow: none !important;
     box-sizing: border-box !important;
@@ -618,9 +806,9 @@ const renderAttachmentContent = (key: string) => {
     break-after: auto;
   }
 
-  /* 封面页保留完整 A4 高度并强制分页(可打印区域 261mm) */
+  /* 封面页保留完整 A4 高度并强制分页(可打印区域 265mm,@page margin 16mm) */
   .print-container .a4-page.cover-a4 {
-    min-height: 261mm !important;
+    min-height: 265mm !important;
     page-break-after: always;
     break-after: page;
   }
@@ -645,8 +833,8 @@ const renderAttachmentContent = (key: string) => {
   }
 
   @page {
-    size: A4;
-    margin: 18mm;
+    size: 210mm 297mm;
+    margin: 16mm;
   }
 }
 </style>

+ 789 - 0
src/views/elderly/contracts/change-confirmation-form/AddForm.vue

@@ -0,0 +1,789 @@
+<template>
+  <el-drawer
+    v-model="dialogVisible"
+    :title="title"
+    resizable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :destroy-on-close="true"
+    size="70%"
+    :before-close="handleClosed"
+  >
+
+    <div class="attack-risk-form">
+      <h1 class="form-title">变更事项确认表(附件)</h1>
+      <!-- 基本信息 -->
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>长者姓名</text>
+          <search-the-elderly ref="selectElderRef" :disabled="isDetail" @update_elder="elderUp" v-model="dataForm.elderName" :tId="dataForm.tenantId"/>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>档案号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.contractNumber" disabled />
+          <el-text v-else disabled="">{{dataForm.contractNumber}}</el-text>
+        </el-col>
+      </el-row>
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>入院日期</text>
+          <el-input v-if="!isDetail" :model-value="dayjs(dataForm.checkInTime).format('YYYY-MM-DD')=='Invalid Date'?'':dayjs(dataForm.checkInTime).format('YYYY-MM-DD')" disabled />
+          <el-text v-else disabled="">{{dayjs(dataForm.checkInTime).format('YYYY-MM-DD')}}</el-text>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>床位号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+          <el-text v-else disabled="">{{dataForm.bedName}}</el-text>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录人</text>
+          <el-input :disabled="isDetail" v-model="form.assessor"  />
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录日期</text>
+          <el-date-picker :disabled="isDetail" v-model="form.assessDate" type="date" style="width: 100%;"/>
+
+        </el-col>
+      </el-row>
+
+      <!-- 内容区域 -->
+      <div class="form-body">
+        <ChangeConfirmationFormBody
+          v-model="changeForm"
+          :is-text-mode="isDetail"
+        />
+      </div>
+
+    </div>
+
+
+
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+      <el-button style="margin-left: 22px;margin-right: 30px" v-loading="formLoading" type="primary" v-show="!isDetail" @click="submitForm">确定</el-button>
+    </template>
+
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch, reactive } from 'vue'
+
+import dayjs from 'dayjs'
+import ChangeConfirmationFormBody from '../components/ChangeConfirmationFormBody.vue'
+
+import { attackRiskCreate, attackRiskGetById, attackRiskUpdate, attackRiskGetByElderId } from "@/api/social-work";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const title = ref('')
+const dialogVisible = ref(false) // 弹窗
+const formRef = ref() // 表单 Ref
+const selectElderRef = ref() // 表单 Ref
+const changeForm = reactive<Record<string, any>>({})
+const isDetail = ref(false) // 是否详情打开
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+let dataForm = ref({
+  // 表单字段
+  id: undefined,
+  idCard: '',
+  contractNumber: '', //档案号
+  elderName: '',//长者姓名
+  bedName: '', //床位号
+  elderAge: '', //年龄
+  elderSex: '', //性别
+  checkInTime: '', //入院日期
+  elderId: '',
+  tenantId: undefined
+})
+
+
+
+
+const elderUp = (e) => {
+  dataForm.value.elderName = e.elderName
+  dataForm.value.elderId = e.id
+  dataForm.value.elderSex = e.elderSex === 1 ? '男' : '女'
+  dataForm.value.bedName = e.bedName || ''
+  dataForm.value.checkInTime = e.checkInTime
+  dataForm.value.contractNumber = e.contractNumber||e.fileNumber
+  dataForm.value.elderAge = e.elderAge
+}
+
+// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+
+
+
+/** 风险程度样式类 */
+const riskLevelClass = computed(() => {
+  const level = form.attackLevel
+  if (!level || level === 0) return 'risk-none'
+  if (level === 1) return 'risk-low'
+  if (level === 2) return 'risk-medium'
+  return 'risk-high'
+})
+
+/** 自动判断风险等级 */
+const autoJudgeRiskLevel = () => {
+  const level = form.attackLevel
+  if (!level || level === 0) form.riskLevel = 'none'
+  else if (level === 1) form.riskLevel = 'low'
+  else if (level === 2) form.riskLevel = 'medium'
+  else form.riskLevel = 'high'
+}
+
+ 
+
+
+
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    // 基本信息
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+
+    // 攻击风险等级(1=I级,2=II级,3=III级,4=IV级)
+    attackLevel: form.attackLevel || 0,
+
+    // 各级别选择的具体项
+    attackLevel1Items: form.attackLevel1Items || [],
+    attackLevel2Items: form.attackLevel2Items || [],
+    attackLevel3Items: form.attackLevel3Items || [],
+    attackLevel4Items: form.attackLevel4Items || [],
+
+    // 风险程度
+    riskLevel: form.riskLevel || '',
+
+    // 预防措施
+    preventiveMeasures: form.preventiveMeasures || [],
+    preventiveMeasuresOther: form.preventiveMeasuresOther || '',
+
+    // 签名
+    familySignature: form.familySignature || '',
+    familySignDate: form.familySignDate ? dayjs(form.familySignDate).format('YYYY-MM-DD') : '',
+
+    // 风险知情书内容
+    riskDisclosure: {
+      companyName: riskDisclosureForm.companyName || '',
+      partyBSign: riskDisclosureForm.partyBSign || '',
+      partyBDate: riskDisclosureForm.partyBDate || '',
+      partyCSign: riskDisclosureForm.partyCSign || '',
+      partyCDate: riskDisclosureForm.partyCDate || ''
+    },
+
+    // 变更事项确认表
+    changeConfirm: JSON.parse(JSON.stringify(changeForm))
+  }
+}
+
+/** 将 JSON 对象反序列化为表单数据 */
+const deserializeFormData = (formData: Record<string, any>) => {
+  if (!formData) return
+
+  // 基本信息
+  form.assessor = formData.assessor || ''
+  form.assessDate = formData.assessDate ? dayjs(formData.assessDate).toDate() : ''
+
+  // 攻击风险等级
+  form.attackLevel = formData.attackLevel || 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = formData.attackLevel1Items || []
+  form.attackLevel2Items = formData.attackLevel2Items || []
+  form.attackLevel3Items = formData.attackLevel3Items || []
+  form.attackLevel4Items = formData.attackLevel4Items || []
+
+  // 风险程度
+  form.riskLevel = formData.riskLevel || ''
+
+  // 预防措施
+  form.preventiveMeasures = formData.preventiveMeasures || []
+  form.preventiveMeasuresOther = formData.preventiveMeasuresOther || ''
+
+  // 签名
+  form.familySignature = formData.familySignature || ''
+  form.familySignDate = formData.familySignDate ? dayjs(formData.familySignDate).toDate() : ''
+
+  // 风险知情书内容
+  if (formData.riskDisclosure) {
+    riskDisclosureForm.companyName = formData.riskDisclosure.companyName || ''
+    riskDisclosureForm.partyBSign = formData.riskDisclosure.partyBSign || ''
+    riskDisclosureForm.partyBDate = formData.riskDisclosure.partyBDate || ''
+    riskDisclosureForm.partyCSign = formData.riskDisclosure.partyCSign || ''
+    riskDisclosureForm.partyCDate = formData.riskDisclosure.partyCDate || ''
+  }
+
+  // 变更事项确认表
+  if (formData.changeConfirm) {
+    Object.assign(changeForm, JSON.parse(JSON.stringify(formData.changeConfirm)))
+  }
+}
+
+/** 重置攻击风险因素评估表表单数据 */
+const resetAttackRiskForm = () => {
+  form.assessor = ''
+  form.assessDate = ''
+
+  // 攻击风险等级
+  form.attackLevel = 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = []
+  form.attackLevel2Items = []
+  form.attackLevel3Items = []
+  form.attackLevel4Items = []
+
+  // 风险程度
+  form.riskLevel = ''
+
+  // 预防措施
+  form.preventiveMeasures = []
+  form.preventiveMeasuresOther = ''
+
+  // 签名
+  form.familySignature = ''
+  form.familySignDate = ''
+
+  // 风险知情书
+  riskDisclosureForm.companyName = '颐年医养'
+  riskDisclosureForm.partyBSign = ''
+  riskDisclosureForm.partyBDate = ''
+  riskDisclosureForm.partyCSign = ''
+  riskDisclosureForm.partyCDate = ''
+
+  // 变更事项确认表
+  Object.keys(changeForm).forEach(key => delete (changeForm as any)[key])
+}
+
+/** 打开弹窗 */
+const open = async (tenantId, id?: any, detail: boolean = false) => {
+  resetForm()
+  dialogVisible.value = true
+  dataForm.value.id = id || undefined
+  dataForm.value.tenantId = tenantId
+  isDetail.value = detail
+  if (id) {
+    title.value = "编辑-攻击风险因素评估"
+    // 加载评估数据
+    await loadAttackRiskData(id)
+  } else {
+    title.value = "新增-攻击风险因素评估"
+    
+  }
+}
+
+/** 加载评估数据 */
+const loadAttackRiskData = async (id: number) => {
+  try {
+    const res = await attackRiskGetById(id)
+    if (res) {
+      // 填充长者基本信息
+      dataForm.value.elderName = res.elderName || ''
+      dataForm.value.elderId = res.elderId || ''
+      dataForm.value.elderSex = res.elderSex || ''
+      dataForm.value.bedName = res.bedName || ''
+      dataForm.value.checkInTime = res.checkInTime || ''
+      dataForm.value.contractNumber = res.fileNumber || ''
+      dataForm.value.elderAge = res.elderAge || ''
+      await selectElderRef.value.upData(res.elderName, res.elderId)
+      // 解析 assessData
+      if (res.assessData) {
+        const formData = JSON.parse(res.assessData)
+        deserializeFormData(formData)
+      }
+    }
+  } catch (error) {
+    message.error('加载评估数据失败')
+  }
+}
+
+/** 根据长者ID加载评估数据 */
+const loadAttackRiskByElderId = async (elderId: number) => {
+  try {
+    const res = await attackRiskGetByElderId(elderId)
+    if (res && res.assessData) {
+      const formData = JSON.parse(res.assessData)
+      deserializeFormData(formData)
+    }
+  } catch (error) {
+    // 无历史数据,不处理
+  }
+}
+
+const form = reactive({
+  // 基本信息
+  assessor: '',
+  assessDate: '',
+
+  // 攻击风险等级(0=无,1=I级,2=II级,3=III级,4=IV级)
+  attackLevel: 0,
+
+  // 各级别选择的具体项
+  attackLevel1Items: [], // I级选择的项(1-7)
+  attackLevel2Items: [], // II级选择的项(1-3)
+  attackLevel3Items: [], // III级选择的项(1-4)
+  attackLevel4Items: [], // IV级选择的项(1-2)
+
+  // 风险程度
+  riskLevel: '',
+
+  // 预防措施
+  preventiveMeasures: [],
+  preventiveMeasuresOther: '',
+
+  // 签名
+  familySignature: '',
+  familySignDate: ''
+})
+
+// 监听攻击等级变化,自动判断风险等级
+watch(() => form.attackLevel, () => {
+  autoJudgeRiskLevel()
+})
+
+// 风险知情书表单数据
+const riskDisclosureForm = reactive({
+  elderName: '',
+  companyName: '颐年医养',
+  assessor: '',
+  assessDate: '',
+  partyBSign: '',
+  partyBDate: '',
+  partyCSign: '',
+  partyCDate: ''
+})
+
+// 长者信息变化时同步到风险告知书
+watch(
+  () => dataForm.value.elderName,
+  (name) => {
+    riskDisclosureForm.elderName = name
+  }
+)
+
+// 记录人变化时同步
+watch(
+  () => form.assessor,
+  (val) => {
+    riskDisclosureForm.assessor = val
+  }
+)
+
+watch(
+  () => form.assessDate,
+  (val) => {
+    riskDisclosureForm.assessDate = val ? dayjs(val).format('YYYY-MM-DD') : ''
+  }
+)
+
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  if (formLoading.value) {
+    return
+  }
+  formLoading.value = true
+  // 提交请求
+  try {
+    const assessData = serializeFormData()
+    const tempParams = {
+      ...dataForm.value,
+      assessData: JSON.stringify(assessData),
+      attackLevel: form.attackLevel,
+      riskLevel: form.riskLevel,
+      assessor: form.assessor,
+      assessScore: form.attackLevel,
+      assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
+    }
+
+    if (dataForm.value.id) {
+      const res = await attackRiskUpdate(tempParams)
+      if (res) {
+        message.success(t('common.updateSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    } else {
+      const res = await attackRiskCreate(tempParams)
+      if (res) {
+        message.success(t('common.createSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    }
+  } finally {
+    setTimeout(() => {
+      formLoading.value = false
+    }, 500)
+  }
+}
+
+
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {
+    id: undefined,
+    idCard: '',
+    contractNumber: '', //档案号
+    elderName: '',//长者姓名
+    bedName: '', //床位号
+    elderAge: '', //年龄
+    elderSex: '', //性别
+    checkInTime: '', //入院日期
+    elderId: '',
+    tenantId: undefined
+  }
+  formRef.value?.resetFields()
+
+  // 重置攻击风险因素评估表表单
+  resetAttackRiskForm()
+}
+
+// 关闭表单
+const handleClosed = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+
+</script>
+
+<style scoped lang="scss">
+
+.form-title {
+  text-align: center;
+  font-size: 20px;
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.attack-risk-form {
+  max-width: 1200px;
+  margin: 0 auto;
+  background: #fff;
+
+
+
+
+
+  .info-row {
+    display: flex;
+    gap: 40px;
+    width: 100%;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 10px;
+
+    .info-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .label {
+        width: 82px;
+        text-align: right;
+        margin-right: 4px;
+        white-space: nowrap;
+      }
+    }
+  }
+
+
+  .form-body {
+    border: 1px solid #333;
+    padding: 15px;
+  }
+
+  // 调整 Element Plus 组件样式
+  :deep(.el-input__inner) {
+    height: 28px;
+    line-height: 28px;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+    border-radius: 0;
+    padding: 0 4px;
+
+    &:focus {
+      border-color: #409eff;
+    }
+  }
+
+  :deep(.el-checkbox__label),
+  :deep(.el-radio__label) {
+    padding-left: 4px;
+  }
+}
+.row{
+  margin-bottom: 12px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  text{
+    text-align: right;
+    margin-right: 4px;
+    width: 82px;
+  }
+}
+
+// NGASR自杀风险评估量表 特有样式
+.total-score-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  margin-bottom: 20px;
+
+  .total-score {
+    display: flex;
+    align-items: baseline;
+    gap: 4px;
+
+    .score-label {
+      font-size: 16px;
+      font-weight: bold;
+    }
+
+    .score-value {
+      font-size: 28px;
+      font-weight: bold;
+      color: #409eff;
+    }
+
+    .score-max {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+
+  .risk-level {
+    font-size: 16px;
+    font-weight: bold;
+    padding: 8px 16px;
+    border-radius: 4px;
+
+    &.risk-none {
+      color: #67c23a;
+      background: #f0f9eb;
+    }
+
+    &.risk-low {
+      color: #e6a23c;
+      background: #fdf6ec;
+    }
+
+    &.risk-medium {
+      color: #f56c6c;
+      background: #fef0f0;
+    }
+
+    &.risk-high {
+      color: #f56c6c;
+      background: #fef0f0;
+      border: 1px solid #f56c6c;
+    }
+  }
+}
+
+.assessment-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 20px;
+
+  table {
+    width: 100%;
+    border: 1px solid #333;
+
+    th, td {
+      border: 1px solid #333;
+      padding: 12px;
+      text-align: left;
+      vertical-align: top;
+    }
+
+    th {
+      background: #f5f7fa;
+      font-weight: bold;
+      text-align: center;
+    }
+
+    .content-col {
+      width: 65%;
+    }
+
+    .level-col {
+      width: 10%;
+      text-align: center;
+    }
+
+    .select-col {
+      width: 25%;
+      text-align: center;
+    }
+
+    .content {
+      text-align: left;
+
+      .content-title {
+        font-weight: bold;
+        margin-bottom: 8px;
+      }
+
+      .content-item {
+        margin-left: 15px;
+        margin-bottom: 4px;
+        font-size: 13px;
+      }
+    }
+
+    .level {
+      text-align: center;
+      font-weight: bold;
+    }
+
+    .select {
+      text-align: center;
+
+      :deep(.el-radio) {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+.risk-judgment-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .risk-judgment-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .risk-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+
+    :deep(.el-radio) {
+      margin-right: 0;
+    }
+  }
+}
+
+.preventive-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .preventive-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .preventive-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+
+    :deep(.el-checkbox) {
+      margin-right: 20px;
+      margin-bottom: 8px;
+    }
+
+    .other-input {
+      width: 200px;
+      margin-left: 10px;
+    }
+  }
+}
+
+.signature-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .signature-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 20px;
+  }
+
+  .signature-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    &.date-item {
+      :deep(.el-date-picker) {
+        width: 150px;
+      }
+    }
+  }
+
+  .signature-label {
+    font-weight: bold;
+    white-space: nowrap;
+  }
+
+  .signature-input {
+    width: 150px;
+  }
+
+  .date-picker {
+    width: 150px;
+  }
+}
+
+.note-section {
+  margin-top: 20px;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .note-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .note-content {
+    font-size: 13px;
+    line-height: 1.8;
+    color: #606266;
+
+    p {
+      margin: 5px 0;
+    }
+
+    .indent {
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 312 - 0
src/views/elderly/contracts/change-confirmation-form/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <ContentWrap>
+    <!-- 保护性约束观察记录表 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="110px"
+    >
+      <el-form-item label="长者姓名">
+        <el-input
+          v-model="queryParams.elderName"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录人">
+        <el-input
+          v-model="queryParams.assessor"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录日期">
+        <el-date-picker
+          size="default"
+          ref="selectRef"
+          class="!w-240px"
+          v-model="queryParams.assessDate"
+          type="daterange"
+          :clearable="true"
+          :editable="false"
+          placeholder="选择记录日期"
+          value-format="YYYY-MM-DD"
+          format="YYYY-MM-DD"
+          date-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery" style="margin-left: 2vw"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="mb-10px">
+
+      <ButtonAdd @click="openForm(undefined)"  />
+      <!--      <el-button @click="inputH" type="success"><Icon icon="fa:cloud-upload" :size="16" class="mr-1"/>导入</el-button>-->
+      <!--      <ButtonImport @click="handleImportCard"  />-->
+
+
+    </div>
+    <el-table v-loading="loading" :data="list" :header-cell-style="tableHeaderColor">
+      <el-table-column header-align="center" align="center" label="序号" width="60">
+        <template #default="scope">
+          {{
+            scope.$index + (queryParams.pageNo * queryParams.pageSize - queryParams.pageSize) + 1
+          }}
+        </template>
+      </el-table-column>
+
+
+      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>
+      <el-table-column prop="assessor" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>
+      <el-table-column header-align="center" align="center" label="风险等级" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag v-if="getRiskLevelInfo(scope.row.assessData).text" :type="getRiskLevelInfo(scope.row.assessData).type">
+            {{ getRiskLevelInfo(scope.row.assessData).text }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="assessDate" header-align="center" align="center" label="记录日期" min-width="200" show-overflow-tooltip/>
+      <el-table-column prop="creator" header-align="center" align="center" label="记录人" min-width="200" show-overflow-tooltip/>
+
+
+      <el-table-column label="操作" align="center" width="200" >
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openFormEdit(scope.row, scope.row.id)"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="warning"
+            @click="openFormDetail(scope.row,scope.row.id)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="openClose(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <AddForm ref="formRef" @success="getList" />
+
+
+
+</template>
+
+<script setup lang="ts">
+import AddForm from "./AddForm.vue";
+import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
+import { useUserStore } from '@/store/modules/user'
+import {
+  attackRiskDelete, attackRiskPage,
+} from "@/api/social-work";
+import { getRiskLevelInfo } from "@/utils/risk-assessment";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const userStore = useUserStore()
+const loading = ref(true) // 列表的加载中
+const detailRef = ref()
+const importRef = ref()
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+
+// 状态管理
+const showBatchExport = ref(false)
+const exportConfig = ref({
+  title: '',
+  batchMin: 1,
+  batchMax: 999,
+  countMin: 1,
+  countMax: 200,
+  defaultBatch: 1,
+  defaultCount: 200,
+  description: [] as string[]
+})
+const exportLoading = ref(false)
+
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  assessor: '',
+  // assessDate: getCurrentMonthRange(),
+  assessDate: [],
+  tenantIds: userStore.orgTenantId
+})
+const queryFormRef = ref() // 搜索的表单
+
+
+// 打开导出弹窗
+const handleImportCard = async () => {
+
+  // showBatchExport.value = true
+}
+
+// 处理批量导出
+const handleBatchExport = async (batch: number, count: number) => {
+  exportLoading.value = true
+  console.log(batch,count)
+  // try {
+  //   let queryParams = {
+  //     pageNo: batch,
+  //     pageSize: count,
+  //     tenantId: userStore.orgTenantId[0]
+  //   }
+  //   const list = await careRecordsPage(queryParams)
+  //   if (list.length <= 0) {
+  //     message.error('暂无数据可以导出!')
+  //     return
+  //   }
+  //   const headers = [
+  //     { key: 'organizationName', title: '机构名称' },
+  //     { key: 'visitName', title: '姓名' },
+  //     { key: 'visitPhone', title: '手机号' },
+  //     { key: 'visitDate', title: '预约时间' },
+  //     { key: 'accompanyCount', title: '陪同人数' },
+  //     { key: 'reason', title: '理由' }
+  //   ]
+  //   exportWithExpandedObjectArrays(list, headers, `医疗护理记录${formatToDateTime()}.xlsx`, '医疗护理记录')
+  // } catch (_) {}
+
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    //let queryP = {...queryParams,discoveryTime:queryParams.discoveryTime?[queryParams.discoveryTime[0]+" 00:00:00",queryParams.discoveryTime[1]+" 23:59:59"]:null}
+    const data = await attackRiskPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = async () => {
+  if (!queryFormRef.value) return
+  const valid = await queryFormRef.value.validate()
+  if (!valid) return
+  queryParams.pageNo = 1
+  await getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.elderName = ''
+  queryParams.assessor = ''
+  queryParams.tenantIds = userStore.orgTenantId
+  // queryParams.assessDate= getCurrentMonthRange()
+  queryParams.assessDate = []
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1){
+    message.error('新增只能选择一个机构')
+    return
+  }
+  formRef.value.open(queryParams.tenantIds[0], id,false)
+}
+
+const editRef = ref()
+const openFormEdit = (row: any = {}, id?: number) => {
+  formRef.value.open(row.tenantId, id,false)
+}
+
+
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
+}
+
+
+
+
+
+const openClose = async (item) => {
+  try {
+    console.log("任务ID",item)
+    const res = await message.confirm('确定要删除吗?', '提示')
+    if (res == 'confirm') {
+      // 发起
+      try {
+        const res = await attackRiskDelete(item.id)
+        if (res){
+          message.success(t('common.updateSuccess'))
+        }
+      }catch(err) {}
+    }
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 取消按钮操作 */
+const cancelDelete = async (id: number) => {
+  try {
+    // 取消的二次确认
+    await message.cancelConfirm()
+    // 发起取消
+    // await elderlyBakDel(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const route = useRoute()
+/** 初始化 **/
+onMounted(() => {
+  if(route.query && route.query.elderName){
+    queryParams.elderName = route.query.elderName as string
+  }
+  getList()
+})
+
+// 表头格式
+const tableHeaderColor = ({ rowIndex }: any) => {
+  if (rowIndex === 0) {
+    return {
+      backgroundColor: '#f8f8f9',
+      color: '#666666',
+      fontWeight: 'bold'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 5 - 7
src/views/elderly/contracts/components/AttachmentPage.vue

@@ -102,24 +102,22 @@ const hasData = computed(() => {
     min-height: auto !important;
     height: auto !important;
     margin: 0 !important;
-    padding: 10mm 14mm !important;
+    padding: 10mm 0mm !important;
     box-shadow: none !important;
+    box-sizing: border-box !important;
     page-break-after: always;
     break-after: page;
   }
 
+  /* 保留内边距,避免内容溢出可打印区域;不要在这里清 0 */
   .attachment-page.is-filled {
-    padding: 0 !important;
+    background: transparent !important;
+    box-shadow: none !important;
   }
 
   .attachment-page:last-child {
     page-break-after: auto;
     break-after: auto;
   }
-
-  @page {
-    size: A4;
-    margin: 0;
-  }
 }
 </style>

+ 351 - 0
src/views/elderly/contracts/components/ChangeConfirmationFormBody.vue

@@ -0,0 +1,351 @@
+<script setup lang="ts">
+import { reactive, watch, ref, onMounted } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+import { getNursingLevelPage } from '@/api/elderly/nursing'
+
+interface FormData {
+  partyBName?: string
+  originalAbilityLevel?: string
+  originalCareLevel?: string | number
+  reAssessDate?: string
+  newAbilityLevel?: string
+  newCareLevel?: string | number
+  changedCareLevel?: string | number
+  newServices?: string[]
+  totalFee?: string
+  bedFee?: string
+  serviceFee?: string
+  mealFee?: string
+  otherFee1Name?: string
+  otherFee1Amount?: string
+  otherFee1Unit?: string
+  otherFee2Name?: string
+  otherFee2Amount?: string
+  otherFee2Unit?: string
+  partyASign?: string
+  partyBSign?: string
+  partyCSign?: string
+  year?: string
+  month?: string
+  day?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  partyBName: '',
+  originalAbilityLevel: '',
+  originalCareLevel: '',
+  reAssessDate: '',
+  newAbilityLevel: '',
+  newCareLevel: '',
+  changedCareLevel: '',
+  newServices: ['', '', '', '', ''],
+  totalFee: '',
+  bedFee: '',
+  serviceFee: '',
+  mealFee: '',
+  otherFee1Name: '',
+  otherFee1Amount: '',
+  otherFee1Unit: '',
+  otherFee2Name: '',
+  otherFee2Amount: '',
+  otherFee2Unit: '',
+  partyASign: '',
+  partyBSign: '',
+  partyCSign: '',
+  year: '',
+  month: '',
+  day: ''
+})
+
+watch(() => props.modelValue, (val) => {
+  if (!val) return
+  Object.assign(form, JSON.parse(JSON.stringify(val)))
+  if (!form.newServices || form.newServices.length === 0) {
+    form.newServices = ['', '', '', '', '']
+  }
+}, { immediate: true, deep: true })
+
+const updateForm = () => {
+  emit('update:modelValue', JSON.parse(JSON.stringify(form)))
+}
+
+const nurseLevelList = ref<{ id: number; nurseLevelName: string }[]>([])
+const fetchNurseLevels = async () => {
+  try {
+    const data = await getNursingLevelPage({ pageNum: 1, pageSize: 100 })
+    nurseLevelList.value = data.list
+  } catch {
+    // ignore
+  }
+}
+onMounted(fetchNurseLevels)
+
+const getNurseLevelLabel = (val: string | number | undefined) => {
+  if (val == null || val === '') return ''
+  const found = nurseLevelList.value.find(item => String(item.id) === String(val))
+  return found ? found.nurseLevelName : String(val)
+}
+</script>
+
+<template>
+  <div class="change-confirmation-body">
+    <div class="doc-title">变更事项确认表</div>
+
+    <div class="doc-content">
+      <div class="row">
+        <span>乙方:</span>
+        <FieldItem v-model="form.partyBName" :is-text-mode="isTextMode" width="200px" placeholder="" @update:model-value="updateForm" />
+      </div>
+
+      <div class="row">
+        <span>乙方入住时经评估能力等级为</span>
+        <FieldItem v-model="form.originalAbilityLevel" :is-text-mode="isTextMode" width="120px" placeholder="" @update:model-value="updateForm" />
+        <span>,甲方提供的照料护理等级为</span>
+        <template v-if="!isTextMode">
+          <el-select v-model="form.originalCareLevel" placeholder="请选择" clearable style="width: 140px" @change="updateForm">
+            <el-option v-for="item in nurseLevelList" :key="item.id" :label="item.nurseLevelName" :value="item.id" />
+          </el-select>
+        </template>
+        <template v-else>
+          <span class="underline-text">{{ getNurseLevelLabel(form.originalCareLevel) }}</span>
+        </template>
+        <span>。</span>
+      </div>
+
+      <div class="row">
+        <span>甲方于</span>
+        <FieldItem v-model="form.reAssessDate" :is-text-mode="isTextMode" type="date" width="140px" placeholder="" @update:model-value="updateForm" />
+        <span>对乙方重新进行了评估,重新评估的能力等级为</span>
+        <FieldItem v-model="form.newAbilityLevel" :is-text-mode="isTextMode" width="120px" placeholder="" @update:model-value="updateForm" />
+        <span>。根据乙方能力等级变化,需要对乙方的照料护理等级和相关的服务内容进行变更,经双方协商一致,同意变更下列事项,具体变更内容如下:</span>
+      </div>
+
+      <div class="section">
+        <div class="section-title">一、照料护理等级变更</div>
+        <div class="row">
+          <span>乙方变更后的照料护理等级为</span>
+          <template v-if="!isTextMode">
+            <el-select v-model="form.changedCareLevel" placeholder="请选择" clearable style="width: 140px" @change="updateForm">
+              <el-option v-for="item in nurseLevelList" :key="item.id" :label="item.nurseLevelName" :value="item.id" />
+            </el-select>
+          </template>
+          <template v-else>
+            <span class="underline-text">{{ getNurseLevelLabel(form.changedCareLevel) }}</span>
+          </template>
+          <span>。</span>
+        </div>
+      </div>
+
+      <div class="section">
+        <div class="section-title">二、服务内容变更</div>
+        <div class="sub-title">1.甲方为乙方新增下列服务内容</div>
+        <div v-if="!isTextMode" class="service-inputs">
+          <el-input v-for="(item, idx) in form.newServices" :key="idx" v-model="form.newServices![idx]" placeholder="请输入服务内容" style="width: 100%; margin-bottom: 8px;" @input="updateForm" />
+        </div>
+        <div v-else class="service-lines">
+          <div v-for="(item, idx) in form.newServices" :key="idx" class="service-line">
+            {{ item || '________________________________________' }}
+          </div>
+        </div>
+      </div>
+
+      <div class="section">
+        <div class="section-title">三、费用变更</div>
+        <div class="row">
+          <span>养老服务总费用为</span>
+          <FieldItem v-model="form.totalFee" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>元/月,其中:(1)床位费:</span>
+          <FieldItem v-model="form.bedFee" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>元/月;(2)服务费:</span>
+          <FieldItem v-model="form.serviceFee" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>元/月;(3)膳食费:</span>
+          <FieldItem v-model="form.mealFee" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>元/月;(4)其他费用:</span>
+          <FieldItem v-model="form.otherFee1Name" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>费,</span>
+          <FieldItem v-model="form.otherFee1Amount" :is-text-mode="isTextMode" width="80px" placeholder="" @update:model-value="updateForm" />
+          <span>元/</span>
+          <FieldItem v-model="form.otherFee1Unit" :is-text-mode="isTextMode" width="80px" placeholder="单位" @update:model-value="updateForm" />
+          <span>(单位)。</span>
+        </div>
+        <div class="row other-fee-row">
+          <FieldItem v-model="form.otherFee2Name" :is-text-mode="isTextMode" width="100px" placeholder="" @update:model-value="updateForm" />
+          <span>费,</span>
+          <FieldItem v-model="form.otherFee2Amount" :is-text-mode="isTextMode" width="80px" placeholder="" @update:model-value="updateForm" />
+          <span>元/</span>
+          <FieldItem v-model="form.otherFee2Unit" :is-text-mode="isTextMode" width="80px" placeholder="单位" @update:model-value="updateForm" />
+          <span>(单位)。</span>
+        </div>
+      </div>
+
+      <div class="confirm-text">
+        乙方、丙方同意并确认上述变更事项。
+      </div>
+
+      <div class="sign-section">
+        <div class="sign-row">
+          <div class="sign-item">
+            <span>甲方(盖章):</span>
+            <SignatureItem v-model="form.partyASign" :is-text-mode="isTextMode" width="180" height="70" placeholder="(盖章处)" title="甲方盖章" @update:model-value="updateForm" />
+          </div>
+          <div class="sign-item">
+            <span>乙方(签名或盖章):</span>
+            <SignatureItem v-model="form.partyBSign" :is-text-mode="isTextMode" width="180" height="70" placeholder="(签字处)" title="乙方签字" @update:model-value="updateForm" />
+          </div>
+        </div>
+        <div class="sign-row single">
+          <div class="sign-item">
+            <span>丙方(签名或盖章):</span>
+            <SignatureItem v-model="form.partyCSign" :is-text-mode="isTextMode" width="180" height="70" placeholder="(签字处)" title="丙方签字" @update:model-value="updateForm" />
+          </div>
+        </div>
+        <div class="date-row">
+          <span>日期:</span>
+          <FieldItem v-model="form.year" :is-text-mode="isTextMode" width="60px" placeholder="" @update:model-value="updateForm" />
+          <span>年</span>
+          <FieldItem v-model="form.month" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+          <span>月</span>
+          <FieldItem v-model="form.day" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+          <span>日</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.change-confirmation-body {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 20px;
+}
+
+.doc-content {
+  line-height: 2;
+
+  .row {
+    margin-bottom: 8px;
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 4px;
+  }
+
+  .underline-text {
+    display: inline-block;
+    min-width: 100px;
+    border-bottom: 1px solid #333;
+    text-align: center;
+    padding: 0 4px;
+  }
+
+  .section {
+    margin-top: 16px;
+
+    .section-title {
+      font-weight: bold;
+      margin-bottom: 8px;
+    }
+
+    .sub-title {
+      margin-bottom: 8px;
+      padding-left: 8px;
+    }
+  }
+
+  .service-inputs {
+    padding-left: 8px;
+  }
+
+  .service-lines {
+    padding-left: 8px;
+
+    .service-line {
+      border-bottom: 1px solid #333;
+      min-height: 28px;
+      line-height: 28px;
+      margin-bottom: 8px;
+      padding-left: 4px;
+    }
+  }
+
+  .other-fee-row {
+    padding-left: 80px;
+  }
+
+  .confirm-text {
+    margin-top: 20px;
+    text-indent: 2em;
+  }
+
+  .sign-section {
+    margin-top: 30px;
+
+    .sign-row {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 20px;
+
+      &.single {
+        justify-content: flex-end;
+        padding-right: 20px;
+      }
+    }
+
+    .sign-item {
+      display: flex;
+      align-items: flex-start;
+      gap: 8px;
+    }
+
+    .date-row {
+      text-align: right;
+      padding-right: 40px;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+      gap: 4px;
+    }
+  }
+}
+
+@media print {
+  .change-confirmation-body {
+    font-size: 12px;
+  }
+}
+</style>

+ 9 - 4
src/views/elderly/contracts/components/CheckInInformationBody.vue

@@ -8,7 +8,7 @@ const props = withDefaults(defineProps<{
 
 <template>
   <div class="check-in-information">
-    <div class="attachment-no">附件3</div>
+    <div class="attachment-no" style="font-size: 14px;width: 100%;text-align: left;margin-top: 16px;">附件3</div>
     <div class="attachment-title">《入住须知》</div>
 
     <div class="content">
@@ -133,15 +133,17 @@ const props = withDefaults(defineProps<{
   font-size: 14px;
   color: #333;
   background: #fff;
-  padding: 5mm;
+  padding: 10mm;
+  box-sizing: border-box;
 
   /* 打印模式优化 */
   @media print {
-    width: 210mm;
+    width: 210mm !important;
     max-width: 210mm;
-    padding: 5mm;
+    padding: 0;
     border: none;
     margin: 0;
+    box-sizing: border-box;
   }
 }
 
@@ -181,6 +183,7 @@ h3 {
 ol {
   margin: 0 0 12px 0;
   padding-left: 2em;
+  box-sizing: border-box;
 }
 
 li {
@@ -196,12 +199,14 @@ li {
 
 .exam-table {
   width: 100%;
+  box-sizing: border-box;
   border-collapse: collapse;
   margin-bottom: 16px;
 
   td {
     border: 1px solid #333;
     padding: 6px 10px;
+    box-sizing: border-box;
     font-size: 13px;
     line-height: 1.6;
   }

+ 13 - 10
src/views/elderly/contracts/components/CheckInRegistrationBody.vue

@@ -341,7 +341,8 @@ const onInlineInput = (key: keyof CheckInRegistrationForm, e: Event) => {
 <template>
   <div class="check-in-registration">
     <!-- 标题 -->
-    <div class="doc-title">附件 2 &nbsp;&nbsp;&nbsp; 入住登记表</div>
+    <div class="doc-title" style="font-size: 14px;margin-bottom: 12px;width: 100%;text-align: left;margin-top: 16px;">附件 2</div>
+     <div class="doc-title" style="width: 100%;text-align: center;">入住登记表</div>
     <div class="doc-meta">
       <span>入住编号:<FieldItem :model-value="form.checkInNo" :is-text-mode="isTextMode" width="140px" @update:model-value="(v:any)=>updateForm('checkInNo',v)" /></span>
       <span>日期:{{ form.signDate || '____年__月__日' }}</span>
@@ -351,13 +352,13 @@ const onInlineInput = (key: keyof CheckInRegistrationForm, e: Event) => {
     <table class="reg-table" border="1" cellspacing="0" cellpadding="0">
       <!-- 明确定义7列,按设计图比例分配 -->
       <colgroup>
-        <col style="width:8%" />   <!-- 第1列:最左标签(老年人/担保人) - 紧凑 -->
-        <col style="width:14%" />  <!-- 第2列:标签/值 -->
-        <col style="width:14%" />  <!-- 第3列:值/标签 -->
-        <col style="width:14%" />  <!-- 第4列:标签/值 -->
-        <col style="width:14%" />  <!-- 第5列:值/标签 -->
-        <col style="width:14%" />  <!-- 第6列:标签/值 -->
-        <col style="width:17%" />  <!-- 第7列:照片/值 -->
+        <col style="width:9%" />   <!-- 第1列:最左标签(老年人/担保人) - 紧凑 -->
+        <col style="width:13%" />  <!-- 第2列:标签/值 -->
+        <col style="width:13%" />  <!-- 第3列:值/标签 -->
+        <col style="width:13%" />  <!-- 第4列:标签/值 -->
+        <col style="width:13%" />  <!-- 第5列:值/标签 -->
+        <col style="width:13%" />  <!-- 第6列:标签/值 -->
+        <col style="width:18%" />  <!-- 第7列:照片/值 -->
       </colgroup>
       <tbody>
 
@@ -913,11 +914,12 @@ const onInlineInput = (key: keyof CheckInRegistrationForm, e: Event) => {
 
   /* 打印模式优化:A4纸尺寸 */
   @media print {
-    width: 210mm;
+    width: 210mm !important;
     max-width: 210mm;
-    padding: 1mm;
+    padding: 0;
     border: none;
     margin: 0;
+    box-sizing: border-box;
   }
 }
 
@@ -1164,6 +1166,7 @@ const onInlineInput = (key: keyof CheckInRegistrationForm, e: Event) => {
 
 /* 打印模式全局样式 */
 @media print {
+  
   .check-in-registration {
     font-size: 12pt;
   }

+ 7 - 10
src/views/elderly/contracts/components/ContractBody.vue

@@ -1821,13 +1821,13 @@ watch(
     background: #fff;
     margin: 0;
     padding: 0;
-    width: 100%;
+    width: 210mm;
   }
 
-  /* 封面页:填满一整页(@page margin 18mm,可打印区域高度 = 297-36=261mm) */
+  /* 封面页:填满一整页(@page margin 16mm,可打印区域高度 = 297-32=265mm) */
   .a4-page.cover-a4 {
-    width: 100% !important;
-    min-height: 261mm !important;
+    width: 210mm !important;
+    min-height: 265mm !important;
     margin: 0 !important;
     padding: 0 !important;
     background: #fff !important;
@@ -1837,9 +1837,9 @@ watch(
     break-after: page;
   }
 
-  /* 其他页:自适应高度,内容连续流动,由浏览器自动分页 */
+  /* 其他页:保留屏幕内边距,由 @page margin 控制物理边距 */
   .a4-page:not(.cover-a4) {
-    width: 100% !important;
+    width: 210mm !important;
     min-height: auto !important;
     margin: 0 !important;
     padding: 0 !important;
@@ -1855,9 +1855,6 @@ watch(
     break-after: auto;
   }
 
-  @page {
-    size: A4;
-    margin: 18mm;
-  }
+  /* 注意:@page 规则统一在 ContractManageForm.vue 中声明,避免多组件冲突 */
 }
 </style>

+ 334 - 0
src/views/elderly/contracts/components/DelegationAgentConfirmationBody.vue

@@ -0,0 +1,334 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface FormData {
+  elderName?: string
+  elderGender?: string
+  elderAge?: string
+  elderIdCardNo?: string
+  elderAddress?: string
+  agentName?: string
+  agentGender?: string
+  agentIdCardNo?: string
+  agentAddress?: string
+  declarerName?: string
+  representedName?: string
+  relationship?: string
+  elderSign?: string
+  agentSign?: string
+  signDate?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  elderName: '',
+  elderGender: '',
+  elderAge: '',
+  elderIdCardNo: '',
+  elderAddress: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentAddress: '',
+  declarerName: '',
+  representedName: '',
+  relationship: '',
+  elderSign: '',
+  agentSign: '',
+  signDate: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      Object.assign(form, val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = (key: keyof FormData, val: any) => {
+  ;(form as any)[key] = val
+  emit('update:modelValue', { ...form })
+}
+</script>
+
+<template>
+  <div class="delegation-agent-confirmation">
+    <div class="doc-title">委托代理人确认表</div>
+
+    <table class="info-table">
+      <tbody>
+        <tr>
+          <td class="label-cell">入住老人</td>
+          <td class="value-cell">
+            <FieldItem
+              :model-value="form.elderName"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('elderName', v)"
+            />
+          </td>
+          <td class="label-cell">性别</td>
+          <td class="value-cell">
+            <FieldItem
+              :model-value="form.elderGender"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder="男/女"
+              @update:model-value="(v: any) => updateForm('elderGender', v)"
+            />
+          </td>
+          <td class="label-cell">年龄</td>
+          <td class="value-cell">
+            <FieldItem
+              :model-value="form.elderAge"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('elderAge', v)"
+            />
+          </td>
+        </tr>
+        <tr>
+          <td class="label-cell">身份证号</td>
+          <td class="value-cell" colspan="5">
+            <FieldItem
+              :model-value="form.elderIdCardNo"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('elderIdCardNo', v)"
+            />
+          </td>
+        </tr>
+        <tr>
+          <td class="label-cell">户籍地址</td>
+          <td class="value-cell" colspan="5">
+            <FieldItem
+              :model-value="form.elderAddress"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('elderAddress', v)"
+            />
+          </td>
+        </tr>
+        <tr>
+          <td class="label-cell" rowspan="3">委托代理人</td>
+          <td class="label-cell">姓名</td>
+          <td class="value-cell">
+            <FieldItem
+              :model-value="form.agentName"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('agentName', v)"
+            />
+          </td>
+          <td class="label-cell">性别</td>
+          <td class="value-cell" colspan="2">
+            <FieldItem
+              :model-value="form.agentGender"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder="男/女"
+              @update:model-value="(v: any) => updateForm('agentGender', v)"
+            />
+          </td>
+        </tr>
+        <tr>
+          <td class="label-cell">身份证</td>
+          <td class="value-cell" colspan="4">
+            <FieldItem
+              :model-value="form.agentIdCardNo"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('agentIdCardNo', v)"
+            />
+          </td>
+        </tr>
+        <tr>
+          <td class="label-cell">户籍地</td>
+          <td class="value-cell" colspan="4">
+            <FieldItem
+              :model-value="form.agentAddress"
+              :is-text-mode="isTextMode"
+              width="100%"
+              placeholder=""
+              @update:model-value="(v: any) => updateForm('agentAddress', v)"
+            />
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <div class="doc-body">
+      <p class="paragraph indent">
+        本人
+        <FieldItem
+          :model-value="form.declarerName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('declarerName', v)"
+        />
+        是
+        <FieldItem
+          :model-value="form.representedName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('representedName', v)"
+        />
+        的委托代理人,关系
+        <FieldItem
+          :model-value="form.relationship"
+          :is-text-mode="isTextMode"
+          width="100px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('relationship', v)"
+        />
+        ,保证所承诺的监护关系真实,对因提供虚假承诺而引发的相关问题和造成的后果,愿意承担相应的法律责任。
+      </p>
+      <p class="paragraph indent">特此承诺!</p>
+    </div>
+
+    <div class="sign-section">
+      <div class="sign-row">
+        <span class="sign-label">入住老人(签名盖手印):</span>
+        <SignatureItem
+          :model-value="form.elderSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="入住老人签字"
+          @update:model-value="(v: any) => updateForm('elderSign', v)"
+        />
+      </div>
+      <div class="sign-row">
+        <span class="sign-label">委托代理人(签名盖手印):</span>
+        <SignatureItem
+          :model-value="form.agentSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="委托代理人签字"
+          @update:model-value="(v: any) => updateForm('agentSign', v)"
+        />
+      </div>
+      <div class="sign-row date-row">
+        <span class="sign-label">日 期:</span>
+        <FieldItem
+          :model-value="form.signDate"
+          :is-text-mode="isTextMode"
+          type="date"
+          width="160px"
+          placeholder="请选择日期"
+          @update:model-value="(v: any) => updateForm('signDate', v)"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.delegation-agent-confirmation {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 30px;
+}
+
+.info-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 30px;
+
+  td {
+    border: 1px solid #333;
+    padding: 8px 12px;
+    vertical-align: middle;
+  }
+
+  .label-cell {
+    text-align: center;
+    white-space: nowrap;
+    background: #fff;
+    font-weight: normal;
+  }
+
+  .value-cell {
+    text-align: left;
+  }
+}
+
+.doc-body {
+  line-height: 2.2;
+  margin-bottom: 60px;
+}
+
+.paragraph {
+  text-indent: 2em;
+  margin: 0;
+}
+
+.sign-section {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.sign-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.sign-label {
+  white-space: nowrap;
+  margin-right: 8px;
+}
+
+.date-row {
+  margin-bottom: 20px;
+}
+</style>

+ 280 - 0
src/views/elderly/contracts/components/DocumentAttachmentBody.vue

@@ -0,0 +1,280 @@
+<script setup lang="ts">
+import { reactive, watch, ref } from 'vue'
+
+interface FormData {
+  images?: string[]
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+  title?: string
+  description?: string
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({}),
+  title: '',
+  description: ''
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  images: []
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (!val) return
+    if (Array.isArray(val.images)) {
+      form.images = [...val.images]
+    } else {
+      form.images = []
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = () => {
+  emit('update:modelValue', {
+    images: form.images ? [...form.images] : []
+  })
+}
+
+const fileInputRef = ref<HTMLInputElement | null>(null)
+
+const triggerUpload = () => {
+  fileInputRef.value?.click()
+}
+
+const handleFileChange = (e: Event) => {
+  const target = e.target as HTMLInputElement
+  const files = target.files
+  if (!files || files.length === 0) return
+
+  const newImages: string[] = []
+  let processed = 0
+
+  for (let i = 0; i < files.length; i++) {
+    const file = files[i]
+    if (!file.type.startsWith('image/')) {
+      processed++
+      continue
+    }
+    const reader = new FileReader()
+    reader.onload = (ev) => {
+      const result = ev.target?.result as string
+      if (result) newImages.push(result)
+      processed++
+      if (processed === files.length) {
+        form.images = [...(form.images || []), ...newImages]
+        updateForm()
+      }
+    }
+    reader.onerror = () => {
+      processed++
+      if (processed === files.length) {
+        form.images = [...(form.images || []), ...newImages]
+        updateForm()
+      }
+    }
+    reader.readAsDataURL(file)
+  }
+
+  target.value = ''
+}
+
+const removeImage = (index: number) => {
+  if (!form.images) return
+  form.images.splice(index, 1)
+  updateForm()
+}
+</script>
+
+<template>
+  <div class="document-attachment-body">
+    <div v-if="title" class="doc-title">{{ title }}</div>
+    <p v-if="description" class="doc-desc">{{ description }}</p>
+
+    <!-- 编辑模式:上传区域 -->
+    <div v-if="!isTextMode" class="edit-section">
+      <div class="upload-box" @click="triggerUpload">
+        <div class="upload-placeholder">
+          <span class="upload-icon">+</span>
+          <span class="upload-text">点击上传图片(支持多张)</span>
+        </div>
+        <input
+          ref="fileInputRef"
+          type="file"
+          accept="image/*"
+          multiple
+          style="display: none"
+          @change="handleFileChange"
+        />
+      </div>
+
+      <div v-if="form.images && form.images.length > 0" class="image-list">
+        <div v-for="(img, idx) in form.images" :key="idx" class="image-item">
+          <img :src="img" class="image-thumb" />
+          <button class="remove-btn" @click.stop="removeImage(idx)">×</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 文本/打印模式:仅展示图片 -->
+    <div v-else class="preview-section">
+      <div v-if="form.images && form.images.length > 0" class="image-preview-list">
+        <img
+          v-for="(img, idx) in form.images"
+          :key="idx"
+          :src="img"
+          class="preview-img"
+        />
+      </div>
+      <div v-else class="empty-box">
+        <span>(暂无图片)</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.document-attachment-body {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 16px;
+}
+
+.doc-desc {
+  text-indent: 2em;
+  margin: 0 0 16px;
+  line-height: 1.8;
+}
+
+.edit-section {
+  .upload-box {
+    width: 100%;
+    height: 160px;
+    border: 2px dashed #ccc;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: border-color 0.2s;
+    margin-bottom: 16px;
+
+    &:hover {
+      border-color: #409eff;
+    }
+  }
+
+  .upload-placeholder {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    color: #999;
+
+    .upload-icon {
+      font-size: 32px;
+      line-height: 1;
+      margin-bottom: 8px;
+    }
+
+    .upload-text {
+      font-size: 14px;
+    }
+  }
+
+  .image-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .image-item {
+    position: relative;
+    width: 100%;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    overflow: hidden;
+
+    .image-thumb {
+      width: 100%;
+      display: block;
+      object-fit: contain;
+    }
+
+    .remove-btn {
+      position: absolute;
+      top: 8px;
+      right: 8px;
+      width: 28px;
+      height: 28px;
+      border: none;
+      border-radius: 50%;
+      background: rgba(0, 0, 0, 0.5);
+      color: #fff;
+      font-size: 18px;
+      line-height: 1;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:hover {
+        background: rgba(0, 0, 0, 0.7);
+      }
+    }
+  }
+}
+
+.preview-section {
+  .image-preview-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .preview-img {
+    width: 100%;
+    display: block;
+    object-fit: contain;
+    border: 1px solid #333;
+    box-sizing: border-box;
+  }
+
+  .empty-box {
+    width: 100%;
+    min-height: 400px;
+    border: 1px solid #333;
+    padding: 12px;
+    box-sizing: border-box;
+    color: #999;
+  }
+}
+</style>

+ 4 - 3
src/views/elderly/contracts/components/ElderSafetyCommitmentBody.vue

@@ -135,11 +135,12 @@ const updateForm = (key: keyof FormData, val: any) => {
 
   /* 打印模式优化 */
   @media print {
-    width: 210mm;
-    max-width: 210mm;
-    padding: 5mm;
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
     border: none;
     margin: 0;
+    box-sizing: border-box;
   }
 }
 

+ 270 - 0
src/views/elderly/contracts/components/ExplanationExaminationReportBody.vue

@@ -0,0 +1,270 @@
+<script setup lang="ts">
+import { reactive, watch, ref } from 'vue'
+
+interface FormData {
+  images?: string[]
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  images: []
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (!val) return
+    if (Array.isArray(val.images)) {
+      form.images = [...val.images]
+    } else {
+      form.images = []
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = () => {
+  emit('update:modelValue', {
+    images: form.images ? [...form.images] : []
+  })
+}
+
+const fileInputRef = ref<HTMLInputElement | null>(null)
+
+const triggerUpload = () => {
+  fileInputRef.value?.click()
+}
+
+const handleFileChange = (e: Event) => {
+  const target = e.target as HTMLInputElement
+  const files = target.files
+  if (!files || files.length === 0) return
+
+  const newImages: string[] = []
+  let processed = 0
+
+  for (let i = 0; i < files.length; i++) {
+    const file = files[i]
+    if (!file.type.startsWith('image/')) {
+      processed++
+      continue
+    }
+    const reader = new FileReader()
+    reader.onload = (ev) => {
+      const result = ev.target?.result as string
+      if (result) newImages.push(result)
+      processed++
+      if (processed === files.length) {
+        form.images = [...(form.images || []), ...newImages]
+        updateForm()
+      }
+    }
+    reader.onerror = () => {
+      processed++
+      if (processed === files.length) {
+        form.images = [...(form.images || []), ...newImages]
+        updateForm()
+      }
+    }
+    reader.readAsDataURL(file)
+  }
+
+  target.value = ''
+}
+
+const removeImage = (index: number) => {
+  if (!form.images) return
+  form.images.splice(index, 1)
+  updateForm()
+}
+</script>
+
+<template>
+  <div class="explanation-examination-report">
+    <p class="doc-desc">
+      (乙方提供二级甲等以上医院出具的《体检报告项目说明》(体检时间应在签订服务合同前30日以内))
+    </p>
+
+    <!-- 编辑模式:上传区域 -->
+    <div v-if="!isTextMode" class="edit-section">
+      <div class="upload-box" @click="triggerUpload">
+        <div class="upload-placeholder">
+          <span class="upload-icon">+</span>
+          <span class="upload-text">点击上传体检报告图片(支持多张)</span>
+        </div>
+        <input
+          ref="fileInputRef"
+          type="file"
+          accept="image/*"
+          multiple
+          style="display: none"
+          @change="handleFileChange"
+        />
+      </div>
+
+      <div v-if="form.images && form.images.length > 0" class="image-list">
+        <div v-for="(img, idx) in form.images" :key="idx" class="image-item">
+          <img :src="img" class="image-thumb" />
+          <button class="remove-btn" @click.stop="removeImage(idx)">×</button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 文本/打印模式:仅展示图片 -->
+    <div v-else class="preview-section">
+      <div v-if="form.images && form.images.length > 0" class="image-preview-list">
+        <img
+          v-for="(img, idx) in form.images"
+          :key="idx"
+          :src="img"
+          class="preview-img"
+        />
+      </div>
+      <div v-else class="empty-box">
+        <span>(体检报告项目粘贴或附注)</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.explanation-examination-report {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-desc {
+  text-indent: 2em;
+  margin: 0 0 16px;
+  line-height: 1.8;
+}
+
+.edit-section {
+  .upload-box {
+    width: 100%;
+    height: 160px;
+    border: 2px dashed #ccc;
+    border-radius: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: border-color 0.2s;
+    margin-bottom: 16px;
+
+    &:hover {
+      border-color: #409eff;
+    }
+  }
+
+  .upload-placeholder {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    color: #999;
+
+    .upload-icon {
+      font-size: 32px;
+      line-height: 1;
+      margin-bottom: 8px;
+    }
+
+    .upload-text {
+      font-size: 14px;
+    }
+  }
+
+  .image-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .image-item {
+    position: relative;
+    width: 100%;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    overflow: hidden;
+
+    .image-thumb {
+      width: 100%;
+      display: block;
+      object-fit: contain;
+    }
+
+    .remove-btn {
+      position: absolute;
+      top: 8px;
+      right: 8px;
+      width: 28px;
+      height: 28px;
+      border: none;
+      border-radius: 50%;
+      background: rgba(0, 0, 0, 0.5);
+      color: #fff;
+      font-size: 18px;
+      line-height: 1;
+      cursor: pointer;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:hover {
+        background: rgba(0, 0, 0, 0.7);
+      }
+    }
+  }
+}
+
+.preview-section {
+  .image-preview-list {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+  }
+
+  .preview-img {
+    width: 100%;
+    display: block;
+    object-fit: contain;
+    border: 1px solid #333;
+    box-sizing: border-box;
+  }
+
+  .empty-box {
+    width: 100%;
+    min-height: 400px;
+    border: 1px solid #333;
+    padding: 12px;
+    box-sizing: border-box;
+    color: #999;
+  }
+}
+</style>

+ 639 - 0
src/views/elderly/contracts/components/FirstServiceProjectConfirmationBody.vue

@@ -0,0 +1,639 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface ServiceItem {
+  selected: string[]
+  other: string
+  remark?: string
+}
+
+interface FormData {
+  partyBName?: string
+  gender?: string
+  roomNo?: string
+  bedNo?: string
+  assessDate?: string
+  careLevel?: string
+  agentName?: string
+  emergencyContact?: string
+  year?: string
+  month?: string
+  day?: string
+  basicServices?: Record<string, ServiceItem>
+  lifeCare?: Record<string, any>
+  elderlyNursing?: Record<string, any>
+  partyBSign?: string
+  partyCSign?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const defaultBasic = (): ServiceItem => ({ selected: [], other: '', remark: '' })
+
+const form = reactive<FormData>({
+  partyBName: '',
+  gender: '',
+  roomNo: '',
+  bedNo: '',
+  assessDate: '',
+  careLevel: '',
+  agentName: '',
+  emergencyContact: '',
+  year: '',
+  month: '',
+  day: '',
+  basicServices: {
+    consultation: defaultBasic(),
+    mealService: defaultBasic(),
+    deliveryService: defaultBasic(),
+    medicalService: defaultBasic(),
+    chronicManagement: defaultBasic(),
+    accompanyMedical: defaultBasic(),
+    mentalSupport: defaultBasic(),
+    safetyProtection: defaultBasic(),
+    recreation: defaultBasic(),
+    education: defaultBasic(),
+    commission: defaultBasic(),
+    sanitation: defaultBasic(),
+    laundry: defaultBasic(),
+    shopping: defaultBasic(),
+    maintenance: defaultBasic(),
+    communication: defaultBasic(),
+    transportation: defaultBasic(),
+    infrastructure: defaultBasic(),
+  },
+  lifeCare: {
+    personalHygiene: { selected: [], other: '' },
+    dressing: { selected: [], other: '' },
+    grooming: { selected: [], other: '' },
+    dietaryCare: { selected: [], other: '' },
+    toiletCare: { selected: [], other: '' },
+    oralCare: { selected: [], other: '' },
+    skinCleaning: { selected: [], other: '' },
+    positionTransfer: { selected: [], other: '' },
+    excretionCare: { selected: [], other: '' },
+    skinCare: { selected: [], other: '' },
+    other: ''
+  },
+  elderlyNursing: {
+    comprehensiveAssessment: false,
+    chronicDisease: { selected: [], other: '' },
+    diseaseNursing: { selected: [], other: '' },
+    nursingTech: { selected: [], other: '' },
+    healthGuidance: false,
+    drugManagement: false,
+    other: '',
+    assistMedicalNursing: { selected: [], other: '' },
+    rehabilitation: { selected: [], other: '' },
+    palliativeCare: { selected: [], other: '' },
+    otherAgreed: ''
+  },
+  partyBSign: '',
+  partyCSign: ''
+})
+
+watch(() => props.modelValue, (val) => {
+  if (!val) return
+  Object.assign(form, JSON.parse(JSON.stringify(val)))
+}, { immediate: true, deep: true })
+
+const updateForm = () => {
+  emit('update:modelValue', JSON.parse(JSON.stringify(form)))
+}
+
+const toggleBasic = (key: string, option: string) => {
+  const item = form.basicServices![key]
+  const idx = item.selected.indexOf(option)
+  if (idx > -1) item.selected.splice(idx, 1)
+  else item.selected.push(option)
+  updateForm()
+}
+
+const toggleLife = (key: string, option: string) => {
+  const item = form.lifeCare![key]
+  const idx = item.selected.indexOf(option)
+  if (idx > -1) item.selected.splice(idx, 1)
+  else item.selected.push(option)
+  updateForm()
+}
+
+const toggleElderly = (key: string, option: string) => {
+  const item = form.elderlyNursing![key]
+  const idx = item.selected.indexOf(option)
+  if (idx > -1) item.selected.splice(idx, 1)
+  else item.selected.push(option)
+  updateForm()
+}
+
+const isChecked = (obj: any, option: string) => (obj?.selected || []).includes(option)
+
+const basicServices = [
+  { name: '咨询服务', key: 'consultation', options: ['入住咨询', '法律咨询', '心理咨询', '医疗咨询', '护理咨询', '康复咨询', '教育咨询', '服务咨询'], other: '其他咨询' },
+  { name: '膳食服务', key: 'mealService', options: ['食谱定制', '营养配餐', '食品加工与制作'], other: '其他膳食服务' },
+  { name: '送餐服务', key: 'deliveryService', options: ['定期订餐', '按时送餐'], other: '其他送餐服务' },
+  { name: '医疗服务', key: 'medicalService', options: ['疾病诊治', '健康指导', '预防保健、体检', '药事管理', '医疗相关风险管理及告知', '转诊转院'], other: '其他医疗服务', sub: { name: '慢病管理', key: 'chronicManagement', options: ['慢病监测', '健康咨询', '用药指导'], other: '其他慢病管理服务' } },
+  { name: '陪同就医服务', key: 'accompanyMedical', options: ['陪同指定医院就医', '协助挂号', '协助完成各项检查', '协助取药'], other: '其他陪同就医服务' },
+  { name: '心理/精神支持服务', key: 'mentalSupport', options: ['情绪疏导', '心理支持', '危机干预', '老年人家属心理支持服务'], other: '其他心理/精神支持服务' },
+  { name: '安全保护服务', key: 'safetyProtection', options: ['安全风险评估', '安全教育', '提供安全设备设置'], other: '其他安全保护服务' },
+  { name: '休闲娱乐服务', key: 'recreation', options: ['文体活动', '棋牌娱乐', '健身活动', '游艺活动', '影视观看服务', '参观游览'], other: '其他娱乐休闲服务' },
+  { name: '教育服务', key: 'education', options: ['读书', '讲座', '老年大学'], other: '其他教育服务' },
+  { name: '委托服务', key: 'commission', options: ['代读、代写书信', '代领物品', '代缴费用'], other: '其他委托服务' },
+  { name: '环境卫生服务', key: 'sanitation', options: ['机构公共区域清洁', '老年人居室清洁'], other: '其他环境卫生服务', extra: ['频次', '频次'] },
+  { name: '洗涤服务', key: 'laundry', options: ['老年人衣物洗涤', '老年人居室布草洗涤'], other: '其他洗涤服务', extra: ['频次', '频次'] },
+  { name: '购物服务', key: 'shopping', options: ['代购日常生活物品', '陪同外出购物', '协助老年人使用网络购物'], other: '其他购物服务' },
+  { name: '维修服务', key: 'maintenance', options: ['公共设施设备维修', '老年人居室设备维修'], other: '其他维修服务' },
+  { name: '通信服务', key: 'communication', options: ['提供电话设备', '提供互联网上网服务'], other: '其他通信服务' },
+  { name: '交通服务', key: 'transportation', options: ['提供交通工具运送老年人', '联系交通工具运送老年人'], other: '其他交通服务' },
+  { name: '基础设施服务', key: 'infrastructure', options: ['提供居室内基本用电用水服务', '提供有线电视收视服务', '提供空调服务', '提供冬季取暖服务', '提供生活饮用水服务', '呼叫应答服务'], other: '其他基础设施服务' },
+]
+
+const lifeCareItems = [
+  { name: '个人清洁卫生服务', key: 'personalHygiene', options: ['洗脸', '洗手', '洗头', '洗脚', '按摩', '拍背', '协助整理个人物品', '清洁整理床铺', '更换床单位'], other: '其他个人清洁卫生服务' },
+  { name: '穿衣服服务', key: 'dressing', options: ['更换上衣、裤子', '协助穿衣', '整理衣物'], other: '其他穿衣服务' },
+  { name: '修饰服务', key: 'grooming', options: ['梳头', '剃须', '剪指/趾甲', '化妆'], other: '其他修饰服务' },
+  { name: '饮食照料服务', key: 'dietaryCare', options: ['经口喂食或水', '鼻胃管喂食或水', '协助用膳'], other: '其他饮食服务' },
+  { name: '如厕照料服务', key: 'toiletCare', options: ['定时提醒如厕', '使用便盆', '使用尿壶', '协助入厕排便、排尿'], other: '其他如厕服务' },
+  { name: '口腔清洁服务', key: 'oralCare', options: ['刷牙', '漱口', '清洁义齿', '口腔擦拭'], other: '其他口腔清洁服务' },
+  { name: '皮肤清洁服务', key: 'skinCleaning', options: ['淋浴', '床上擦浴', '清洗会阴'], other: '其他皮肤清洁服务' },
+  { name: '体位转移服务', key: 'positionTransfer', options: ['进行床上体位转换', '床与轮椅转移', '床与平车转移'], other: '其他体位转移服务' },
+  { name: '便溺照料服务', key: 'excretionCare', options: ['协助进行床上排便', '人工排便', '药物及辅助用品肛注排便', '床上排尿', '更换一次性尿垫', '更换一次性尿裤'], other: '其他便溺服务' },
+  { name: '皮肤护理服务', key: 'skinCare', options: ['卧床老年人预防压疮', '老年人皮肤观察', '定时更换体位', '清洁皮肤', '使用预防压疮的器具'], other: '其他皮肤护理服务' },
+]
+
+const elderlyNursingItems = [
+  { name: '慢病管理服务', key: 'chronicDisease', options: ['老年人慢性非传染性疾病情况制定护理计划', '实施维持性治疗', '观察老年人症状变化、定期检测', '生活方式干预', '健康教育'], other: '其他慢病管理服务' },
+  { name: '病症护理服务', key: 'diseaseNursing', options: ['常见病症进行观察', '按照医嘱针对护理'], other: '其他病症护理服务' },
+  { name: '护理技术操作服务', key: 'nursingTech', options: ['清洁与舒适管理', '营养与排泄护理', '常见症状护理', '皮肤、伤口、造口护理', '气道护理', '引流护理', '生命体征监测', '急救技术', '常用标本采集', '给药治疗与护理'], other: '其他护理服务' },
+  { name: '协助医疗护理服务', key: 'assistMedicalNursing', options: ['老年人日常生活观察', '协助或指导老年人使用辅助器具', '化验标本的收集送检'], other: '其他协助医疗护理服务' },
+  { name: '康复服务', key: 'rehabilitation', options: ['康复评定和制定计划', '物理治疗', '作业治疗', '言语治疗', '中医康复治疗'], other: '其他康复服务' },
+  { name: '安宁服务', key: 'palliativeCare', options: ['疼痛及其他症状护理服务', '舒适照护'], other: '其他安宁服务' },
+]
+</script>
+
+<template>
+  <div class="first-service-project">
+    <div class="doc-title">首次服务项目确认表</div>
+    <div class="doc-date">
+      <span>时间:</span>
+      <FieldItem v-model="form.year" :is-text-mode="isTextMode" width="60px" placeholder="" @update:model-value="updateForm" />
+      <span>年</span>
+      <FieldItem v-model="form.month" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+      <span>月</span>
+      <FieldItem v-model="form.day" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+      <span>日</span>
+    </div>
+
+    <!-- 基本信息 -->
+    <table class="info-table">
+      <tbody>
+        <tr>
+          <td class="label">乙方姓名</td>
+          <td><FieldItem v-model="form.partyBName" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+          <td class="label">性别</td>
+          <td><FieldItem v-model="form.gender" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+          <td class="label">房间号/床号</td>
+          <td><FieldItem v-model="form.roomNo" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" />/<FieldItem v-model="form.bedNo" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+        </tr>
+        <tr>
+          <td class="label">评估日期</td>
+          <td><FieldItem v-model="form.assessDate" :is-text-mode="isTextMode" type="date" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+          <td class="label">照料护理等级</td>
+          <td colspan="3"><FieldItem v-model="form.careLevel" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+        </tr>
+        <tr>
+          <td class="label">代理人姓名</td>
+          <td><FieldItem v-model="form.agentName" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+          <td class="label">紧急联系人姓名</td>
+          <td colspan="3"><FieldItem v-model="form.emergencyContact" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" /></td>
+        </tr>
+      </tbody>
+    </table>
+
+    <!-- 基础服务 -->
+    <table class="service-table">
+      <thead>
+        <tr>
+          <th colspan="3" class="section-title">基础服务</th>
+        </tr>
+        <tr>
+          <th class="col-service">服务项目</th>
+          <th class="col-content">服务内容</th>
+          <th class="col-remark">备注</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="svc in basicServices" :key="svc.key">
+          <td class="service-name">{{ svc.name }}</td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label v-for="(opt, idx) in svc.options" :key="opt" class="chk-label">
+                <input type="checkbox" :checked="isChecked(form.basicServices![svc.key], opt)" @change="toggleBasic(svc.key, opt)" />
+                <span>{{ opt }}</span>
+                <span v-if="svc.extra && svc.extra[idx]" class="extra-label">{{ svc.extra[idx] }}:<FieldItem v-model="(form.basicServices as any)[svc.key]['extra' + idx]" :is-text-mode="isTextMode" width="60px" placeholder="" @update:model-value="updateForm" /></span>
+              </label>
+              <div v-if="svc.other" class="other-row">
+                <span>{{ svc.other }}:</span>
+                <FieldItem v-model="(form.basicServices as any)[svc.key].other" :is-text-mode="isTextMode" width="200px" placeholder="" @update:model-value="updateForm" />
+              </div>
+              <div v-if="svc.sub" class="sub-service">
+                <div class="sub-name">{{ svc.sub.name }}</div>
+                <label v-for="opt in svc.sub.options" :key="opt" class="chk-label">
+                  <input type="checkbox" :checked="isChecked(form.basicServices![svc.sub.key], opt)" @change="toggleBasic(svc.sub.key, opt)" />
+                  <span>{{ opt }}</span>
+                </label>
+                <div v-if="svc.sub.other" class="other-row">
+                  <span>{{ svc.sub.other }}:</span>
+                  <FieldItem v-model="(form.basicServices as any)[svc.sub.key].other" :is-text-mode="isTextMode" width="200px" placeholder="" @update:model-value="updateForm" />
+                </div>
+              </div>
+            </template>
+            <template v-else>
+              <span v-for="opt in svc.options" :key="opt" class="text-chk">
+                {{ isChecked(form.basicServices![svc.key], opt) ? '☑' : '□' }}{{ opt }}
+                <span v-if="svc.extra && svc.extra[svc.options.indexOf(opt)]" class="extra-text">{{ svc.extra[svc.options.indexOf(opt)] }}:{{ (form.basicServices as any)[svc.key]['extra' + svc.options.indexOf(opt)] || '____' }}</span>
+              </span>
+              <span v-if="(form.basicServices as any)[svc.key].other" class="text-chk">☑{{ svc.other }}:{{ (form.basicServices as any)[svc.key].other }}</span>
+              <span v-else-if="svc.other" class="text-chk">□{{ svc.other }}:____</span>
+              <div v-if="svc.sub" class="sub-service">
+                <div class="sub-name">{{ svc.sub.name }}</div>
+                <span v-for="opt in svc.sub.options" :key="opt" class="text-chk">
+                  {{ isChecked(form.basicServices![svc.sub.key], opt) ? '☑' : '□' }}{{ opt }}
+                </span>
+                <span v-if="(form.basicServices as any)[svc.sub.key].other" class="text-chk">☑{{ svc.sub.other }}:{{ (form.basicServices as any)[svc.sub.key].other }}</span>
+                <span v-else-if="svc.sub.other" class="text-chk">□{{ svc.sub.other }}:____</span>
+              </div>
+            </template>
+          </td>
+          <td class="service-remark">
+            <FieldItem v-model="(form.basicServices as any)[svc.key].remark" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" />
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <!-- 等级护理照料服务 -->
+    <div class="section-title-bar">等级护理照料服务</div>
+
+    <!-- 生活照料服务 -->
+    <table class="service-table no-remark">
+      <thead>
+        <tr>
+          <th class="col-service">服务项目</th>
+          <th class="col-content">服务内容</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="svc in lifeCareItems" :key="svc.key">
+          <td class="service-name">{{ svc.name }}</td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label v-for="opt in svc.options" :key="opt" class="chk-label">
+                <input type="checkbox" :checked="isChecked(form.lifeCare![svc.key], opt)" @change="toggleLife(svc.key, opt)" />
+                <span>{{ opt }}</span>
+              </label>
+              <div v-if="svc.other" class="other-row">
+                <span>{{ svc.other }}:</span>
+                <FieldItem v-model="(form.lifeCare as any)[svc.key].other" :is-text-mode="isTextMode" width="200px" placeholder="" @update:model-value="updateForm" />
+              </div>
+            </template>
+            <template v-else>
+              <span v-for="opt in svc.options" :key="opt" class="text-chk">
+                {{ isChecked(form.lifeCare![svc.key], opt) ? '☑' : '□' }}{{ opt }}
+              </span>
+              <span v-if="(form.lifeCare as any)[svc.key].other" class="text-chk">☑{{ svc.other }}:{{ (form.lifeCare as any)[svc.key].other }}</span>
+              <span v-else-if="svc.other" class="text-chk">□{{ svc.other }}:____</span>
+            </template>
+          </td>
+        </tr>
+        <tr>
+          <td class="service-name"></td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label class="chk-label">
+                <input type="checkbox" v-model="form.lifeCare!.otherChecked" @change="updateForm" />
+                <span>其他生活照料服务:</span>
+              </label>
+              <FieldItem v-model="form.lifeCare!.other" :is-text-mode="isTextMode" width="300px" placeholder="" @update:model-value="updateForm" />
+            </template>
+            <template v-else>
+              <span class="text-chk">{{ form.lifeCare!.otherChecked ? '☑' : '□' }}其他生活照料服务:{{ form.lifeCare!.other || '____' }}</span>
+            </template>
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <!-- 老年护理服务 -->
+    <table class="service-table no-remark">
+      <thead>
+        <tr>
+          <th class="col-service">服务项目</th>
+          <th class="col-content">服务内容</th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr>
+          <td class="service-name">老年人综合评估</td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label class="chk-label">
+                <input type="checkbox" v-model="form.elderlyNursing!.comprehensiveAssessment" @change="updateForm" />
+                <span>老年人综合评估</span>
+              </label>
+            </template>
+            <template v-else>
+              <span class="text-chk">{{ form.elderlyNursing!.comprehensiveAssessment ? '☑' : '□' }}老年人综合评估</span>
+            </template>
+          </td>
+        </tr>
+        <tr v-for="svc in elderlyNursingItems" :key="svc.key">
+          <td class="service-name">{{ svc.name }}</td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label v-for="opt in svc.options" :key="opt" class="chk-label">
+                <input type="checkbox" :checked="isChecked(form.elderlyNursing![svc.key], opt)" @change="toggleElderly(svc.key, opt)" />
+                <span>{{ opt }}</span>
+              </label>
+              <div v-if="svc.other" class="other-row">
+                <span>{{ svc.other }}:</span>
+                <FieldItem v-model="(form.elderlyNursing as any)[svc.key].other" :is-text-mode="isTextMode" width="200px" placeholder="" @update:model-value="updateForm" />
+              </div>
+            </template>
+            <template v-else>
+              <span v-for="opt in svc.options" :key="opt" class="text-chk">
+                {{ isChecked(form.elderlyNursing![svc.key], opt) ? '☑' : '□' }}{{ opt }}
+              </span>
+              <span v-if="(form.elderlyNursing as any)[svc.key].other" class="text-chk">☑{{ svc.other }}:{{ (form.elderlyNursing as any)[svc.key].other }}</span>
+              <span v-else-if="svc.other" class="text-chk">□{{ svc.other }}:____</span>
+            </template>
+          </td>
+        </tr>
+        <tr>
+          <td class="service-name"></td>
+          <td class="service-content">
+            <template v-if="!isTextMode">
+              <label class="chk-label">
+                <input type="checkbox" v-model="form.elderlyNursing!.healthGuidance" @change="updateForm" />
+                <span>健康指导</span>
+              </label>
+              <label class="chk-label">
+                <input type="checkbox" v-model="form.elderlyNursing!.drugManagement" @change="updateForm" />
+                <span>药品管理服务</span>
+              </label>
+              <div class="other-row">
+                <span>其他老年护理服务:</span>
+                <FieldItem v-model="form.elderlyNursing!.other" :is-text-mode="isTextMode" width="300px" placeholder="" @update:model-value="updateForm" />
+              </div>
+            </template>
+            <template v-else>
+              <span class="text-chk">{{ form.elderlyNursing!.healthGuidance ? '☑' : '□' }}健康指导</span>
+              <span class="text-chk">{{ form.elderlyNursing!.drugManagement ? '☑' : '□' }}药品管理服务</span>
+              <span class="text-chk">其他老年护理服务:{{ form.elderlyNursing!.other || '____' }}</span>
+            </template>
+          </td>
+        </tr>
+        <tr>
+          <td class="service-name">其他约定服务</td>
+          <td class="service-content">
+            <FieldItem v-model="form.elderlyNursing!.otherAgreed" :is-text-mode="isTextMode" width="100%" placeholder="" @update:model-value="updateForm" />
+          </td>
+        </tr>
+      </tbody>
+    </table>
+
+    <!-- 签名 -->
+    <div class="sign-row">
+      <div class="sign-block">
+        <span class="sign-label">乙方(签名):</span>
+        <SignatureItem v-model="form.partyBSign" :is-text-mode="isTextMode" width="200" height="80" placeholder="(签字处)" title="乙方签字" @update:model-value="updateForm" />
+      </div>
+      <div class="sign-block">
+        <span class="sign-label">丙方(签名或盖章):</span>
+        <SignatureItem v-model="form.partyCSign" :is-text-mode="isTextMode" width="200" height="80" placeholder="(签字处)" title="丙方签字" @update:model-value="updateForm" />
+      </div>
+    </div>
+    <div class="date-row">
+      <FieldItem v-model="form.year" :is-text-mode="isTextMode" width="60px" placeholder="" @update:model-value="updateForm" />
+      <span>年</span>
+      <FieldItem v-model="form.month" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+      <span>月</span>
+      <FieldItem v-model="form.day" :is-text-mode="isTextMode" width="40px" placeholder="" @update:model-value="updateForm" />
+      <span>日</span>
+    </div>
+
+    <!-- 备注 -->
+    <div class="notice">
+      <p>注:1. 根据乙方的照料护理等级确定相应服务内容。</p>
+      <p>  2. “服务内容”一栏在确认内容后的“□”内打“√”,未选的在“□”内打“×”。</p>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.first-service-project {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 13px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+.doc-date {
+  text-align: center;
+  margin-bottom: 12px;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+}
+
+.info-table,
+.service-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 12px;
+
+  th,
+  td {
+    border: 1px solid #333;
+    padding: 6px 8px;
+    vertical-align: top;
+  }
+
+  th {
+    background: #f5f5f5;
+    font-weight: bold;
+    text-align: center;
+  }
+
+  .label {
+    background: #f5f5f5;
+    font-weight: bold;
+    text-align: center;
+    white-space: nowrap;
+    width: 100px;
+  }
+}
+
+.service-table {
+  .section-title {
+    background: #f5f5f5;
+    font-weight: bold;
+    text-align: center;
+    font-size: 14px;
+  }
+
+  .col-service {
+    width: 120px;
+    text-align: center;
+  }
+
+  .col-content {
+    min-width: 400px;
+  }
+
+  .col-remark {
+    width: 100px;
+  }
+
+  .service-name {
+    text-align: center;
+    font-weight: bold;
+  }
+
+  .service-content {
+    .chk-label {
+      display: inline-flex;
+      align-items: center;
+      margin-right: 12px;
+      margin-bottom: 4px;
+      cursor: pointer;
+
+      input[type='checkbox'] {
+        margin-right: 4px;
+      }
+    }
+
+    .text-chk {
+      display: inline-block;
+      margin-right: 12px;
+      margin-bottom: 4px;
+    }
+
+    .other-row {
+      margin-top: 4px;
+      display: flex;
+      align-items: center;
+      gap: 4px;
+    }
+
+    .sub-service {
+      margin-top: 6px;
+      padding-left: 8px;
+      border-left: 2px solid #ccc;
+
+      .sub-name {
+        font-weight: bold;
+        margin-bottom: 4px;
+      }
+    }
+
+    .extra-label,
+    .extra-text {
+      margin-left: 4px;
+    }
+  }
+}
+
+.section-title-bar {
+  text-align: center;
+  font-weight: bold;
+  font-size: 14px;
+  padding: 8px;
+  border: 1px solid #333;
+  border-bottom: none;
+  background: #f5f5f5;
+}
+
+.sign-row {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 20px;
+  padding: 0 20px;
+
+  .sign-block {
+    display: flex;
+    align-items: flex-start;
+    gap: 8px;
+
+    .sign-label {
+      white-space: nowrap;
+      padding-top: 4px;
+    }
+  }
+}
+
+.date-row {
+  text-align: right;
+  margin-top: 12px;
+  padding-right: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 4px;
+}
+
+.notice {
+  margin-top: 16px;
+  font-size: 12px;
+  color: #666;
+
+  p {
+    margin: 4px 0;
+  }
+}
+
+@media print {
+  .first-service-project {
+    font-size: 12px;
+  }
+
+  .service-table {
+    .col-content {
+      min-width: auto;
+    }
+  }
+}
+</style>

+ 327 - 0
src/views/elderly/contracts/components/OutingConfirmationBody.vue

@@ -0,0 +1,327 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface FormData {
+  elderName?: string
+  outingAbility?: 'restricted' | 'unrestricted' | ''
+  elderSign?: string
+  guardianSign?: string
+  signDate?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  elderName: '',
+  outingAbility: '',
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      Object.assign(form, val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = (key: keyof FormData, val: any) => {
+  ;(form as any)[key] = val
+  emit('update:modelValue', { ...form })
+}
+
+const onAbilityChange = (val: 'restricted' | 'unrestricted') => {
+  form.outingAbility = val
+  emit('update:modelValue', { ...form })
+}
+</script>
+
+<template>
+  <div class="outing-confirmation">
+    <div class="attachment-title" style="text-align: left;font-size: 14px;font-weight: normal;">附件5</div>
+    <div class="attachment-title">长者外出情况确认书</div>
+
+    <div class="content">
+      <p class="salutation">
+        尊敬的<u class="underline-blank">{{ form.elderName || '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' }}</u>:
+      </p>
+
+      <p class="indent">
+        欢迎您入住颐年集团旗下养老机构,现将我院关于长者外出事项告知如下:
+      </p>
+
+      <p class="indent">
+        <strong>一、在您入住期间,我院不会对您是否具备独自外出能力作出判断,也不对此承担责任。请您或监护人从以下选项中"二选一"进行确认(选中的打✓,不选的打×):</strong>
+      </p>
+
+      <!-- 二选一选项 -->
+      <div class="choice-list">
+        <div class="choice-item">
+          <template v-if="!isTextMode">
+            <el-checkbox
+              :model-value="form.outingAbility === 'restricted'"
+              @change="(val: any) => onAbilityChange(val ? 'restricted' : '')"
+            >
+              不具备独自外出能力,需接受限制外出服务管理。
+            </el-checkbox>
+          </template>
+          <template v-else>
+            <span class="checkbox-display">{{ form.outingAbility === 'restricted' ? '☑' : '☐' }}</span>
+            <span>不具备独自外出能力,需接受限制外出服务管理。</span>
+          </template>
+        </div>
+        <div class="choice-item">
+          <template v-if="!isTextMode">
+            <el-checkbox
+              :model-value="form.outingAbility === 'unrestricted'"
+              @change="(val: any) => onAbilityChange(val ? 'unrestricted' : '')"
+            >
+              具备独自外出能力,无需接受限制外出服务管理。
+            </el-checkbox>
+          </template>
+          <template v-else>
+            <span class="checkbox-display">{{ form.outingAbility === 'unrestricted' ? '☑' : '☐' }}</span>
+            <span>具备独自外出能力,无需接受限制外出服务管理。</span>
+          </template>
+        </div>
+      </div>
+
+      <p class="indent">
+        <strong>二、</strong>若您不具备独自外出的能力,您或您的监护人/委托代理人应向本院确认"需限制外出"的服务管理。本院将对您的外出进行统一管理。您外出时必须指定陪同人员(如家属等),且每次外出均需由您或您的监护人/委托代理人提前通知本院,陪同人员应在您外出及返回时进行登记。
+      </p>
+
+      <p class="indent">
+        <strong>三、</strong>若您具备独自外出的能力,您或您的监护人/委托代理人应向本院确认"不需限制外出"的服务管理。您每次外出时,应在外出及返回时进行登记。
+      </p>
+
+      <p class="indent">
+        <strong>四、</strong>若您患有认知症等疾病,本院将统一实行"限制外出"的服务管理。但若您或您的监护人/委托代理人拒绝本院为您提供限制外出管理服務,由此造成的后果由您或您的监护人/委托代理人承担,本院不承担责任。
+      </p>
+
+      <p class="indent">
+        <strong>五、</strong>无论您是否具备独自外出的能力、外出是否进行登记,若您在外出期间发生事故(包括但不限于摔伤、走失、财物丢失)、突发疾病、遭受人身损害、死亡或者造成他人损害,由此造成的后果由您或您的监护人/委托代理人承担,本院不承担责任。
+      </p>
+
+      <p class="indent">
+        <strong>六、外出存在如下风险:</strong>
+      </p>
+
+      <ol class="risk-list">
+        <li>走失,或突发严重疾病而无法获得及时救治。</li>
+        <li>遭遇交通事故及其他人身伤害。</li>
+        <li>遭遇偷、盗、抢、骗、拐等暴力侵犯。</li>
+        <li>遭遇其他无法预知的意外风险。</li>
+      </ol>
+
+      <p class="indent">
+        基于上述缘由,我们期望您能安心调养身体,减少外出活动。
+      </p>
+
+      <p class="indent">
+        倘若您执意选择外出,由此产生的后果将由您本人或您的监护人/委托代理人承担,我院概不负责。
+      </p>
+
+      <p class="confirm-title">
+        以上内容由入住长者及监护人/委托代理人确认:
+      </p>
+
+      <p class="confirm-statement">
+        "我们在此郑重确认:已仔细阅读上述外出情况告知书,若出现告知书中明确提及的养老机构免责情形,所有责任与后果均由我们自行承担,养老机构无需担责。"
+      </p>
+    </div>
+
+    <!-- 签名区域 -->
+    <div class="sign-section">
+      <div class="sign-row dual-sign">
+        <div class="sign-item">
+          <span class="sign-label">入住长者签名:</span>
+          <SignatureItem
+            v-model="form.elderSign"
+            :is-text-mode="isTextMode"
+            width="200"
+          />
+        </div>
+        <div class="sign-item">
+          <span class="sign-label">监护人/委托代理人签名:</span>
+          <SignatureItem
+            v-model="form.guardianSign"
+            :is-text-mode="isTextMode"
+            width="200"
+          />
+        </div>
+      </div>
+      <div class="sign-row2 date-row">
+        <span class="sign-label">日 期:</span>
+        <template v-if="!isTextMode">
+          <el-date-picker
+            v-model="form.signDate"
+            type="date"
+            placeholder="请选择日期"
+            style="width: 160px"
+            value-format="YYYY-MM-DD"
+            @change="updateForm('signDate', form.signDate)"
+          />
+        </template>
+        <template v-else>
+          <span class="date-value">{{ form.signDate || '____年__月__日' }}</span>
+        </template>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.outing-confirmation {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 10mm;
+
+  /* 打印模式优化 */
+  @media print {
+    width: 210mm !important;
+    max-width: 210mm;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.attachment-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 20px;
+}
+
+.content {
+  line-height: 1.8;
+}
+
+.salutation {
+  margin: 0 0 12px 0;
+}
+
+.underline-blank {
+  min-width: 80px;
+  display: inline-block;
+  text-align: center;
+  text-decoration: underline;
+  text-underline-offset: 4px;
+}
+
+.indent {
+  text-indent: 2em;
+  margin: 0 0 12px 0;
+}
+
+.choice-list {
+  padding-left: 2em;
+  margin-bottom: 12px;
+}
+
+.choice-item {
+  margin-bottom: 8px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.checkbox-display {
+  font-size: 16px;
+  display: inline-block;
+  width: 20px;
+  text-align: center;
+}
+
+.risk-list {
+  margin: 0 0 12px 0;
+  padding-left: 3em;
+  line-height: 1.8;
+}
+
+.risk-list li {
+  margin-bottom: 4px;
+}
+
+.confirm-title {
+  text-indent: 2em;
+  margin: 16px 0 8px 0;
+  font-weight: bold;
+  text-decoration: underline;
+  text-underline-offset: 4px;
+}
+
+.confirm-statement {
+  text-indent: 2em;
+  margin: 0 0 20px 0;
+  font-weight: bold;
+}
+
+.sign-section {
+  display: flex;
+  flex-direction: column;
+  align-items: start;
+  margin-top: 30px;
+}
+
+.sign-row {
+  display: flex;
+  flex-direction: column;
+  align-items: start;
+  margin-bottom: 16px;
+}
+
+.sign-row2 {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.dual-sign {
+  display: flex;
+  justify-content: space-between;
+  gap: 40px;
+}
+
+.sign-item {
+  display: flex;
+  align-items: center;
+  flex: 1;
+}
+
+.sign-label {
+  white-space: nowrap;
+  margin-right: 8px;
+}
+
+.date-row {
+  margin-top: 8px;
+}
+
+.date-value {
+  min-width: 120px;
+  display: inline-block;
+}
+</style>

+ 263 - 0
src/views/elderly/contracts/components/PowerOfAttorneyBody.vue

@@ -0,0 +1,263 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface FormData {
+  principalName?: string
+  principalGender?: string
+  principalIdCardNo?: string
+  institutionName?: string
+  agentName?: string
+  agentGender?: string
+  agentIdCardNo?: string
+  agentPhone?: string
+  elderName?: string
+  relationship?: string
+  principalSign?: string
+  agentSign?: string
+  signDate?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  principalName: '',
+  principalGender: '',
+  principalIdCardNo: '',
+  institutionName: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentPhone: '',
+  elderName: '',
+  relationship: '',
+  principalSign: '',
+  agentSign: '',
+  signDate: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      Object.assign(form, val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = (key: keyof FormData, val: any) => {
+  ;(form as any)[key] = val
+  emit('update:modelValue', { ...form })
+}
+</script>
+
+<template>
+  <div class="power-of-attorney">
+    <div class="doc-title">授权委托书</div>
+    <div class="doc-body">
+      <p class="paragraph indent">
+        本人
+        <FieldItem
+          :model-value="form.principalName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('principalName', v)"
+        />
+        (
+        <FieldItem
+          :model-value="form.principalGender"
+          :is-text-mode="isTextMode"
+          width="60px"
+          placeholder="男/女"
+          @update:model-value="(v: any) => updateForm('principalGender', v)"
+        />
+        ),身份证号:
+        <FieldItem
+          :model-value="form.principalIdCardNo"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('principalIdCardNo', v)"
+        />
+      </p>
+      <p class="paragraph indent">
+        自愿入住
+        <FieldItem
+          :model-value="form.institutionName"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('institutionName', v)"
+        />
+        ,兹授权
+        <FieldItem
+          :model-value="form.agentName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('agentName', v)"
+        />
+        (
+        <FieldItem
+          :model-value="form.agentGender"
+          :is-text-mode="isTextMode"
+          width="60px"
+          placeholder="男/女"
+          @update:model-value="(v: any) => updateForm('agentGender', v)"
+        />
+        ),
+      </p>
+      <p class="paragraph indent">
+        身份证号:
+        <FieldItem
+          :model-value="form.agentIdCardNo"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('agentIdCardNo', v)"
+        />
+        手机号
+        <FieldItem
+          :model-value="form.agentPhone"
+          :is-text-mode="isTextMode"
+          width="140px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('agentPhone', v)"
+        />
+        ,
+      </p>
+      <p class="paragraph indent">
+        办理本人
+        <FieldItem
+          :model-value="form.elderName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('elderName', v)"
+        />
+        的入住手续相关事宜。
+      </p>
+    </div>
+
+    <div class="sign-section">
+      <div class="sign-row">
+        <span class="sign-label">授权人与被授权人关系:</span>
+        <FieldItem
+          :model-value="form.relationship"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('relationship', v)"
+        />
+      </div>
+      <div class="sign-row">
+        <span class="sign-label">授权人(签名盖手印):</span>
+        <SignatureItem
+          :model-value="form.principalSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="授权人签字"
+          @update:model-value="(v: any) => updateForm('principalSign', v)"
+        />
+      </div>
+      <div class="sign-row">
+        <span class="sign-label">被授权人(签名盖手印):</span>
+        <SignatureItem
+          :model-value="form.agentSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="被授权人签字"
+          @update:model-value="(v: any) => updateForm('agentSign', v)"
+        />
+      </div>
+      <div class="sign-row date-row">
+        <span class="sign-label">日 期:</span>
+        <FieldItem
+          :model-value="form.signDate"
+          :is-text-mode="isTextMode"
+          type="date"
+          width="160px"
+          placeholder="请选择日期"
+          @update:model-value="(v: any) => updateForm('signDate', v)"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.power-of-attorney {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 30px;
+}
+
+.doc-body {
+  line-height: 2.2;
+  margin-bottom: 60px;
+}
+
+.paragraph {
+  text-indent: 2em;
+  margin: 0;
+}
+
+.sign-section {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.sign-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.sign-label {
+  white-space: nowrap;
+  margin-right: 8px;
+}
+
+.date-row {
+  margin-bottom: 20px;
+}
+</style>

+ 20 - 40
src/views/elderly/contracts/components/RiskDisclosureBody.vue

@@ -54,7 +54,7 @@ const updateForm = (key: keyof FormData, val: any) => {
 </script>
 
 <template>
-  <div class="risk-disclosure">
+  
     <div class="a4-page">
       <div class="doc-logo" style="margin-bottom: 20px;">
         附件1.知情同意书
@@ -261,21 +261,33 @@ const updateForm = (key: keyof FormData, val: any) => {
         </div>
       </div>
     </div>
-  </div>
+ 
 </template>
 
 <style scoped lang="scss">
-.risk-disclosure {
-  width: 100%;
-}
+ 
 
 .a4-page {
   width: 210mm;
   margin: 0 auto 20px;
-  padding: 10mm 18mm;
+  padding: 10mm 12mm;
   background: #fff;
   box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
   box-sizing: border-box;
+
+   
+  @media print {
+    &.a4-page {
+      width: 100% !important;
+      max-width: 100%;
+      padding: 0 !important;
+      border: none;
+      margin: 0 !important;
+      box-sizing: border-box;
+    }
+  }
+  
+
 }
 
 .doc-header {
@@ -361,39 +373,7 @@ const updateForm = (key: keyof FormData, val: any) => {
 .signature-group p {
   margin-bottom: 10px;
 }
+ 
+  
 
-@media print {
-  .a4-page {
-    width: 100% !important;
-    margin: 0 !important;
-    padding: 0 !important;
-    background: #fff !important;
-    box-shadow: none !important;
-    box-sizing: border-box !important;
-  }
-  .section-title {
-    break-after: avoid-page;
-    page-break-after: avoid;
-  }
-  .sub-title {
-    break-after: avoid-page;
-    page-break-after: avoid;
-  }
-  .num-list li {
-    break-inside: avoid-page;
-    page-break-inside: avoid;
-  }
-  .signature-group {
-    break-inside: avoid-page;
-    page-break-inside: avoid;
-  }
-  @page {
-    size: A4;
-    margin: 18mm;
-  }
-  body {
-    margin: 0;
-    padding: 0;
-  }
-}
 </style>

+ 458 - 0
src/views/elderly/contracts/components/RoomFacilitiesEquipmentBody.vue

@@ -0,0 +1,458 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface EquipmentItem {
+  name: string
+  quantity: string
+  remark: string
+}
+
+interface FormData {
+  elderName?: string
+  roomNo?: string
+  leftItems?: EquipmentItem[]
+  rightItems?: EquipmentItem[]
+  blankItems?: EquipmentItem[]
+  elderSign?: string
+  guardianSign?: string
+  signDate?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const defaultLeftNames = [
+  '窗帘', '床隔帘', '浴帘', '垃圾桶', '木床', '床垫',
+  '不锈钢拌勾', '床头柜', '书桌(含一张椅子)', '衣柜', '壁扇',
+  '电视机', '机顶盒遥控器', '机顶盒', '空调'
+]
+
+const defaultRightNames = [
+  '门牌', '客厅灯具', '卫生间灯具', '水龙头', '马桶', '呼叫器'
+]
+
+const createItems = (names: string[]): EquipmentItem[] =>
+  names.map((name) => ({ name, quantity: '1', remark: '' }))
+
+const createBlankItems = (count: number): EquipmentItem[] =>
+  Array.from({ length: count }, () => ({ name: '', quantity: '', remark: '' }))
+
+const form = reactive<FormData>({
+  elderName: '',
+  roomNo: '',
+  leftItems: createItems(defaultLeftNames),
+  rightItems: createItems(defaultRightNames),
+  blankItems: createBlankItems(5),
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (!val) return
+    if (val.elderName !== undefined) form.elderName = val.elderName
+    if (val.roomNo !== undefined) form.roomNo = val.roomNo
+    if (val.elderSign !== undefined) form.elderSign = val.elderSign
+    if (val.guardianSign !== undefined) form.guardianSign = val.guardianSign
+    if (val.signDate !== undefined) form.signDate = val.signDate
+    if (Array.isArray(val.leftItems) && val.leftItems.length === 15) {
+      form.leftItems = val.leftItems.map((it, i) => ({
+        name: it.name ?? defaultLeftNames[i],
+        quantity: it.quantity ?? '1',
+        remark: it.remark ?? ''
+      }))
+    }
+    if (Array.isArray(val.rightItems) && val.rightItems.length === 6) {
+      form.rightItems = val.rightItems.map((it, i) => ({
+        name: it.name ?? defaultRightNames[i],
+        quantity: it.quantity ?? '1',
+        remark: it.remark ?? ''
+      }))
+    }
+    if (Array.isArray(val.blankItems) && val.blankItems.length === 5) {
+      form.blankItems = val.blankItems.map((it) => ({
+        name: it.name ?? '',
+        quantity: it.quantity ?? '',
+        remark: it.remark ?? ''
+      }))
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = () => {
+  emit('update:modelValue', {
+    elderName: form.elderName,
+    roomNo: form.roomNo,
+    leftItems: form.leftItems?.map((it) => ({ ...it })),
+    rightItems: form.rightItems?.map((it) => ({ ...it })),
+    blankItems: form.blankItems?.map((it) => ({ ...it })),
+    elderSign: form.elderSign,
+    guardianSign: form.guardianSign,
+    signDate: form.signDate
+  })
+}
+
+const updateItem = (list: 'leftItems' | 'rightItems' | 'blankItems', index: number, key: keyof EquipmentItem, val: any) => {
+  const arr = form[list]
+  if (!arr) return
+  ;(arr[index] as any)[key] = val
+  updateForm()
+}
+</script>
+
+<template>
+  <div class="room-facilities-equipment">
+    <div class="doc-title">标准房间物品配备</div>
+
+    <div class="main-table-wrapper">
+      <!-- 顶部信息栏 -->
+      <div class="table-header">
+        <div class="header-left">
+          <span class="header-label">入住长者姓名</span>
+          <FieldItem
+            :model-value="form.elderName"
+            :is-text-mode="isTextMode"
+            width="160px"
+            placeholder=""
+            @update:model-value="(v: any) => { form.elderName = v; updateForm() }"
+          />
+        </div>
+        <div class="header-right">
+          <span class="header-label">房间号</span>
+          <FieldItem
+            :model-value="form.roomNo"
+            :is-text-mode="isTextMode"
+            width="120px"
+            placeholder=""
+            @update:model-value="(v: any) => { form.roomNo = v; updateForm() }"
+          />
+        </div>
+      </div>
+
+      <!-- 左右表格 -->
+      <div class="tables-row">
+        <table class="half-table">
+          <thead>
+            <tr>
+              <th class="col-no">序号</th>
+              <th class="col-name">物品名称</th>
+              <th class="col-qty">数量</th>
+              <th class="col-remark">备注</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(item, idx) in form.leftItems" :key="'l' + idx">
+              <td class="col-no">{{ idx + 1 }}</td>
+              <td class="col-name">{{ item.name }}</td>
+              <td class="col-qty">
+                <FieldItem
+                  :model-value="item.quantity"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('leftItems', idx, 'quantity', v)"
+                />
+              </td>
+              <td class="col-remark">
+                <FieldItem
+                  :model-value="item.remark"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('leftItems', idx, 'remark', v)"
+                />
+              </td>
+            </tr>
+          </tbody>
+        </table>
+
+        <table class="half-table">
+          <thead>
+            <tr>
+              <th class="col-no">序号</th>
+              <th class="col-name">物品名称</th>
+              <th class="col-qty">数量</th>
+              <th class="col-remark">备注</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr v-for="(item, idx) in form.rightItems" :key="'r' + idx">
+              <td class="col-no">{{ idx + 16 }}</td>
+              <td class="col-name">{{ item.name }}</td>
+              <td class="col-qty">
+                <FieldItem
+                  :model-value="item.quantity"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('rightItems', idx, 'quantity', v)"
+                />
+              </td>
+              <td class="col-remark">
+                <FieldItem
+                  :model-value="item.remark"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('rightItems', idx, 'remark', v)"
+                />
+              </td>
+            </tr>
+            <tr v-for="(item, idx) in form.blankItems" :key="'b' + idx">
+              <td class="col-no">{{ idx + 22 }}</td>
+              <td class="col-name">
+                <FieldItem
+                  :model-value="item.name"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('blankItems', idx, 'name', v)"
+                />
+              </td>
+              <td class="col-qty">
+                <FieldItem
+                  :model-value="item.quantity"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('blankItems', idx, 'quantity', v)"
+                />
+              </td>
+              <td class="col-remark">
+                <FieldItem
+                  :model-value="item.remark"
+                  :is-text-mode="isTextMode"
+                  width="100%"
+                  placeholder=""
+                  @update:model-value="(v: any) => updateItem('blankItems', idx, 'remark', v)"
+                />
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+
+      <!-- 底部签名栏 -->
+      <div class="table-footer">
+        <div class="footer-left">
+          <div class="sign-block">
+            <span class="sign-label">入住老人签名:</span>
+            <SignatureItem
+              :model-value="form.elderSign"
+              :is-text-mode="isTextMode"
+              width="200"
+              height="80"
+              placeholder="(签字处)"
+              title="入住老人签字"
+              @update:model-value="(v: any) => { form.elderSign = v; updateForm() }"
+            />
+          </div>
+        </div>
+        <div class="footer-right">
+          <div class="notice-block">
+            <p class="notice-title">特别告知:</p>
+            <p class="notice-content">
+              房间物品配备属于福利院公共物品,请入住长者与房友共同维护和文明使用,如有人为损坏则照价赔偿。
+            </p>
+          </div>
+          <div class="sign-block">
+            <span class="sign-label">监护人/委托代理人签名:</span>
+            <SignatureItem
+              :model-value="form.guardianSign"
+              :is-text-mode="isTextMode"
+              width="200"
+              height="80"
+              placeholder="(签字处)"
+              title="监护人/委托代理人签字"
+              @update:model-value="(v: any) => { form.guardianSign = v; updateForm() }"
+            />
+          </div>
+        </div>
+      </div>
+
+      <!-- 日期栏 -->
+      <div class="date-row-wrapper">
+        <span class="date-label">日期:</span>
+        <FieldItem
+          :model-value="form.signDate"
+          :is-text-mode="isTextMode"
+          type="date"
+          width="160px"
+          placeholder="请选择日期"
+          @update:model-value="(v: any) => { form.signDate = v; updateForm() }"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.room-facilities-equipment {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 20px;
+}
+
+.main-table-wrapper {
+  border: 1px solid #333;
+}
+
+.table-header {
+  display: flex;
+  border-bottom: 1px solid #333;
+
+  .header-left,
+  .header-right {
+    flex: 1;
+    display: flex;
+    align-items: center;
+    padding: 10px 12px;
+    gap: 8px;
+  }
+
+  .header-left {
+    border-right: 1px solid #333;
+  }
+
+  .header-label {
+    white-space: nowrap;
+    font-weight: normal;
+  }
+}
+
+.tables-row {
+  display: flex;
+  border-bottom: 1px solid #333;
+
+  .half-table {
+    flex: 1;
+    border-collapse: collapse;
+    width: 50%;
+
+    &:first-child {
+      border-right: 1px solid #333;
+    }
+
+    th,
+    td {
+      border: 1px solid #333;
+      padding: 6px 8px;
+      text-align: center;
+      vertical-align: middle;
+    }
+
+    th {
+      font-weight: normal;
+      background: #fff;
+    }
+
+    .col-no {
+      width: 40px;
+    }
+
+    .col-name {
+      width: auto;
+    }
+
+    .col-qty {
+      width: 60px;
+    }
+
+    .col-remark {
+      width: 80px;
+    }
+  }
+}
+
+.table-footer {
+  display: flex;
+  border-bottom: 1px solid #333;
+
+  .footer-left {
+    flex: 1;
+    border-right: 1px solid #333;
+    padding: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .footer-right {
+    flex: 1;
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .notice-block {
+    .notice-title {
+      font-weight: bold;
+      margin: 0 0 4px;
+    }
+
+    .notice-content {
+      margin: 0;
+      line-height: 1.6;
+      font-size: 13px;
+    }
+  }
+
+  .sign-block {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+
+    .sign-label {
+      white-space: nowrap;
+    }
+  }
+}
+
+.date-row-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 10px 12px;
+  gap: 8px;
+
+  .date-label {
+    white-space: nowrap;
+  }
+}
+</style>

+ 209 - 0
src/views/elderly/contracts/components/ServiceChargingStandardsBody.vue

@@ -0,0 +1,209 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface FormData {
+  signatorySign?: string
+  signDate?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  signatorySign: '',
+  signDate: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      Object.assign(form, val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = (key: keyof FormData, val: any) => {
+  ;(form as any)[key] = val
+  emit('update:modelValue', { ...form })
+}
+</script>
+
+<template>
+  <div class="service-charging-standards">
+    <div class="doc-title">机构服务范围及收费标准</div>
+    <div class="doc-body">
+      <p class="section-title">一、依据护理级别提供个人生活照料服务</p>
+      <p class="paragraph indent">
+        涵盖协助长者开展个人清洁卫生以及起居、穿衣、修饰、饮食、口腔清洁、入厕、皮肤清洁护理、压疮预防、便溺护理等事项。
+      </p>
+      <p class="paragraph indent">1. 个人日常清洁卫生包含洗脸、洗手、洗头(包含床上洗头)、洗脚、整理个人物品、清洁并平整床铺、更换床单等。</p>
+      <p class="paragraph indent">2. 穿衣涵盖协助穿衣、更换衣物以及整理衣物等方面。</p>
+      <p class="paragraph indent">3. 修饰行为涵盖梳头、修剪指(趾)甲等内容。</p>
+      <p class="paragraph indent">4. 饮食照料涵盖协助用膳、饮水或喂饭以及肠内营养(鼻饲法、滴注法)等方面。</p>
+      <p class="paragraph indent">5. 口腔清洁涵盖刷牙、漱口、口腔清洁以及假牙清洁等方面。</p>
+      <p class="paragraph indent">6. 入厕服务涵盖定时提醒入厕、提供便盆与尿壶使用,以及协助进行排便、排尿等入厕相关事宜。</p>
+      <p class="paragraph indent">7. 皮肤清洁护理涵盖清洗会阴、擦洗胸背部与腿部以及沐浴等操作。</p>
+      <p class="paragraph indent">8. 压疮预防应确保床单处于干燥、整洁状态,定时进行卧位更换与翻身操作,以减轻皮肤受压状况,并对皮肤及会阴部进行清洁。</p>
+      <p class="paragraph indent">9. 便溺护理涵盖协助存在大小便失禁或便尿状况的老人进行排便、排尿操作,实施人工排便,以及对尿布进行清洗与更换等工作。</p>
+
+      <p class="section-title">二、膳食服务:</p>
+      <p class="paragraph indent">由专业营养师提供每周的食谱,并根据老年人生理需求提供多种膳食服务和送餐服务。</p>
+
+      <p class="section-title">三、环境卫生服务:</p>
+      <p class="paragraph indent">包括长者居室及室外环境的清洁卫生。</p>
+
+      <p class="section-title">四、休闲娱乐服务</p>
+      <p class="paragraph indent">提供长者活动室、文化阅览室,并定期或不定期组织相关活动。</p>
+
+      <p class="section-title">五、洗衣服务:</p>
+      <p class="paragraph indent">每日收集脏污衣物;对衣物进行分类清洗;针对被污染的衣物,采取单独洗涤的方式,并先进行消毒处理后再清洗;将清洗后的衣物整理折叠后送还给长者。</p>
+
+      <p class="section-title">六、安全保护服务</p>
+      <p class="paragraph indent">1. 涵盖提供床档、安全标识、安全扶手、紧急呼救系统等物品。</p>
+      <p class="paragraph indent">2. 在长者或其家属知悉并同意的情况下使用约束物品。</p>
+      <p class="paragraph indent">3. 实施安全预防措施:包括评估长者存在的不安全因素,制定针对常见意外的预防措施以及应急预案,并定期检查安全程序的执行情况。</p>
+
+      <p class="section-title">七、通讯服务</p>
+      <p class="paragraph indent">1、通讯服务包括公用电话、公用计算机网络、代办电报、书信代写与投寄等相关服务</p>
+
+      <p class="section-title">八、心理(精神)支持服务</p>
+      <p class="paragraph indent">涵盖访视、访谈、个案服务、小组活动、危机处理、咨询活动等内容。</p>
+
+      <p class="section-title">九、老年护理服务</p>
+      <p class="paragraph indent">依据需求提供基础护理、老年专科疾病护理、老年心理护理、老年康复指导、老年期健康教育、健康咨询、护理技术操作、院内感染控制、临终护理等服务。</p>
+      <p class="paragraph indent">1. 基础护理包含老年人的清洁护理、饮食护理、排泄护理以及基础护理技术。</p>
+      <p class="paragraph indent">2. 老年专科护理包括老年专科疾病护理及相关技术操作。</p>
+      <p class="paragraph indent">3. 老年心理护理涵盖老年心理卫生教育、老年人心理问题评估以及实施老年心理护理干预举措。</p>
+      <p class="paragraph indent">4. 老年康复指导包括传授老年期自我护理技术、老年病并发症康复预防以及指导康复治疗技术的运用。</p>
+      <p class="paragraph indent">5. 老年期健康教育包括传播老年期健康知识、纠正不良健康行为。</p>
+      <p class="paragraph indent">6. 健康咨询包括老年病的预防、康复、老年期的营养、精神卫生和社会活动等方面的咨询服务。</p>
+      <p class="paragraph indent">7. 院内感染控制包括采取预防性措施、监测并控制传染病的爆发流行。</p>
+      <p class="paragraph indent">8. 护理技术操作包括基础护理技术操作、老年专科护理技术操作、急救技术操作。</p>
+      <p class="paragraph indent">9. 临终护理包括缓解终期老年人的疼痛与困难,提升终期老年人的生活质量,做好终期老年人的心理护理、死亡教育以及家属的心理(精神)支持,消除老年人的临终恐惧。</p>
+
+      <p class="section-title">十、医疗保健服务:</p>
+      <p class="paragraph indent">为入住老人提供“绿色通道”定点医疗机构。当长者遭遇急症或意外情况时,工作人员将代为联系120急救中心并送往医院。同时,为入住老人开展健康管理、健康咨询、康复指导以及预防保健等工作,为老年人建立健康档案,旨在维持或改善老年人的身心状态,减轻病痛,做好老年人常见疾病、多发疾病以及慢性非传染性疾病的预防工作。</p>
+
+      <p class="section-title">十一、咨询服务</p>
+      <p class="paragraph indent">咨询服务包括开展心理、医疗、护理、康复、教育、服务信息等方面的咨询。</p>
+
+      <p class="section-title">十二、委托代办服务</p>
+      <p class="paragraph indent">包括为老年人代购物品、陪同购物、帮助老年人书信、文书书写或领取物品、交纳费用等。陪同就医服务。</p>
+
+      <p class="section-title">十三、教育服务</p>
+      <p class="paragraph indent">提供教育服务的范围包括开展各类知识讲座、健康知识、时事教育、绘画技巧、音乐常识、照相技术、运动知识、电脑知识等。</p>
+
+      <p class="appendix-title">附:《养老机构收费标准》</p>
+    </div>
+
+    <div class="sign-section">
+      <div class="sign-row">
+        <span class="sign-label">长者/监护人/委托代理人签阅(签字按手印):</span>
+        <SignatureItem
+          :model-value="form.signatorySign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="签阅人签字"
+          @update:model-value="(v: any) => updateForm('signatorySign', v)"
+        />
+      </div>
+      <div class="sign-row date-row">
+        <span class="sign-label">日 期:</span>
+        <FieldItem
+          :model-value="form.signDate"
+          :is-text-mode="isTextMode"
+          type="date"
+          width="160px"
+          placeholder="请选择日期"
+          @update:model-value="(v: any) => updateForm('signDate', v)"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.service-charging-standards {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 30px;
+}
+
+.doc-body {
+  line-height: 2;
+  margin-bottom: 60px;
+}
+
+.section-title {
+  font-weight: bold;
+  margin: 16px 0 8px;
+  text-indent: 0;
+}
+
+.paragraph {
+  text-indent: 2em;
+  margin: 0 0 4px;
+}
+
+.appendix-title {
+  font-weight: bold;
+  margin: 30px 0 8px;
+}
+
+.sign-section {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.sign-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.sign-label {
+  white-space: nowrap;
+  margin-right: 8px;
+}
+
+.date-row {
+  margin-bottom: 20px;
+}
+</style>

+ 209 - 0
src/views/elderly/contracts/components/SignatureProxyApplicationBody.vue

@@ -0,0 +1,209 @@
+<script setup lang="ts">
+import { reactive, watch } from 'vue'
+import FieldItem from './FieldItem.vue'
+import SignatureItem from './SignatureItem.vue'
+
+interface FormData {
+  elderName?: string
+  idCardNo?: string
+  agentName?: string
+  agentIdCardNo?: string
+  applicantSign?: string
+  agentSign?: string
+  signDate?: string
+  handlerSign?: string
+}
+
+const props = withDefaults(defineProps<{
+  isTextMode?: boolean
+  modelValue?: FormData
+}>(), {
+  isTextMode: false,
+  modelValue: () => ({})
+})
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', val: FormData): void
+}>()
+
+const form = reactive<FormData>({
+  elderName: '',
+  idCardNo: '',
+  agentName: '',
+  agentIdCardNo: '',
+  applicantSign: '',
+  agentSign: '',
+  signDate: '',
+  handlerSign: ''
+})
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    if (val) {
+      Object.assign(form, val)
+    }
+  },
+  { immediate: true, deep: true }
+)
+
+const updateForm = (key: keyof FormData, val: any) => {
+  ;(form as any)[key] = val
+  emit('update:modelValue', { ...form })
+}
+</script>
+
+<template>
+  <div class="signature-proxy-application">
+    <div class="doc-title">签名代理申请</div>
+    <div class="doc-body">
+      <p class="paragraph indent">
+        现入住长者
+        <FieldItem
+          :model-value="form.elderName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('elderName', v)"
+        />
+        (身份证号:
+        <FieldItem
+          :model-value="form.idCardNo"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('idCardNo', v)"
+        />
+        )与我司办理托养服务、入住相关事宜,由于长者因自体原因不能亲自签字,长者特此申请委托代理人
+        <FieldItem
+          :model-value="form.agentName"
+          :is-text-mode="isTextMode"
+          width="120px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('agentName', v)"
+        />
+        身份证号:
+        <FieldItem
+          :model-value="form.agentIdCardNo"
+          :is-text-mode="isTextMode"
+          width="200px"
+          placeholder=""
+          @update:model-value="(v: any) => updateForm('agentIdCardNo', v)"
+        />
+        代为长者签名,由此而导致的一切行为均代表入住长者的意愿且长者已知悉(正常入住视为已知悉)。
+      </p>
+    </div>
+    <div class="sign-section" style="align-items: flex-end;">
+    <div class="sign-section">
+      <div class="sign-row">
+        <span class="sign-label">申请人:</span>
+        <SignatureItem
+          :model-value="form.applicantSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="申请人签字"
+          @update:model-value="(v: any) => updateForm('applicantSign', v)"
+        />
+      </div>
+      <div class="sign-row">
+        <span class="sign-label">委托代理人:</span>
+        <SignatureItem
+          :model-value="form.agentSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="委托代理人签字"
+          @update:model-value="(v: any) => updateForm('agentSign', v)"
+        />
+      </div>
+      <div class="sign-row date-row">
+        <span class="sign-label">日 期:</span>
+        <FieldItem
+          :model-value="form.signDate"
+          :is-text-mode="isTextMode"
+          type="date"
+          width="160px"
+          placeholder="请选择日期"
+          @update:model-value="(v: any) => updateForm('signDate', v)"
+        />
+      </div>
+      <div class="sign-row">
+        <span class="sign-label">经办人:</span>
+        <SignatureItem
+          :model-value="form.handlerSign"
+          :is-text-mode="isTextMode"
+          width="200"
+          height="80"
+          placeholder="(签字处)"
+          title="经办人签字"
+          @update:model-value="(v: any) => updateForm('handlerSign', v)"
+        />
+      </div>
+    </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.signature-proxy-application {
+  width: 210mm;
+  max-width: 100%;
+  margin: 0 auto;
+  font-family: 'SimSun', 'Microsoft YaHei', serif;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  padding: 5mm;
+
+  @media print {
+    width: 210mm !important;
+    max-width: 100%;
+    padding: 0;
+    border: none;
+    margin: 0;
+    box-sizing: border-box;
+  }
+}
+
+.doc-title {
+  text-align: center;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 30px;
+}
+
+.doc-body {
+  line-height: 2.2;
+  margin-bottom: 60px;
+}
+
+.paragraph {
+  text-indent: 2em;
+  margin: 0;
+}
+
+.sign-section {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+
+}
+
+.sign-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+}
+
+.sign-label {
+  white-space: nowrap;
+  margin-right: 8px;
+}
+
+.date-row {
+  margin-bottom: 20px;
+}
+</style>

+ 792 - 0
src/views/elderly/contracts/comprehensive-assessment-capabilities/AddForm.vue

@@ -0,0 +1,792 @@
+<template>
+  <el-drawer
+    v-model="dialogVisible"
+    :title="title"
+    resizable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :destroy-on-close="true"
+    size="70%"
+    :before-close="handleClosed"
+  >
+
+    <div class="attack-risk-form">
+      <h1 class="form-title">老年人能力综合评估结果(附件)</h1>
+      <!-- 基本信息 -->
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>长者姓名</text>
+          <search-the-elderly ref="selectElderRef" :disabled="isDetail" @update_elder="elderUp" v-model="dataForm.elderName" :tId="dataForm.tenantId"/>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>档案号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.contractNumber" disabled />
+          <el-text v-else disabled="">{{dataForm.contractNumber}}</el-text>
+        </el-col>
+      </el-row>
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>入院日期</text>
+          <el-input v-if="!isDetail" :model-value="dayjs(dataForm.checkInTime).format('YYYY-MM-DD')=='Invalid Date'?'':dayjs(dataForm.checkInTime).format('YYYY-MM-DD')" disabled />
+          <el-text v-else disabled="">{{dayjs(dataForm.checkInTime).format('YYYY-MM-DD')}}</el-text>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>床位号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+          <el-text v-else disabled="">{{dataForm.bedName}}</el-text>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录人</text>
+          <el-input :disabled="isDetail" v-model="form.assessor"  />
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录日期</text>
+          <el-date-picker :disabled="isDetail" v-model="form.assessDate" type="date" style="width: 100%;"/>
+
+        </el-col>
+      </el-row>
+
+      <!-- 内容区域 -->
+      <div class="form-body">
+        <DocumentAttachmentBody
+          v-model="attachmentForm"
+          :is-text-mode="isDetail"
+          title="老年人能力综合评估结果"
+        />
+      </div>
+
+    </div>
+
+
+
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+      <el-button style="margin-left: 22px;margin-right: 30px" v-loading="formLoading" type="primary" v-show="!isDetail" @click="submitForm">确定</el-button>
+    </template>
+
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch, reactive } from 'vue'
+
+import dayjs from 'dayjs'
+import DocumentAttachmentBody from '../components/DocumentAttachmentBody.vue'
+
+import { attackRiskCreate, attackRiskGetById, attackRiskUpdate, attackRiskGetByElderId } from "@/api/social-work";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const title = ref('')
+const dialogVisible = ref(false) // 弹窗
+const formRef = ref() // 表单 Ref
+const selectElderRef = ref() // 表单 Ref
+const attachmentForm = reactive({
+  images: []
+})
+const isDetail = ref(false) // 是否详情打开
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+let dataForm = ref({
+  // 表单字段
+  id: undefined,
+  idCard: '',
+  contractNumber: '', //档案号
+  elderName: '',//长者姓名
+  bedName: '', //床位号
+  elderAge: '', //年龄
+  elderSex: '', //性别
+  checkInTime: '', //入院日期
+  elderId: '',
+  tenantId: undefined
+})
+
+
+
+
+const elderUp = (e) => {
+  dataForm.value.elderName = e.elderName
+  dataForm.value.elderId = e.id
+  dataForm.value.elderSex = e.elderSex === 1 ? '男' : '女'
+  dataForm.value.bedName = e.bedName || ''
+  dataForm.value.checkInTime = e.checkInTime
+  dataForm.value.contractNumber = e.contractNumber||e.fileNumber
+  dataForm.value.elderAge = e.elderAge
+}
+
+// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+
+
+
+/** 风险程度样式类 */
+const riskLevelClass = computed(() => {
+  const level = form.attackLevel
+  if (!level || level === 0) return 'risk-none'
+  if (level === 1) return 'risk-low'
+  if (level === 2) return 'risk-medium'
+  return 'risk-high'
+})
+
+/** 自动判断风险等级 */
+const autoJudgeRiskLevel = () => {
+  const level = form.attackLevel
+  if (!level || level === 0) form.riskLevel = 'none'
+  else if (level === 1) form.riskLevel = 'low'
+  else if (level === 2) form.riskLevel = 'medium'
+  else form.riskLevel = 'high'
+}
+
+ 
+
+
+
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    // 基本信息
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+
+    // 攻击风险等级(1=I级,2=II级,3=III级,4=IV级)
+    attackLevel: form.attackLevel || 0,
+
+    // 各级别选择的具体项
+    attackLevel1Items: form.attackLevel1Items || [],
+    attackLevel2Items: form.attackLevel2Items || [],
+    attackLevel3Items: form.attackLevel3Items || [],
+    attackLevel4Items: form.attackLevel4Items || [],
+
+    // 风险程度
+    riskLevel: form.riskLevel || '',
+
+    // 预防措施
+    preventiveMeasures: form.preventiveMeasures || [],
+    preventiveMeasuresOther: form.preventiveMeasuresOther || '',
+
+    // 签名
+    familySignature: form.familySignature || '',
+    familySignDate: form.familySignDate ? dayjs(form.familySignDate).format('YYYY-MM-DD') : '',
+
+    // 风险知情书内容
+    riskDisclosure: {
+      companyName: riskDisclosureForm.companyName || '',
+      partyBSign: riskDisclosureForm.partyBSign || '',
+      partyBDate: riskDisclosureForm.partyBDate || '',
+      partyCSign: riskDisclosureForm.partyCSign || '',
+      partyCDate: riskDisclosureForm.partyCDate || ''
+    },
+
+    // 附件图片内容
+    attachment: { ...attachmentForm }
+  }
+}
+
+/** 将 JSON 对象反序列化为表单数据 */
+const deserializeFormData = (formData: Record<string, any>) => {
+  if (!formData) return
+
+  // 基本信息
+  form.assessor = formData.assessor || ''
+  form.assessDate = formData.assessDate ? dayjs(formData.assessDate).toDate() : ''
+
+  // 攻击风险等级
+  form.attackLevel = formData.attackLevel || 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = formData.attackLevel1Items || []
+  form.attackLevel2Items = formData.attackLevel2Items || []
+  form.attackLevel3Items = formData.attackLevel3Items || []
+  form.attackLevel4Items = formData.attackLevel4Items || []
+
+  // 风险程度
+  form.riskLevel = formData.riskLevel || ''
+
+  // 预防措施
+  form.preventiveMeasures = formData.preventiveMeasures || []
+  form.preventiveMeasuresOther = formData.preventiveMeasuresOther || ''
+
+  // 签名
+  form.familySignature = formData.familySignature || ''
+  form.familySignDate = formData.familySignDate ? dayjs(formData.familySignDate).toDate() : ''
+
+  // 风险知情书内容
+  if (formData.riskDisclosure) {
+    riskDisclosureForm.companyName = formData.riskDisclosure.companyName || ''
+    riskDisclosureForm.partyBSign = formData.riskDisclosure.partyBSign || ''
+    riskDisclosureForm.partyBDate = formData.riskDisclosure.partyBDate || ''
+    riskDisclosureForm.partyCSign = formData.riskDisclosure.partyCSign || ''
+    riskDisclosureForm.partyCDate = formData.riskDisclosure.partyCDate || ''
+  }
+
+  // 附件图片内容
+  if (formData.attachment) {
+    attachmentForm.images = formData.attachment.images || []
+  }
+}
+
+/** 重置攻击风险因素评估表表单数据 */
+const resetAttackRiskForm = () => {
+  form.assessor = ''
+  form.assessDate = ''
+
+  // 攻击风险等级
+  form.attackLevel = 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = []
+  form.attackLevel2Items = []
+  form.attackLevel3Items = []
+  form.attackLevel4Items = []
+
+  // 风险程度
+  form.riskLevel = ''
+
+  // 预防措施
+  form.preventiveMeasures = []
+  form.preventiveMeasuresOther = ''
+
+  // 签名
+  form.familySignature = ''
+  form.familySignDate = ''
+
+  // 风险知情书
+  riskDisclosureForm.companyName = '颐年医养'
+  riskDisclosureForm.partyBSign = ''
+  riskDisclosureForm.partyBDate = ''
+  riskDisclosureForm.partyCSign = ''
+  riskDisclosureForm.partyCDate = ''
+
+  // 附件图片
+  attachmentForm.images = []
+}
+
+/** 打开弹窗 */
+const open = async (tenantId, id?: any, detail: boolean = false) => {
+  resetForm()
+  dialogVisible.value = true
+  dataForm.value.id = id || undefined
+  dataForm.value.tenantId = tenantId
+  isDetail.value = detail
+  if (id) {
+    title.value = "编辑-攻击风险因素评估"
+    // 加载评估数据
+    await loadAttackRiskData(id)
+  } else {
+    title.value = "新增-攻击风险因素评估"
+    
+  }
+}
+
+/** 加载评估数据 */
+const loadAttackRiskData = async (id: number) => {
+  try {
+    const res = await attackRiskGetById(id)
+    if (res) {
+      // 填充长者基本信息
+      dataForm.value.elderName = res.elderName || ''
+      dataForm.value.elderId = res.elderId || ''
+      dataForm.value.elderSex = res.elderSex || ''
+      dataForm.value.bedName = res.bedName || ''
+      dataForm.value.checkInTime = res.checkInTime || ''
+      dataForm.value.contractNumber = res.fileNumber || ''
+      dataForm.value.elderAge = res.elderAge || ''
+      await selectElderRef.value.upData(res.elderName, res.elderId)
+      // 解析 assessData
+      if (res.assessData) {
+        const formData = JSON.parse(res.assessData)
+        deserializeFormData(formData)
+      }
+    }
+  } catch (error) {
+    message.error('加载评估数据失败')
+  }
+}
+
+/** 根据长者ID加载评估数据 */
+const loadAttackRiskByElderId = async (elderId: number) => {
+  try {
+    const res = await attackRiskGetByElderId(elderId)
+    if (res && res.assessData) {
+      const formData = JSON.parse(res.assessData)
+      deserializeFormData(formData)
+    }
+  } catch (error) {
+    // 无历史数据,不处理
+  }
+}
+
+const form = reactive({
+  // 基本信息
+  assessor: '',
+  assessDate: '',
+
+  // 攻击风险等级(0=无,1=I级,2=II级,3=III级,4=IV级)
+  attackLevel: 0,
+
+  // 各级别选择的具体项
+  attackLevel1Items: [], // I级选择的项(1-7)
+  attackLevel2Items: [], // II级选择的项(1-3)
+  attackLevel3Items: [], // III级选择的项(1-4)
+  attackLevel4Items: [], // IV级选择的项(1-2)
+
+  // 风险程度
+  riskLevel: '',
+
+  // 预防措施
+  preventiveMeasures: [],
+  preventiveMeasuresOther: '',
+
+  // 签名
+  familySignature: '',
+  familySignDate: ''
+})
+
+// 监听攻击等级变化,自动判断风险等级
+watch(() => form.attackLevel, () => {
+  autoJudgeRiskLevel()
+})
+
+// 风险知情书表单数据
+const riskDisclosureForm = reactive({
+  elderName: '',
+  companyName: '颐年医养',
+  assessor: '',
+  assessDate: '',
+  partyBSign: '',
+  partyBDate: '',
+  partyCSign: '',
+  partyCDate: ''
+})
+
+// 长者信息变化时同步到风险告知书
+watch(
+  () => dataForm.value.elderName,
+  (name) => {
+    riskDisclosureForm.elderName = name
+  }
+)
+
+// 记录人变化时同步
+watch(
+  () => form.assessor,
+  (val) => {
+    riskDisclosureForm.assessor = val
+  }
+)
+
+watch(
+  () => form.assessDate,
+  (val) => {
+    riskDisclosureForm.assessDate = val ? dayjs(val).format('YYYY-MM-DD') : ''
+  }
+)
+
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  if (formLoading.value) {
+    return
+  }
+  formLoading.value = true
+  // 提交请求
+  try {
+    const assessData = serializeFormData()
+    const tempParams = {
+      ...dataForm.value,
+      assessData: JSON.stringify(assessData),
+      attackLevel: form.attackLevel,
+      riskLevel: form.riskLevel,
+      assessor: form.assessor,
+      assessScore: form.attackLevel,
+      assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
+    }
+
+    if (dataForm.value.id) {
+      const res = await attackRiskUpdate(tempParams)
+      if (res) {
+        message.success(t('common.updateSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    } else {
+      const res = await attackRiskCreate(tempParams)
+      if (res) {
+        message.success(t('common.createSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    }
+  } finally {
+    setTimeout(() => {
+      formLoading.value = false
+    }, 500)
+  }
+}
+
+
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {
+    id: undefined,
+    idCard: '',
+    contractNumber: '', //档案号
+    elderName: '',//长者姓名
+    bedName: '', //床位号
+    elderAge: '', //年龄
+    elderSex: '', //性别
+    checkInTime: '', //入院日期
+    elderId: '',
+    tenantId: undefined
+  }
+  formRef.value?.resetFields()
+
+  // 重置攻击风险因素评估表表单
+  resetAttackRiskForm()
+}
+
+// 关闭表单
+const handleClosed = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+
+</script>
+
+<style scoped lang="scss">
+
+.form-title {
+  text-align: center;
+  font-size: 20px;
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.attack-risk-form {
+  max-width: 1200px;
+  margin: 0 auto;
+  background: #fff;
+
+
+
+
+
+  .info-row {
+    display: flex;
+    gap: 40px;
+    width: 100%;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 10px;
+
+    .info-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .label {
+        width: 82px;
+        text-align: right;
+        margin-right: 4px;
+        white-space: nowrap;
+      }
+    }
+  }
+
+
+  .form-body {
+    border: 1px solid #333;
+    padding: 15px;
+  }
+
+  // 调整 Element Plus 组件样式
+  :deep(.el-input__inner) {
+    height: 28px;
+    line-height: 28px;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+    border-radius: 0;
+    padding: 0 4px;
+
+    &:focus {
+      border-color: #409eff;
+    }
+  }
+
+  :deep(.el-checkbox__label),
+  :deep(.el-radio__label) {
+    padding-left: 4px;
+  }
+}
+.row{
+  margin-bottom: 12px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  text{
+    text-align: right;
+    margin-right: 4px;
+    width: 82px;
+  }
+}
+
+// NGASR自杀风险评估量表 特有样式
+.total-score-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  margin-bottom: 20px;
+
+  .total-score {
+    display: flex;
+    align-items: baseline;
+    gap: 4px;
+
+    .score-label {
+      font-size: 16px;
+      font-weight: bold;
+    }
+
+    .score-value {
+      font-size: 28px;
+      font-weight: bold;
+      color: #409eff;
+    }
+
+    .score-max {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+
+  .risk-level {
+    font-size: 16px;
+    font-weight: bold;
+    padding: 8px 16px;
+    border-radius: 4px;
+
+    &.risk-none {
+      color: #67c23a;
+      background: #f0f9eb;
+    }
+
+    &.risk-low {
+      color: #e6a23c;
+      background: #fdf6ec;
+    }
+
+    &.risk-medium {
+      color: #f56c6c;
+      background: #fef0f0;
+    }
+
+    &.risk-high {
+      color: #f56c6c;
+      background: #fef0f0;
+      border: 1px solid #f56c6c;
+    }
+  }
+}
+
+.assessment-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 20px;
+
+  table {
+    width: 100%;
+    border: 1px solid #333;
+
+    th, td {
+      border: 1px solid #333;
+      padding: 12px;
+      text-align: left;
+      vertical-align: top;
+    }
+
+    th {
+      background: #f5f7fa;
+      font-weight: bold;
+      text-align: center;
+    }
+
+    .content-col {
+      width: 65%;
+    }
+
+    .level-col {
+      width: 10%;
+      text-align: center;
+    }
+
+    .select-col {
+      width: 25%;
+      text-align: center;
+    }
+
+    .content {
+      text-align: left;
+
+      .content-title {
+        font-weight: bold;
+        margin-bottom: 8px;
+      }
+
+      .content-item {
+        margin-left: 15px;
+        margin-bottom: 4px;
+        font-size: 13px;
+      }
+    }
+
+    .level {
+      text-align: center;
+      font-weight: bold;
+    }
+
+    .select {
+      text-align: center;
+
+      :deep(.el-radio) {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+.risk-judgment-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .risk-judgment-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .risk-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+
+    :deep(.el-radio) {
+      margin-right: 0;
+    }
+  }
+}
+
+.preventive-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .preventive-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .preventive-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+
+    :deep(.el-checkbox) {
+      margin-right: 20px;
+      margin-bottom: 8px;
+    }
+
+    .other-input {
+      width: 200px;
+      margin-left: 10px;
+    }
+  }
+}
+
+.signature-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .signature-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 20px;
+  }
+
+  .signature-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    &.date-item {
+      :deep(.el-date-picker) {
+        width: 150px;
+      }
+    }
+  }
+
+  .signature-label {
+    font-weight: bold;
+    white-space: nowrap;
+  }
+
+  .signature-input {
+    width: 150px;
+  }
+
+  .date-picker {
+    width: 150px;
+  }
+}
+
+.note-section {
+  margin-top: 20px;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .note-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .note-content {
+    font-size: 13px;
+    line-height: 1.8;
+    color: #606266;
+
+    p {
+      margin: 5px 0;
+    }
+
+    .indent {
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 312 - 0
src/views/elderly/contracts/comprehensive-assessment-capabilities/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <ContentWrap>
+    <!-- 保护性约束观察记录表 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="110px"
+    >
+      <el-form-item label="长者姓名">
+        <el-input
+          v-model="queryParams.elderName"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录人">
+        <el-input
+          v-model="queryParams.assessor"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录日期">
+        <el-date-picker
+          size="default"
+          ref="selectRef"
+          class="!w-240px"
+          v-model="queryParams.assessDate"
+          type="daterange"
+          :clearable="true"
+          :editable="false"
+          placeholder="选择记录日期"
+          value-format="YYYY-MM-DD"
+          format="YYYY-MM-DD"
+          date-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery" style="margin-left: 2vw"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="mb-10px">
+
+      <ButtonAdd @click="openForm(undefined)"  />
+      <!--      <el-button @click="inputH" type="success"><Icon icon="fa:cloud-upload" :size="16" class="mr-1"/>导入</el-button>-->
+      <!--      <ButtonImport @click="handleImportCard"  />-->
+
+
+    </div>
+    <el-table v-loading="loading" :data="list" :header-cell-style="tableHeaderColor">
+      <el-table-column header-align="center" align="center" label="序号" width="60">
+        <template #default="scope">
+          {{
+            scope.$index + (queryParams.pageNo * queryParams.pageSize - queryParams.pageSize) + 1
+          }}
+        </template>
+      </el-table-column>
+
+
+      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>
+      <el-table-column prop="assessor" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>
+      <el-table-column header-align="center" align="center" label="风险等级" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag v-if="getRiskLevelInfo(scope.row.assessData).text" :type="getRiskLevelInfo(scope.row.assessData).type">
+            {{ getRiskLevelInfo(scope.row.assessData).text }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="assessDate" header-align="center" align="center" label="记录日期" min-width="200" show-overflow-tooltip/>
+      <el-table-column prop="creator" header-align="center" align="center" label="记录人" min-width="200" show-overflow-tooltip/>
+
+
+      <el-table-column label="操作" align="center" width="200" >
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openFormEdit(scope.row, scope.row.id)"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="warning"
+            @click="openFormDetail(scope.row,scope.row.id)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="openClose(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <AddForm ref="formRef" @success="getList" />
+
+
+
+</template>
+
+<script setup lang="ts">
+import AddForm from "./AddForm.vue";
+import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
+import { useUserStore } from '@/store/modules/user'
+import {
+  attackRiskDelete, attackRiskPage,
+} from "@/api/social-work";
+import { getRiskLevelInfo } from "@/utils/risk-assessment";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const userStore = useUserStore()
+const loading = ref(true) // 列表的加载中
+const detailRef = ref()
+const importRef = ref()
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+
+// 状态管理
+const showBatchExport = ref(false)
+const exportConfig = ref({
+  title: '',
+  batchMin: 1,
+  batchMax: 999,
+  countMin: 1,
+  countMax: 200,
+  defaultBatch: 1,
+  defaultCount: 200,
+  description: [] as string[]
+})
+const exportLoading = ref(false)
+
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  assessor: '',
+  // assessDate: getCurrentMonthRange(),
+  assessDate: [],
+  tenantIds: userStore.orgTenantId
+})
+const queryFormRef = ref() // 搜索的表单
+
+
+// 打开导出弹窗
+const handleImportCard = async () => {
+
+  // showBatchExport.value = true
+}
+
+// 处理批量导出
+const handleBatchExport = async (batch: number, count: number) => {
+  exportLoading.value = true
+  console.log(batch,count)
+  // try {
+  //   let queryParams = {
+  //     pageNo: batch,
+  //     pageSize: count,
+  //     tenantId: userStore.orgTenantId[0]
+  //   }
+  //   const list = await careRecordsPage(queryParams)
+  //   if (list.length <= 0) {
+  //     message.error('暂无数据可以导出!')
+  //     return
+  //   }
+  //   const headers = [
+  //     { key: 'organizationName', title: '机构名称' },
+  //     { key: 'visitName', title: '姓名' },
+  //     { key: 'visitPhone', title: '手机号' },
+  //     { key: 'visitDate', title: '预约时间' },
+  //     { key: 'accompanyCount', title: '陪同人数' },
+  //     { key: 'reason', title: '理由' }
+  //   ]
+  //   exportWithExpandedObjectArrays(list, headers, `医疗护理记录${formatToDateTime()}.xlsx`, '医疗护理记录')
+  // } catch (_) {}
+
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    //let queryP = {...queryParams,discoveryTime:queryParams.discoveryTime?[queryParams.discoveryTime[0]+" 00:00:00",queryParams.discoveryTime[1]+" 23:59:59"]:null}
+    const data = await attackRiskPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = async () => {
+  if (!queryFormRef.value) return
+  const valid = await queryFormRef.value.validate()
+  if (!valid) return
+  queryParams.pageNo = 1
+  await getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.elderName = ''
+  queryParams.assessor = ''
+  queryParams.tenantIds = userStore.orgTenantId
+  // queryParams.assessDate= getCurrentMonthRange()
+  queryParams.assessDate = []
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1){
+    message.error('新增只能选择一个机构')
+    return
+  }
+  formRef.value.open(queryParams.tenantIds[0], id,false)
+}
+
+const editRef = ref()
+const openFormEdit = (row: any = {}, id?: number) => {
+  formRef.value.open(row.tenantId, id,false)
+}
+
+
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
+}
+
+
+
+
+
+const openClose = async (item) => {
+  try {
+    console.log("任务ID",item)
+    const res = await message.confirm('确定要删除吗?', '提示')
+    if (res == 'confirm') {
+      // 发起
+      try {
+        const res = await attackRiskDelete(item.id)
+        if (res){
+          message.success(t('common.updateSuccess'))
+        }
+      }catch(err) {}
+    }
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 取消按钮操作 */
+const cancelDelete = async (id: number) => {
+  try {
+    // 取消的二次确认
+    await message.cancelConfirm()
+    // 发起取消
+    // await elderlyBakDel(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const route = useRoute()
+/** 初始化 **/
+onMounted(() => {
+  if(route.query && route.query.elderName){
+    queryParams.elderName = route.query.elderName as string
+  }
+  getList()
+})
+
+// 表头格式
+const tableHeaderColor = ({ rowIndex }: any) => {
+  if (rowIndex === 0) {
+    return {
+      backgroundColor: '#f8f8f9',
+      color: '#666666',
+      fontWeight: 'bold'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 37 - 6
src/views/elderly/contracts/delegation-agent-confirmation-form/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">委托代理人确认表(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <DelegationAgentConfirmationBody
+          v-model="delegationAgentForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import DelegationAgentConfirmationBody from '../components/DelegationAgentConfirmationBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,23 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const delegationAgentForm = reactive({
+  elderName: '',
+  elderGender: '',
+  elderAge: '',
+  elderIdCardNo: '',
+  elderAddress: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentAddress: '',
+  declarerName: '',
+  representedName: '',
+  relationship: '',
+  elderSign: '',
+  agentSign: '',
+  signDate: ''
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,9 +128,24 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
 
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    delegationAgent: { ...delegationAgentForm }
+  }
+}
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗

+ 4 - 1
src/views/elderly/contracts/explanation-examination-report-items copy/AddForm.vue

@@ -114,7 +114,10 @@ const elderUp = (e) => {
 
 // ========== 攻击风险因素评估量表 表单序列化方法 ==========
 
-
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗

+ 25 - 6
src/views/elderly/contracts/explanation-examination-report-items/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">体检报告项目说明(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <ExplanationExaminationReportBody
+          v-model="examinationReportForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import ExplanationExaminationReportBody from '../components/ExplanationExaminationReportBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,9 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const examinationReportForm = reactive({
+  images: []
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,8 +114,25 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
 
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    examinationReport: { ...examinationReportForm }
+  }
+}
+
+
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 
 

+ 789 - 0
src/views/elderly/contracts/first-service-project-confirmation/AddForm.vue

@@ -0,0 +1,789 @@
+<template>
+  <el-drawer
+    v-model="dialogVisible"
+    :title="title"
+    resizable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :destroy-on-close="true"
+    size="70%"
+    :before-close="handleClosed"
+  >
+
+    <div class="attack-risk-form">
+      <h1 class="form-title">首次服务项目确认表(附件)</h1>
+      <!-- 基本信息 -->
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>长者姓名</text>
+          <search-the-elderly ref="selectElderRef" :disabled="isDetail" @update_elder="elderUp" v-model="dataForm.elderName" :tId="dataForm.tenantId"/>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>档案号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.contractNumber" disabled />
+          <el-text v-else disabled="">{{dataForm.contractNumber}}</el-text>
+        </el-col>
+      </el-row>
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>入院日期</text>
+          <el-input v-if="!isDetail" :model-value="dayjs(dataForm.checkInTime).format('YYYY-MM-DD')=='Invalid Date'?'':dayjs(dataForm.checkInTime).format('YYYY-MM-DD')" disabled />
+          <el-text v-else disabled="">{{dayjs(dataForm.checkInTime).format('YYYY-MM-DD')}}</el-text>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>床位号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+          <el-text v-else disabled="">{{dataForm.bedName}}</el-text>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录人</text>
+          <el-input :disabled="isDetail" v-model="form.assessor"  />
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录日期</text>
+          <el-date-picker :disabled="isDetail" v-model="form.assessDate" type="date" style="width: 100%;"/>
+
+        </el-col>
+      </el-row>
+
+      <!-- 内容区域 -->
+      <div class="form-body">
+        <FirstServiceProjectConfirmationBody
+          v-model="firstServiceForm"
+          :is-text-mode="isDetail"
+        />
+      </div>
+
+    </div>
+
+
+
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+      <el-button style="margin-left: 22px;margin-right: 30px" v-loading="formLoading" type="primary" v-show="!isDetail" @click="submitForm">确定</el-button>
+    </template>
+
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch, reactive } from 'vue'
+
+import dayjs from 'dayjs'
+import FirstServiceProjectConfirmationBody from '../components/FirstServiceProjectConfirmationBody.vue'
+
+import { attackRiskCreate, attackRiskGetById, attackRiskUpdate, attackRiskGetByElderId } from "@/api/social-work";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const title = ref('')
+const dialogVisible = ref(false) // 弹窗
+const formRef = ref() // 表单 Ref
+const selectElderRef = ref() // 表单 Ref
+const firstServiceForm = reactive<Record<string, any>>({})
+const isDetail = ref(false) // 是否详情打开
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+let dataForm = ref({
+  // 表单字段
+  id: undefined,
+  idCard: '',
+  contractNumber: '', //档案号
+  elderName: '',//长者姓名
+  bedName: '', //床位号
+  elderAge: '', //年龄
+  elderSex: '', //性别
+  checkInTime: '', //入院日期
+  elderId: '',
+  tenantId: undefined
+})
+
+
+
+
+const elderUp = (e) => {
+  dataForm.value.elderName = e.elderName
+  dataForm.value.elderId = e.id
+  dataForm.value.elderSex = e.elderSex === 1 ? '男' : '女'
+  dataForm.value.bedName = e.bedName || ''
+  dataForm.value.checkInTime = e.checkInTime
+  dataForm.value.contractNumber = e.contractNumber||e.fileNumber
+  dataForm.value.elderAge = e.elderAge
+}
+
+// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+
+
+
+/** 风险程度样式类 */
+const riskLevelClass = computed(() => {
+  const level = form.attackLevel
+  if (!level || level === 0) return 'risk-none'
+  if (level === 1) return 'risk-low'
+  if (level === 2) return 'risk-medium'
+  return 'risk-high'
+})
+
+/** 自动判断风险等级 */
+const autoJudgeRiskLevel = () => {
+  const level = form.attackLevel
+  if (!level || level === 0) form.riskLevel = 'none'
+  else if (level === 1) form.riskLevel = 'low'
+  else if (level === 2) form.riskLevel = 'medium'
+  else form.riskLevel = 'high'
+}
+
+ 
+
+
+
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    // 基本信息
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+
+    // 攻击风险等级(1=I级,2=II级,3=III级,4=IV级)
+    attackLevel: form.attackLevel || 0,
+
+    // 各级别选择的具体项
+    attackLevel1Items: form.attackLevel1Items || [],
+    attackLevel2Items: form.attackLevel2Items || [],
+    attackLevel3Items: form.attackLevel3Items || [],
+    attackLevel4Items: form.attackLevel4Items || [],
+
+    // 风险程度
+    riskLevel: form.riskLevel || '',
+
+    // 预防措施
+    preventiveMeasures: form.preventiveMeasures || [],
+    preventiveMeasuresOther: form.preventiveMeasuresOther || '',
+
+    // 签名
+    familySignature: form.familySignature || '',
+    familySignDate: form.familySignDate ? dayjs(form.familySignDate).format('YYYY-MM-DD') : '',
+
+    // 风险知情书内容
+    riskDisclosure: {
+      companyName: riskDisclosureForm.companyName || '',
+      partyBSign: riskDisclosureForm.partyBSign || '',
+      partyBDate: riskDisclosureForm.partyBDate || '',
+      partyCSign: riskDisclosureForm.partyCSign || '',
+      partyCDate: riskDisclosureForm.partyCDate || ''
+    },
+
+    // 首次服务项目确认表
+    firstService: JSON.parse(JSON.stringify(firstServiceForm))
+  }
+}
+
+/** 将 JSON 对象反序列化为表单数据 */
+const deserializeFormData = (formData: Record<string, any>) => {
+  if (!formData) return
+
+  // 基本信息
+  form.assessor = formData.assessor || ''
+  form.assessDate = formData.assessDate ? dayjs(formData.assessDate).toDate() : ''
+
+  // 攻击风险等级
+  form.attackLevel = formData.attackLevel || 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = formData.attackLevel1Items || []
+  form.attackLevel2Items = formData.attackLevel2Items || []
+  form.attackLevel3Items = formData.attackLevel3Items || []
+  form.attackLevel4Items = formData.attackLevel4Items || []
+
+  // 风险程度
+  form.riskLevel = formData.riskLevel || ''
+
+  // 预防措施
+  form.preventiveMeasures = formData.preventiveMeasures || []
+  form.preventiveMeasuresOther = formData.preventiveMeasuresOther || ''
+
+  // 签名
+  form.familySignature = formData.familySignature || ''
+  form.familySignDate = formData.familySignDate ? dayjs(formData.familySignDate).toDate() : ''
+
+  // 风险知情书内容
+  if (formData.riskDisclosure) {
+    riskDisclosureForm.companyName = formData.riskDisclosure.companyName || ''
+    riskDisclosureForm.partyBSign = formData.riskDisclosure.partyBSign || ''
+    riskDisclosureForm.partyBDate = formData.riskDisclosure.partyBDate || ''
+    riskDisclosureForm.partyCSign = formData.riskDisclosure.partyCSign || ''
+    riskDisclosureForm.partyCDate = formData.riskDisclosure.partyCDate || ''
+  }
+
+  // 首次服务项目确认表
+  if (formData.firstService) {
+    Object.assign(firstServiceForm, JSON.parse(JSON.stringify(formData.firstService)))
+  }
+}
+
+/** 重置攻击风险因素评估表表单数据 */
+const resetAttackRiskForm = () => {
+  form.assessor = ''
+  form.assessDate = ''
+
+  // 攻击风险等级
+  form.attackLevel = 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = []
+  form.attackLevel2Items = []
+  form.attackLevel3Items = []
+  form.attackLevel4Items = []
+
+  // 风险程度
+  form.riskLevel = ''
+
+  // 预防措施
+  form.preventiveMeasures = []
+  form.preventiveMeasuresOther = ''
+
+  // 签名
+  form.familySignature = ''
+  form.familySignDate = ''
+
+  // 风险知情书
+  riskDisclosureForm.companyName = '颐年医养'
+  riskDisclosureForm.partyBSign = ''
+  riskDisclosureForm.partyBDate = ''
+  riskDisclosureForm.partyCSign = ''
+  riskDisclosureForm.partyCDate = ''
+
+  // 首次服务项目确认表
+  Object.keys(firstServiceForm).forEach(key => delete (firstServiceForm as any)[key])
+}
+
+/** 打开弹窗 */
+const open = async (tenantId, id?: any, detail: boolean = false) => {
+  resetForm()
+  dialogVisible.value = true
+  dataForm.value.id = id || undefined
+  dataForm.value.tenantId = tenantId
+  isDetail.value = detail
+  if (id) {
+    title.value = "编辑-攻击风险因素评估"
+    // 加载评估数据
+    await loadAttackRiskData(id)
+  } else {
+    title.value = "新增-攻击风险因素评估"
+    
+  }
+}
+
+/** 加载评估数据 */
+const loadAttackRiskData = async (id: number) => {
+  try {
+    const res = await attackRiskGetById(id)
+    if (res) {
+      // 填充长者基本信息
+      dataForm.value.elderName = res.elderName || ''
+      dataForm.value.elderId = res.elderId || ''
+      dataForm.value.elderSex = res.elderSex || ''
+      dataForm.value.bedName = res.bedName || ''
+      dataForm.value.checkInTime = res.checkInTime || ''
+      dataForm.value.contractNumber = res.fileNumber || ''
+      dataForm.value.elderAge = res.elderAge || ''
+      await selectElderRef.value.upData(res.elderName, res.elderId)
+      // 解析 assessData
+      if (res.assessData) {
+        const formData = JSON.parse(res.assessData)
+        deserializeFormData(formData)
+      }
+    }
+  } catch (error) {
+    message.error('加载评估数据失败')
+  }
+}
+
+/** 根据长者ID加载评估数据 */
+const loadAttackRiskByElderId = async (elderId: number) => {
+  try {
+    const res = await attackRiskGetByElderId(elderId)
+    if (res && res.assessData) {
+      const formData = JSON.parse(res.assessData)
+      deserializeFormData(formData)
+    }
+  } catch (error) {
+    // 无历史数据,不处理
+  }
+}
+
+const form = reactive({
+  // 基本信息
+  assessor: '',
+  assessDate: '',
+
+  // 攻击风险等级(0=无,1=I级,2=II级,3=III级,4=IV级)
+  attackLevel: 0,
+
+  // 各级别选择的具体项
+  attackLevel1Items: [], // I级选择的项(1-7)
+  attackLevel2Items: [], // II级选择的项(1-3)
+  attackLevel3Items: [], // III级选择的项(1-4)
+  attackLevel4Items: [], // IV级选择的项(1-2)
+
+  // 风险程度
+  riskLevel: '',
+
+  // 预防措施
+  preventiveMeasures: [],
+  preventiveMeasuresOther: '',
+
+  // 签名
+  familySignature: '',
+  familySignDate: ''
+})
+
+// 监听攻击等级变化,自动判断风险等级
+watch(() => form.attackLevel, () => {
+  autoJudgeRiskLevel()
+})
+
+// 风险知情书表单数据
+const riskDisclosureForm = reactive({
+  elderName: '',
+  companyName: '颐年医养',
+  assessor: '',
+  assessDate: '',
+  partyBSign: '',
+  partyBDate: '',
+  partyCSign: '',
+  partyCDate: ''
+})
+
+// 长者信息变化时同步到风险告知书
+watch(
+  () => dataForm.value.elderName,
+  (name) => {
+    riskDisclosureForm.elderName = name
+  }
+)
+
+// 记录人变化时同步
+watch(
+  () => form.assessor,
+  (val) => {
+    riskDisclosureForm.assessor = val
+  }
+)
+
+watch(
+  () => form.assessDate,
+  (val) => {
+    riskDisclosureForm.assessDate = val ? dayjs(val).format('YYYY-MM-DD') : ''
+  }
+)
+
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  if (formLoading.value) {
+    return
+  }
+  formLoading.value = true
+  // 提交请求
+  try {
+    const assessData = serializeFormData()
+    const tempParams = {
+      ...dataForm.value,
+      assessData: JSON.stringify(assessData),
+      attackLevel: form.attackLevel,
+      riskLevel: form.riskLevel,
+      assessor: form.assessor,
+      assessScore: form.attackLevel,
+      assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
+    }
+
+    if (dataForm.value.id) {
+      const res = await attackRiskUpdate(tempParams)
+      if (res) {
+        message.success(t('common.updateSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    } else {
+      const res = await attackRiskCreate(tempParams)
+      if (res) {
+        message.success(t('common.createSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    }
+  } finally {
+    setTimeout(() => {
+      formLoading.value = false
+    }, 500)
+  }
+}
+
+
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {
+    id: undefined,
+    idCard: '',
+    contractNumber: '', //档案号
+    elderName: '',//长者姓名
+    bedName: '', //床位号
+    elderAge: '', //年龄
+    elderSex: '', //性别
+    checkInTime: '', //入院日期
+    elderId: '',
+    tenantId: undefined
+  }
+  formRef.value?.resetFields()
+
+  // 重置攻击风险因素评估表表单
+  resetAttackRiskForm()
+}
+
+// 关闭表单
+const handleClosed = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+
+</script>
+
+<style scoped lang="scss">
+
+.form-title {
+  text-align: center;
+  font-size: 20px;
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.attack-risk-form {
+  max-width: 1200px;
+  margin: 0 auto;
+  background: #fff;
+
+
+
+
+
+  .info-row {
+    display: flex;
+    gap: 40px;
+    width: 100%;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 10px;
+
+    .info-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .label {
+        width: 82px;
+        text-align: right;
+        margin-right: 4px;
+        white-space: nowrap;
+      }
+    }
+  }
+
+
+  .form-body {
+    border: 1px solid #333;
+    padding: 15px;
+  }
+
+  // 调整 Element Plus 组件样式
+  :deep(.el-input__inner) {
+    height: 28px;
+    line-height: 28px;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+    border-radius: 0;
+    padding: 0 4px;
+
+    &:focus {
+      border-color: #409eff;
+    }
+  }
+
+  :deep(.el-checkbox__label),
+  :deep(.el-radio__label) {
+    padding-left: 4px;
+  }
+}
+.row{
+  margin-bottom: 12px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  text{
+    text-align: right;
+    margin-right: 4px;
+    width: 82px;
+  }
+}
+
+// NGASR自杀风险评估量表 特有样式
+.total-score-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  margin-bottom: 20px;
+
+  .total-score {
+    display: flex;
+    align-items: baseline;
+    gap: 4px;
+
+    .score-label {
+      font-size: 16px;
+      font-weight: bold;
+    }
+
+    .score-value {
+      font-size: 28px;
+      font-weight: bold;
+      color: #409eff;
+    }
+
+    .score-max {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+
+  .risk-level {
+    font-size: 16px;
+    font-weight: bold;
+    padding: 8px 16px;
+    border-radius: 4px;
+
+    &.risk-none {
+      color: #67c23a;
+      background: #f0f9eb;
+    }
+
+    &.risk-low {
+      color: #e6a23c;
+      background: #fdf6ec;
+    }
+
+    &.risk-medium {
+      color: #f56c6c;
+      background: #fef0f0;
+    }
+
+    &.risk-high {
+      color: #f56c6c;
+      background: #fef0f0;
+      border: 1px solid #f56c6c;
+    }
+  }
+}
+
+.assessment-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 20px;
+
+  table {
+    width: 100%;
+    border: 1px solid #333;
+
+    th, td {
+      border: 1px solid #333;
+      padding: 12px;
+      text-align: left;
+      vertical-align: top;
+    }
+
+    th {
+      background: #f5f7fa;
+      font-weight: bold;
+      text-align: center;
+    }
+
+    .content-col {
+      width: 65%;
+    }
+
+    .level-col {
+      width: 10%;
+      text-align: center;
+    }
+
+    .select-col {
+      width: 25%;
+      text-align: center;
+    }
+
+    .content {
+      text-align: left;
+
+      .content-title {
+        font-weight: bold;
+        margin-bottom: 8px;
+      }
+
+      .content-item {
+        margin-left: 15px;
+        margin-bottom: 4px;
+        font-size: 13px;
+      }
+    }
+
+    .level {
+      text-align: center;
+      font-weight: bold;
+    }
+
+    .select {
+      text-align: center;
+
+      :deep(.el-radio) {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+.risk-judgment-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .risk-judgment-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .risk-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+
+    :deep(.el-radio) {
+      margin-right: 0;
+    }
+  }
+}
+
+.preventive-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .preventive-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .preventive-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+
+    :deep(.el-checkbox) {
+      margin-right: 20px;
+      margin-bottom: 8px;
+    }
+
+    .other-input {
+      width: 200px;
+      margin-left: 10px;
+    }
+  }
+}
+
+.signature-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .signature-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 20px;
+  }
+
+  .signature-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    &.date-item {
+      :deep(.el-date-picker) {
+        width: 150px;
+      }
+    }
+  }
+
+  .signature-label {
+    font-weight: bold;
+    white-space: nowrap;
+  }
+
+  .signature-input {
+    width: 150px;
+  }
+
+  .date-picker {
+    width: 150px;
+  }
+}
+
+.note-section {
+  margin-top: 20px;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .note-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .note-content {
+    font-size: 13px;
+    line-height: 1.8;
+    color: #606266;
+
+    p {
+      margin: 5px 0;
+    }
+
+    .indent {
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 312 - 0
src/views/elderly/contracts/first-service-project-confirmation/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <ContentWrap>
+    <!-- 保护性约束观察记录表 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="110px"
+    >
+      <el-form-item label="长者姓名">
+        <el-input
+          v-model="queryParams.elderName"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录人">
+        <el-input
+          v-model="queryParams.assessor"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录日期">
+        <el-date-picker
+          size="default"
+          ref="selectRef"
+          class="!w-240px"
+          v-model="queryParams.assessDate"
+          type="daterange"
+          :clearable="true"
+          :editable="false"
+          placeholder="选择记录日期"
+          value-format="YYYY-MM-DD"
+          format="YYYY-MM-DD"
+          date-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery" style="margin-left: 2vw"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="mb-10px">
+
+      <ButtonAdd @click="openForm(undefined)"  />
+      <!--      <el-button @click="inputH" type="success"><Icon icon="fa:cloud-upload" :size="16" class="mr-1"/>导入</el-button>-->
+      <!--      <ButtonImport @click="handleImportCard"  />-->
+
+
+    </div>
+    <el-table v-loading="loading" :data="list" :header-cell-style="tableHeaderColor">
+      <el-table-column header-align="center" align="center" label="序号" width="60">
+        <template #default="scope">
+          {{
+            scope.$index + (queryParams.pageNo * queryParams.pageSize - queryParams.pageSize) + 1
+          }}
+        </template>
+      </el-table-column>
+
+
+      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>
+      <el-table-column prop="assessor" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>
+      <el-table-column header-align="center" align="center" label="风险等级" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag v-if="getRiskLevelInfo(scope.row.assessData).text" :type="getRiskLevelInfo(scope.row.assessData).type">
+            {{ getRiskLevelInfo(scope.row.assessData).text }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="assessDate" header-align="center" align="center" label="记录日期" min-width="200" show-overflow-tooltip/>
+      <el-table-column prop="creator" header-align="center" align="center" label="记录人" min-width="200" show-overflow-tooltip/>
+
+
+      <el-table-column label="操作" align="center" width="200" >
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openFormEdit(scope.row, scope.row.id)"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="warning"
+            @click="openFormDetail(scope.row,scope.row.id)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="openClose(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <AddForm ref="formRef" @success="getList" />
+
+
+
+</template>
+
+<script setup lang="ts">
+import AddForm from "./AddForm.vue";
+import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
+import { useUserStore } from '@/store/modules/user'
+import {
+  attackRiskDelete, attackRiskPage,
+} from "@/api/social-work";
+import { getRiskLevelInfo } from "@/utils/risk-assessment";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const userStore = useUserStore()
+const loading = ref(true) // 列表的加载中
+const detailRef = ref()
+const importRef = ref()
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+
+// 状态管理
+const showBatchExport = ref(false)
+const exportConfig = ref({
+  title: '',
+  batchMin: 1,
+  batchMax: 999,
+  countMin: 1,
+  countMax: 200,
+  defaultBatch: 1,
+  defaultCount: 200,
+  description: [] as string[]
+})
+const exportLoading = ref(false)
+
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  assessor: '',
+  // assessDate: getCurrentMonthRange(),
+  assessDate: [],
+  tenantIds: userStore.orgTenantId
+})
+const queryFormRef = ref() // 搜索的表单
+
+
+// 打开导出弹窗
+const handleImportCard = async () => {
+
+  // showBatchExport.value = true
+}
+
+// 处理批量导出
+const handleBatchExport = async (batch: number, count: number) => {
+  exportLoading.value = true
+  console.log(batch,count)
+  // try {
+  //   let queryParams = {
+  //     pageNo: batch,
+  //     pageSize: count,
+  //     tenantId: userStore.orgTenantId[0]
+  //   }
+  //   const list = await careRecordsPage(queryParams)
+  //   if (list.length <= 0) {
+  //     message.error('暂无数据可以导出!')
+  //     return
+  //   }
+  //   const headers = [
+  //     { key: 'organizationName', title: '机构名称' },
+  //     { key: 'visitName', title: '姓名' },
+  //     { key: 'visitPhone', title: '手机号' },
+  //     { key: 'visitDate', title: '预约时间' },
+  //     { key: 'accompanyCount', title: '陪同人数' },
+  //     { key: 'reason', title: '理由' }
+  //   ]
+  //   exportWithExpandedObjectArrays(list, headers, `医疗护理记录${formatToDateTime()}.xlsx`, '医疗护理记录')
+  // } catch (_) {}
+
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    //let queryP = {...queryParams,discoveryTime:queryParams.discoveryTime?[queryParams.discoveryTime[0]+" 00:00:00",queryParams.discoveryTime[1]+" 23:59:59"]:null}
+    const data = await attackRiskPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = async () => {
+  if (!queryFormRef.value) return
+  const valid = await queryFormRef.value.validate()
+  if (!valid) return
+  queryParams.pageNo = 1
+  await getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.elderName = ''
+  queryParams.assessor = ''
+  queryParams.tenantIds = userStore.orgTenantId
+  // queryParams.assessDate= getCurrentMonthRange()
+  queryParams.assessDate = []
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1){
+    message.error('新增只能选择一个机构')
+    return
+  }
+  formRef.value.open(queryParams.tenantIds[0], id,false)
+}
+
+const editRef = ref()
+const openFormEdit = (row: any = {}, id?: number) => {
+  formRef.value.open(row.tenantId, id,false)
+}
+
+
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
+}
+
+
+
+
+
+const openClose = async (item) => {
+  try {
+    console.log("任务ID",item)
+    const res = await message.confirm('确定要删除吗?', '提示')
+    if (res == 'confirm') {
+      // 发起
+      try {
+        const res = await attackRiskDelete(item.id)
+        if (res){
+          message.success(t('common.updateSuccess'))
+        }
+      }catch(err) {}
+    }
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 取消按钮操作 */
+const cancelDelete = async (id: number) => {
+  try {
+    // 取消的二次确认
+    await message.cancelConfirm()
+    // 发起取消
+    // await elderlyBakDel(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const route = useRoute()
+/** 初始化 **/
+onMounted(() => {
+  if(route.query && route.query.elderName){
+    queryParams.elderName = route.query.elderName as string
+  }
+  getList()
+})
+
+// 表头格式
+const tableHeaderColor = ({ rowIndex }: any) => {
+  if (rowIndex === 0) {
+    return {
+      backgroundColor: '#f8f8f9',
+      color: '#666666',
+      fontWeight: 'bold'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 26 - 6
src/views/elderly/contracts/guardian-party-b/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">乙方监护人(丙方)身份证、户口本复印件(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,9 +51,11 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <DocumentAttachmentBody
+          v-model="attachmentForm"
           :is-text-mode="isDetail"
+          title="乙方监护人(丙方)身份证、户口本复印件"
+          description="丙方:(系自然人的)有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页);(系单位的)的有效注册登登记材料复印件(加盖公章)、法定代表人/负责人及联系人的身份证件复印件(身份证件需正反面复印)"
         />
       </div>
 
@@ -74,7 +76,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import DocumentAttachmentBody from '../components/DocumentAttachmentBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +84,9 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const attachmentForm = reactive({
+  images: []
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,9 +116,25 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: '',
+  attackLevel: 0,
+  riskLevel: ''
+})
 
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    attachment: { ...attachmentForm }
+  }
+}
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗

+ 26 - 7
src/views/elderly/contracts/identification-document-partyb/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">乙方有效证件(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,9 +51,11 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <DocumentAttachmentBody
+          v-model="attachmentForm"
           :is-text-mode="isDetail"
+          title="乙方有效证件"
+          description="乙方:有效身份证件复印件(正反面复印)及户口本复印件(户主页和本人页)"
         />
       </div>
 
@@ -74,7 +76,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import DocumentAttachmentBody from '../components/DocumentAttachmentBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +84,9 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const attachmentForm = reactive({
+  images: []
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,10 +116,25 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
-
+const form = reactive({
+  assessor: '',
+  assessDate: '',
+  attackLevel: 0,
+  riskLevel: ''
+})
 
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    attachment: { ...attachmentForm }
+  }
+}
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 

+ 28 - 15
src/views/elderly/contracts/outing-confirmation-form/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">外出情况确认书</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -40,19 +40,19 @@
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
           <text>记录人</text>
-          <el-input :disabled="isDetail" v-model="form.assessor"  />
+          <el-input :disabled="isDetail" v-model="dataForm.assessor"  />
         </el-col>
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
           <text>记录日期</text>
-          <el-date-picker :disabled="isDetail" v-model="form.assessDate" type="date" style="width: 100%;"/>
+          <el-date-picker :disabled="isDetail" v-model="dataForm.assessDate" type="date" style="width: 100%;"/>
 
         </el-col>
       </el-row>
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <OutingConfirmationBody
+          v-model="outingConfirmationForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,15 +74,22 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import OutingConfirmationBody from '../components/OutingConfirmationBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
+import { deleteForm } from '@/api/bpm/form/index.js';
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const outingConfirmationForm = ref({
+  elderName: '',
+  outingAbility: '',
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+}) // 外出情况确认书表单数据
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -110,11 +117,18 @@ const elderUp = (e) => {
   dataForm.value.checkInTime = e.checkInTime
   dataForm.value.contractNumber = e.contractNumber||e.fileNumber
   dataForm.value.elderAge = e.elderAge
+  // 同步长者姓名到外出情况确认书
+  outingConfirmationForm.value.elderName = e.elderName
 }
 
 // ========== 攻击风险因素评估量表 表单序列化方法 ==========
 
-
+const open = (params1, params2, params3) => {
+  dialogVisible.value = true
+  title.value = params.title
+  isDetail.value = params.isDetail
+  dataForm.value = params.data
+}
 
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -128,15 +142,14 @@ const submitForm = async () => {
   formLoading.value = true
   // 提交请求
   try {
-    const assessData = serializeFormData()
+   
     const tempParams = {
       ...dataForm.value,
-      assessData: JSON.stringify(assessData),
-      attackLevel: form.attackLevel,
-      riskLevel: form.riskLevel,
-      assessor: form.assessor,
-      assessScore: form.attackLevel,
-      assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
+      assessData: JSON.stringify({
+        outingConfirmation: outingConfirmationForm.value
+      }),
+      assessor: dataForm.value.assessor,
+      assessDate: dataForm.value.assessDate ? dayjs(dataForm.value.assessDate).format('YYYY-MM-DD') : ''
     }
 
     if (dataForm.value.id) {

+ 35 - 6
src/views/elderly/contracts/power-of-attorney/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">授权委托书(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <PowerOfAttorneyBody
+          v-model="powerOfAttorneyForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import PowerOfAttorneyBody from '../components/PowerOfAttorneyBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,21 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const powerOfAttorneyForm = reactive({
+  principalName: '',
+  principalGender: '',
+  principalIdCardNo: '',
+  institutionName: '',
+  agentName: '',
+  agentGender: '',
+  agentIdCardNo: '',
+  agentPhone: '',
+  elderName: '',
+  relationship: '',
+  principalSign: '',
+  agentSign: '',
+  signDate: ''
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,10 +126,25 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
 
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    powerOfAttorney: { ...powerOfAttorneyForm }
+  }
+}
 
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 

+ 792 - 0
src/views/elderly/contracts/registration-certificate-civil-affairs-department/AddForm.vue

@@ -0,0 +1,792 @@
+<template>
+  <el-drawer
+    v-model="dialogVisible"
+    :title="title"
+    resizable
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :destroy-on-close="true"
+    size="70%"
+    :before-close="handleClosed"
+  >
+
+    <div class="attack-risk-form">
+      <h1 class="form-title">甲方登记证书及民政部门备案回执(附件)</h1>
+      <!-- 基本信息 -->
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>长者姓名</text>
+          <search-the-elderly ref="selectElderRef" :disabled="isDetail" @update_elder="elderUp" v-model="dataForm.elderName" :tId="dataForm.tenantId"/>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>档案号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.contractNumber" disabled />
+          <el-text v-else disabled="">{{dataForm.contractNumber}}</el-text>
+        </el-col>
+      </el-row>
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>入院日期</text>
+          <el-input v-if="!isDetail" :model-value="dayjs(dataForm.checkInTime).format('YYYY-MM-DD')=='Invalid Date'?'':dayjs(dataForm.checkInTime).format('YYYY-MM-DD')" disabled />
+          <el-text v-else disabled="">{{dayjs(dataForm.checkInTime).format('YYYY-MM-DD')}}</el-text>
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>床位号</text>
+          <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+          <el-text v-else disabled="">{{dataForm.bedName}}</el-text>
+        </el-col>
+      </el-row>
+
+      <el-row :gutter="40">
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录人</text>
+          <el-input :disabled="isDetail" v-model="form.assessor"  />
+        </el-col>
+        <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
+          <text>记录日期</text>
+          <el-date-picker :disabled="isDetail" v-model="form.assessDate" type="date" style="width: 100%;"/>
+
+        </el-col>
+      </el-row>
+
+      <!-- 内容区域 -->
+      <div class="form-body">
+        <DocumentAttachmentBody
+          v-model="attachmentForm"
+          :is-text-mode="isDetail"
+          description="甲方登记证书及民政部门备案回执"
+        />
+      </div>
+
+    </div>
+
+
+
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+      <el-button style="margin-left: 22px;margin-right: 30px" v-loading="formLoading" type="primary" v-show="!isDetail" @click="submitForm">确定</el-button>
+    </template>
+
+  </el-drawer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch, reactive } from 'vue'
+
+import dayjs from 'dayjs'
+import DocumentAttachmentBody from '../components/DocumentAttachmentBody.vue'
+
+import { attackRiskCreate, attackRiskGetById, attackRiskUpdate, attackRiskGetByElderId } from "@/api/social-work";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const title = ref('')
+const dialogVisible = ref(false) // 弹窗
+const formRef = ref() // 表单 Ref
+const selectElderRef = ref() // 表单 Ref
+const attachmentForm = reactive({
+  images: []
+})
+const isDetail = ref(false) // 是否详情打开
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+let dataForm = ref({
+  // 表单字段
+  id: undefined,
+  idCard: '',
+  contractNumber: '', //档案号
+  elderName: '',//长者姓名
+  bedName: '', //床位号
+  elderAge: '', //年龄
+  elderSex: '', //性别
+  checkInTime: '', //入院日期
+  elderId: '',
+  tenantId: undefined
+})
+
+
+
+
+const elderUp = (e) => {
+  dataForm.value.elderName = e.elderName
+  dataForm.value.elderId = e.id
+  dataForm.value.elderSex = e.elderSex === 1 ? '男' : '女'
+  dataForm.value.bedName = e.bedName || ''
+  dataForm.value.checkInTime = e.checkInTime
+  dataForm.value.contractNumber = e.contractNumber||e.fileNumber
+  dataForm.value.elderAge = e.elderAge
+}
+
+// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+
+ 
+
+/** 风险程度样式类 */
+const riskLevelClass = computed(() => {
+  const level = form.attackLevel
+  if (!level || level === 0) return 'risk-none'
+  if (level === 1) return 'risk-low'
+  if (level === 2) return 'risk-medium'
+  return 'risk-high'
+})
+
+/** 自动判断风险等级 */
+const autoJudgeRiskLevel = () => {
+  const level = form.attackLevel
+  if (!level || level === 0) form.riskLevel = 'none'
+  else if (level === 1) form.riskLevel = 'low'
+  else if (level === 2) form.riskLevel = 'medium'
+  else form.riskLevel = 'high'
+}
+
+ 
+
+
+
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    // 基本信息
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+
+    // 攻击风险等级(1=I级,2=II级,3=III级,4=IV级)
+    attackLevel: form.attackLevel || 0,
+
+    // 各级别选择的具体项
+    attackLevel1Items: form.attackLevel1Items || [],
+    attackLevel2Items: form.attackLevel2Items || [],
+    attackLevel3Items: form.attackLevel3Items || [],
+    attackLevel4Items: form.attackLevel4Items || [],
+
+    // 风险程度
+    riskLevel: form.riskLevel || '',
+
+    // 预防措施
+    preventiveMeasures: form.preventiveMeasures || [],
+    preventiveMeasuresOther: form.preventiveMeasuresOther || '',
+
+    // 签名
+    familySignature: form.familySignature || '',
+    familySignDate: form.familySignDate ? dayjs(form.familySignDate).format('YYYY-MM-DD') : '',
+
+    // 风险知情书内容
+    riskDisclosure: {
+      companyName: riskDisclosureForm.companyName || '',
+      partyBSign: riskDisclosureForm.partyBSign || '',
+      partyBDate: riskDisclosureForm.partyBDate || '',
+      partyCSign: riskDisclosureForm.partyCSign || '',
+      partyCDate: riskDisclosureForm.partyCDate || ''
+    },
+
+    // 附件图片内容
+    attachment: { ...attachmentForm }
+  }
+}
+
+/** 将 JSON 对象反序列化为表单数据 */
+const deserializeFormData = (formData: Record<string, any>) => {
+  if (!formData) return
+
+  // 基本信息
+  form.assessor = formData.assessor || ''
+  form.assessDate = formData.assessDate ? dayjs(formData.assessDate).toDate() : ''
+
+  // 攻击风险等级
+  form.attackLevel = formData.attackLevel || 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = formData.attackLevel1Items || []
+  form.attackLevel2Items = formData.attackLevel2Items || []
+  form.attackLevel3Items = formData.attackLevel3Items || []
+  form.attackLevel4Items = formData.attackLevel4Items || []
+
+  // 风险程度
+  form.riskLevel = formData.riskLevel || ''
+
+  // 预防措施
+  form.preventiveMeasures = formData.preventiveMeasures || []
+  form.preventiveMeasuresOther = formData.preventiveMeasuresOther || ''
+
+  // 签名
+  form.familySignature = formData.familySignature || ''
+  form.familySignDate = formData.familySignDate ? dayjs(formData.familySignDate).toDate() : ''
+
+  // 风险知情书内容
+  if (formData.riskDisclosure) {
+    riskDisclosureForm.companyName = formData.riskDisclosure.companyName || ''
+    riskDisclosureForm.partyBSign = formData.riskDisclosure.partyBSign || ''
+    riskDisclosureForm.partyBDate = formData.riskDisclosure.partyBDate || ''
+    riskDisclosureForm.partyCSign = formData.riskDisclosure.partyCSign || ''
+    riskDisclosureForm.partyCDate = formData.riskDisclosure.partyCDate || ''
+  }
+
+  // 附件图片内容
+  if (formData.attachment) {
+    attachmentForm.images = formData.attachment.images || []
+  }
+}
+
+/** 重置攻击风险因素评估表表单数据 */
+const resetAttackRiskForm = () => {
+  form.assessor = ''
+  form.assessDate = ''
+
+  // 攻击风险等级
+  form.attackLevel = 0
+
+  // 各级别选择的具体项
+  form.attackLevel1Items = []
+  form.attackLevel2Items = []
+  form.attackLevel3Items = []
+  form.attackLevel4Items = []
+
+  // 风险程度
+  form.riskLevel = ''
+
+  // 预防措施
+  form.preventiveMeasures = []
+  form.preventiveMeasuresOther = ''
+
+  // 签名
+  form.familySignature = ''
+  form.familySignDate = ''
+
+  // 风险知情书
+  riskDisclosureForm.companyName = '颐年医养'
+  riskDisclosureForm.partyBSign = ''
+  riskDisclosureForm.partyBDate = ''
+  riskDisclosureForm.partyCSign = ''
+  riskDisclosureForm.partyCDate = ''
+
+  // 附件图片
+  attachmentForm.images = []
+}
+
+/** 打开弹窗 */
+const open = async (tenantId, id?: any, detail: boolean = false) => {
+  resetForm()
+  dialogVisible.value = true
+  dataForm.value.id = id || undefined
+  dataForm.value.tenantId = tenantId
+  isDetail.value = detail
+  if (id) {
+    title.value = "编辑-攻击风险因素评估"
+    // 加载评估数据
+    await loadAttackRiskData(id)
+  } else {
+    title.value = "新增-攻击风险因素评估"
+    
+  }
+}
+
+/** 加载评估数据 */
+const loadAttackRiskData = async (id: number) => {
+  try {
+    const res = await attackRiskGetById(id)
+    if (res) {
+      // 填充长者基本信息
+      dataForm.value.elderName = res.elderName || ''
+      dataForm.value.elderId = res.elderId || ''
+      dataForm.value.elderSex = res.elderSex || ''
+      dataForm.value.bedName = res.bedName || ''
+      dataForm.value.checkInTime = res.checkInTime || ''
+      dataForm.value.contractNumber = res.fileNumber || ''
+      dataForm.value.elderAge = res.elderAge || ''
+      await selectElderRef.value.upData(res.elderName, res.elderId)
+      // 解析 assessData
+      if (res.assessData) {
+        const formData = JSON.parse(res.assessData)
+        deserializeFormData(formData)
+      }
+    }
+  } catch (error) {
+    message.error('加载评估数据失败')
+  }
+}
+
+/** 根据长者ID加载评估数据 */
+const loadAttackRiskByElderId = async (elderId: number) => {
+  try {
+    const res = await attackRiskGetByElderId(elderId)
+    if (res && res.assessData) {
+      const formData = JSON.parse(res.assessData)
+      deserializeFormData(formData)
+    }
+  } catch (error) {
+    // 无历史数据,不处理
+  }
+}
+
+const form = reactive({
+  // 基本信息
+  assessor: '',
+  assessDate: '',
+
+  // 攻击风险等级(0=无,1=I级,2=II级,3=III级,4=IV级)
+  attackLevel: 0,
+
+  // 各级别选择的具体项
+  attackLevel1Items: [], // I级选择的项(1-7)
+  attackLevel2Items: [], // II级选择的项(1-3)
+  attackLevel3Items: [], // III级选择的项(1-4)
+  attackLevel4Items: [], // IV级选择的项(1-2)
+
+  // 风险程度
+  riskLevel: '',
+
+  // 预防措施
+  preventiveMeasures: [],
+  preventiveMeasuresOther: '',
+
+  // 签名
+  familySignature: '',
+  familySignDate: ''
+})
+
+// 监听攻击等级变化,自动判断风险等级
+watch(() => form.attackLevel, () => {
+  autoJudgeRiskLevel()
+})
+
+// 风险知情书表单数据
+const riskDisclosureForm = reactive({
+  elderName: '',
+  companyName: '颐年医养',
+  assessor: '',
+  assessDate: '',
+  partyBSign: '',
+  partyBDate: '',
+  partyCSign: '',
+  partyCDate: ''
+})
+
+// 长者信息变化时同步到风险告知书
+watch(
+  () => dataForm.value.elderName,
+  (name) => {
+    riskDisclosureForm.elderName = name
+  }
+)
+
+// 记录人变化时同步
+watch(
+  () => form.assessor,
+  (val) => {
+    riskDisclosureForm.assessor = val
+  }
+)
+
+watch(
+  () => form.assessDate,
+  (val) => {
+    riskDisclosureForm.assessDate = val ? dayjs(val).format('YYYY-MM-DD') : ''
+  }
+)
+
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  if (formLoading.value) {
+    return
+  }
+  formLoading.value = true
+  // 提交请求
+  try {
+    const assessData = serializeFormData()
+    const tempParams = {
+      ...dataForm.value,
+      assessData: JSON.stringify(assessData),
+      attackLevel: form.attackLevel,
+      riskLevel: form.riskLevel,
+      assessor: form.assessor,
+      assessScore: form.attackLevel,
+      assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
+    }
+
+    if (dataForm.value.id) {
+      const res = await attackRiskUpdate(tempParams)
+      if (res) {
+        message.success(t('common.updateSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    } else {
+      const res = await attackRiskCreate(tempParams)
+      if (res) {
+        message.success(t('common.createSuccess'))
+        dialogVisible.value = false
+        // 发送操作成功的事件
+        emit('success')
+      }
+    }
+  } finally {
+    setTimeout(() => {
+      formLoading.value = false
+    }, 500)
+  }
+}
+
+
+
+/** 重置表单 */
+const resetForm = () => {
+  dataForm.value = {
+    id: undefined,
+    idCard: '',
+    contractNumber: '', //档案号
+    elderName: '',//长者姓名
+    bedName: '', //床位号
+    elderAge: '', //年龄
+    elderSex: '', //性别
+    checkInTime: '', //入院日期
+    elderId: '',
+    tenantId: undefined
+  }
+  formRef.value?.resetFields()
+
+  // 重置攻击风险因素评估表表单
+  resetAttackRiskForm()
+}
+
+// 关闭表单
+const handleClosed = () => {
+  dialogVisible.value = false
+  resetForm()
+}
+
+
+</script>
+
+<style scoped lang="scss">
+
+.form-title {
+  text-align: center;
+  font-size: 20px;
+  width: 100%;
+  margin-bottom: 20px;
+}
+
+.attack-risk-form {
+  max-width: 1200px;
+  margin: 0 auto;
+  background: #fff;
+
+
+
+
+
+  .info-row {
+    display: flex;
+    gap: 40px;
+    width: 100%;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 10px;
+
+    .info-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .label {
+        width: 82px;
+        text-align: right;
+        margin-right: 4px;
+        white-space: nowrap;
+      }
+    }
+  }
+
+
+  .form-body {
+    border: 1px solid #333;
+    padding: 15px;
+  }
+
+  // 调整 Element Plus 组件样式
+  :deep(.el-input__inner) {
+    height: 28px;
+    line-height: 28px;
+    border-top: none;
+    border-left: none;
+    border-right: none;
+    border-radius: 0;
+    padding: 0 4px;
+
+    &:focus {
+      border-color: #409eff;
+    }
+  }
+
+  :deep(.el-checkbox__label),
+  :deep(.el-radio__label) {
+    padding-left: 4px;
+  }
+}
+.row{
+  margin-bottom: 12px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  text{
+    text-align: right;
+    margin-right: 4px;
+    width: 82px;
+  }
+}
+
+// NGASR自杀风险评估量表 特有样式
+.total-score-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  margin-bottom: 20px;
+
+  .total-score {
+    display: flex;
+    align-items: baseline;
+    gap: 4px;
+
+    .score-label {
+      font-size: 16px;
+      font-weight: bold;
+    }
+
+    .score-value {
+      font-size: 28px;
+      font-weight: bold;
+      color: #409eff;
+    }
+
+    .score-max {
+      font-size: 14px;
+      color: #909399;
+    }
+  }
+
+  .risk-level {
+    font-size: 16px;
+    font-weight: bold;
+    padding: 8px 16px;
+    border-radius: 4px;
+
+    &.risk-none {
+      color: #67c23a;
+      background: #f0f9eb;
+    }
+
+    &.risk-low {
+      color: #e6a23c;
+      background: #fdf6ec;
+    }
+
+    &.risk-medium {
+      color: #f56c6c;
+      background: #fef0f0;
+    }
+
+    &.risk-high {
+      color: #f56c6c;
+      background: #fef0f0;
+      border: 1px solid #f56c6c;
+    }
+  }
+}
+
+.assessment-table {
+  width: 100%;
+  border-collapse: collapse;
+  margin-bottom: 20px;
+
+  table {
+    width: 100%;
+    border: 1px solid #333;
+
+    th, td {
+      border: 1px solid #333;
+      padding: 12px;
+      text-align: left;
+      vertical-align: top;
+    }
+
+    th {
+      background: #f5f7fa;
+      font-weight: bold;
+      text-align: center;
+    }
+
+    .content-col {
+      width: 65%;
+    }
+
+    .level-col {
+      width: 10%;
+      text-align: center;
+    }
+
+    .select-col {
+      width: 25%;
+      text-align: center;
+    }
+
+    .content {
+      text-align: left;
+
+      .content-title {
+        font-weight: bold;
+        margin-bottom: 8px;
+      }
+
+      .content-item {
+        margin-left: 15px;
+        margin-bottom: 4px;
+        font-size: 13px;
+      }
+    }
+
+    .level {
+      text-align: center;
+      font-weight: bold;
+    }
+
+    .select {
+      text-align: center;
+
+      :deep(.el-radio) {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
+.risk-judgment-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .risk-judgment-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .risk-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 20px;
+
+    :deep(.el-radio) {
+      margin-right: 0;
+    }
+  }
+}
+
+.preventive-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .preventive-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .preventive-options {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 15px;
+
+    :deep(.el-checkbox) {
+      margin-right: 20px;
+      margin-bottom: 8px;
+    }
+
+    .other-input {
+      width: 200px;
+      margin-left: 10px;
+    }
+  }
+}
+
+.signature-section {
+  margin: 20px 0;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .signature-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 20px;
+  }
+
+  .signature-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+
+    &.date-item {
+      :deep(.el-date-picker) {
+        width: 150px;
+      }
+    }
+  }
+
+  .signature-label {
+    font-weight: bold;
+    white-space: nowrap;
+  }
+
+  .signature-input {
+    width: 150px;
+  }
+
+  .date-picker {
+    width: 150px;
+  }
+}
+
+.note-section {
+  margin-top: 20px;
+  padding: 15px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+
+  .note-title {
+    font-weight: bold;
+    font-size: 16px;
+    margin-bottom: 12px;
+    color: #303133;
+  }
+
+  .note-content {
+    font-size: 13px;
+    line-height: 1.8;
+    color: #606266;
+
+    p {
+      margin: 5px 0;
+    }
+
+    .indent {
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 312 - 0
src/views/elderly/contracts/registration-certificate-civil-affairs-department/index.vue

@@ -0,0 +1,312 @@
+<template>
+  <ContentWrap>
+    <!-- 保护性约束观察记录表 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="110px"
+    >
+      <el-form-item label="长者姓名">
+        <el-input
+          v-model="queryParams.elderName"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录人">
+        <el-input
+          v-model="queryParams.assessor"
+          placeholder="长者姓名"
+          class="!w-240px"
+          clearable
+        />
+      </el-form-item>
+
+      <el-form-item label="记录日期">
+        <el-date-picker
+          size="default"
+          ref="selectRef"
+          class="!w-240px"
+          v-model="queryParams.assessDate"
+          type="daterange"
+          :clearable="true"
+          :editable="false"
+          placeholder="选择记录日期"
+          value-format="YYYY-MM-DD"
+          format="YYYY-MM-DD"
+          date-format="YYYY-MM-DD"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button @click="handleQuery" style="margin-left: 2vw"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
+        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <div class="mb-10px">
+
+      <ButtonAdd @click="openForm(undefined)"  />
+      <!--      <el-button @click="inputH" type="success"><Icon icon="fa:cloud-upload" :size="16" class="mr-1"/>导入</el-button>-->
+      <!--      <ButtonImport @click="handleImportCard"  />-->
+
+
+    </div>
+    <el-table v-loading="loading" :data="list" :header-cell-style="tableHeaderColor">
+      <el-table-column header-align="center" align="center" label="序号" width="60">
+        <template #default="scope">
+          {{
+            scope.$index + (queryParams.pageNo * queryParams.pageSize - queryParams.pageSize) + 1
+          }}
+        </template>
+      </el-table-column>
+
+
+      <el-table-column prop="elderName" header-align="center" align="center" label="长者姓名" min-width="150" show-overflow-tooltip/>
+      <el-table-column prop="assessor" header-align="center" align="center" label="床位号" min-width="200" show-overflow-tooltip/>
+      <el-table-column header-align="center" align="center" label="风险等级" min-width="200" show-overflow-tooltip>
+        <template #default="scope">
+          <el-tag v-if="getRiskLevelInfo(scope.row.assessData).text" :type="getRiskLevelInfo(scope.row.assessData).type">
+            {{ getRiskLevelInfo(scope.row.assessData).text }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column prop="assessDate" header-align="center" align="center" label="记录日期" min-width="200" show-overflow-tooltip/>
+      <el-table-column prop="creator" header-align="center" align="center" label="记录人" min-width="200" show-overflow-tooltip/>
+
+
+      <el-table-column label="操作" align="center" width="200" >
+        <template #default="scope">
+          <el-button
+            link
+            type="primary"
+            @click="openFormEdit(scope.row, scope.row.id)"
+          >
+            编辑
+          </el-button>
+          <el-button
+            link
+            type="warning"
+            @click="openFormDetail(scope.row,scope.row.id)"
+          >
+            详情
+          </el-button>
+          <el-button
+            link
+            type="danger"
+            @click="openClose(scope.row)"
+          >
+            删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <AddForm ref="formRef" @success="getList" />
+
+
+
+</template>
+
+<script setup lang="ts">
+import AddForm from "./AddForm.vue";
+import ButtonAdd from "@/components/ButtonAdd/src/ButtonAdd.vue";
+import { useUserStore } from '@/store/modules/user'
+import {
+  attackRiskDelete, attackRiskPage,
+} from "@/api/social-work";
+import { getRiskLevelInfo } from "@/utils/risk-assessment";
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+const userStore = useUserStore()
+const loading = ref(true) // 列表的加载中
+const detailRef = ref()
+const importRef = ref()
+const total = ref(0) // 列表的总页数
+const list = ref([]) // 列表的数据
+
+// 状态管理
+const showBatchExport = ref(false)
+const exportConfig = ref({
+  title: '',
+  batchMin: 1,
+  batchMax: 999,
+  countMin: 1,
+  countMax: 200,
+  defaultBatch: 1,
+  defaultCount: 200,
+  description: [] as string[]
+})
+const exportLoading = ref(false)
+
+let queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  assessor: '',
+  // assessDate: getCurrentMonthRange(),
+  assessDate: [],
+  tenantIds: userStore.orgTenantId
+})
+const queryFormRef = ref() // 搜索的表单
+
+
+// 打开导出弹窗
+const handleImportCard = async () => {
+
+  // showBatchExport.value = true
+}
+
+// 处理批量导出
+const handleBatchExport = async (batch: number, count: number) => {
+  exportLoading.value = true
+  console.log(batch,count)
+  // try {
+  //   let queryParams = {
+  //     pageNo: batch,
+  //     pageSize: count,
+  //     tenantId: userStore.orgTenantId[0]
+  //   }
+  //   const list = await careRecordsPage(queryParams)
+  //   if (list.length <= 0) {
+  //     message.error('暂无数据可以导出!')
+  //     return
+  //   }
+  //   const headers = [
+  //     { key: 'organizationName', title: '机构名称' },
+  //     { key: 'visitName', title: '姓名' },
+  //     { key: 'visitPhone', title: '手机号' },
+  //     { key: 'visitDate', title: '预约时间' },
+  //     { key: 'accompanyCount', title: '陪同人数' },
+  //     { key: 'reason', title: '理由' }
+  //   ]
+  //   exportWithExpandedObjectArrays(list, headers, `医疗护理记录${formatToDateTime()}.xlsx`, '医疗护理记录')
+  // } catch (_) {}
+
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    //let queryP = {...queryParams,discoveryTime:queryParams.discoveryTime?[queryParams.discoveryTime[0]+" 00:00:00",queryParams.discoveryTime[1]+" 23:59:59"]:null}
+    const data = await attackRiskPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = async () => {
+  if (!queryFormRef.value) return
+  const valid = await queryFormRef.value.validate()
+  if (!valid) return
+  queryParams.pageNo = 1
+  await getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryParams.elderName = ''
+  queryParams.assessor = ''
+  queryParams.tenantIds = userStore.orgTenantId
+  // queryParams.assessDate= getCurrentMonthRange()
+  queryParams.assessDate = []
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (id?: number) => {
+  if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1){
+    message.error('新增只能选择一个机构')
+    return
+  }
+  formRef.value.open(queryParams.tenantIds[0], id,false)
+}
+
+const editRef = ref()
+const openFormEdit = (row: any = {}, id?: number) => {
+  formRef.value.open(row.tenantId, id,false)
+}
+
+
+const openFormDetail = (row: any = {},id?: number) => {
+  formRef.value.open(row.tenantId,id,true)
+}
+
+
+
+
+
+const openClose = async (item) => {
+  try {
+    console.log("任务ID",item)
+    const res = await message.confirm('确定要删除吗?', '提示')
+    if (res == 'confirm') {
+      // 发起
+      try {
+        const res = await attackRiskDelete(item.id)
+        if (res){
+          message.success(t('common.updateSuccess'))
+        }
+      }catch(err) {}
+    }
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 取消按钮操作 */
+const cancelDelete = async (id: number) => {
+  try {
+    // 取消的二次确认
+    await message.cancelConfirm()
+    // 发起取消
+    // await elderlyBakDel(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+const route = useRoute()
+/** 初始化 **/
+onMounted(() => {
+  if(route.query && route.query.elderName){
+    queryParams.elderName = route.query.elderName as string
+  }
+  getList()
+})
+
+// 表头格式
+const tableHeaderColor = ({ rowIndex }: any) => {
+  if (rowIndex === 0) {
+    return {
+      backgroundColor: '#f8f8f9',
+      color: '#666666',
+      fontWeight: 'bold'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss"></style>

+ 30 - 6
src/views/elderly/contracts/room-facilities-equipment/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">标准房间物品配备(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <RoomFacilitiesEquipmentBody
+          v-model="roomFacilitiesForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import RoomFacilitiesEquipmentBody from '../components/RoomFacilitiesEquipmentBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,16 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const roomFacilitiesForm = reactive({
+  elderName: '',
+  roomNo: '',
+  leftItems: [],
+  rightItems: [],
+  blankItems: [],
+  elderSign: '',
+  guardianSign: '',
+  signDate: ''
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,9 +121,24 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
 
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    roomFacilities: { ...roomFacilitiesForm }
+  }
+}
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗

+ 25 - 7
src/views/elderly/contracts/service-charging-standards-institution/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">机构服务范围及收费标准(附件)</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <ServiceChargingStandardsBody
+          v-model="serviceChargingForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import ServiceChargingStandardsBody from '../components/ServiceChargingStandardsBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,10 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const serviceChargingForm = reactive({
+  signatorySign: '',
+  signDate: ''
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -100,7 +103,10 @@ let dataForm = ref({
 })
 
 
-
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 const elderUp = (e) => {
   dataForm.value.elderName = e.elderName
@@ -112,7 +118,19 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
+
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    serviceCharging: { ...serviceChargingForm }
+  }
+}
 
 
 

+ 30 - 10
src/views/elderly/contracts/signature-proxy-application/AddForm.vue

@@ -11,7 +11,7 @@
   >
 
     <div class="attack-risk-form">
-      <h1 class="form-title">风险知情书(附件)</h1>
+      <h1 class="form-title">签名代理申请</h1>
       <!-- 基本信息 -->
       <el-row :gutter="40">
         <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12" class="row">
@@ -51,8 +51,8 @@
 
       <!-- 内容区域 -->
       <div class="form-body">
-        <RiskDisclosureBody
-          v-model="riskDisclosureForm"
+        <SignatureProxyApplicationBody
+          v-model="signatureProxyForm"
           :is-text-mode="isDetail"
         />
       </div>
@@ -74,7 +74,7 @@
 import { computed, ref, watch, reactive } from 'vue'
 
 import dayjs from 'dayjs'
-import RiskDisclosureBody from '../components/RiskDisclosureBody.vue'
+import SignatureProxyApplicationBody from '../components/SignatureProxyApplicationBody.vue'
 
 import { attackRiskCreate, attackRiskUpdate } from "@/api/social-work";
 const message = useMessage() // 消息弹窗
@@ -82,7 +82,16 @@ const { t } = useI18n() // 国际化
 const title = ref('')
 const dialogVisible = ref(false) // 弹窗
 const formRef = ref() // 表单 Ref
-const riskDisclosureForm = ref() // 表单 Ref
+const signatureProxyForm = reactive({
+  elderName: '',
+  idCardNo: '',
+  agentName: '',
+  agentIdCardNo: '',
+  applicantSign: '',
+  agentSign: '',
+  signDate: '',
+  handlerSign: ''
+})
 const isDetail = ref(false) // 是否详情打开
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 let dataForm = ref({
@@ -112,10 +121,24 @@ const elderUp = (e) => {
   dataForm.value.elderAge = e.elderAge
 }
 
-// ========== 攻击风险因素评估量表 表单序列化方法 ==========
-
+const form = reactive({
+  assessor: '',
+  assessDate: ''
+})
 
+/** 将表单数据序列化为 JSON 对象 */
+const serializeFormData = () => {
+  return {
+    assessor: form.assessor || '',
+    assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : '',
+    signatureProxy: { ...signatureProxyForm }
+  }
+}
 
+const open = (params1, params2,params3) => {
+  dialogVisible.value = true
+  
+}
 
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
@@ -132,10 +155,7 @@ const submitForm = async () => {
     const tempParams = {
       ...dataForm.value,
       assessData: JSON.stringify(assessData),
-      attackLevel: form.attackLevel,
-      riskLevel: form.riskLevel,
       assessor: form.assessor,
-      assessScore: form.attackLevel,
       assessDate: form.assessDate ? dayjs(form.assessDate).format('YYYY-MM-DD') : ''
     }
 

+ 2 - 2
src/views/elderly/elder/contract/index.vue

@@ -154,8 +154,8 @@ const resetQuery = () => {
 /** 添加/修改操作 */
 const formRef = ref()
 const openForm = (row: any = {}, type: number = 1) => {
- // contractManageFormRef.value.open(row, type)
-  formRef.value.open(row, type)
+  contractManageFormRef.value.open(row, type)
+ // formRef.value.open(row, type)
 }
 
 // 变更/续签