WXL
5 天以前 4c9fdc87987140e8b2c3f0dfc2f6608cdb6be774
src/views/business/affirm/affirmInfo.vue
@@ -1,261 +1,521 @@
<template>
  <div class="confirmation-detail">
    <el-card class="detail-card">
    <case-basic-info :case-id="caseId" :show-attachment="true" :column="3" />
    <el-card class="detail-card paper-card">
      <!-- 基础信息 -->
      <div slot="header" class="clearfix">
        <span class="detail-title">捐献确认基本信息</span>
        <el-button
          v-if="$route.query.confirm"
          type="primary"
          style="float: right; padding: 3px 0"
          @click="handleSave"
        >
          保存确认信息
        </el-button>
        <span class="detail-title">捐献确认信息</span>
      </div>
      <el-form :model="form" ref="form" label-width="120px">
        <el-row :gutter="20">
      <el-form :model="form" ref="form" label-width="120px" class="paper-form">
        <!-- <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="案例编号" prop="caseNo">
              <el-input v-model="form.caseNo" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="捐献者姓名" prop="donorName">
              <el-input v-model="form.donorName" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="性别" prop="gender">
              <el-select
                v-model="form.gender"
                :disabled="!isEdit"
                style="width: 100%"
              >
                <el-option label="男" value="0" />
                <el-option label="女" value="1" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="年龄" prop="age">
              <el-input v-model="form.age" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="疾病诊断" prop="diagnosis">
              <el-input v-model="form.diagnosis" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="所在医疗机构" prop="hospitalName">
              <el-input v-model="form.hospitalName" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="协调员1" prop="coordinator1">
              <el-input v-model="form.coordinator1" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="协调员2" prop="coordinator2">
              <el-input v-model="form.coordinator2" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="业务人员" prop="assignee">
              <el-input v-model="form.assignee" :readonly="!isEdit" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="确认状态" prop="confirmationStatus">
              <el-select
                v-model="form.confirmationStatus"
                :disabled="!isEdit"
                style="width: 100%"
              >
                <el-option label="未确认" value="0" />
                <el-option label="家属确认" value="1" />
                <el-option label="不同意捐献" value="2" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="确认时间" prop="confirmationTime">
            <el-form-item label="确认时间" prop="signdate">
              <el-date-picker
                v-model="form.confirmationTime"
                v-model="form.signdate"
                type="datetime"
                value-format="yyyy-MM-dd HH:mm:ss"
                style="width: 100%"
                :disabled="!isEdit"
              />
            </el-form-item>
          </el-col>
        </el-row>
        </el-row> -->
        <el-form-item label="家属意见备注" prop="familyRemark">
          <el-input
            type="textarea"
            :rows="3"
            v-model="form.familyRemark"
            :readonly="!isEdit"
            placeholder="记录家属的意见和沟通情况"
          />
        </el-form-item>
        <!-- 亲属信息 -->
        <el-divider content-position="left">亲属确认信息</el-divider>
        <!-- 主要亲属表单 -->
        <div class="form-row">
          <el-form-item label="亲属姓名" class="col-1-3">
            <el-input
              v-model="familyMemberList[0].name"
              :disabled="isReadOnly"
              placeholder="请输入亲属姓名"
            />
          </el-form-item>
          <el-form-item label="关系" class="col-1-3">
            <el-select
              v-model="familyMemberList[0].relation"
              :disabled="isReadOnly"
              placeholder="请选择关系"
            >
              <el-option
                v-for="dict in dict.type.sys_FamilyRelation"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="身份证号" class="col-1-3">
            <el-input
              v-model="familyMemberList[0].idcard"
              :disabled="isReadOnly"
              placeholder="请输入身份证号"
              maxlength="18"
            />
          </el-form-item>
        </div>
        <div class="form-row">
          <el-form-item label="住址" class="col-full">
            <template v-if="!isReadOnly">
              <div class="area-select-box">
                <li_area_select v-model="familyMemberList[0].address" />
              </div>
              <el-input
                v-model="familyMemberList[0].address.detail"
                placeholder="请输入详细地址"
                class="address-detail"
              />
            </template>
            <span v-else>{{ formatAddress(familyMemberList[0].address) }}</span>
          </el-form-item>
        </div>
        <div class="form-row">
          <el-form-item label="现居住地" class="col-full">
            <template v-if="!isReadOnly">
              <div class="area-select-box">
                <li_area_select
                  v-model="familyMemberList[0].currentResidence"
                />
              </div>
              <el-input
                v-model="familyMemberList[0].currentResidence.detail"
                placeholder="请输入详细地址"
                class="address-detail"
              />
            </template>
            <span v-else>{{
              formatAddress(familyMemberList[0].currentResidence)
            }}</span>
          </el-form-item>
        </div>
        <div class="form-row">
          <el-form-item label="手机" class="col-1-3">
            <el-input
              v-model="familyMemberList[0].phone"
              :disabled="isReadOnly"
              placeholder="请输入手机号"
              maxlength="11"
            />
          </el-form-item>
        </div>
        <!-- 亲属关系确认 -->
        <div class="form-row">
          <el-form-item label="亲属关系确认" class="col-full">
            <el-checkbox-group
              v-model="form.kinship"
              :disabled="isReadOnly"
              @change="syncKinshipRelatives"
            >
              <el-checkbox
                v-for="dict in kinshipCheckboxOptions"
                :key="dict.value"
                :label="String(dict.value)"
              >
                {{
                  isChildLabel(dict.label) ? dict.label + "(人)" : dict.label
                }}
              </el-checkbox>
            </el-checkbox-group>
          </el-form-item>
        </div>
        <div class="form-row" v-if="adultChildSelected">
          <el-form-item label="子女数量" class="col-1-3">
            <el-input
              v-model="form.kinshipChildrennum"
              :disabled="isReadOnly"
              placeholder="请输入子女数量"
              @input="syncKinshipRelatives"
            />
          </el-form-item>
        </div>
        <!-- 亲属表(由亲属关系勾选自动生成,姓名可编辑) -->
        <el-table
          :data="otherFamilyMemberList"
          size="small"
          border
          style="margin-top: 15px;"
        >
          <el-table-column label="序号" width="60" align="center">
            <template slot-scope="scope">
              {{ scope.$index + 1 }}
            </template>
          </el-table-column>
          <el-table-column label="与捐赠者关系" width="160" align="center">
            <template slot-scope="scope">
              {{ getKinshipLabel(scope.row.relation) }}
            </template>
          </el-table-column>
          <el-table-column label="姓名" min-width="200">
            <template slot-scope="scope">
              <el-input
                style="width: 160px"
                v-model="scope.row.name"
                :disabled="isReadOnly"
                placeholder="请输入姓名"
                size="small"
              />
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isReadOnly"
            label="操作"
            width="80"
            align="center"
          >
            <template slot-scope="scope">
              <el-button
                type="text"
                size="small"
                style="color: #f56c6c"
                @click="handleDeleteFamilyRow(scope.$index)"
                >删除</el-button
              >
            </template>
          </el-table-column>
        </el-table>
        <div class="form-row">
          <el-form-item label-width="100px" label="捐献决定" class="col-full">
            <div class="decision-statement">
              我(们)已知悉捐献的相关法律法规及规定,同意并代表捐献者做出逝世后自愿、无偿捐献决定:
            </div>
            <template v-if="!isReadOnly">
              <el-checkbox-group v-model="organdecision">
                <el-checkbox
                  v-for="item in organselection"
                  :key="item"
                  :label="item"
                  >{{ item }}
                </el-checkbox>
              </el-checkbox-group>
              <el-input
                v-if="organdecision.includes('其他')"
                v-model="organdecisionOther"
                placeholder="请输入其他捐献决定的具体内容"
                class="decision-other"
              ></el-input>
            </template>
            <span v-else class="decision-selected">{{
              displayOrganDecision
            }}</span>
            <div class="decision-statement">
              同意上述所捐用于临床医疗、医学教学和科学研究。
            </div>
          </el-form-item>
        </div>
        <!-- <div class="form-row">
          <el-form-item
            label="家属意见备注"
            prop="relativeRemark"
            class="col-full"
          >
            <el-input
              type="textarea"
              :rows="3"
              v-model="form.relativeRemark"
              placeholder="记录家属的意见和沟通情况"
              :disabled="isReadOnly"
            />
          </el-form-item>
        </div> -->
        <!-- 人体器官获取组织与负责人签字 -->
        <div class="form-row">
          <el-form-item label="人体器官获取组织" class="col-half">
            <el-input
              v-model="form.acquisitiontissuename"
              :disabled="isReadOnly"
              placeholder="请输入人体器官获取组织"
            />
          </el-form-item>
          <el-form-item
            label="负责人签字"
            prop="responsibleusername"
            class="col-half"
          >
            <el-input
              v-model="form.responsibleusername"
              :disabled="isReadOnly"
              placeholder="请输入负责人签字"
            />
          </el-form-item>
        </div>
        <!-- 协调员与签署日期 -->
        <div class="form-row">
          <el-form-item
            label="协调员1"
            prop="coordinatedusernameo"
            class="col-1-3"
          >
            <el-input
              v-model="form.coordinatedusernameo"
              :disabled="isReadOnly"
              placeholder="请输入协调员1"
            />
          </el-form-item>
          <el-form-item
            label="协调员2"
            prop="coordinatedusernamet"
            class="col-1-3"
          >
            <el-input
              v-model="form.coordinatedusernamet"
              :disabled="isReadOnly"
              placeholder="请输入协调员2"
            />
          </el-form-item>
          <el-form-item label="签署日期" prop="signdate" class="col-1-3">
            <el-date-picker
              clearable
              v-model="form.signdate"
              type="date"
              value-format="yyyy-MM-dd"
              placeholder="选择签署日期"
              :disabled="isReadOnly"
            />
          </el-form-item>
        </div>
      </el-form>
    </el-card>
    <!-- 附件列表 -->
    <!-- 添加这里:底部居中按钮 -->
    <!-- 捐献确认阶段附件(可编辑) -->
    <el-card class="attachment-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">相关附件上传</span>
        <el-button
          v-if="isEdit"
          type="primary"
          size="mini"
          @click="handleSaveAll"
          :loading="saveLoading"
        >
          保存所有附件
        </el-button>
        <span class="detail-title">捐献确认阶段附件</span>
      </div>
      <!-- 附件类型选项卡 -->
      <el-tabs v-model="activeAttachmentType" type="card">
        <el-tab-pane
          v-for="type in attachmentTypes"
          v-for="type in confirmAttachmentTypes"
          :key="type.value"
          :label="type.label"
          :name="type.value"
        >
          <div class="attachment-upload-section">
            <div class="upload-header">
          <div class="attachment-section">
            <div class="attachment-header">
              <span class="upload-title">{{ type.label }}</span>
              <el-tooltip content="点击上传该类型附件" placement="top">
                <el-button
                  v-if="!isReadOnly"
                  size="mini"
                  type="primary"
                  icon="el-icon-plus"
                  @click="openUploadDialog(type.value)"
                  :disabled="!isEdit"
                >
                  添加附件
                </el-button>
              </el-tooltip>
            </div>
            <!-- 附件列表 -->
            <el-table
              :data="getAttachmentsByType(type.value)"
              v-loading="attachmentLoading"
              style="width: 100%; margin-top: 15px;"
            >
              <el-table-column label="文件名称" min-width="200">
                <template slot-scope="scope">
                  <div class="file-info">
            <div class="attachment-list">
              <el-table
                :data="getConfirmAttachments(type.value)"
                size="small"
                v-loading="attachmentLoading"
                style="width: 100%;"
              >
                <el-table-column label="文件名" min-width="200">
                  <template slot-scope="scope">
                    <i
                      class="el-icon-document"
                      style="margin-right: 8px; color: #409EFF;"
                      style="color: #409EFF; margin-right: 8px;"
                    ></i>
                    <span>{{ scope.row.fileName }}</span>
                  </div>
                </template>
              </el-table-column>
                    <span class="file-name">{{ scope.row.fileName }}</span>
                  </template>
                </el-table-column>
              <el-table-column label="文件大小" width="100" align="center">
                <template slot-scope="scope">
                  <span>{{ formatFileSize(scope.row.fileSize) }}</span>
                </template>
              </el-table-column>
                <el-table-column label="文件类型" width="90" align="center">
                  <template slot-scope="scope">
                    <el-tag size="small">{{
                      getFileType(scope.row.fileName)
                    }}</el-tag>
                  </template>
                </el-table-column>
              <el-table-column label="上传时间" width="160" align="center">
                <template slot-scope="scope">
                  <span>{{ parseTime(scope.row.uploadTime) }}</span>
                </template>
              </el-table-column>
                <el-table-column label="文件大小" width="90" align="center">
                  <template slot-scope="scope">
                    <span>{{ formatFileSize(scope.row.fileSize) }}</span>
                  </template>
                </el-table-column>
              <el-table-column label="上传人" width="100" align="center">
                <template slot-scope="scope">
                  <span>{{ scope.row.uploader }}</span>
                </template>
              </el-table-column>
                <el-table-column label="上传时间" width="155" align="center">
                  <template slot-scope="scope">
                    <span>{{ formatDateTime(scope.row.uploadTime) }}</span>
                  </template>
                </el-table-column>
              <el-table-column
                label="操作"
                width="120"
                align="center"
                v-if="isEdit"
                <el-table-column label="操作" width="145" align="center">
                  <template slot-scope="scope">
                    <el-button
                      size="mini"
                      type="primary"
                      @click="handlePreview(scope.row)"
                    >
                      预览
                    </el-button>
                    <el-button
                      v-if="!isReadOnly"
                      size="mini"
                      type="danger"
                      @click="handleRemoveAttachment(type.value, scope.$index)"
                    >
                      删除
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
              <div
                v-if="getConfirmAttachments(type.value).length === 0"
                class="empty-attachment"
              >
                <template slot-scope="scope">
                  <el-button
                    size="mini"
                    type="text"
                    icon="el-icon-view"
                    @click="handlePreview(scope.row)"
                    >预览</el-button
                  >
                  <el-button
                    size="mini"
                    type="text"
                    icon="el-icon-delete"
                    style="color: #F56C6C;"
                    @click="handleRemoveAttachment(scope.row, type.value)"
                    >删除</el-button
                  >
                </template>
              </el-table-column>
              <el-table-column label="操作" width="80" align="center" v-else>
                <template slot-scope="scope">
                  <el-button
                    size="mini"
                    type="text"
                    icon="el-icon-view"
                    @click="handlePreview(scope.row)"
                    >预览</el-button
                  >
                </template>
              </el-table-column>
            </el-table>
            <div
              v-if="getAttachmentsByType(type.value).length === 0"
              class="empty-attachment"
            >
              <el-empty description="暂无附件" :image-size="80"></el-empty>
                <el-empty
                  :description="`暂无${type.label}附件`"
                  :image-size="80"
                ></el-empty>
              </div>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- 捐献者登记阶段附件(仅查看) -->
    <el-card class="attachment-card">
      <div slot="header" class="clearfix">
        <span class="detail-title">捐献者登记阶段附件(仅查看)</span>
      </div>
      <el-tabs v-model="activeRegisterType" type="card">
        <el-tab-pane
          v-for="type in registerAttachmentTypes"
          :key="type.value"
          :label="type.label"
          :name="type.value"
        >
          <div class="attachment-section">
            <div class="attachment-header">
              <span class="upload-title">{{ type.label }}</span>
            </div>
            <div class="attachment-list">
              <el-table
                :data="getRegisterAttachments(type.value)"
                size="small"
                v-loading="attachmentLoading"
                style="width: 100%;"
              >
                <el-table-column label="文件名" min-width="200">
                  <template slot-scope="scope">
                    <i
                      class="el-icon-document"
                      style="color: #409EFF; margin-right: 8px;"
                    ></i>
                    <span class="file-name">{{ scope.row.fileName }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="文件类型" width="90" align="center">
                  <template slot-scope="scope">
                    <el-tag size="small">{{
                      getFileType(scope.row.fileName)
                    }}</el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="文件大小" width="90" align="center">
                  <template slot-scope="scope">
                    <span>{{ formatFileSize(scope.row.fileSize) }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="上传时间" width="155" align="center">
                  <template slot-scope="scope">
                    <span>{{
                      formatDateTime(
                        scope.row.uploadTime || scope.row.createTime
                      )
                    }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="90" align="center">
                  <template slot-scope="scope">
                    <el-button
                      size="mini"
                      type="primary"
                      @click="handlePreview(scope.row)"
                    >
                      预览
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
              <div
                v-if="getRegisterAttachments(type.value).length === 0"
                class="empty-attachment"
              >
                <el-empty
                  :description="`暂无${type.label}附件`"
                  :image-size="80"
                ></el-empty>
              </div>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-card>
    <!-- 底部操作按钮 -->
    <div style="text-align: center; margin: 30px 0;">
      <el-button
        v-if="!isReadOnly"
        type="primary"
        @click="handleSave"
        :loading="saveLoading"
        style="margin-right: 20px; min-width: 140px;"
      >
        保存确认信息
      </el-button>
      <el-button
        v-if="!isReadOnly"
        type="success"
        @click="accomplish"
        :loading="saveLoading"
        style="min-width: 140px;"
      >
        确认完成
      </el-button>
    </div>
    <!-- 上传对话框 -->
    <el-dialog
      :title="`上传${getCurrentTypeLabel}附件`"
      :visible.sync="uploadDialogVisible"
      width="500px"
      width="480px"
      :close-on-click-modal="false"
    >
      <el-upload
        ref="uploadRef"
        class="upload-demo"
        drag
        action="#"
        :action="uploadAction"
        :headers="headers"
        multiple
        :file-list="tempFileList"
        :before-upload="beforeUpload"
        :on-change="handleFileChange"
        :on-remove="handleTempRemove"
        :on-success="handleUploadSuccess"
        :auto-upload="false"
        :http-request="handleHttpRequest"
      >
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
@@ -276,205 +536,616 @@
        </el-button>
      </span>
    </el-dialog>
    <!-- 文件预览弹窗 -->
    <FilePreviewDialog
      :visible="previewVisible"
      :file="currentPreviewFile"
      @close="previewVisible = false"
      @download="handleDownload"
    />
  </div>
</template>
<script>
import {
  getConfirmationDetail,
  updateConfirmation
} from "./mockConfirmationApi";
  relativesList,
  relativesEdit,
  relativesAdd
} from "@/api/businessApi";
import {
  listRelativesconfirmation,
  getRelativesconfirmation
} from "@/api/project/relativesconfirmation";
import {
  getDonatebaseinfo,
  updateDonatebaseinfo
} from "@/api/project/donatebaseinfo";
import FilePreviewDialog from "@/components/FilePreviewDialog";
import CaseBasicInfo from "@/components/CaseBasicInfo";
import Li_area_select from "@/components/Address";
import { getToken } from "@/utils/auth";
export default {
  name: "ConfirmationDetail",
  components: {
    FilePreviewDialog,
    CaseBasicInfo,
    Li_area_select
  },
  dicts: ["sys_FamilyRelation", "sys_KinshipConfirm", "sys_Kinship"],
  data() {
    return {
      // 是否编辑模式
      caseId: null,
      isEdit: false,
      // 表单数据
      form: {
        id: undefined,
        infoid: undefined,
        caseNo: "",
        donorName: "",
        gender: "",
        name: "",
        sex: "",
        age: "",
        diagnosis: "",
        hospitalName: "",
        coordinator1: "",
        coordinator2: "",
        assignee: "",
        confirmationStatus: "0",
        confirmationTime: "",
        familyRemark: ""
        diagnosisname: "",
        treatmenthospitalname: "",
        coordinatedusernameo: "",
        coordinatedusernamet: "",
        responsibleusername: "",
        acquisitiontissuename: "",
        relativeconfirmationsign: "0",
        signdate: "",
        relativeconfirmationsignname: "",
        signfamilyrelations: "",
        relativeidcardno: "",
        relativephone: "",
        phone: "",
        kinship: [],
        kinshipChildrennum: null,
        relativeRemark: "",
        assessannex: "",
        otherFamilyMembers: ""
      },
      // 附件列表
      // 附件相关数据
      activeAttachmentType: "1",
      // 统一亲属列表(第一条为主要亲属,其余为其他亲属)
      familyMemberList: [
        {
          name: "",
          relation: "",
          phone: "",
          idcard: "",
          address: { sheng: "", shi: "", qu: "", detail: "" },
          currentResidence: { sheng: "", shi: "", qu: "", detail: "" },
          isMain: true
        }
      ],
      // 标记本次 syncKinshipRelatives 属于"回显填充"(此时允许用已有子女行数兜底数量)
      kinshipHydrated: false,
      uploadAction: process.env.VUE_APP_BASE_API + "/common/upload",
      headers: {
        Authorization: "Bearer " + getToken()
      },
      organdecision: [],
      organdecisionOther: "",
      organselection: [
        "肝脏",
        "肾脏",
        "心脏",
        "肺脏",
        "胰腺",
        "小肠",
        "眼角膜",
        "其他"
      ],
      loading: false,
      saveLoading: false,
      infoid: null,
      archiveData: null,
      activeAttachmentType: "2",
      activeRegisterType: "1",
      attachmentLoading: false,
      uploadDialogVisible: false,
      uploadLoading: false,
      saveLoading: false,
      tempFileList: [],
      currentUploadType: "",
      // 附件类型定义
      attachmentTypes: [
        { value: "1", label: "人体器官潜在捐献者登记表" },
      previewVisible: false,
      currentPreviewFile: null,
      // 捐献确认阶段附件(可编辑,存储于亲属确认表 assessannex)
      confirmAttachmentTypes: [
        { value: "2", label: "人体器官捐献亲属确认登记表" },
        { value: "3", label: "捐献者及直系亲属身份证、户口簿相关证明" },
        { value: "4", label: "公民身故后人体器官(角膜)遗体捐献告知书" },
        { value: "5", label: "脑死亡判定知情同意书" },
        { value: "6", label: "心死亡判定知情同意书" }
        { value: "7", label: "授权委托资料" }
      ],
      // 附件列表数据
      attachmentList: [
        // 模拟数据 - 实际项目中从接口获取
        {
          id: 1,
          type: "1",
          typeName: "人体器官潜在捐献者登记表",
          fileName: "潜在捐献者登记表_202512001.pdf",
          fileSize: 2548321,
          uploadTime: "2025-12-01 10:30:00",
          uploader: "张三",
          fileUrl: "/attachments/1.pdf"
        },
        {
          id: 2,
          type: "1",
          typeName: "人体器官潜在捐献者登记表",
          fileName: "补充说明.docx",
          fileSize: 512345,
          uploadTime: "2025-12-01 14:20:00",
          uploader: "李四",
          fileUrl: "/attachments/2.docx"
        }
      ]
      confirmAttachmentData: {
        "2": [],
        "3": [],
        "4": [],
        "7": []
      },
      // 捐献者登记阶段附件(仅查看,存储于亲属确认表 assessannex)
      registerAttachmentTypes: [
        { value: "1", label: "人体器官潜在捐献者登记表附件" },
        { value: "5", label: "证件照" },
        { value: "6", label: "疾病诊断治疗" }
      ],
      registerAttachmentData: {
        "1": [],
        "5": [],
        "6": []
      }
    };
  },
  computed: {
    isEdit() {
      return this.$route.query.confirm === "true";
    // 查看模式:只读,禁用表单并隐藏保存按钮
    isReadOnly() {
      return this.$route.query.mode === "view";
    },
    // 是否选中"成年子女"
    adultChildSelected() {
      const value = this.findAdultChildValue();
      return value != null && (this.form.kinship || []).includes(value);
    },
    // 亲属关系确认字典(优先 sys_KinshipConfirm,其次 sys_Kinship)
    kinshipDictOptions() {
      const a = this.dict.type.sys_KinshipConfirm || [];
      const b = this.dict.type.sys_Kinship || [];
      return a.length ? a : b;
    },
    // 重排亲属关系选项:子女/成年子女排倒数第二,受托人排最后
    orderedKinshipOptions() {
      const list = this.kinshipDictOptions;
      const children = list.filter(d => this.isChildLabel(d.label));
      const trustee = list.filter(d => d.label === "受托人");
      const others = list.filter(
        d => !this.isChildLabel(d.label) && d.label !== "受托人"
      );
      return [...others, ...children, ...trustee];
    },
    // 渲染勾选框实际使用的选项 = 当前字典 + 历史脏数据中映射不到的值
    // 脏值如果不在选项里,el-checkbox-group 根本没有对应复选框,视觉上就是"勾不上";
    // 补成带"(历史数据)"标记的项后既能显示勾选,也允许用户手动取消。
    // 独立成计算属性(不并入 orderedKinshipOptions),避免与 watch 互相触发形成循环。
    kinshipCheckboxOptions() {
      const options = (this.orderedKinshipOptions || []).map(d => ({
        value: String(d.value),
        label: d.label
      }));
      const extra = [];
      (this.form.kinship || []).forEach(v => {
        const raw = String(v);
        if (!raw) return;
        if (options.some(o => o.value === raw)) return;
        if (extra.some(o => o.value === raw)) return;
        const label = this.getKinshipLabel(raw);
        extra.push({
          value: raw,
          label: label === raw ? `历史数据(${raw})` : `${label}(历史数据)`
        });
      });
      return [...options, ...extra];
    },
    // 其他亲属列表(排除第一条主要亲属)
    otherFamilyMemberList() {
      return this.familyMemberList.slice(1);
    },
    // 捐献决定(查看模式只展示已勾选项)
    displayOrganDecision() {
      return (this.organdecision || [])
        .map(item => {
          if (item === "其他" && this.organdecisionOther) {
            return `其他(${this.organdecisionOther})`;
          }
          return item;
        })
        .join("、");
    },
    getCurrentTypeLabel() {
      const type = this.attachmentTypes.find(
        t => t.value === this.currentUploadType
      );
      const type =
        this.confirmAttachmentTypes.find(
          t => t.value === this.currentUploadType
        ) ||
        this.registerAttachmentTypes.find(
          t => t.value === this.currentUploadType
        );
      return type ? type.label : "";
    }
  },
  created() {
    const id = this.$route.query.id;
    this.isEdit = this.$route.query.confirm === "true";
    if (id) {
      this.getDetail(id);
  watch: {
    // 字典是异步加载的,加载完成后重新校准已保存的勾选值并重算亲属表
    orderedKinshipOptions(val) {
      if (!val || !val.length) return;
      const normalized = this.normalizeKinshipValues(this.form.kinship || []);
      if (normalized.join(",") !== (this.form.kinship || []).join(",")) {
        this.form.kinship = normalized;
      }
      this.kinshipHydrated = true;
      this.syncKinshipRelatives();
    }
    this.getAttachmentList();
  },
  created() {
    this.infoid = this.$route.query.infoid;
    this.caseId = this.$route.query.infoid;
    this.isEdit = this.$route.query.confirm === "true";
    if (this.infoid) {
      this.getDetail(this.infoid);
    }
  },
  methods: {
    // 获取详情
    getDetail(id) {
      getConfirmationDetail(id).then(response => {
        if (response.code === 200) {
          this.form = response.data;
    async getDetail(infoid) {
      this.loading = true;
      try {
        const response = await relativesList({
          infoid,
          pageNum: 1,
          pageSize: 10
        });
        // 亲属确认完整记录(含 kinship 多选字段),用于保证勾选回显
        let fullRecord = null;
        try {
          const fullRes = await listRelativesconfirmation({
            infoid,
            pageNum: 1,
            pageSize: 10
          });
          if (
            fullRes.code === 200 &&
            Array.isArray(fullRes.rows) &&
            fullRes.rows.length
          ) {
            // 必须校验 infoid 一致,否则可能拿到全表第一条记录
            const matched = fullRes.rows.find(
              r => String(r.infoid) === String(infoid)
            );
            fullRecord = matched || fullRes.rows[0];
          }
        } catch (e) {
          console.warn("获取亲属确认完整记录失败:", e);
        }
        if (response.code === 200) {
          this.handleDetailData(response, fullRecord);
          await this.loadArchiveInfo();
          // 再用主键精确拉一次完整记录,确保 kinship 一定能拿到
          await this.loadFullKinship();
        } else {
          this.$message.error("获取详情失败:" + (response.msg || "未知错误"));
        }
      } catch (error) {
        console.error("获取捐献确认详情失败:", error);
        this.$message.error("获取详情失败");
      } finally {
        this.loading = false;
      }
    },
    handleDetailData(response, fullRecord) {
      let detailData = null;
      if (response.data) {
        if (Array.isArray(response.data)) {
          detailData = response.data[0] || {};
        } else if (response.data.rows && Array.isArray(response.data.rows)) {
          detailData = response.data.rows[0] || {};
        } else if (Array.isArray(response.data.list)) {
          detailData = response.data.list[0] || {};
        } else {
          detailData = response.data;
        }
      } else if (response.rows && Array.isArray(response.rows)) {
        detailData = response.rows[0] || {};
      } else {
        detailData = response;
      }
      // 先解析亲属大字段:勾选值可能需要从它反推
      this.familyMemberList = [];
      let parsedMembers = [];
      const rawMembers =
        detailData.otherFamilyMembers ||
        (fullRecord && fullRecord.otherFamilyMembers) ||
        "";
      if (rawMembers) {
        try {
          parsedMembers =
            typeof rawMembers === "string"
              ? JSON.parse(rawMembers)
              : rawMembers || [];
          if (!Array.isArray(parsedMembers)) parsedMembers = [];
        } catch (e) {
          console.warn("解析其他家属数据失败:", e);
          parsedMembers = [];
        }
      }
      // 亲属关系确认勾选值:完整记录 → 详情 → 从亲属表反推(三重兜底)
      let kinshipRaw = null;
      if (
        fullRecord &&
        fullRecord.kinship != null &&
        fullRecord.kinship !== ""
      ) {
        kinshipRaw = fullRecord.kinship;
      } else if (detailData.kinship != null && detailData.kinship !== "") {
        kinshipRaw = detailData.kinship;
      }
      let kinshipValues = this.parseKinship(kinshipRaw);
      if (!kinshipValues.length) {
        // 兜底:亲属表能复现,说明它存的 relation 就是当时勾选的值
        kinshipValues = Array.from(
          new Set(
            parsedMembers
              .filter(
                m =>
                  m &&
                  m.isMain !== true &&
                  m.relation != null &&
                  m.relation !== ""
              )
              .map(m => String(m.relation).trim())
              .filter(v => v !== "")
          )
        );
      }
      this.form = {
        ...this.form,
        id: detailData.id || this.$route.query.id,
        infoid: detailData.infoid || this.infoid,
        caseNo: detailData.caseNo || "",
        name: detailData.name || "",
        sex: detailData.sex || "",
        age: detailData.age || "",
        diagnosisname: detailData.diagnosisname || "",
        treatmenthospitalname: detailData.treatmenthospitalname || "",
        coordinatedusernameo: detailData.coordinatedusernameo || "",
        coordinatedusernamet: detailData.coordinatedusernamet || "",
        responsibleusername: detailData.responsibleusername || "",
        acquisitiontissuename: "",
        relativeconfirmationsign: detailData.relativeconfirmationsign || "0",
        signdate: this.normalizeSigndate(detailData.signdate),
        relativeconfirmationsignname:
          detailData.relativeconfirmationsignname || "",
        signfamilyrelations: detailData.signfamilyrelations || "",
        relativeidcardno: detailData.relativeidcardno || "",
        relativephone: detailData.relativephone || "",
        phone: detailData.phone || detailData.relativephone || "",
        kinship: kinshipValues,
        kinshipChildrennum:
          detailData.kinshipChildrennum != null &&
          detailData.kinshipChildrennum !== ""
            ? detailData.kinshipChildrennum
            : fullRecord && fullRecord.kinshipChildrennum != null
            ? fullRecord.kinshipChildrennum
            : null,
        relativeRemark: detailData.relativeRemark || "",
        assessannex: detailData.assessannex || ""
      };
      // 主要亲属:优先取 JSON 中标记 isMain 的项(新格式,含住址/现居住地)
      let mainMember = (parsedMembers || []).find(m => m && m.isMain === true);
      // 命中项在解析数组中的位置;-1 表示主亲属不来自 otherFamilyMembers
      let mainIndex = mainMember ? parsedMembers.indexOf(mainMember) : -1;
      if (!mainMember) {
        const hasMain =
          detailData.relativeconfirmationsignname ||
          detailData.signfamilyrelations ||
          detailData.relativeidcardno;
        if (hasMain) {
          // 老格式:主亲属存在专用字段里,otherFamilyMembers 只存"其他亲属"
          mainMember = {
            name: detailData.relativeconfirmationsignname || "",
            relation: detailData.signfamilyrelations || "",
            idcard: detailData.relativeidcardno || "",
            phone: detailData.phone || detailData.relativephone || "",
            address: "",
            currentResidence: "",
            isMain: true
          };
        } else if (parsedMembers.length) {
          // 脏数据兜底:主亲属专用字段全空、JSON 里也没有 isMain 标记时,
          // 把第一条当作主亲属,其余作为其他亲属,避免主亲属整块空白
          mainMember = parsedMembers[0];
          mainIndex = 0;
        }
      }
      this.familyMemberList.push({
        name: (mainMember && mainMember.name) || "",
        relation: (mainMember && mainMember.relation) || "",
        idcard: (mainMember && mainMember.idcard) || "",
        phone: (mainMember && mainMember.phone) || "",
        address: this.normalizeAddress(mainMember && mainMember.address),
        currentResidence: this.normalizeAddress(
          mainMember && mainMember.currentResidence
        ),
        isMain: true
      });
      // 其他亲属(姓名来自 JSON,关系按 kinship 勾选生成)
      // 历史脏数据的行没有 isMain / seq / 住址字段,这里统一补齐默认值
      (parsedMembers || []).forEach((member, idx) => {
        if (!member) return;
        if (idx === mainIndex) return;
        if (member.isMain === true) return;
        this.familyMemberList.push({
          relation: member.relation != null ? String(member.relation) : "",
          seq: member.seq != null ? member.seq : 0,
          name: member.name || "",
          phone: member.phone || "",
          idcard: member.idcard || "",
          address: this.normalizeAddress(member.address),
          currentResidence: this.normalizeAddress(member.currentResidence),
          isMain: false
        });
      });
      // 字典若已缓存,watch 不会触发,这里主动把勾选值归一化一次
      this.form.kinship = this.normalizeKinshipValues(this.form.kinship || []);
      // 根据 kinship 勾选自动生成亲属表行(保留已有姓名)
      this.kinshipHydrated = true;
      this.syncKinshipRelatives();
      if (detailData.organdecision) {
        this.organdecision = Array.isArray(detailData.organdecision)
          ? detailData.organdecision
          : detailData.organdecision.split(",");
      }
      this.organdecisionOther = detailData.organdecisionOther || "";
      this.processAssessannexData();
      // 调试用:确认 kinship / 子女数量 / 亲属大字段 是否都回来了
      console.log("[affirmInfo] 亲属关系回显", {
        kinship: this.form.kinship,
        kinshipChildrennum: this.form.kinshipChildrennum,
        otherFamilyMembers: this.form.otherFamilyMembers
      });
    },
    // 获取附件列表
    getAttachmentList() {
      this.attachmentLoading = true;
      // 模拟附件数据
      this.attachmentList = [
        {
          id: 1,
          type: "1",
          typeName: "人体器官潜在捐献者登记表",
          fileName: "潜在捐献者登记表_202512001.pdf",
          uploadTime: "2025-12-01 10:30:00",
          uploader: "张三",
          fileSize: "2.5MB",
          fileUrl: "/attachments/1.pdf"
        },
        {
          id: 2,
          type: "2",
          typeName: "人体器官捐献亲属确认登记表",
          fileName: "亲属确认登记表_202512001.pdf",
          uploadTime: "2025-12-01 14:20:00",
          uploader: "李四",
          fileSize: "1.8MB",
          fileUrl: "/attachments/2.pdf"
        },
        {
          id: 3,
          type: "3",
          typeName: "捐献者及直系亲属身份证、户口簿相关证明",
          fileName: "身份证明_202512001.zip",
          uploadTime: "2025-12-01 16:45:00",
          uploader: "王五",
          fileSize: "5.2MB",
          fileUrl: "/attachments/3.zip"
        },
        {
          id: 4,
          type: "4",
          typeName: "公民身故后人体器官(角膜)遗体捐献告知书",
          fileName: "捐献告知书_202512001.pdf",
          uploadTime: "2025-12-02 09:15:00",
          uploader: "张三",
          fileSize: "1.2MB",
          fileUrl: "/attachments/4.pdf"
        },
        {
          id: 5,
          type: "5",
          typeName: "脑死亡判定知情同意书",
          fileName: "脑死亡判定同意书_202512001.pdf",
          uploadTime: "2025-12-02 11:30:00",
          uploader: "李四",
          fileSize: "0.8MB",
          fileUrl: "/attachments/5.pdf"
        },
        {
          id: 6,
          type: "6",
          typeName: "心死亡判定知情同意书",
          fileName: "心死亡判定同意书_202512001.pdf",
          uploadTime: "2025-12-02 13:20:00",
          uploader: "王五",
          fileSize: "0.9MB",
          fileUrl: "/attachments/6.pdf"
    // 加载档案表信息(人体器官获取组织等字段属于档案表,不在亲属确认表)
    async loadArchiveInfo() {
      if (!this.caseId) return;
      try {
        const caseRes = await getDonatebaseinfo(this.caseId);
        if (caseRes.code === 200 && caseRes.data) {
          this.archiveData = caseRes.data;
          this.form.acquisitiontissuename =
            caseRes.data.acquisitiontissuename || "";
        }
      ];
      this.attachmentLoading = false;
      } catch (e) {
        console.warn("获取档案表信息失败:", e);
      }
    },
    // 下载附件
    handleDownload(row) {
      // 实际项目中这里调用文件下载接口
      this.$message.success(`下载文件: ${row.fileName}`);
      console.log("下载文件:", row.fileUrl);
    // 用主键精确拉取完整记录,补齐列表接口可能缺失的 kinship 字段
    async loadFullKinship() {
      const rid = this.form.id || this.$route.query.id;
      if (!rid) return;
      try {
        const res = await getRelativesconfirmation(rid);
        const data = res && res.data;
        if (!data) return;
        let changed = false;
        if (data.kinship != null && data.kinship !== "") {
          const values = this.parseKinship(data.kinship);
          // 接口返回的真实值优先于从 otherFamilyMembers 反推的兜底值
          // (反推值只用于字段缺失的情况,属于降级真相源)
          if (
            values.length &&
            values.join(",") !== (this.form.kinship || []).join(",")
          ) {
            this.form.kinship = values;
            changed = true;
          }
        }
        if (
          (this.form.kinshipChildrennum == null ||
            this.form.kinshipChildrennum === "") &&
          data.kinshipChildrennum != null
        ) {
          this.form.kinshipChildrennum = data.kinshipChildrennum;
          changed = true;
        }
        if (changed) {
          this.kinshipHydrated = true;
          this.syncKinshipRelatives();
        }
      } catch (e) {
        console.warn("获取亲属确认完整记录失败(getInfo):", e);
      }
    },
    // 保存确认信息
    handleSave() {
      this.$refs.form.validate(valid => {
        if (valid) {
          updateConfirmation(this.form).then(response => {
            if (response.code === 200) {
              this.$message.success("保存成功");
              this.isEdit = false;
              this.$router.push("/case/confirmation");
    // 把已保存的勾选值映射到当前字典的 value(兼容两套字典编码不一致)
    // 注意:映射不到的历史脏值(如 relation="4"/"6")必须原样保留,不能丢弃;
    // 字典是异步加载的,若在此处过滤,字典一到位就会把反推出来的勾选清空。
    normalizeKinshipValues(values) {
      const options = this.orderedKinshipOptions || [];
      if (!options.length) return values;
      const result = [];
      (values || []).forEach(v => {
        const mapped = this.toCurrentKinshipValue(v);
        if (mapped !== "" && !result.includes(mapped)) result.push(mapped);
      });
      return result;
    },
    // 单个关系值 → 当前字典体系下的值
    // 命中顺序:当前字典 value → 当前字典同名 label → 子女类归一化(子女 ⇄ 成年子女)
    // 全都命中不了说明是历史脏数据,原样返回,保证表格行不丢、勾选不被抹掉
    toCurrentKinshipValue(v) {
      if (v == null || v === "") return "";
      const options = this.orderedKinshipOptions || [];
      if (!options.length) return String(v);
      const raw = String(v);
      if (options.some(o => String(o.value) === raw)) return raw;
      const label = this.getKinshipLabel(raw);
      if (!label || label === raw) return raw; // 三个字典都查不到 → 脏值原样保留
      const sameLabel = options.find(o => o.label === label);
      if (sameLabel) return String(sameLabel.value);
      // 子女类关系在不同字典里叫法不同("子女"/"成年子女"),视为同一关系
      if (this.isChildLabel(label)) {
        const exact = options.find(o => o.label === "成年子女");
        if (exact) return String(exact.value);
        const any = options.find(o => this.isChildLabel(o.label));
        if (any) return String(any.value);
      }
      return raw;
    },
    // 解析亲属关系确认(多选,; 或 , 分隔,统一转为字符串数组)
    parseKinship(val) {
      if (!val) return [];
      const arr = Array.isArray(val) ? val : String(val).split(/[;,]/);
      return arr
        .map(v => (v == null ? "" : String(v).trim()))
        .filter(v => v !== "");
    },
    // 签署日期统一转成 yyyy-MM-dd,兼容后端返回的多种日期格式
    normalizeSigndate(val) {
      if (!val) return null;
      const m = String(val).match(/^(\d{4}-\d{2}-\d{2})/);
      return m ? m[1] : null;
    },
    processAssessannexData() {
      Object.keys(this.confirmAttachmentData).forEach(key => {
        this.confirmAttachmentData[key] = [];
      });
      Object.keys(this.registerAttachmentData).forEach(key => {
        this.registerAttachmentData[key] = [];
      });
      if (!this.form.assessannex) return;
      try {
        const annexData =
          typeof this.form.assessannex === "string"
            ? JSON.parse(this.form.assessannex)
            : this.form.assessannex;
        if (Array.isArray(annexData)) {
          annexData.forEach(attachment => {
            const type = String(attachment.type || "");
            // 确认阶段(2/3/4)与登记阶段(1/5/6)均维护在 assessannex
            if (this.confirmAttachmentData[type]) {
              this.confirmAttachmentData[type].push(attachment);
            } else if (this.registerAttachmentData[type]) {
              this.registerAttachmentData[type].push(attachment);
            }
          });
        }
      });
    },
 // 根据类型获取附件
    getAttachmentsByType(type) {
      return this.attachmentList.filter(item => item.type === type);
      } catch (error) {
        console.warn("assessannex数据解析失败:", error);
      }
    },
    // 打开上传对话框
    getConfirmAttachments(type) {
      return this.confirmAttachmentData[type] || [];
    },
    getRegisterAttachments(type) {
      return this.registerAttachmentData[type] || [];
    },
    openUploadDialog(type) {
      this.currentUploadType = type;
      this.tempFileList = [];
@@ -486,164 +1157,572 @@
      });
    },
    // 上传前校验
    beforeUpload(file) {
      const allowedTypes = [
        'application/pdf',
        'image/jpeg',
        'image/png',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        "application/pdf",
        "image/jpeg",
        "image/png",
        "application/msword",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
      ];
      const maxSize = 10 * 1024 * 1024; // 10MB
      // 校验文件类型
      const isTypeOk = allowedTypes.includes(file.type) ||
                      file.name.endsWith('.pdf') ||
                      file.name.endsWith('.jpg') ||
                      file.name.endsWith('.jpeg') ||
                      file.name.endsWith('.png') ||
                      file.name.endsWith('.doc') ||
                      file.name.endsWith('.docx');
      const maxSize = 10 * 1024 * 1024;
      const isTypeOk =
        allowedTypes.includes(file.type) ||
        file.name.endsWith(".pdf") ||
        file.name.endsWith(".jpg") ||
        file.name.endsWith(".jpeg") ||
        file.name.endsWith(".png") ||
        file.name.endsWith(".doc") ||
        file.name.endsWith(".docx");
      if (!isTypeOk) {
        this.$message.error('文件格式不支持,请上传pdf、jpg、png、doc或docx格式文件');
        this.$message.error(
          "文件格式不支持,请上传pdf、jpg、png、doc或docx格式文件"
        );
        return false;
      }
      // 校验文件大小
      if (file.size > maxSize) {
        this.$message.error('文件大小不能超过10MB');
        this.$message.error("文件大小不能超过10MB");
        return false;
      }
      return true;
    },
    // 文件选择变化
    handleFileChange(file, fileList) {
      this.tempFileList = fileList;
    },
    // 移除临时文件
    handleTempRemove(file, fileList) {
      this.tempFileList = fileList;
    },
    // 自定义上传请求
    handleHttpRequest(options) {
      // 模拟上传过程
      return new Promise((resolve, reject) => {
        this.uploadLoading = true;
        // 模拟上传延迟
        setTimeout(() => {
          const newAttachment = {
            id: Date.now(),
            type: this.currentUploadType,
            typeName: this.getCurrentTypeLabel,
            fileName: options.file.name,
            fileSize: options.file.size,
            uploadTime: new Date().toISOString(),
            uploader: '当前用户', // 实际项目中从用户信息获取
            fileUrl: URL.createObjectURL(options.file) // 临时URL,实际项目中为服务器返回的URL
          };
          this.attachmentList.push(newAttachment);
          this.uploadLoading = false;
          resolve({ code: 200, data: newAttachment });
        }, 1500);
      });
    },
    // 提交上传
    async submitUpload() {
      if (this.tempFileList.length === 0) {
        this.$message.warning('请先选择要上传的文件');
    handleUploadSuccess(response, file) {
      if (response.code !== 200) {
        this.$message.error(response.msg || "上传失败");
        return;
      }
      try {
        // 依次上传所有文件
        for (const file of this.tempFileList) {
          await this.$refs.uploadRef.submit();
        }
        this.$message.success('文件上传成功');
        this.uploadDialogVisible = false;
        this.tempFileList = [];
      } catch (error) {
        this.$message.error('文件上传失败');
        console.error('上传失败:', error);
      const newAttachment = {
        id: Date.now(),
        fileName: file.name,
        fileUrl: response.url,
        fileSize: file.size,
        fileType: this.getFileExtension(file.name),
        type: this.currentUploadType,
        uploadTime: this.getCurrentTime(),
        uploader: "当前用户"
      };
      if (this.confirmAttachmentData[this.currentUploadType]) {
        this.confirmAttachmentData[this.currentUploadType].push(newAttachment);
      } else if (this.registerAttachmentData[this.currentUploadType]) {
        this.registerAttachmentData[this.currentUploadType].push(newAttachment);
      }
      this.updateAssessannexField();
      this.$message.success("上传成功");
      this.uploadLoading = false;
      this.uploadDialogVisible = false;
      this.tempFileList = [];
    },
    // 删除附件
    handleRemoveAttachment(attachment, type) {
      this.$confirm('确定要删除这个附件吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        const index = this.attachmentList.findIndex(item => item.id === attachment.id);
        if (index !== -1) {
          this.attachmentList.splice(index, 1);
          this.$message.success('附件删除成功');
    submitUpload() {
      if (this.tempFileList.length === 0) {
        this.$message.warning("请先选择要上传的文件");
        return;
      }
      this.uploadLoading = true;
      this.$refs.uploadRef.submit();
    },
          // 实际项目中调用删除接口
          // this.deleteAttachment(attachment.id);
    handleRemoveAttachment(type, index) {
      this.$confirm("确定要删除这个附件吗?", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          const container =
            this.confirmAttachmentData[type] || this.registerAttachmentData[type];
          if (container && container[index]) {
            container.splice(index, 1);
            this.$message.success("附件删除成功");
            this.updateAssessannexField();
          }
        })
        .catch(() => {});
    },
    // 根据 kinship 勾选自动生成亲属表行,同时保留已有亲属数据(合并,避免丢失)
    syncKinshipRelatives() {
      // 勾选值统一映射到当前字典体系,保证与表格行的 relation 处在同一值域。
      // 必须去重:历史脏数据里多个不同编码(如 "4"、"6")可能映射成同一个 value,
      // 不去重的话每个重复值都会再生成一遍行,表现就是"行数成倍变多"。
      const kinship = Array.from(
        new Set(
          (this.form.kinship || [])
            .map(v => this.toCurrentKinshipValue(v))
            .filter(v => v !== "")
        )
      );
      const adultChildValue = this.findAdultChildValue();
      // 已有亲属(非主要亲属),保留姓名与关系;关系值统一映射到当前字典体系
      const existing = this.familyMemberList
        .slice(1)
        .filter(m => m && !m.isMain)
        .map(m => ({ ...m, relation: this.toCurrentKinshipValue(m.relation) }));
      // 子女数量:接口可能不返回该字段
      // 回显时用已有子女行数兜底;用户主动清空("")则不兜底,行照常清掉
      const existingChildCount = adultChildValue
        ? existing.filter(e => e.relation === adultChildValue).length
        : 0;
      const rawCountStr = this.form.kinshipChildrennum;
      // null / "" / 数字 0 都视为"没填数量"(真要 0 个,取消勾选即可)
      const rawMissing =
        rawCountStr == null || rawCountStr === "" || rawCountStr === 0;
      let childCount;
      if (rawMissing) {
        childCount = this.kinshipHydrated ? existingChildCount : 0;
        // 回显兜底:把推算出的数量写回表单,否则数量框空白、保存时又丢
        // (程序赋值不会触发 el-input 的 @input,不会形成循环)
        if (this.kinshipHydrated && existingChildCount > 0) {
          this.form.kinshipChildrennum = String(existingChildCount);
        }
      }).catch(() => {});
    },
    // 预览附件
    handlePreview(attachment) {
      // 实际项目中根据文件类型调用不同的预览方式
      if (attachment.fileName.endsWith('.pdf')) {
        // PDF预览
        window.open(attachment.fileUrl, '_blank');
      } else if (attachment.fileName.match(/\.(jpg|jpeg|png)$/i)) {
        // 图片预览
        this.$alert(`<img src="${attachment.fileUrl}" style="max-width: 100%;" alt="${attachment.fileName}">`,
          '图片预览', {
            dangerouslyUseHTMLString: true,
            customClass: 'image-preview-dialog'
          });
      } else {
        // 其他文件类型提示下载
        this.$message.info('该文件类型暂不支持在线预览,请下载后查看');
        const n = parseInt(rawCountStr, 10);
        childCount = isNaN(n) || n < 0 ? 0 : n;
      }
      // 取一行已有数据复用姓名:先按 关系+序号 精确匹配,再退化为同关系顺序取
      const used = new Set();
      const takeExisting = (relation, seq) => {
        let idx = existing.findIndex(
          (e, i) =>
            !used.has(i) && e.relation === relation && (e.seq || 0) === seq
        );
        if (idx === -1) {
          idx = existing.findIndex(
            (e, i) => !used.has(i) && e.relation === relation
          );
        }
        if (idx === -1) return null;
        used.add(idx);
        return existing[idx];
      };
      // 生成一行:命中已有行时整行复用(含 phone/idcard/住址等),
      // 历史脏数据里这些字段可能有值,重建时不能只保留 relation/name 把其余抹掉
      const buildRow = (relation, seq, hit) => ({
        relation: relation,
        seq: seq,
        name: (hit && hit.name) || "",
        phone: (hit && hit.phone) || "",
        idcard: (hit && hit.idcard) || "",
        address: this.normalizeAddress(hit && hit.address),
        currentResidence: this.normalizeAddress(hit && hit.currentResidence),
        isMain: false
      });
      const merged = [];
      kinship.forEach(rel => {
        if (adultChildValue && rel === adultChildValue) {
          // 成年子女:子女数量决定行数
          for (let i = 0; i < childCount; i++) {
            const hit = takeExisting(rel, i);
            merged.push(buildRow(rel, i, hit));
          }
        } else {
          const hit = takeExisting(rel, 0);
          merged.push(buildRow(rel, 0, hit));
        }
      });
      // 未被勾选命中的已有亲属一律保留。
      // 不能按关系去重:历史数据里"两个兄弟姐妹"这类同关系多行是合法的,
      // 去重会静默丢人。之前出现"同关系重复行"的根因是 takeExisting 没命中,
      // 现在 used 保证每个已有行最多被消耗一次,不会重复生成。
      existing.forEach((e, idx) => {
        if (used.has(idx)) return;
        // 子女行数由子女数量决定,多余的子女行不保留
        if (adultChildValue && e.relation === adultChildValue) return;
        merged.push(buildRow(e.relation || "", e.seq != null ? e.seq : 0, e));
      });
      this.familyMemberList = [this.familyMemberList[0], ...merged];
      this.updateFamilyMemberField();
      // 兜底只在回显那一次生效
      this.kinshipHydrated = false;
    },
    // 删除亲属表某一行,并同步取消上方对应的关系勾选
    handleDeleteFamilyRow(index) {
      const rowIndex = index + 1; // otherFamilyMemberList = familyMemberList.slice(1)
      const row = this.familyMemberList[rowIndex];
      if (!row) return;
      if (row.isMain) return;
      const relation = row.relation
        ? this.toCurrentKinshipValue(row.relation)
        : "";
      const adultChildValue = this.findAdultChildValue();
      const isChild = !!adultChildValue && relation === adultChildValue;
      // 勾选里存的可能是历史编码(如 "4")而不是映射后的值(如 "2"),
      // 取消勾选时两种都要比对,否则会出现"行删了但勾选还在"
      const removeKinship = target => {
        const mapped = this.toCurrentKinshipValue(target);
        this.form.kinship = (this.form.kinship || []).filter(
          v =>
            String(v) !== String(target) &&
            this.toCurrentKinshipValue(v) !== mapped
        );
      };
      console.log("[affirmInfo] 删除亲属行", {
        index,
        relation,
        adultChildValue,
        isChild,
        before: this.familyMemberList.slice(1).map(m => m.relation),
        kinship: this.form.kinship
      });
      if (isChild) {
        // 成年子女:直接删这一行,数量按剩余子女行数重算,归零才取消勾选
        this.familyMemberList.splice(rowIndex, 1);
        const rest = this.familyMemberList.filter(
          (m, i) =>
            i > 0 &&
            !m.isMain &&
            this.toCurrentKinshipValue(m.relation) === adultChildValue
        ).length;
        if (rest > 0) {
          this.form.kinshipChildrennum = String(rest);
        } else {
          this.form.kinshipChildrennum = "";
          removeKinship(adultChildValue);
        }
      } else if (relation) {
        // 其余关系:一行对应一个勾选,取消勾选并整组清掉
        removeKinship(relation);
        this.familyMemberList = this.familyMemberList.filter(
          (m, i) =>
            i === 0 ||
            m.isMain ||
            this.toCurrentKinshipValue(m.relation) !== relation
        );
      } else {
        // 关系为空的历史遗留行,只删这一行
        this.familyMemberList.splice(rowIndex, 1);
      }
      // 直接回写大字段。不调用 syncKinshipRelatives,避免行被按勾选重新生成回来
      this.updateFamilyMemberField();
      if (relation) {
        this.$message.success(
          `已移除「${this.getKinshipLabel(relation)}」并同步取消勾选`
        );
      }
    },
    // 保存所有附件信息
    handleSaveAll() {
      this.saveLoading = true;
      // 模拟保存过程
      setTimeout(() => {
        this.$message.success('附件信息保存成功');
        this.saveLoading = false;
        // 实际项目中调用保存接口
        // this.saveAttachments();
      }, 1000);
    getKinshipLabel(value) {
      if (value == null || value === "") return "";
      const dictLists = [
        this.dict.type.sys_KinshipConfirm,
        this.dict.type.sys_Kinship,
        this.dict.type.sys_FamilyRelation
      ];
      for (const list of dictLists) {
        const dict = (list || []).find(d => String(d.value) === String(value));
        if (dict) return dict.label;
      }
      // 三个字典都查不到 → 老编码/脏数据,按原值展示,并记录一次便于排查
      if (!this._warnedKinshipValues) this._warnedKinshipValues = new Set();
      const key = String(value);
      if (!this._warnedKinshipValues.has(key)) {
        this._warnedKinshipValues.add(key);
        console.warn(
          `[affirmInfo] 亲属关系编码「${key}」在 sys_KinshipConfirm / sys_Kinship / sys_FamilyRelation 中均不存在,按原值展示`
        );
      }
      return value;
    },
    // 文件大小格式化
    isChildLabel(label) {
      return label === "子女" || label === "成年子女";
    },
    findAdultChildValue() {
      const list = this.kinshipDictOptions || [];
      // 优先精确匹配"成年子女",避免把"子女"当成成年子女处理
      const exact = list.find(d => d.label === "成年子女");
      if (exact) return String(exact.value);
      const any = list.find(d => this.isChildLabel(d.label));
      return any ? String(any.value) : null;
    },
    formatAddress(addr) {
      if (!addr) return "";
      if (typeof addr === "string") return addr;
      return [addr.sheng, addr.shi, addr.qu, addr.detail]
        .filter(p => p)
        .join("");
    },
    normalizeAddress(addr) {
      if (!addr) {
        return { sheng: "", shi: "", qu: "", detail: "" };
      }
      if (typeof addr === "string") {
        return { sheng: "", shi: "", qu: "", detail: addr };
      }
      return {
        sheng: (addr && addr.sheng) || "",
        shi: (addr && addr.shi) || "",
        qu: (addr && addr.qu) || "",
        detail: (addr && addr.detail) || ""
      };
    },
    updateFamilyMemberField() {
      const mainMember = this.familyMemberList[0];
      if (mainMember) {
        this.form.relativeconfirmationsignname = mainMember.name || "";
        this.form.signfamilyrelations = mainMember.relation || "";
        this.form.relativeidcardno = mainMember.idcard || "";
        this.form.phone = mainMember.phone || "";
        this.form.relativephone = mainMember.phone || "";
      } else {
        this.form.relativeconfirmationsignname = "";
        this.form.signfamilyrelations = "";
        this.form.relativeidcardno = "";
        this.form.phone = "";
        this.form.relativephone = "";
      }
      // 所有亲属(含主要亲属)统一存入一个 JSON 大字段,住址/现居住地也存这里
      const allMembers = this.familyMemberList.map((member, index) => ({
        name: member.name || "",
        relation: member.relation || "",
        idcard: member.idcard || "",
        phone: member.phone || "",
        address: member.address || "",
        currentResidence: member.currentResidence || "",
        seq: member.seq != null ? member.seq : 0,
        isMain: index === 0
      }));
      this.form.otherFamilyMembers = JSON.stringify(allMembers);
    },
    updateAssessannexField() {
      const allAttachments = [];
      [
        ...Object.values(this.confirmAttachmentData),
        ...Object.values(this.registerAttachmentData)
      ].forEach(attachments => {
        allAttachments.push(...attachments);
      });
      this.form.assessannex = JSON.stringify(allAttachments);
    },
    handlePreview(file) {
      this.currentPreviewFile = {
        fileName: file.fileName,
        fileUrl: file.fileUrl,
        fileType: this.getFileType(file.fileName)
      };
      this.previewVisible = true;
    },
    handleDownload(file) {
      const fileUrl = file.fileUrl;
      const fileName = file.fileName;
      if (fileUrl) {
        const link = document.createElement("a");
        link.href = fileUrl;
        link.download = fileName;
        link.style.display = "none";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        this.$message.success("开始下载文件");
      } else {
        this.$message.warning("文件路径不存在,无法下载");
      }
    },
    getFileType(fileName) {
      if (!fileName) return "other";
      const extension = this.getFileExtension(fileName);
      const imageTypes = ["jpg", "jpeg", "png"];
      const pdfTypes = ["pdf"];
      const officeTypes = ["doc", "docx"];
      if (imageTypes.includes(extension)) return "image";
      if (pdfTypes.includes(extension)) return "pdf";
      if (officeTypes.includes(extension)) return "office";
      return "other";
    },
    getFileExtension(filename) {
      return filename
        .split(".")
        .pop()
        .toLowerCase();
    },
    formatFileSize(size) {
      if (size === 0) return '0 B';
      if (!size) return "0 B";
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const sizes = ["B", "KB", "MB", "GB"];
      const i = Math.floor(Math.log(size) / Math.log(k));
      return parseFloat((size / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
      return parseFloat((size / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
    },
    // 时间格式化
    parseTime(time) {
      if (!time) return '';
      const date = new Date(time);
      return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
    formatDateTime(dateTime) {
      if (!dateTime) return "";
      try {
        const date = new Date(dateTime);
        if (isNaN(date.getTime())) return dateTime;
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, "0");
        const day = String(date.getDate()).padStart(2, "0");
        const hours = String(date.getHours()).padStart(2, "0");
        const minutes = String(date.getMinutes()).padStart(2, "0");
        return `${year}-${month}-${day} ${hours}:${minutes}`;
      } catch (error) {
        return dateTime;
      }
    },
    getCurrentTime() {
      const now = new Date();
      return `${now.getFullYear()}-${(now.getMonth() + 1)
        .toString()
        .padStart(2, "0")}-${now
        .getDate()
        .toString()
        .padStart(2, "0")} ${now
        .getHours()
        .toString()
        .padStart(2, "0")}:${now
        .getMinutes()
        .toString()
        .padStart(2, "0")}:${now
        .getSeconds()
        .toString()
        .padStart(2, "0")}`;
    },
    accomplish() {
      this.$confirm("是否完成该案例捐献确认步骤?", "提醒", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning"
      })
        .then(() => {
          this.form.state = 3;
          this.handleSave();
        })
        .catch(() => {});
    },
    async handleSave() {
      if (this.saveLoading) return;
      try {
        await this.$refs.form.validate();
        this.saveLoading = true;
        this.updateFamilyMemberField();
        this.updateAssessannexField();
        const saveData = {
          ...this.form,
          infoid: this.infoid,
          organdecision: this.organdecision.join(","),
          organdecisionOther: this.organdecisionOther,
          kinship: (this.form.kinship || []).join(",")
        };
        // 人体器官获取组织属于档案表字段,不随亲属确认表保存
        delete saveData.acquisitiontissuename;
        // 签署日期为空时不提交,避免后端 Date 反序列化报错
        if (!saveData.signdate) {
          delete saveData.signdate;
        }
        if (saveData.state == 1 || !saveData.state) {
          saveData.state = 2;
        }
        // 新增时校验是否已有相同患者
        if (!saveData.id) {
          const exists = await this.checkPatientExists();
          if (exists) {
            this.$message.warning("该患者已存在,请勿重复添加");
            return;
          }
        }
        let response = null;
        if (saveData.id) {
          response = await relativesEdit(saveData);
        } else {
          response = await relativesAdd(saveData);
        }
        if (response.code === 200) {
          // 人体器官获取组织单独保存到档案表
          await this.saveArchiveAcquisitionTissue();
          this.$message.success("保存成功");
          this.$router.push("/case/affirm");
        } else {
          this.$message.error("保存失败:" + (response.msg || "未知错误"));
        }
      } catch (error) {
        if (error !== "cancel") {
          console.error("保存失败:", error);
          this.$message.error("保存失败");
        }
      } finally {
        this.saveLoading = false;
      }
    },
    // 保存档案表的人体器官获取组织字段
    async saveArchiveAcquisitionTissue() {
      if (!this.caseId) return;
      try {
        const payload = {
          ...(this.archiveData || {}),
          id: this.caseId,
          acquisitiontissuename: this.form.acquisitiontissuename
        };
        await updateDonatebaseinfo(payload);
      } catch (e) {
        console.warn("保存档案表人体器官获取组织失败:", e);
      }
    },
    // 新增时校验是否已有相同患者(按住院号)
    async checkPatientExists() {
      let inpatientno = this.form.inpatientno;
      if (!inpatientno && this.caseId) {
        try {
          const caseRes = await getDonatebaseinfo(this.caseId);
          inpatientno = (caseRes.data && caseRes.data.inpatientno) || "";
        } catch (e) {
          inpatientno = "";
        }
      }
      if (!inpatientno) return false;
      try {
        const res = await relativesList({
          inpatientno,
          pageNum: 1,
          pageSize: 1
        });
        const list = res.data || [];
        return list.some(item => item && item.id != null && item.id !== "");
      } catch (e) {
        return false;
      }
    }
  }
};
@@ -658,24 +1737,9 @@
  margin-bottom: 20px;
}
.attachment-card {
  margin-bottom: 20px;
}
.detail-title {
  font-size: 16px;
.main-family {
  font-weight: bold;
}
.fixed-width .el-button {
  margin: 0 5px;
}
.confirmation-detail {
  padding: 20px;
}
.detail-card {
  margin-bottom: 20px;
  color: #e6a23c;
}
.attachment-card {
@@ -685,17 +1749,20 @@
.detail-title {
  font-size: 16px;
  font-weight: bold;
  margin-right: 20px;
}
.attachment-upload-section {
  padding: 10px;
.attachment-section {
  padding: 15px;
}
.upload-header {
.attachment-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  padding-bottom: 10px;
  border-bottom: 1px solid #ebeef5;
}
.upload-title {
@@ -704,24 +1771,161 @@
  color: #303133;
}
.file-info {
  display: flex;
  align-items: center;
.attachment-list {
  margin-top: 15px;
}
.empty-attachment {
  text-align: center;
  padding: 40px 0;
  padding: 30px 0;
  color: #909399;
}
/* 图片预览对话框样式 */
:deep(.image-preview-dialog) {
  width: auto;
  max-width: 90vw;
.file-name {
  font-size: 13px;
  color: #606266;
}
:deep(.image-preview-dialog .el-message-box__content) {
/* ===== 纸质表格网格样式 ===== */
.paper-card >>> .el-card__body {
  padding: 16px;
}
.paper-form {
  border-left: 1px solid #d0d3db;
  background: #fff;
}
.paper-form >>> .form-row {
  display: flex;
  width: 100%;
  border-top: 1px solid #d0d3db;
  /* 上移1px与上一行的border-bottom重叠,两行之间只显示一条1px细线 */
  margin-top: -1px;
}
.paper-form >>> .form-row:first-child {
  margin-top: 0;
}
/* 行尾空白填充:不满整行的右侧自动补一个空白格子,网格保持闭合(右侧适当留白) */
.paper-form >>> .form-row::after {
  content: "";
  flex: 1 1 0;
  min-height: 42px;
  background: linear-gradient(to left, #d0d3db 1px, transparent 1px),
    linear-gradient(to top, #d0d3db 1px, transparent 1px);
  background-color: #fff;
}
.paper-form >>> .el-form-item {
  flex: 0 0 33.3333%;
  display: flex;
  margin: 0;
  background: #fff;
  border-right: 1px solid #d0d3db;
  border-bottom: 1px solid #d0d3db;
}
.paper-form >>> .el-form-item::after,
.paper-form >>> .el-form-item::before {
  display: none;
}
.paper-form >>> .col-1-3 {
  flex: 0 0 33.3333%;
}
.paper-form >>> .col-half {
  flex: 0 0 50%;
}
.paper-form >>> .col-2-3 {
  flex: 0 0 66.6666%;
}
.paper-form >>> .col-full {
  flex: 0 0 100%;
}
.paper-form >>> .el-form-item__label {
  flex: 0 0 150px;
  width: 150px !important;
  float: none;
  padding: 0 8px !important;
  background: #ecf5ff;
  border-right: 1px solid #d0d3db;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: #2c3038;
  line-height: 1.3;
  white-space: nowrap;
}
.paper-form
  >>> .el-form-item.is-required:not(.is-no-asterisk)
  > .el-form-item__label:before {
  color: #f56c6c;
  margin-right: 2px;
}
.paper-form >>> .el-form-item__content {
  flex: 1;
  margin-left: 0 !important;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  padding: 6px 10px;
  background: #fff;
  min-height: 42px;
  line-height: 1.4;
}
/* 控件宽度适中,不铺满整格(内容短时输入框不再拉得很长);窄屏时自动收缩避免溢出 */
.paper-form >>> .el-form-item__content .el-input,
.paper-form >>> .el-form-item__content .el-select,
.paper-form >>> .el-form-item__content .el-cascader {
  width: 100%;
  max-width: 220px;
}
.paper-form >>> .el-form-item__content .el-date-editor {
  width: 100%;
  max-width: 240px;
}
.paper-form >>> .el-form-item__content .el-range-editor {
  width: 360px;
  max-width: 100%;
}
/* 地址选择器(省市区三级联动 + 详细地址,详细地址分行显示) */
.paper-form >>> .area-select-box {
  flex: 0 0 auto;
  width: 480px;
  max-width: 100%;
}
.paper-form >>> .address-detail {
  flex: 0 0 100%;
  width: 100% !important;
  margin: 6px 0 0 0;
}
/* 捐献决定语句排版 */
.paper-form >>> .decision-statement {
  flex: 0 0 100%;
  width: 100%;
  line-height: 1.8;
  font-size: 13px;
  color: #303133;
  padding: 2px 0;
}
.paper-form >>> .decision-other {
  width: 320px !important;
  margin-left: 8px;
}
.paper-form >>> .decision-selected {
  flex: 0 0 100%;
  width: 100%;
  line-height: 1.8;
  font-size: 13px;
  color: #303133;
  padding: 2px 0;
}
</style>