<template>
  <basic-container class="customer-archive-page">
    <avue-crud
      :option="option"
      :table-loading="loading"
      :data="data"
      v-model:page="page"
      v-model="form"
      ref="crud"
      :permission="permissionList"
      @search-change="searchChange"
      @search-reset="searchReset"
      @selection-change="selectionChange"
      @current-change="currentChange"
      @size-change="sizeChange"
      @refresh-change="refreshChange"
      @on-load="onLoad"
    >
      <template #menu-left>
        <el-button
          type="primary"
          icon="el-icon-plus"
          v-if="hasPermission('customer_archive_add')"
          @click="openArchive()"
          >新增
        </el-button>
        <el-button
          type="primary"
          icon="el-icon-download"
          plain
          v-if="hasPermission('customer_archive_export')"
          @click="handleExport"
          >批量导出
        </el-button>
        <el-button
          type="danger"
          icon="el-icon-delete"
          plain
          v-if="hasPermission('customer_archive_delete')"
          @click="handleDelete"
          >批量删除
        </el-button>
      </template>
      <template #customerCode="{ row }">
        <el-button type="primary" link @click="openArchive(row, true)">
          {{ row.customerCode }}
        </el-button>
      </template>
      <template #accessType="{ row }">
        <el-tag :type="row.accessType === 'formal' ? 'success' : 'warning'">
          {{ accessTypeMap[row.accessType] || '临时客商' }}
        </el-tag>
      </template>
      <template #approvalStatus="{ row }">
        <el-tag :type="approvalStatusMap[row.approvalStatus]?.type || 'info'">
          {{ approvalStatusMap[row.approvalStatus]?.label || '草稿' }}
        </el-tag>
      </template>
      <template #status="{ row }">
        <el-tag :type="row.status === 1 ? 'primary' : 'info'">
          {{ row.status === 1 ? '启用' : '停用' }}
        </el-tag>
      </template>
      <template #menu="{ row }">
        <el-link
          type="primary"
          v-if="hasPermission('customer_archive_view')"
          @click="openArchive(row, true)"
        >
          查看
        </el-link>
        <el-link
          type="primary"
          v-if="hasPermission('customer_archive_edit') && row.approvalStatus !== 'reviewing'"
          @click="openArchive(row)"
        >
          编辑
        </el-link>
        <el-link
          type="primary"
          v-if="
            hasPermission('customer_archive_submit') &&
            ['draft', 'rejected'].includes(row.approvalStatus)
          "
          @click="handleSubmitApproval(row)"
        >
          提交
        </el-link>
        <el-link
          type="warning"
          v-if="
            hasPermission('customer_archive_submit') &&
            ['draft', 'reviewing'].includes(row.approvalStatus)
          "
          @click="handleWithdrawApproval(row)"
        >
          撤回
        </el-link>
        <el-link
          type="success"
          v-if="hasPermission('customer_archive_approve') && row.approvalStatus === 'reviewing'"
          @click="handleApprove(row)"
        >
          审核通过
        </el-link>
        <el-link
          type="warning"
          v-if="hasPermission('customer_archive_reject') && row.approvalStatus === 'reviewing'"
          @click="handleReject(row)"
        >
          审核不通过
        </el-link>
        <el-link
          type="primary"
          v-if="hasPermission('customer_archive_status')"
          @click="handleStatus(row)"
        >
          {{ row.status === 1 ? '停用' : '启用' }}
        </el-link>
        <el-link
          type="danger"
          v-if="hasPermission('customer_archive_delete') && row.approvalStatus === 'draft'"
          @click="rowDel(row)"
        >
          删除
        </el-link>
      </template>
    </avue-crud>
    <empty-pagination
      :page="page"
      @size-change="sizeChange"
      @current-change="currentChange"
      @load="onLoad(page, query)"
    />

    <el-dialog
      :title="dialogTitle"
      append-to-body
      v-model="archiveBox"
      width="92%"
      top="4vh"
      class="archive-dialog"
      @closed="resetArchive"
    >
      <el-form
        ref="archiveForm"
        :model="archiveForm"
        :rules="formRules"
        label-position="right"
        label-width="auto"
        :disabled="readonly"
        class="archive-form"
      >
        <section-card title="工商信息">
          <el-row :gutter="18">
            <el-col :span="6">
              <el-form-item label="客商编号" prop="customerCode">
                <el-input v-model="archiveForm.customerCode" disabled placeholder="保存后自动生成" />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="客商性质" prop="customerNature">
                <el-select
                  v-model="archiveForm.customerNature"
                  placeholder="请输入"
                  filterable
                  clearable
                >
                  <el-option
                    v-for="item in customerNatureOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="统一社会信用代码" prop="unifiedCreditCode">
                <el-input
                  v-model="archiveForm.unifiedCreditCode"
                  placeholder="请输入"
                  maxlength="18"
                  show-word-limit
                  @input="
                    archiveForm.unifiedCreditCode = String(
                      archiveForm.unifiedCreditCode || ''
                    ).toUpperCase()
                  "
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="客商名称" prop="fullName">
                <el-input
                  v-model="archiveForm.fullName"
                  placeholder="请输入"
                  maxlength="100"
                  show-word-limit
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="客商类型" prop="customerType">
                <el-select
                  v-model="archiveForm.customerType"
                  multiple
                  placeholder="请选择"
                  filterable
                  clearable
                >
                  <el-option
                    v-for="item in customerTypeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="企业法人" prop="legalPerson">
                <el-input v-model="archiveForm.legalPerson" maxlength="10" show-word-limit />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="经营范围" prop="businessScope" required>
                <el-select v-model="archiveForm.businessScope" multiple filterable clearable>
                  <el-option
                    v-for="item in businessScopeOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item
                label="营业期限"
                prop="businessEndDate"
                :required="archiveForm.businessTermType === '固定期限'"
              >
                <div class="business-term-field">
                  <el-date-picker
                    v-model="archiveForm.businessEndDate"
                    type="date"
                    value-format="YYYY-MM-DD"
                    :disabled="archiveForm.businessTermType === '长期'"
                    @change="handleBusinessEndDateChange"
                  />
                  <el-checkbox
                    :model-value="archiveForm.businessTermType === '长期'"
                    @change="handleNoFixedTermChange"
                  >
                    无固定期限
                  </el-checkbox>
                </div>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="助记码" prop="mnemonicCode">
                <el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="客户简称" prop="shortName">
                <el-input
                  v-model="archiveForm.shortName"
                  placeholder="请输入"
                  maxlength="20"
                  show-word-limit
                />
              </el-form-item>
            </el-col>
            <el-col :span="24">
              <el-form-item label="地址" prop="registeredDetailAddress" required>
                <div class="archive-form__address">
                  <el-cascader
                    ref="registeredRegionCascader"
                    v-model="archiveForm.registeredRegionPath"
                    :options="regionOptions"
                    :props="regionCascaderProps"
                    :placeholder="archiveForm.registeredRegionName || '请选择省市区'"
                    clearable
                    filterable
                    @change="handleRegisteredRegionChange"
                  />
                  <el-input
                    v-model="archiveForm.registeredDetailAddress"
                    maxlength="255"
                    placeholder="请输入详细地址"
                  />
                </div>
              </el-form-item>
            </el-col>
          </el-row>
        </section-card>

        <section-card title="联系信息">
          <el-row :gutter="18">
            <el-col :span="8">
              <el-form-item label="客商负责人" prop="principal">
                <el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="联系电话" prop="contactPhone">
                <el-input v-model="archiveForm.contactPhone" maxlength="20" />
              </el-form-item>
            </el-col>
            <el-col :span="8">
              <el-form-item label="所属组织" prop="deptName">
                <el-tree-select
                  v-model="archiveForm.deptId"
                  :data="deptTree"
                  :props="{ label: 'label', value: 'value', children: 'children' }"
                  multiple
                  node-key="value"
                  check-strictly
                  filterable
                  clearable
                  :render-after-expand="false"
                  @change="onDeptChange"
                />
              </el-form-item>
            </el-col>
          </el-row>
        </section-card>

        <section-card title="财务/信用信息">
          <el-row :gutter="18">
            <el-col :span="6">
              <el-form-item label="注册资金(万元)" prop="registeredCapital">
                <el-input
                  v-model="archiveForm.registeredCapital"
                  maxlength="18"
                  @input="value => handleDecimalInput('registeredCapital', value)"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="开票税点" prop="invoiceTaxRate">
                <el-input
                  v-model="archiveForm.invoiceTaxRate"
                  maxlength="5"
                  @input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="客户等级" prop="customerLevel">
                <el-input v-model="archiveForm.customerLevel" disabled />
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="最大资金使用额度(万元)" prop="maxCreditLimit">
                <el-input
                  v-model="archiveForm.maxCreditLimit"
                  maxlength="18"
                  disabled
                  @input="value => handleDecimalInput('maxCreditLimit', value)"
                />
              </el-form-item>
            </el-col>
            <el-col :span="6">
            <el-form-item label="申请总资金使用额度(万元)" prop="applyCreditLimit">
              <el-input
                v-model="archiveForm.applyCreditLimit"
                maxlength="18"
                disabled
              />
            </el-form-item>
            </el-col>
          </el-row>
        </section-card>

        <section-card title="其他信息">
          <el-row :gutter="18">
            <el-col :span="24">
              <el-form-item label="备注" prop="remark">
                <el-input
                  v-model="archiveForm.remark"
                  type="textarea"
                  maxlength="500"
                  show-word-limit
                />
              </el-form-item>
            </el-col>
          </el-row>
        </section-card>
      </el-form>

      <el-tabs v-model="activeTab" class="archive-tabs">
        <el-tab-pane label="评分表" name="scores">
          <section-card>
            <template #extra>
              <el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="addScore">
                添加评分记录
              </el-button>
            </template>
            <el-table :data="scoreTableData" border class="score-list-table">
            <el-table-column label="序号" width="90" align="center">
              <template #default="{ row }">
                <span>{{ row.__index }}</span>
              </template>
            </el-table-column>
            <el-table-column label="评分日期" min-width="150" align="center" sortable>
              <template #default="{ row }">
                <span>{{ row.scoreDate }}</span>
              </template>
            </el-table-column>
            <el-table-column label="自评得分" min-width="145" align="center">
              <template #default="{ row }">
                <span>{{ formatScoreValue(row.selfScore) }}</span>
              </template>
            </el-table-column>
            <el-table-column label="复评得分" min-width="145" align="center">
              <template #default="{ row }">
                <span>{{ formatScoreValue(row.reviewScore) }}</span>
              </template>
            </el-table-column>
            <el-table-column label="信用等级" min-width="150" align="center">
              <template #default="{ row }">
                <span>{{ row.creditLevel }}</span>
              </template>
            </el-table-column>
            <el-table-column min-width="185" align="center">
              <template #header>
                <span>最大资金使用额度（万<br />元）</span>
              </template>
              <template #default="{ row }">
                <span>{{ formatScoreValue(row.maxCreditLimit) }}</span>
              </template>
            </el-table-column>
            <el-table-column min-width="190" align="center">
              <template #header>
                <span>拟申请总资金使用额<br />度（万元）</span>
              </template>
              <template #default="{ row }">
                <span>{{ formatScoreValue(row.applyCreditLimit) }}</span>
              </template>
            </el-table-column>
            <el-table-column label="自评状态" min-width="130" align="center">
              <template #default="{ row }">
                <span>{{ row.selfStatus }}</span>
              </template>
            </el-table-column>
            <el-table-column label="复评状态" min-width="130" align="center">
              <template #default="{ row }">
                <span>{{ row.reviewStatus }}</span>
              </template>
            </el-table-column>
            <el-table-column label="操作" min-width="135" align="center">
              <template #default="{ row }">
                <el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
                  详情
                </el-link>
              </template>
            </el-table-column>
          </el-table>
          <empty-pagination
            :page="scorePaginationPage"
            always-show
            @size-change="handleScoreSizeChange"
            @current-change="handleScoreCurrentChange"
          />
          </section-card>
        </el-tab-pane>
        <el-tab-pane label="联系人" name="contacts">
          <section-card>
            <template #extra>
              <el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="openContact()">
                新增联系人
              </el-button>
            </template>
            <el-table :data="contactTableData" border class="contact-table">
            <el-table-column label="序号" prop="__index" width="90" align="center" />
            <el-table-column label="联系人姓名" prop="contactName" min-width="150" />
            <el-table-column label="联系人电话" prop="contactPhone" min-width="150" />
            <el-table-column label="邮箱" prop="email" min-width="180" />
            <el-table-column label="联系地址" min-width="240">
              <template #default="{ row }">
                <span>{{ formatContactAddress(row) || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="备注" prop="remark" min-width="180" />
            <el-table-column label="所属分公司/事业部" min-width="170">
              <template #default="{ row }">
                <span>{{ row.branchName || archiveForm.deptName || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="130" align="center" v-if="!readonly">
              <template #default="{ row }">
                <el-link type="primary" @click="openContact(row.__raw, row.__rawIndex)">
                  修改
                </el-link>
                <el-link type="danger" @click="deleteContact(row.__rawIndex)">删除</el-link>
              </template>
            </el-table-column>
          </el-table>
          <empty-pagination
            :page="contactPaginationPage"
            always-show
            @size-change="size => handleDetailSizeChange('contact', size)"
            @current-change="page => handleDetailCurrentChange('contact', page)"
          />
          </section-card>
        </el-tab-pane>
        <el-tab-pane label="收款信息" name="receiptAccounts">
          <section-card>
            <template #extra>
              <el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="openReceipt()">
                新增收款信息
              </el-button>
            </template>
            <el-table :data="receiptTableData" border class="receipt-table">
            <el-table-column label="序号" prop="__index" width="90" align="center" />
            <el-table-column label="收款单位名称" prop="accountName" min-width="180" />
            <el-table-column label="开户人姓名" prop="accountHolderName" min-width="150" />
            <el-table-column label="收款账号" prop="bankAccount" min-width="180" />
            <el-table-column label="开户行" prop="bankName" min-width="180" />
            <el-table-column label="备注" prop="remark" min-width="180" />
            <el-table-column label="操作" width="130" align="center" v-if="!readonly">
              <template #default="{ row }">
                <el-link type="primary" @click="openReceipt(row.__raw, row.__rawIndex)">
                  修改
                </el-link>
                <el-link type="danger" @click="deleteReceipt(row.__rawIndex)">删除</el-link>
              </template>
            </el-table-column>
          </el-table>
          <empty-pagination
            :page="receiptPaginationPage"
            always-show
            @size-change="size => handleDetailSizeChange('receipt', size)"
            @current-change="page => handleDetailCurrentChange('receipt', page)"
          />
          </section-card>
        </el-tab-pane>
        <el-tab-pane label="发票信息" name="invoices">
          <section-card>
            <template #extra>
              <el-button type="primary" icon="el-icon-plus" v-if="!readonly" @click="openInvoice()">
                新增发票信息
              </el-button>
            </template>
            <el-table :data="invoiceTableData" border class="invoice-table">
            <el-table-column label="序号" prop="__index" width="90" align="center" />
            <el-table-column label="收票方名称" prop="invoiceTitle" min-width="180" />
            <el-table-column label="纳税人识别号" prop="taxNo" min-width="180" />
            <el-table-column label="开户行名称" prop="bankName" min-width="180" />
            <el-table-column label="注册电话" prop="registeredPhone" min-width="150" />
            <el-table-column label="银行账号" prop="bankAccount" min-width="180" />
            <el-table-column label="注册地址" min-width="220">
              <template #default="{ row }">
                <span>{{ formatInvoiceRegisteredAddress(row) || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column label="邮箱" prop="email" min-width="180" />
            <el-table-column label="操作" width="130" align="center" v-if="!readonly">
              <template #default="{ row }">
                <el-link type="primary" @click="openInvoice(row.__raw, row.__rawIndex)">
                  修改
                </el-link>
                <el-link type="danger" @click="deleteInvoice(row.__rawIndex)">删除</el-link>
              </template>
            </el-table-column>
          </el-table>
          <empty-pagination
            :page="invoicePaginationPage"
            always-show
            @size-change="size => handleDetailSizeChange('invoice', size)"
            @current-change="page => handleDetailCurrentChange('invoice', page)"
          />
          </section-card>
        </el-tab-pane>
      </el-tabs>

      <section-card title="客商材料">
        <template #extra>
          <div v-if="!readonly" class="section-actions">
            <el-button icon="el-icon-download" @click="downloadAttachments">批量下载</el-button>
            <el-button type="primary" icon="el-icon-plus" @click="addAttachment"
              >普通上传</el-button
            >
            <el-button type="primary" icon="el-icon-upload" @click="addAttachment">
              OCR上传识别
            </el-button>
          </div>
        </template>
        <el-alert
          type="info"
          :closable="false"
          title="支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式，单个文件大小限制500M"
          class="archive-rule-alert"
        />
        <el-alert
          v-if="missingQualificationText"
          type="error"
          :closable="false"
          :title="missingQualificationText"
          class="archive-rule-alert"
        />
        <el-table :data="qualificationFiles" border>
          <el-table-column label="序号" type="index" width="90" align="center" />
          <el-table-column label="附件类型" min-width="160">
            <template #default="{ row }">
              <el-select v-model="row.type" :disabled="readonly" filterable>
                <el-option label="法人身份证" value="法人身份证" />
                <el-option label="道路运输许可证" value="道路运输许可证" />
                <el-option label="其他附件" value="其他附件" />
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="文件名称" min-width="180">
            <template #default="{ row }">
              <vehicle-attachment-upload
                v-model="row.files"
                :readonly="readonly"
                :multiple="false"
                accept=".pdf,.bmp,.jpeg,.png,.jpg,.doc,.docx,.ppt,.pptx,.xlsx,.xls,.eml,.msg,.zip"
                :max-size="500"
                button-text="上传文件"
                :show-tip="false"
                @success="file => handleQualificationUploadSuccess(file, row)"
              />
            </template>
          </el-table-column>
          <el-table-column label="附件描述" min-width="220">
            <template #default="{ row }">
              <el-input v-model="row.description" :disabled="readonly" maxlength="200" />
            </template>
          </el-table-column>
          <el-table-column label="文件大小" min-width="120">
            <template #default="{ row }">
              <span>{{ row.size || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="上传人" min-width="120">
            <template #default="{ row }">
              <span>{{ row.uploadUserName || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="上传时间" min-width="170" sortable>
            <template #default="{ row }">
              <span>{{ row.uploadTime || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="90" align="center" v-if="!readonly">
            <template #default="{ $index }">
              <el-link type="danger" @click="qualificationFiles.splice($index, 1)">
                删除
              </el-link>
            </template>
          </el-table-column>
        </el-table>
      </section-card>

      <section-card title="变更记录">
        <el-table :data="archiveForm.changeRecords" border>
          <el-table-column label="序号" type="index" width="90" align="center" />
          <el-table-column label="变更日期" prop="changeTime" min-width="170" sortable />
          <el-table-column label="变更内容" prop="changeContent" min-width="260" />
          <el-table-column label="变更账号" prop="changeUserName" min-width="160" />
        </el-table>
      </section-card>

      <template #footer>
        <el-button @click="archiveBox = false">{{ readonly ? '关闭' : '取消' }}</el-button>
        <el-button type="primary" v-if="!readonly" @click="saveArchive">保存</el-button>
        <el-button type="success" v-if="!readonly" @click="saveAndSubmitArchive">提交</el-button>
        <el-button v-if="!readonly" @click="archiveBox = false">返回</el-button>
      </template>
    </el-dialog>

    <el-dialog
      :title="contactDialogTitle"
      append-to-body
      v-model="contactBox"
      width="680px"
      class="contact-dialog"
      @closed="resetContact"
    >
      <el-form
        ref="contactForm"
        :model="contactForm"
        :rules="contactRules"
        label-position="right"
        label-width="110px"
        class="contact-form"
      >
        <el-form-item label="联系人姓名" prop="contactName" class="contact-form__compact-field">
          <el-input v-model="contactForm.contactName" maxlength="30" />
        </el-form-item>
        <el-form-item label="手机号" prop="contactPhone" class="contact-form__compact-field">
          <el-input v-model="contactForm.contactPhone" maxlength="11" />
        </el-form-item>
        <el-form-item label="邮箱" prop="email" class="contact-form__compact-field">
          <el-input v-model="contactForm.email" maxlength="100" />
        </el-form-item>
        <el-form-item label="地址" prop="detailAddress">
          <div class="contact-form__address">
            <el-input
              v-model="contactForm.regionName"
              readonly
              maxlength="100"
              placeholder="行政区划自动带出"
            />
            <el-input
              v-model="contactForm.detailAddress"
              readonly
              maxlength="255"
              placeholder="点击后弹出地图组件"
              suffix-icon="el-icon-location"
              @click="handleContactMapPick"
            />
          </div>
        </el-form-item>
        <el-form-item label="备注" prop="remark" class="contact-form__compact-field">
          <el-input v-model="contactForm.remark" maxlength="200" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="contact-dialog__footer-actions">
          <el-button type="primary" @click="saveContact">保存</el-button>
          <el-button @click="contactBox = false">返回</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      title="地图选择行政区划"
      append-to-body
      v-model="contactMapBox"
      width="920px"
      class="contact-form__map-dialog"
      @opened="initContactAmap"
    >
      <div class="contact-form__map-toolbar">
        <el-input
          v-model="contactMapKeyword"
          clearable
          placeholder="输入地址关键词"
          @keyup.enter="searchContactMapKeyword"
        />
        <el-button
          type="primary"
          icon="el-icon-search"
          :loading="contactMapLoading"
          @click="searchContactMapKeyword"
        >
          搜索
        </el-button>
      </div>
      <div ref="contactAmap" class="contact-form__map"></div>
      <div class="contact-form__map-info">
        <span>{{ contactMapStatus }}</span>
        <span v-if="contactMapSelected.regionName">
          行政区划：{{ contactMapSelected.regionName }}
        </span>
      </div>
      <template #footer>
        <el-button @click="contactMapBox = false">取消</el-button>
        <el-button
          type="primary"
          :disabled="!contactMapSelected.longitude"
          @click="confirmContactMapPick"
        >
          确定
        </el-button>
      </template>
    </el-dialog>

    <el-dialog
      :title="receiptDialogTitle"
      append-to-body
      v-model="receiptBox"
      width="640px"
      class="receipt-dialog"
      @closed="resetReceipt"
    >
      <el-form
        ref="receiptForm"
        :model="receiptForm"
        :rules="receiptRules"
        label-position="right"
        label-width="120px"
        class="receipt-form"
      >
        <el-form-item label="收款单位名称" prop="accountName">
          <el-input v-model="receiptForm.accountName" maxlength="100" />
        </el-form-item>
        <el-form-item label="开户人姓名" prop="accountHolderName">
          <el-input v-model="receiptForm.accountHolderName" maxlength="50" />
        </el-form-item>
        <el-form-item label="开户行" prop="bankName">
          <el-select v-model="receiptForm.bankName" filterable clearable>
            <el-option
              v-for="item in bankListOptions"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="收款账号" prop="bankAccount">
          <el-input v-model="receiptForm.bankAccount" maxlength="50" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="receiptForm.remark" maxlength="200" />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="receipt-dialog__footer-actions">
          <el-button type="primary" @click="saveReceipt">保存</el-button>
          <el-button @click="receiptBox = false">返回</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      :title="invoiceDialogTitle"
      append-to-body
      v-model="invoiceBox"
      width="84%"
      class="invoice-dialog"
      @closed="resetInvoice"
    >
      <el-form
        ref="invoiceForm"
        :model="invoiceForm"
        :rules="invoiceRules"
        label-position="right"
        label-width="120px"
        class="invoice-form"
      >
        <section-card title="发票信息">
          <el-row :gutter="34">
          <el-col :span="12">
            <el-form-item label="纳税人识别号" prop="taxNo">
              <el-input v-model="invoiceForm.taxNo" maxlength="30" />
            </el-form-item>
            <el-form-item label="注册电话" prop="registeredPhone">
              <el-input v-model="invoiceForm.registeredPhone" maxlength="20" />
            </el-form-item>
            <el-form-item label="注册地址" prop="registeredRegionName">
              <el-input
                v-model="invoiceForm.registeredRegionName"
                readonly
                maxlength="100"
                placeholder="点击后弹出地图组件"
                suffix-icon="el-icon-location"
                @click="handleInvoiceRegisteredMapPick"
              />
            </el-form-item>
            <el-form-item label="详细地址" prop="registeredDetailAddress">
              <el-input
                v-model="invoiceForm.registeredDetailAddress"
                readonly
                maxlength="255"
                placeholder="点击后弹出地图组件"
                suffix-icon="el-icon-location"
                @click="handleInvoiceRegisteredMapPick"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="收票方名称" prop="invoiceTitle">
              <el-input v-model="invoiceForm.invoiceTitle" maxlength="100" />
            </el-form-item>
            <el-form-item label="开户行名称" prop="bankName">
              <el-select v-model="invoiceForm.bankName" filterable clearable>
                <el-option
                  v-for="item in bankListOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
            <el-form-item label="银行账号" prop="bankAccount">
              <el-input v-model="invoiceForm.bankAccount" maxlength="50" />
            </el-form-item>
          </el-col>
        </el-row>
        </section-card>

        <section-card title="邮寄信息">
          <el-row :gutter="34">
          <el-col :span="12">
            <el-form-item label="收件人姓名" prop="receiverName">
              <el-input v-model="invoiceForm.receiverName" maxlength="30" />
            </el-form-item>
            <el-form-item label="收件人地址" prop="receiverRegionName">
              <div class="invoice-form__address">
                <el-input
                  v-model="invoiceForm.receiverRegionName"
                  maxlength="100"
                  placeholder="地址控件选择"
                />
                <el-input
                  v-model="invoiceForm.receiverDetailAddress"
                  maxlength="255"
                  placeholder="请输入详细地址"
                />
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="收件人电话" prop="receiverPhone">
              <el-input v-model="invoiceForm.receiverPhone" maxlength="20" />
            </el-form-item>
            <el-form-item label="邮箱" prop="email">
              <el-input v-model="invoiceForm.email" maxlength="100" />
            </el-form-item>
          </el-col>
        </el-row>
        </section-card>
      </el-form>
      <template #footer>
        <div class="invoice-dialog__footer-actions">
          <el-button type="primary" @click="saveInvoice">保存</el-button>
          <el-button @click="invoiceBox = false">返回</el-button>
        </div>
      </template>
    </el-dialog>

    <el-dialog
      title="评分详情表"
      append-to-body
      v-model="scoreBox"
      width="92%"
      top="3vh"
      class="score-detail-dialog"
      @closed="resetScoreDialog"
    >
      <el-form
        ref="scoreForm"
        :model="currentScore"
        label-position="right"
        label-width="auto"
        :disabled="readonly"
        class="score-detail-form"
      >
        <el-row :gutter="42">
          <el-col :span="12">
            <el-form-item label="评定日期" required>
              <el-date-picker
                v-model="currentScore.scoreDate"
                type="date"
                value-format="YYYY-MM-DD"
              />
            </el-form-item>
            <el-form-item label="拟申请总资金使用额度（万元）" required>
              <el-input
                v-model="currentScore.applyCreditLimit"
                maxlength="18"
                @input="value => handleScoreDecimalInput('applyCreditLimit', value)"
              />
            </el-form-item>
            <el-form-item label="最终得分">
              <el-input :model-value="formatScoreValue(currentScore.finalScore)" disabled />
            </el-form-item>
            <el-form-item label="临时额度申请使用期限">
              <div class="score-detail-form__range">
                <el-date-picker
                  v-model="currentScore.tempCreditStartDate"
                  type="date"
                  value-format="YYYY-MM-DD"
                />
                <span>至</span>
                <el-date-picker
                  v-model="currentScore.tempCreditEndDate"
                  type="date"
                  value-format="YYYY-MM-DD"
                />
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最大资金使用额度（万元）">
              <el-input
                :model-value="formatScoreValue(currentScore.maxCreditLimit)"
                disabled
              />
            </el-form-item>
            <el-form-item label="信用等级">
              <el-input :model-value="formatScoreValue(currentScore.creditLevel)" disabled />
            </el-form-item>
            <el-form-item label="临时申请额度">
              <el-input
                v-model="currentScore.tempApplyCreditLimit"
                maxlength="18"
                @input="value => handleScoreDecimalInput('tempApplyCreditLimit', value)"
              />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="备注">
              <el-input v-model="currentScore.remark" maxlength="200" />
            </el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="评分量化表" required>
              <el-select
                v-model="currentScore.quantificationId"
                filterable
                clearable
                :loading="scoreQuantificationLoading"
                placeholder="请选择评分量化表"
                @change="handleScoreQuantificationChange"
              >
                <el-option
                  v-for="item in scoreQuantificationOptions"
                  :key="item.id"
                  :label="item.name"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>

      <el-table
        v-if="currentScore.quantificationId"
        ref="scoreCategoryTable"
        :data="scoreCategoryRows"
        border
        :show-header="false"
        row-key="categoryCode"
        :expand-row-keys="expandedScoreCategoryKeys"
        class="score-category-table"
        @row-click="toggleScoreCategory"
        @expand-change="handleScoreCategoryExpandChange"
      >
        <el-table-column type="expand" width="1" class-name="score-category-table__expand-column">
          <template #default="{ row }">
            <el-table
              :data="getScoreCategoryDetails(row.categoryCode)"
              border
              class="score-detail-table score-detail-table--inline"
              max-height="520"
            >
              <el-table-column label="评分项目" prop="itemName" width="150" align="center" />
              <el-table-column label="评分标准" min-width="430">
                <template #default="{ row: detail }">
                  <div class="score-standard-cell">
                    <div class="score-standard-cell__label">请选择：</div>
                    <el-select
                      v-model="detail.selectedOption"
                      filterable
                      :disabled="readonly"
                      @change="scoreOptionChange(detail, $event)"
                    >
                      <el-option
                        v-for="item in parseScoreOptions(detail.optionsJson)"
                        :key="item.value"
                        :label="item.label"
                        :value="item.value"
                      />
                    </el-select>
                  </div>
                </template>
              </el-table-column>
              <el-table-column label="得分说明" prop="scoreDescription" min-width="160" />
              <el-table-column label="自评得分" width="130" align="center">
                <template #default="{ row: detail }">
                  <span>{{ formatScoreValue(detail.selfScore) }}分</span>
                </template>
              </el-table-column>
              <el-table-column label="复评得分" width="150" align="center">
                <template #default="{ row: detail }">
                  <el-input
                    v-model="detail.reviewScore"
                    maxlength="10"
                    :disabled="readonly"
                    @input="value => handleScoreDetailScoreInput(detail, 'reviewScore', value)"
                  >
                    <template #suffix>分</template>
                  </el-input>
                </template>
              </el-table-column>
            </el-table>
          </template>
        </el-table-column>
        <el-table-column label="序号" prop="index" width="50" align="center" />
        <el-table-column min-width="560">
          <template #default="{ row }">
            <span class="score-category-table__name">{{ row.categoryName }}</span>
            <span>自评：{{ row.selfScore }}分</span>
            <span>复评：{{ row.reviewScore }}分</span>
          </template>
        </el-table-column>
        <el-table-column width="60" align="center">
          <template #default="{ row }">
            <el-icon
              class="score-category-table__arrow"
              :class="{ 'is-expanded': isScoreCategoryExpanded(row.categoryCode) }"
            >
              <ArrowRight />
            </el-icon>
          </template>
        </el-table-column>
      </el-table>

      <section-card title="证明材料" class="score-material-card">
        <template #extra>
          <vehicle-attachment-upload
            v-if="!readonly"
            v-model="scoreAttachmentFiles"
            button-text="上传附件"
            button-icon="el-icon-upload"
            :show-tip="false"
            @success="handleScoreAttachmentUploadSuccess"
          />
        </template>
        <el-table :data="scoreAttachmentFiles" border class="score-material-table">
          <el-table-column label="序号" type="index" width="90" align="center" />
          <el-table-column label="文件名" prop="name" min-width="220" />
          <el-table-column label="附件描述" prop="description" min-width="220" />
          <el-table-column label="文件大小" min-width="120" align="center">
            <template #default="{ row }">
              <span>{{ formatAttachmentSize(row.size) || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="上传人" min-width="140" align="center">
            <template #default="{ row }">
              <span>{{ row.uploadUserName || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="上传时间" min-width="180" align="center" sortable>
            <template #default="{ row }">
              <span>{{ row.uploadTime || '-' }}</span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="120" align="center" v-if="!readonly">
            <template #default="{ $index }">
              <el-link type="primary" @click="deleteScoreAttachment($index)">
                删除
              </el-link>
            </template>
          </el-table-column>
        </el-table>
      </section-card>

      <template #footer>
        <el-button type="primary" v-if="!readonly" @click="saveScoreDetail">保存</el-button>
        <el-button type="primary" v-if="!readonly" @click="confirmReviewScore">复评确认</el-button>
        <el-button type="primary" @click="scoreBox = false">关闭</el-button>
      </template>
    </el-dialog>
  </basic-container>
</template>

<script>
import {
  getList,
  getDetail,
  submit,
  submitApproval,
  withdrawApproval,
  approve,
  reject,
  changeStatus,
  remove,
  getScoreTemplate,
} from '@/api/vehicle/customer-archive';
import {
  getList as getCreditScoreQuantificationList,
  getDetail as getCreditScoreQuantificationDetail,
} from '@/api/vehicle/credit-score-quantification';
import { getDictionary } from '@/api/system/dictbiz';
import { getDeptTree } from '@/api/system/dept';
import { getLazyTree } from '@/api/base/region';
import { exportBlob } from '@/api/common';
import { downloadXls } from '@/utils/util';
import { getToken } from '@/utils/auth';
import { getUploadHeaders } from '@/utils/upload';
import { normalizeSearchRangeParams } from '@/utils/search-range';
import { ArrowRight } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';

const createTimeRangeMap = {
  createTimeRange: ['createTimeStart', 'createTimeEnd'],
};

const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;

export default {
  components: {
    ArrowRight,
  },
  data() {
    const nonNegativeLabelMap = {
      invoiceTaxRate: '开票税点',
      registeredCapital: '注册资金',
      maxCreditLimit: '最大资金使用额度',
      applyCreditLimit: '申请总资金使用额度',
    };
    const validateNonNegative = (rule, value, callback) => {
      if (value === undefined || value === null || value === '') {
        callback();
      } else if (Number(value) < 0) {
        callback(new Error(`${nonNegativeLabelMap[rule.field] || '数值'}不能小于0`));
      } else {
        callback();
      }
    };
    const validateInvoiceTaxRate = (rule, value, callback) => {
      if (value === undefined || value === null || value === '') {
        callback();
      } else if (Number(value) < 0 || Number(value) > 13) {
        callback(new Error('开票税点必须在0到13之间'));
      } else {
        callback();
      }
    };
    const validateBusinessEndDate = (rule, value, callback) => {
      if (this.archiveForm.businessTermType === '固定期限' && !value) {
        callback(new Error('请选择营业期限截止日'));
      } else {
        callback();
      }
    };
    const validateRegisteredAddress = (rule, value, callback) => {
      const regionName = (this.archiveForm.registeredRegionName || '').trim();
      const detailAddress = String(value || '').trim();
      if (!regionName) {
        callback(new Error('请选择省市区'));
      } else if (!detailAddress) {
        callback(new Error('请输入详细地址'));
      } else if (detailAddress.length > 255) {
        callback(new Error('详细地址最多255个字符'));
      } else {
        callback();
      }
    };
    return {
      form: {},
      query: {},
      deptSearchInitialized: false,
      loading: true,
      data: [],
      page: {
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
        currentPage: 1,
        total: 0,
      },
      selectionList: [],
      archiveBox: false,
      scoreBox: false,
      contactBox: false,
      receiptBox: false,
      invoiceBox: false,
      readonly: false,
      activeTab: 'scores',
      archiveForm: this.emptyArchive(),
      currentScore: { details: [] },
      scoreRecordIndex: -1,
      scoreAttachmentFiles: [],
      expandedScoreCategoryKeys: [],
      scoreQuantificationLoading: false,
      scoreQuantificationOptions: [],
      scorePage: {
        currentPage: 1,
        pageSize: 3,
        pageSizes: [3, 10, 20, 50],
      },
      contactPage: {
        currentPage: 1,
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
      },
      receiptPage: {
        currentPage: 1,
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
      },
      invoicePage: {
        currentPage: 1,
        pageSize: 10,
        pageSizes: [10, 20, 50, 100],
      },
      contactIndex: -1,
      contactForm: this.emptyContact(),
      contactMapBox: false,
      contactMapLoading: false,
      contactMapKeyword: '',
      contactMapStatus: '可搜索地址或点击地图选点',
      contactMapSelected: {},
      contactMapTarget: 'contact',
      contactAmap: null,
      contactAmapMarker: null,
      contactAmapGeocoder: null,
      receiptIndex: -1,
      receiptForm: this.emptyReceiptAccount(),
      invoiceIndex: -1,
      invoiceForm: this.emptyInvoice(),
      qualificationFiles: [],
      deptTree: [],
      deptOptions: [],
      regionOptions: [],
      customerNatureOptions: [],
      customerTypeOptions: [],
      businessScopeOptions: [],
      bankListOptions: [],
      creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({
        label: `${item}级`,
        value: `${item}级`,
      })),
      accessTypeMap: {
        temporary: '临时客商',
        formal: '正式客商',
      },
      approvalStatusOptions: [
        { label: '草稿', value: 'draft' },
        { label: '审核中', value: 'reviewing' },
        { label: '审核通过', value: 'approved' },
        { label: '审核不通过', value: 'rejected' },
      ],
      approvalStatusMap: {
        draft: { label: '草稿', type: 'info' },
        reviewing: { label: '审核中', type: 'warning' },
        approved: { label: '审核通过', type: 'success' },
        rejected: { label: '审核不通过', type: 'danger' },
      },
      formRules: {
        fullName: [{ required: true, message: '请输入客商名称', trigger: 'blur' }],
        customerNature: [{ required: true, message: '请选择客商性质', trigger: 'change' }],
        unifiedCreditCode: [
          { required: true, message: '请输入统一社会信用代码', trigger: 'blur' },
          {
            pattern: /^[A-Z0-9]{18}$/,
            message: '统一社会信用代码必须为18位大写字母或数字',
            trigger: 'blur',
          },
        ],
        customerType: [{ required: true, message: '请选择客商类型', trigger: 'change' }],
        legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }],
        contactPhone: [
          { required: true, message: '请输入联系电话', trigger: 'blur' },
          { pattern: /^1\d{10}$/, message: '联系电话必须为11位手机号', trigger: 'blur' },
        ],
        deptName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
        invoiceTaxRate: [{ validator: validateInvoiceTaxRate, trigger: 'blur' }],
        registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
        maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
        applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
        businessEndDate: [{ validator: validateBusinessEndDate, trigger: 'change' }],
        registeredDetailAddress: [
          { validator: validateRegisteredAddress, trigger: ['change', 'blur'] },
        ],
        businessScope: [
          {
            required: true,
            type: 'array',
            min: 1,
            message: '请选择经营范围',
            trigger: 'change',
          },
        ],
        remark: [{ max: 500, message: '备注最多500个字符', trigger: 'blur' }],
      },
      contactRules: {
        contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
        contactPhone: [
          { required: true, message: '请输入手机号', trigger: 'blur' },
          { pattern: /^1\d{10}$/, message: '手机号必须为11位手机号', trigger: 'blur' },
        ],
        email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }],
        detailAddress: [{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' }],
        remark: [{ max: 200, message: '备注最多200个字符', trigger: 'blur' }],
      },
      receiptRules: {
        accountName: [{ required: true, message: '请输入收款单位名称', trigger: 'blur' }],
        accountHolderName: [{ required: true, message: '请输入开户人姓名', trigger: 'blur' }],
        bankName: [{ required: true, message: '请输入开户行', trigger: 'blur' }],
        bankAccount: [{ required: true, message: '请输入收款账号', trigger: 'blur' }],
        remark: [{ max: 200, message: '备注最多200个字符', trigger: 'blur' }],
      },
      invoiceRules: {
        taxNo: [{ required: true, message: '请输入纳税人识别号', trigger: 'blur' }],
        invoiceTitle: [{ required: true, message: '请输入收票方名称', trigger: 'blur' }],
        registeredPhone: [{ required: true, message: '请输入注册电话', trigger: 'blur' }],
        bankName: [{ required: true, message: '请输入开户行名称', trigger: 'blur' }],
        registeredRegionName: [
          { required: true, message: '请选择注册地址', trigger: 'blur' },
        ],
        registeredDetailAddress: [
          { required: true, message: '请输入详细地址', trigger: 'blur' },
          { max: 255, message: '详细地址最多255个字符', trigger: 'blur' },
        ],
        bankAccount: [{ required: true, message: '请输入银行账号', trigger: 'blur' }],
        receiverDetailAddress: [
          { max: 255, message: '收件人详细地址最多255个字符', trigger: 'blur' },
        ],
        email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }],
      },
      option: {
        height: 'auto',
        calcHeight: 32,
        tip: false,
        searchBtnText: '查询',
        emptyBtnText: '重置',
        searchShow: true,
        searchMenuSpan: 24,
        searchIcon: true,
        searchIndex: 4,
        searchMenuPosition: 'right',
        border: true,
        index: true,
        indexLabel: '序号',
        indexWidth: 90,
        addBtn: false,
        viewBtn: false,
        delBtn: false,
        editBtn: false,
        selection: true,
        dialogClickModal: false,
        menuWidth: 280,
        column: [
          {
            label: '客商编号',
            prop: 'customerCode',
            slot: true,
            search: true,
            searchOrder: 10,
            searchPlaceholder: '请输入',
            minWidth: 140,
          },
          {
            label: '客商简称',
            prop: 'shortName',
            search: true,
            searchOrder: 2,
            searchPlaceholder: '请输入',
            minWidth: 150,
          },
          {
            label: '客商名称',
            prop: 'fullName',
            search: true,
            searchOrder: 9,
            searchPlaceholder: '请输入',
            minWidth: 220,
            overHidden: true,
          },
          {
            label: '客户类型',
            prop: 'customerType',
            search: true,
            searchType: 'select',
            searchOrder: 7,
            searchValue: '',
            searchPlaceholder: '请输入',
            minWidth: 140,
            dicData: [{ label: '全部', value: '' }],
          },
          {
            label: '客户性质',
            prop: 'customerNature',
            type: 'select',
            search: true,
            searchOrder: 5,
            searchValue: '',
            searchPlaceholder: '请输入',
            minWidth: 120,
            dicData: [
              { label: '全部', value: '' },
              { label: '有限公司', value: '有限公司' },
              { label: '个体工商户', value: '个体工商户' },
            ],
          },
          {
            label: '统一信用代码',
            prop: 'unifiedCreditCode',
            search: true,
            searchOrder: 3,
            searchPlaceholder: '请输入',
            minWidth: 190,
          },
          {
            label: '所属组织',
            prop: 'deptName',
            search: true,
            searchType: 'select',
            searchOrder: 1,
            searchValue: '',
            minWidth: 180,
            dicData: [{ label: '全部', value: '' }],
          },
          {
            label: '准入标准',
            prop: 'accessType',
            slot: true,
            type: 'select',
            search: true,
            searchOrder: 8,
            minWidth: 120,
            dicData: [
              { label: '临时', value: 'temporary' },
              { label: '正式', value: 'formal' },
            ],
          },
          {
            label: '审批状态',
            prop: 'approvalStatus',
            slot: true,
            type: 'select',
            search: true,
            searchOrder: 4,
            searchValue: '',
            minWidth: 130,
            dicData: [
              { label: '全部', value: '' },
              { label: '草稿', value: 'draft' },
              { label: '审核中', value: 'reviewing' },
              { label: '审核通过', value: 'approved' },
              { label: '审核不通过', value: 'rejected' },
            ],
          },
          { label: '当前节点', prop: 'currentNode', minWidth: 150 },
          { label: '当前处理人', prop: 'currentProcessor', minWidth: 150 },
          {
            label: '审核通过时间',
            prop: 'approvedTime',
            sortable: true,
            type: 'datetime',
            format: 'YYYY-MM-DD HH:mm:ss',
            valueFormat: 'YYYY-MM-DD HH:mm:ss',
            minWidth: 170,
          },
          {
            label: '状态',
            prop: 'status',
            slot: true,
            type: 'select',
            search: true,
            searchOrder: 6,
            searchValue: '',
            dataType: 'number',
            minWidth: 100,
            dicData: [
              { label: '全部', value: '' },
              { label: '启用', value: 1 },
              { label: '停用', value: 2 },
            ],
            clearable: true,
          },
        ],
      },
    };
  },
  created() {
    this.initDeptTree();
    this.initRegionOptions();
    this.initBusinessDictionaries();
    this.initScoreQuantificationOptions();
  },
  computed: {
    ...mapGetters(['permission', 'userInfo']),
    isAdmin() {
      const authority = this.userInfo.authority || '';
      return authority.includes('admin');
    },
    permissionList() {
      return {
        addBtn: false,
      };
    },
    regionCascaderProps() {
      return {
        value: 'id',
        label: 'title',
        children: 'children',
        emitPath: true,
      };
    },
    ids() {
      let ids = [];
      this.selectionList.forEach(ele => {
        ids.push(ele.id);
      });
      return ids.join(',');
    },
    dialogTitle() {
      if (this.readonly) return '查看客商档案';
      return this.archiveForm.id ? '编辑客商档案' : '新增客商档案';
    },
    contactDialogTitle() {
      return this.contactIndex > -1 ? '编辑联系人' : '新增联系人';
    },
    receiptDialogTitle() {
      return this.receiptIndex > -1 ? '编辑收款信息' : '新增收款信息';
    },
    invoiceDialogTitle() {
      return this.invoiceIndex > -1 ? '编辑发票信息' : '新增发票信息';
    },
    missingQualificationText() {
      if (this.archiveForm.accessType !== 'formal') return '';
      const types = this.qualificationFiles
        .filter(item => item.url)
        .map(item => item.type || item.name)
        .filter(Boolean);
      const requiredMaterials = [
        { label: '法人身份证', aliases: ['法人身份证'] },
        { label: '道路运输许可证', aliases: ['道路运输许可证', '道路运输证'] },
      ];
      const missing = requiredMaterials
        .filter(
          item => !types.some(type => item.aliases.some(alias => String(type).includes(alias)))
        )
        .map(item => item.label);
      return missing.length ? `缺失核心客商材料：${missing.join('、')}` : '';
    },
    uploadHeaders() {
      return getUploadHeaders();
    },
    scoreList() {
      return this.archiveForm.scores || [];
    },
    scorePageCount() {
      return Math.max(Math.ceil(this.scoreList.length / this.scorePage.pageSize), 1);
    },
    scorePaginationPage() {
      return {
        ...this.scorePage,
        total: this.scoreList.length,
      };
    },
    scoreTableData() {
      const start = (this.scorePage.currentPage - 1) * this.scorePage.pageSize;
      return this.scoreList
        .slice(start, start + this.scorePage.pageSize)
        .map((item, index) => ({
          ...item,
          __raw: item,
          __rawIndex: start + index,
          __index: start + index + 1,
          maxCreditLimit: item.maxCreditLimit || '',
        }));
    },
    contactPaginationPage() {
      return this.buildDetailPaginationPage('contact');
    },
    contactTableData() {
      return this.buildDetailTableData('contact');
    },
    receiptPaginationPage() {
      return this.buildDetailPaginationPage('receipt');
    },
    receiptTableData() {
      return this.buildDetailTableData('receipt');
    },
    invoicePaginationPage() {
      return this.buildDetailPaginationPage('invoice');
    },
    invoiceTableData() {
      return this.buildDetailTableData('invoice');
    },
    scoreCategoryRows() {
      const categoryMap = [
        { categoryCode: 'basic', categoryName: '基础得分项' },
        { categoryCode: 'plus', categoryName: '加分项目' },
        { categoryCode: 'minus', categoryName: '减分项目' },
      ];
      return categoryMap.map((category, index) => {
        const details = this.getScoreCategoryDetails(category.categoryCode);
        const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
        const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
        return {
          ...category,
          index: index + 1,
          selfScore: Number(selfScore.toFixed(2)),
          reviewScore: Number(reviewScore.toFixed(2)),
        };
      });
    },
  },
  methods: {
    hasPermission(code) {
      return this.isAdmin || this.validData(this.permission[code], false);
    },
    emptyArchive() {
      return {
        accessType: 'temporary',
        approvalStatus: 'draft',
        status: 1,
        deptId: [],
        deptIds: '',
        deptName: '',
        customerType: [],
        businessScope: [],
        businessTermType: '固定期限',
        registeredRegionPath: [],
        registeredRegionName: '',
        registeredDetailAddress: '',
        registeredAddress: '',
        contacts: [],
        receiptAccounts: [],
        invoices: [],
        scores: [],
        changeRecords: [],
      };
    },
    getDetailList(type) {
      const listMap = {
        contact: this.archiveForm.contacts,
        receipt: this.archiveForm.receiptAccounts,
        invoice: this.archiveForm.invoices,
      };
      return listMap[type] || [];
    },
    getDetailPage(type) {
      const pageMap = {
        contact: this.contactPage,
        receipt: this.receiptPage,
        invoice: this.invoicePage,
      };
      return pageMap[type];
    },
    getDetailPageCount(type) {
      const page = this.getDetailPage(type);
      return Math.max(Math.ceil(this.getDetailList(type).length / page.pageSize), 1);
    },
    buildDetailPaginationPage(type) {
      return {
        ...this.getDetailPage(type),
        total: this.getDetailList(type).length,
      };
    },
    buildDetailTableData(type) {
      const page = this.getDetailPage(type);
      const start = (page.currentPage - 1) * page.pageSize;
      return this.getDetailList(type)
        .slice(start, start + page.pageSize)
        .map((item, index) => ({
          ...item,
          __raw: item,
          __rawIndex: start + index,
          __index: start + index + 1,
        }));
    },
    handleDetailCurrentChange(type, currentPage) {
      const page = this.getDetailPage(type);
      const nextPage = Number(currentPage) || 1;
      page.currentPage = Math.min(Math.max(nextPage, 1), this.getDetailPageCount(type));
    },
    handleDetailSizeChange(type, pageSize) {
      const page = this.getDetailPage(type);
      page.pageSize = Number(pageSize) || 10;
      this.handleDetailCurrentChange(type, 1);
    },
    resetDetailPagination() {
      this.handleScoreCurrentChange(1);
      this.handleDetailCurrentChange('contact', 1);
      this.handleDetailCurrentChange('receipt', 1);
      this.handleDetailCurrentChange('invoice', 1);
    },
    emptyContact() {
      return {
        contactName: '',
        contactPhone: '',
        email: '',
        regionName: '',
        regionCode: '',
        detailAddress: '',
        longitude: '',
        latitude: '',
        contactAddress: '',
        branchName: '',
        positionName: '',
        isDefault: 0,
        remark: '',
      };
    },
    emptyReceiptAccount() {
      return {
        accountName: '',
        accountHolderName: '',
        bankName: '',
        bankAccount: '',
        registeredPhone: '',
        registeredAddress: '',
        taxNo: '',
        isDefault: 0,
        remark: '',
      };
    },
    emptyInvoice() {
      return {
        invoiceTitle: '',
        taxNo: '',
        bankName: '',
        registeredPhone: '',
        bankAccount: '',
        registeredRegionName: '',
        registeredDetailAddress: '',
        registeredAddress: '',
        email: '',
        receiverName: '',
        receiverPhone: '',
        receiverRegionName: '',
        receiverDetailAddress: '',
        receiverAddress: '',
        isDefault: 0,
      };
    },
    initDeptTree() {
      getDeptTree(this.userInfo.tenantId)
        .then(res => {
          this.deptTree = this.normalizeDeptTree(res.data.data || []);
          this.deptOptions = this.flattenDept(this.deptTree);
          const deptColumn = this.findColumn(this.option.column, 'deptName');
          deptColumn.dicData = [
            { label: '全部', value: '' },
            ...this.deptOptions.map(item => ({
              label: item.label,
              value: item.rawLabel,
            })),
          ];
          this.applyDefaultDeptSearch(deptColumn);
        })
        .finally(() => {
          this.deptSearchInitialized = true;
          this.$nextTick(() => this.onLoad(this.page, this.query));
        });
    },
    applyDefaultDeptSearch(deptColumn) {
      if (this.isAdmin) return;
      const currentDeptId = this.userInfo.deptId || this.userInfo.dept_id;
      const currentDept = this.deptOptions.find(
        item => String(item.value) === String(currentDeptId)
      );
      const deptName = currentDept?.rawLabel || this.userInfo.deptName || this.userInfo.dept_name;
      if (!deptName) return;
      deptColumn.searchValue = deptName;
      this.query = { ...this.query, deptName };
      if (this.$refs.crud?.searchForm) {
        this.$refs.crud.searchForm.deptName = deptName;
      }
    },
    initRegionOptions() {
      getLazyTree().then(res => {
        const regions = this.buildRegionTree(res.data.data || []);
        this.regionOptions = this.extractChinaRegionOptions(regions);
      });
    },
    buildRegionTree(regions = []) {
      const flatRegions = [];
      const collectRegions = list => {
        (list || []).forEach(item => {
          flatRegions.push({
            ...item,
            children: undefined,
          });
          if (item.children?.length) collectRegions(item.children);
        });
      };
      collectRegions(regions);

      const nodeMap = new Map();
      flatRegions.forEach(item => {
        const id = item.id === undefined || item.id === null ? item.value : item.id;
        if (id === undefined || id === null) return;
        nodeMap.set(String(id), {
          ...item,
          id: String(id),
          parentId:
            item.parentId === undefined || item.parentId === null ? '' : String(item.parentId),
          children: [],
        });
      });

      const rootNodes = [];
      nodeMap.forEach(node => {
        const parent = nodeMap.get(node.parentId);
        if (parent && parent !== node) {
          parent.children.push(node);
        } else {
          rootNodes.push(node);
        }
      });
      return rootNodes.map(item => this.normalizeRegionTreeNode(item));
    },
    normalizeRegionTreeNode(region) {
      const children = (region.children || []).map(item => this.normalizeRegionTreeNode(item));
      return {
        ...region,
        children: children.length ? children : undefined,
        leaf: !children.length,
      };
    },
    isChinaRegion(region = {}) {
      const title = String(region.title || region.name || '').trim();
      return title === '中国' || title === '中华人民共和国';
    },
    extractChinaRegionOptions(regions) {
      const china = (regions || []).find(item => this.isChinaRegion(item));
      if (china?.children?.length) return china.children;
      if (regions.length === 1 && regions[0].children?.length) return regions[0].children;
      return regions;
    },
    findRegionOption(options, value) {
      for (const item of options || []) {
        if (String(item.id) === String(value)) return item;
        const child = this.findRegionOption(item.children, value);
        if (child) return child;
      }
      return null;
    },
    getRegisteredRegionLabels(value) {
      const nodes = this.$refs.registeredRegionCascader?.getCheckedNodes?.() || [];
      if (nodes.length && nodes[0].pathLabels?.length) {
        return nodes[0].pathLabels;
      }
      return (value || [])
        .map(code => this.findRegionOption(this.regionOptions, code)?.title)
        .filter(Boolean);
    },
    handleRegisteredRegionChange(value) {
      if (!value || !value.length) {
        this.archiveForm.registeredRegionName = '';
        this.$refs.archiveForm?.validateField('registeredDetailAddress');
        return;
      }
      this.$nextTick(() => {
        this.archiveForm.registeredRegionName = this.getRegisteredRegionLabels(value).join('');
        this.$refs.archiveForm?.validateField('registeredDetailAddress');
      });
    },
    initBusinessDictionaries() {
      this.loadDictOptions(
        'nature_of_client',
        'customerNatureOptions',
        ['有限公司', '个体工商户'],
        options => {
          const customerNatureColumn = this.findColumn(this.option.column, 'customerNature');
          customerNatureColumn.dicData = [{ label: '全部', value: '' }, ...options];
        }
      );
      this.loadDictOptions('customer_type', 'customerTypeOptions', ['客户', '承运商'], options => {
          const customerTypeColumn = this.findColumn(this.option.column, 'customerType');
          customerTypeColumn.dicData = [{ label: '全部', value: '' }, ...options];
      });
      this.loadDictOptions('scope_of_business', 'businessScopeOptions');
      this.loadDictOptions('bank_list', 'bankListOptions');
    },
    initScoreQuantificationOptions() {
      this.scoreQuantificationLoading = true;
      getCreditScoreQuantificationList(1, 9999, { status: 1 })
        .then(res => {
          const data = res.data.data || {};
          const records = Array.isArray(data) ? data : data.records || [];
          this.scoreQuantificationOptions = records
            .filter(item => Number(item.status) === 1)
            .map(item => ({
              id: item.id,
              name: item.name || item.configName || item.title || item.id,
            }));
        })
        .finally(() => {
          this.scoreQuantificationLoading = false;
        });
    },
    loadDictOptions(code, target, fallback = [], callback) {
      getDictionary({ code }).then(res => {
        const options = (res.data.data || []).map(item => ({
          label: item.dictValue,
          value: item.dictValue,
        }));
        fallback.forEach(value => {
          if (!options.some(item => item.value === value)) {
            options.push({ label: value, value });
          }
        });
        this[target] = options;
        if (callback) callback(options);
      });
    },
    normalizeDeptTree(tree = []) {
      return (tree || []).map(item => {
        const label = item.label || item.title || item.deptName || item.name || '';
        const value = String(item.value || item.id || '');
        return {
          ...item,
          label,
          value,
          children: this.normalizeDeptTree(item.children || []),
        };
      });
    },
    flattenDept(tree, level = 0) {
      const result = [];
      tree.forEach(item => {
        result.push({
          label: `${'　'.repeat(level)}${item.label || item.title || item.deptName || item.name}`,
          value: item.value || item.id,
          rawLabel: item.label || item.title || item.deptName || item.name,
        });
        if (item.children && item.children.length) {
          result.push(...this.flattenDept(item.children, level + 1));
        }
      });
      return result;
    },
    onDeptChange(value) {
      const deptIds = Array.isArray(value) ? value : value ? [value] : [];
      const deptNames = deptIds
        .map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel)
        .filter(Boolean);
      this.archiveForm.deptIds = deptIds.join(',');
      this.archiveForm.deptName = deptNames.join(',');
      this.$refs.archiveForm?.validateField('deptName');
    },
    handleNoFixedTermChange(checked) {
      this.archiveForm.businessTermType = checked ? '长期' : '固定期限';
      if (checked) {
        this.archiveForm.businessEndDate = '';
      }
      this.$nextTick(() => {
        this.$refs.archiveForm?.clearValidate('businessEndDate');
        if (!checked) {
          this.$refs.archiveForm?.validateField('businessEndDate');
        }
      });
    },
    handleBusinessEndDateChange(value) {
      if (value) {
        this.archiveForm.businessTermType = '固定期限';
      }
    },
    handleDecimalInput(prop, value, max) {
      const normalized = String(value || '')
        .replace(/[^\d.]/g, '')
        .replace(/^\./, '')
        .replace(/(\..*)\./g, '$1')
        .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
      if (max !== undefined && normalized !== '' && Number(normalized) > max) {
        this.archiveForm[prop] = String(max);
        return;
      }
      this.archiveForm[prop] = normalized;
    },
    splitValue(value) {
      return value
        ? String(value)
            .split(',')
            .map(item => item.trim())
            .filter(Boolean)
        : [];
    },
    normalizeDeptIds(detail) {
      const value = detail.deptIds || detail.deptId;
      return value
        ? String(value)
            .split(',')
            .map(item => item.trim())
            .filter(item => item !== '')
        : [];
    },
    formatArchiveAddress(archive = {}) {
      return [archive.registeredRegionName, archive.registeredDetailAddress]
        .map(item => String(item || '').trim())
        .filter(Boolean)
        .join('');
    },
    formatContactAddress(row = {}) {
      return (
        row.contactAddress ||
        [row.regionName, row.detailAddress]
          .map(item => String(item || '').trim())
          .filter(Boolean)
          .join('')
      );
    },
    formatInvoiceRegisteredAddress(row = {}) {
      return (
        row.registeredAddress ||
        [row.registeredRegionName, row.registeredDetailAddress]
          .map(item => String(item || '').trim())
          .filter(Boolean)
          .join('')
      );
    },
    formatInvoiceReceiverAddress(row = {}) {
      return (
        row.receiverAddress ||
        [row.receiverRegionName, row.receiverDetailAddress]
          .map(item => String(item || '').trim())
          .filter(Boolean)
          .join('')
      );
    },
    normalizeContact(contact = {}) {
      const contactAddress =
        contact.contactAddress || contact.address || this.formatContactAddress(contact);
      return {
        ...this.emptyContact(),
        ...contact,
        contactAddress,
        branchName: contact.branchName || contact.deptName || this.archiveForm.deptName || '',
      };
    },
    openContact(row, index = -1) {
      this.contactIndex = index;
      this.contactForm = row
        ? this.normalizeContact(row)
        : {
            ...this.emptyContact(),
            branchName: this.archiveForm.deptName || '',
          };
      this.contactBox = true;
    },
    resetContact() {
      this.contactIndex = -1;
      this.contactForm = this.emptyContact();
      this.$refs.contactForm?.clearValidate();
    },
    saveContact() {
      this.$refs.contactForm.validate(valid => {
        if (!valid) return;
        const contact = this.normalizeContact({
          ...this.contactForm,
          contactAddress: this.formatContactAddress(this.contactForm),
        });
        if (this.contactIndex > -1) {
          this.archiveForm.contacts.splice(this.contactIndex, 1, contact);
        } else {
          this.archiveForm.contacts.push(contact);
          this.handleDetailCurrentChange('contact', this.getDetailPageCount('contact'));
        }
        this.contactBox = false;
      });
    },
    deleteContact(index) {
      this.$confirm('确定删除该联系人?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          this.archiveForm.contacts.splice(index, 1);
          this.handleDetailCurrentChange('contact', this.contactPage.currentPage);
        })
        .catch(() => {});
    },
    handleContactMapPick() {
      this.contactMapTarget = 'contact';
      this.contactMapKeyword = this.contactForm.detailAddress || this.contactForm.regionName || '';
      this.contactMapSelected = this.buildContactMapSelection({
        lng: this.contactForm.longitude,
        lat: this.contactForm.latitude,
        address: this.contactForm.detailAddress,
        regionName: this.contactForm.regionName,
        regionCode: this.contactForm.regionCode,
      });
      this.contactMapStatus = this.contactMapSelected.longitude
        ? '已加载当前选点，可重新选点'
        : '可搜索地址或点击地图选点';
      this.contactMapBox = true;
    },
    handleInvoiceRegisteredMapPick() {
      this.contactMapTarget = 'invoiceRegistered';
      this.contactMapKeyword =
        this.invoiceForm.registeredDetailAddress || this.invoiceForm.registeredRegionName || '';
      this.contactMapSelected = this.buildContactMapSelection({
        address: this.invoiceForm.registeredDetailAddress,
        regionName: this.invoiceForm.registeredRegionName,
      });
      this.contactMapStatus = '可搜索地址或点击地图选点';
      this.contactMapBox = true;
    },
    loadAmap() {
      if (window.AMap && window.AMap.Map) {
        return Promise.resolve();
      }
      if (!amapLoader) {
        amapLoader = new Promise((resolve, reject) => {
          window._AMapSecurityConfig = {
            securityJsCode: AMAP_SECURITY_CODE,
          };
          const script = document.createElement('script');
          script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
          script.async = true;
          script.onload = resolve;
          script.onerror = () => reject(new Error('高德地图组件加载失败'));
          document.body.appendChild(script);
        });
      }
      return amapLoader;
    },
    initContactAmap() {
      this.loadAmap()
        .then(() => {
          this.$nextTick(() => {
            if (!this.contactAmap) {
              const center = this.toLngLat(
                this.contactMapSelected.longitude || 116.40769,
                this.contactMapSelected.latitude || 39.89945
              );
              this.contactAmap = new window.AMap.Map(this.$refs.contactAmap, {
                center,
                zoom: this.contactMapSelected.longitude ? 14 : 11,
              });
              window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => {
                this.contactAmap.addControl(new window.AMap.ToolBar());
                if (!this.contactAmapGeocoder) {
                  this.contactAmapGeocoder = new window.AMap.Geocoder();
                }
              });
              this.contactAmap.on('click', event => this.pickContactMapPoint(event.lnglat));
            } else if (typeof this.contactAmap.resize === 'function') {
              this.contactAmap.resize();
            }
            if (this.contactMapSelected.longitude) {
              this.renderContactMapMarker(
                this.toLngLat(
                  this.contactMapSelected.longitude,
                  this.contactMapSelected.latitude
                )
              );
            } else {
              this.clearContactMapMarker();
            }
          });
        })
        .catch(() => {
          this.$message.error('高德地图组件加载失败，请稍后重试或重新打开弹窗');
          this.contactMapBox = false;
        });
    },
    searchContactMapKeyword() {
      const keyword = String(this.contactMapKeyword || '').trim();
      if (!keyword) {
        this.$message.warning('请输入地址关键词');
        return;
      }
      this.loadAmap()
        .then(() => {
          this.contactMapLoading = true;
          this.ensureContactAmapGeocoder()
            .then(() => this.runAmapGeocode('location', keyword))
            .then(result => {
              const point = this.resolveMapPoint(result);
              if (!point) {
                this.contactMapStatus = '未找到匹配地址';
                this.$message.warning('地图搜索无匹配地址');
                return;
              }
              this.pickContactMapPoint(point, keyword);
            })
            .catch(() => {
              this.contactMapStatus = '地图搜索失败';
              this.$message.error('地图搜索失败，请稍后重试');
            })
            .finally(() => {
              this.contactMapLoading = false;
            });
        })
        .catch(() => {
          this.$message.error('高德地图组件加载失败，请稍后重试或重新打开弹窗');
        });
    },
    pickContactMapPoint(lnglat, keyword) {
      const longitude = this.getPointLng(lnglat);
      const latitude = this.getPointLat(lnglat);
      if (longitude === undefined || latitude === undefined) {
        this.$message.warning('选点坐标无效');
        return;
      }
      const point = this.toLngLat(longitude, latitude);
      this.renderContactMapMarker(point);
      this.contactMapSelected = this.buildContactMapSelection({
        lng: longitude,
        lat: latitude,
        address: keyword,
      });
      this.contactMapStatus = '正在反查地址...';
      this.ensureContactAmapGeocoder()
        .then(() => this.runAmapGeocode('address', point))
        .then(result => {
          const address = this.resolveMapAddress(result);
          this.contactMapSelected = {
            ...this.contactMapSelected,
            detailAddress: address.detailAddress || this.contactMapSelected.detailAddress,
            regionName: address.regionName || this.contactMapSelected.regionName,
            regionCode: address.regionCode || this.contactMapSelected.regionCode,
          };
          this.contactMapKeyword = this.contactMapSelected.detailAddress || this.contactMapKeyword;
          this.contactMapStatus = this.contactMapSelected.detailAddress || '已选点，可确认回填';
        })
        .catch(() => {
          this.contactMapStatus = '反查地址失败';
          this.$message.error('反查地址失败，请重新选点');
        });
    },
    renderContactMapMarker(point) {
      if (!this.contactAmap || !window.AMap) return;
      this.clearContactMapMarker();
      this.contactAmapMarker = new window.AMap.Marker({
        position: point,
      });
      this.contactAmapMarker.setMap(this.contactAmap);
      this.contactAmap.setCenter(point);
    },
    clearContactMapMarker() {
      if (this.contactAmapMarker) {
        this.contactAmapMarker.setMap(null);
        this.contactAmapMarker = null;
      }
    },
    confirmContactMapPick() {
      if (!this.contactMapSelected.longitude) {
        this.$message.warning('请先搜索或点击地图完成选点');
        return;
      }
      if (this.contactMapTarget === 'invoiceRegistered') {
        this.invoiceForm.registeredRegionName =
          this.contactMapSelected.regionName || this.invoiceForm.registeredRegionName;
        this.invoiceForm.registeredDetailAddress = this.formatMapDetailAddress(
          this.contactMapSelected.detailAddress || this.invoiceForm.registeredDetailAddress,
          this.invoiceForm.registeredRegionName
        );
        this.$refs.invoiceForm?.validateField('registeredRegionName');
        this.contactMapBox = false;
        return;
      }
      this.contactForm.detailAddress =
        this.contactMapSelected.detailAddress || this.contactForm.detailAddress;
      this.contactForm.regionName =
        this.contactMapSelected.regionName || this.contactForm.regionName;
      this.contactForm.regionCode =
        this.contactMapSelected.regionCode || this.contactForm.regionCode;
      this.contactForm.longitude = this.contactMapSelected.longitude;
      this.contactForm.latitude = this.contactMapSelected.latitude;
      this.$refs.contactForm?.validateField('detailAddress');
      this.contactMapBox = false;
    },
    formatMapDetailAddress(address, regionName) {
      const rawAddress = String(address || '').trim();
      const rawRegionName = String(regionName || '').trim();
      if (!rawAddress || !rawRegionName) return rawAddress;
      return rawAddress.startsWith(rawRegionName)
        ? rawAddress.slice(rawRegionName.length).trim()
        : rawAddress;
    },
    buildContactMapSelection({ lng, lat, address, regionName, regionCode }) {
      const longitude = this.formatCoordinate(lng);
      const latitude = this.formatCoordinate(lat);
      return {
        longitude,
        latitude,
        detailAddress: address || '',
        regionName: regionName || '',
        regionCode: regionCode || '',
      };
    },
    formatCoordinate(value) {
      if (value === undefined || value === null || value === '') {
        return '';
      }
      const numberValue = Number(value);
      return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
    },
    toLngLat(lng, lat) {
      return new window.AMap.LngLat(Number(lng), Number(lat));
    },
    getPointLng(point) {
      if (!point) return undefined;
      if (typeof point.getLng === 'function') return point.getLng();
      return point.lng ?? point.lon;
    },
    getPointLat(point) {
      if (!point) return undefined;
      if (typeof point.getLat === 'function') return point.getLat();
      return point.lat;
    },
    ensureContactAmapGeocoder() {
      if (this.contactAmapGeocoder) {
        return Promise.resolve(this.contactAmapGeocoder);
      }
      return new Promise((resolve, reject) => {
        if (!window.AMap || !window.AMap.plugin) {
          reject(new Error('高德地图组件未就绪'));
          return;
        }
        window.AMap.plugin(['AMap.Geocoder'], () => {
          try {
            this.contactAmapGeocoder = new window.AMap.Geocoder();
            resolve(this.contactAmapGeocoder);
          } catch (error) {
            reject(error);
          }
        });
      });
    },
    runAmapGeocode(action, input) {
      return new Promise((resolve, reject) => {
        const timer = window.setTimeout(() => {
          reject(new Error('高德地图请求超时'));
        }, 10000);
        const done = (status, result) => {
          window.clearTimeout(timer);
          if (status === 'complete' && result) {
            resolve(result);
            return;
          }
          reject(new Error('高德地图请求失败'));
        };
        try {
          if (action === 'location') {
            this.contactAmapGeocoder.getLocation(input, done);
          } else {
            this.contactAmapGeocoder.getAddress(input, done);
          }
        } catch (error) {
          window.clearTimeout(timer);
          reject(error);
        }
      });
    },
    resolveMapPoint(result) {
      if (!result) return null;
      const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null;
      if (geocode && geocode.location) return geocode.location;
      if (result.location) return result.location;
      if (result.lnglat) return result.lnglat;
      if (result.getLng || result.lng || result.lon) return result;
      return null;
    },
    resolveMapAddress(result = {}) {
      const regeocode = result.regeocode || {};
      const component = regeocode.addressComponent || {};
      const city = Array.isArray(component.city) ? '' : component.city;
      const regionName = [component.province, city, component.district].filter(Boolean).join('');
      return {
        detailAddress: regeocode.formattedAddress || '',
        regionName,
        regionCode: component.adcode || '',
      };
    },
    normalizeReceipt(receipt = {}) {
      return {
        ...this.emptyReceiptAccount(),
        ...receipt,
      };
    },
    openReceipt(row, index = -1) {
      this.receiptIndex = index;
      this.receiptForm = row ? this.normalizeReceipt(row) : this.emptyReceiptAccount();
      this.receiptBox = true;
    },
    resetReceipt() {
      this.receiptIndex = -1;
      this.receiptForm = this.emptyReceiptAccount();
      this.$refs.receiptForm?.clearValidate();
    },
    saveReceipt() {
      this.$refs.receiptForm.validate(valid => {
        if (!valid) return;
        const receipt = this.normalizeReceipt(this.receiptForm);
        if (this.receiptIndex > -1) {
          this.archiveForm.receiptAccounts.splice(this.receiptIndex, 1, receipt);
        } else {
          this.archiveForm.receiptAccounts.push(receipt);
          this.handleDetailCurrentChange('receipt', this.getDetailPageCount('receipt'));
        }
        this.receiptBox = false;
      });
    },
    deleteReceipt(index) {
      this.$confirm('确定删除该收款信息?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          this.archiveForm.receiptAccounts.splice(index, 1);
          this.handleDetailCurrentChange('receipt', this.receiptPage.currentPage);
        })
        .catch(() => {});
    },
    normalizeInvoice(invoice = {}) {
      const registeredDetailAddress =
        invoice.registeredDetailAddress ||
        (invoice.registeredRegionName ? '' : invoice.registeredAddress) ||
        '';
      const receiverDetailAddress =
        invoice.receiverDetailAddress ||
        (invoice.receiverRegionName ? '' : invoice.receiverAddress) ||
        '';
      return {
        ...this.emptyInvoice(),
        ...invoice,
        registeredDetailAddress,
        registeredAddress: this.formatInvoiceRegisteredAddress({
          ...invoice,
          registeredDetailAddress,
        }),
        receiverDetailAddress,
        receiverAddress: this.formatInvoiceReceiverAddress({
          ...invoice,
          receiverDetailAddress,
        }),
      };
    },
    openInvoice(row, index = -1) {
      this.invoiceIndex = index;
      this.invoiceForm = row ? this.normalizeInvoice(row) : this.emptyInvoice();
      this.invoiceBox = true;
    },
    resetInvoice() {
      this.invoiceIndex = -1;
      this.invoiceForm = this.emptyInvoice();
      this.$refs.invoiceForm?.clearValidate();
    },
    saveInvoice() {
      this.$refs.invoiceForm.validate(valid => {
        if (!valid) return;
        const invoice = this.normalizeInvoice({
          ...this.invoiceForm,
          registeredAddress: this.formatInvoiceRegisteredAddress(this.invoiceForm),
          receiverAddress: this.formatInvoiceReceiverAddress(this.invoiceForm),
        });
        if (this.invoiceIndex > -1) {
          this.archiveForm.invoices.splice(this.invoiceIndex, 1, invoice);
        } else {
          this.archiveForm.invoices.push(invoice);
          this.handleDetailCurrentChange('invoice', this.getDetailPageCount('invoice'));
        }
        this.invoiceBox = false;
      });
    },
    deleteInvoice(index) {
      this.$confirm('确定删除该发票信息?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          this.archiveForm.invoices.splice(index, 1);
          this.handleDetailCurrentChange('invoice', this.invoicePage.currentPage);
        })
        .catch(() => {});
    },
    deleteScoreAttachment(index) {
      this.$confirm('确定删除该评分证明材料?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          this.scoreAttachmentFiles.splice(index, 1);
        })
        .catch(() => {});
    },
    parseAttachments(value) {
      if (!value) return [];
      if (Array.isArray(value)) return value;
      try {
        const list = JSON.parse(value);
        return Array.isArray(list)
          ? list.map(item => ({
              ...item,
              files: item.url ? [item] : [],
            }))
          : [];
      } catch (error) {
        return String(value)
          .split(',')
          .map(item => ({ name: item.trim(), url: item.trim() }))
          .filter(item => item.url)
          .map(item => ({
            ...item,
            files: [item],
          }));
      }
    },
    stringifyAttachments(list) {
      const files = (list || [])
        .filter(item => item.url)
        .map(item => ({
          type: String(item.type || '').trim(),
          name: String(item.name || '').trim(),
          description: String(item.description || '').trim(),
          size: String(item.size || '').trim(),
          uploadUserName: String(item.uploadUserName || '').trim(),
          uploadTime: String(item.uploadTime || '').trim(),
          url: String(item.url || '').trim(),
        }))
        .filter(item => item.url);
      return files.length ? JSON.stringify(files) : '';
    },
    addAttachment() {
      this.qualificationFiles.push({
        type: '',
        name: '',
        description: '',
        size: '',
        uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
        uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
        url: '',
        files: [],
      });
    },
    formatAttachmentSize(size) {
      if (!size) return '';
      const byteSize = Number(size);
      if (!Number.isFinite(byteSize)) return String(size);
      const mb = byteSize / 1024 / 1024;
      if (mb >= 1) return `${mb.toFixed(2)} MB`;
      return `${(byteSize / 1024).toFixed(2)} KB`;
    },
    handleQualificationUploadSuccess(file, row) {
      row.name = file.name || row.name || '客商材料';
      row.size = this.formatAttachmentSize(file.size || row.size);
      row.uploadUserName = this.userInfo.realName || this.userInfo.userName || '';
      row.uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
      row.url = file.url || row.url || '';
      row.files = [file];
    },
    downloadAttachments() {
      const files = this.qualificationFiles.filter(item => item.url);
      if (!files.length) {
        this.$message.warning('暂无可下载附件');
        return;
      }
      files.forEach(item => window.open(item.url, '_blank'));
    },
    normalizeDetail(detail) {
      const businessScope = this.splitValue(detail.businessScope);
      const registeredDetailAddress =
        detail.registeredDetailAddress ||
        (detail.registeredRegionName ? '' : detail.registeredAddress) ||
        '';
      const archive = {
        ...this.emptyArchive(),
        ...detail,
        deptId: this.normalizeDeptIds(detail),
        customerType: this.splitValue(detail.customerType),
        invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
        registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
        businessScope,
        businessTermType: detail.businessTermType || '固定期限',
        registeredRegionPath: Array.isArray(detail.registeredRegionPath)
          ? detail.registeredRegionPath
          : [],
        registeredRegionName: detail.registeredRegionName || '',
        registeredDetailAddress,
        contacts: (detail.contacts || []).map(item =>
          this.normalizeContact({
            ...item,
            branchName: item.branchName || item.deptName || detail.deptName || '',
          })
        ),
        receiptAccounts: (detail.receiptAccounts || []).map(item => this.normalizeReceipt(item)),
        invoices: (detail.invoices || []).map(item => this.normalizeInvoice(item)),
        scores: (detail.scores || []).map(score => this.calculateScore(this.normalizeScore(score))),
        changeRecords: detail.changeRecords || [],
      };
      return this.applyArchiveCreditFromScores(archive);
    },
    normalizeScore(score) {
      return {
        quantificationId: '',
        scoreDate: this.$dayjs().format('YYYY-MM-DD'),
        applyCreditLimit: this.archiveForm.applyCreditLimit || '',
        maxCreditLimit: '',
        creditLevel: '',
        standards: [],
        tempApplyCreditLimit: '',
        tempCreditStartDate: '',
        tempCreditEndDate: '',
        remark: '',
        selfStatus: '未完成',
        reviewStatus: '未完成',
        proofAttachments: '',
        attachments: [],
        details: [],
        ...score,
        tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
        attachments: score.attachments || this.parseAttachments(score.proofAttachments),
        details: score.details || [],
      };
    },
    normalizeOptionalAmount(value) {
      const amount = Number(value);
      return Number.isFinite(amount) && amount < 0 ? '' : value ?? '';
    },
    getScoreTimeValue(value) {
      if (!value) return 0;
      const time = this.$dayjs(value).valueOf();
      return Number.isFinite(time) ? time : 0;
    },
    getLatestCreditScore(scores = []) {
      return (scores || [])
        .map((score, index) => ({ score, index }))
        .filter(item => String(item.score.creditLevel || '').trim())
        .sort((a, b) => {
          const scoreDateDiff =
            this.getScoreTimeValue(b.score.scoreDate) - this.getScoreTimeValue(a.score.scoreDate);
          if (scoreDateDiff) return scoreDateDiff;
          const updateTimeDiff =
            this.getScoreTimeValue(b.score.updateTime) - this.getScoreTimeValue(a.score.updateTime);
          if (updateTimeDiff) return updateTimeDiff;
          const createTimeDiff =
            this.getScoreTimeValue(b.score.createTime) - this.getScoreTimeValue(a.score.createTime);
          if (createTimeDiff) return createTimeDiff;
          return b.index - a.index;
        })[0]?.score;
    },
    applyArchiveCreditFromScores(archive) {
      const latestScore = this.getLatestCreditScore(archive.scores || []);
      archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || '';
      archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || '';
      archive.applyCreditLimit = archive.maxCreditLimit;
      return archive;
    },
    syncArchiveCreditFromScores() {
      this.applyArchiveCreditFromScores(this.archiveForm);
    },
    openArchive(row, readonly = false) {
      this.readonly = readonly;
      this.activeTab = 'scores';
      this.resetDetailPagination();
      if (row && row.id) {
        getDetail(row.id).then(res => {
          this.archiveForm = this.normalizeDetail(res.data.data);
          this.qualificationFiles = this.parseAttachments(
            this.archiveForm.qualificationAttachments
          );
          this.archiveBox = true;
        });
        return;
      }
      this.archiveForm = this.emptyArchive();
      this.qualificationFiles = [];
      this.archiveBox = true;
    },
    resetArchive() {
      this.readonly = false;
      this.archiveForm = this.emptyArchive();
      this.qualificationFiles = [];
      this.scoreBox = false;
      this.resetScoreDialog();
      this.resetDetailPagination();
      this.contactBox = false;
      this.contactIndex = -1;
      this.contactForm = this.emptyContact();
      this.receiptBox = false;
      this.receiptIndex = -1;
      this.receiptForm = this.emptyReceiptAccount();
      this.invoiceBox = false;
      this.invoiceIndex = -1;
      this.invoiceForm = this.emptyInvoice();
      this.$refs.archiveForm?.clearValidate();
    },
    validateFormalForApproval(archive) {
      if (archive.accessType !== 'formal') return true;
      if (!archive.qualificationAttachments) {
        this.$message.warning('正式客商提交审核前必须维护客商材料');
        return false;
      }
      const hasCompletedScore = (archive.scores || []).some(
        item => item.selfStatus === '已完成' || item.reviewStatus === '已完成' || item.finalScore
      );
      if (!hasCompletedScore) {
        this.$message.warning('正式客商提交审核前必须完成信用评分');
        return false;
      }
      return true;
    },
    normalizeArchive() {
      const archive = JSON.parse(JSON.stringify(this.archiveForm));
      if (archive.businessTermType === '长期') {
        archive.businessEndDate = null;
      }
      const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
      const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
      archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
      archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
      archive.customerType = Array.isArray(archive.customerType)
        ? archive.customerType.join(',')
        : archive.customerType;
      archive.businessScope = Array.isArray(archive.businessScope)
        ? archive.businessScope.join(',')
        : archive.businessScope;
      const deptIds = Array.isArray(archive.deptId)
        ? archive.deptId
        : archive.deptId
        ? [archive.deptId]
        : [];
      const deptNames = deptIds
        .map(id => this.deptOptions.find(item => String(item.value) === String(id))?.rawLabel)
        .filter(Boolean);
      archive.deptIds = deptIds.join(',');
      archive.deptId = deptIds.length ? deptIds[0] : '';
      archive.deptName = deptNames.length ? deptNames.join(',') : archive.deptName;
      archive.registeredAddress = this.formatArchiveAddress(archive);
      delete archive.registeredRegionPath;
      archive.qualificationAttachments = this.stringifyAttachments(this.qualificationFiles);
      archive.contacts = (archive.contacts || [])
        .map(item =>
          this.normalizeContact({
            ...item,
            contactAddress: this.formatContactAddress(item),
          })
        )
        .filter(item => item.contactName || item.contactPhone);
      archive.receiptAccounts = (archive.receiptAccounts || [])
        .map(item => this.normalizeReceipt(item))
        .filter(item => item.accountName || item.accountHolderName || item.bankAccount);
      archive.invoices = (archive.invoices || [])
        .map(item => this.normalizeInvoice(item))
        .filter(item => item.invoiceTitle || item.taxNo || item.bankAccount);
      archive.scores = (archive.scores || []).map(score => this.calculateScore(score));
      this.applyArchiveCreditFromScores(archive);
      return archive;
    },
    saveArchive() {
      this.$refs.archiveForm.validate(valid => {
        if (!valid) {
          this.$message.warning('请先完整填写必填项');
          return;
        }
        submit(this.normalizeArchive()).then(() => {
          this.archiveBox = false;
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
        });
      });
    },
    saveAndSubmitArchive() {
      this.$refs.archiveForm.validate(valid => {
        if (!valid) {
          this.$message.warning('请先完整填写必填项');
          return;
        }
        const archive = this.normalizeArchive();
        if (!this.validateFormalForApproval(archive)) return;
        submit(archive).then(res => {
          const data = res.data.data;
          const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id;
          if (!id) {
            this.archiveBox = false;
            this.onLoad(this.page);
            this.$message({ type: 'success', message: '保存成功，请在列表提交审批' });
            return;
          }
          submitApproval(id).then(() => {
            this.archiveBox = false;
            this.onLoad(this.page);
            this.$message({ type: 'success', message: '提交成功!' });
          });
        });
      });
    },
    addScore() {
      this.scoreRecordIndex = -1;
      this.currentScore = this.normalizeScore({});
      this.scoreAttachmentFiles = [];
      this.expandedScoreCategoryKeys = [];
      this.scoreBox = true;
      if (!this.scoreQuantificationOptions.length) {
        this.initScoreQuantificationOptions();
      }
    },
    openScore(row, index = -1) {
      this.scoreRecordIndex = index;
      this.currentScore = this.normalizeScore(JSON.parse(JSON.stringify(row || {})));
      this.scoreAttachmentFiles = JSON.parse(JSON.stringify(this.currentScore.attachments || []));
      this.expandedScoreCategoryKeys = [];
      this.scoreBox = true;
      if (!this.scoreQuantificationOptions.length) {
        this.initScoreQuantificationOptions();
      }
    },
    resetScoreDialog() {
      this.scoreRecordIndex = -1;
      this.currentScore = { details: [] };
      this.scoreAttachmentFiles = [];
      this.expandedScoreCategoryKeys = [];
    },
    handleScoreQuantificationChange(quantificationId) {
      if (!quantificationId) {
        this.currentScore.details = [];
        this.expandedScoreCategoryKeys = [];
        return;
      }
      Promise.all([
        getScoreTemplate(quantificationId),
        getCreditScoreQuantificationDetail(quantificationId),
      ]).then(([templateRes, quantificationRes]) => {
        const templateData = templateRes.data.data || {};
        const quantificationData = quantificationRes.data.data || {};
        const template = this.normalizeScore({
          ...templateData,
          quantificationId,
          scoreDate: this.currentScore.scoreDate,
          applyCreditLimit: this.currentScore.applyCreditLimit,
          maxCreditLimit: '',
          creditLevel: '',
          tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
          tempCreditStartDate: this.currentScore.tempCreditStartDate,
          tempCreditEndDate: this.currentScore.tempCreditEndDate,
          remark: this.currentScore.remark,
          attachments: this.scoreAttachmentFiles,
          standards: quantificationData.standards || templateData.standards || [],
          details: this.normalizeScoreTemplateDetails(templateData),
        });
        if (!template.details || !template.details.length) {
          this.$message.warning('当前评分量化表未配置评分项目');
        }
        this.currentScore = template;
        this.expandedScoreCategoryKeys = [];
        this.calculateScore(this.currentScore);
      });
    },
    normalizeScoreTemplateDetails(template = {}) {
      const categoryDetails = (template.categories || []).flatMap(category =>
        (category.items || []).map(item => ({
          ...item,
          categoryCode: category.categoryCode,
          categoryName: category.categoryName,
          optionsJson:
            item.optionsJson ||
            JSON.stringify(
              (item.options || []).map(option => ({
                label: option.label || option.optionName || option.value,
                value: option.value || option.optionName || option.label,
                score: option.score,
              }))
            ),
        }))
      );
      if (!template.details || !template.details.length) return categoryDetails;
      return template.details.map(detail => {
        const category = (template.categories || []).find(item => {
          if (detail.categoryCode) return item.categoryCode === detail.categoryCode;
          if (detail.categoryName) return item.categoryName === detail.categoryName;
          return (item.items || []).some(scoreItem => {
            if (detail.itemId && scoreItem.id) return String(scoreItem.id) === String(detail.itemId);
            return scoreItem.itemName && scoreItem.itemName === detail.itemName;
          });
        });
        return {
          ...detail,
          categoryCode: detail.categoryCode || category?.categoryCode || '',
          categoryName: detail.categoryName || category?.categoryName || '',
        };
      });
    },
    getScoreDetailCategoryCode(detail = {}) {
      const rawCode = String(
        detail.categoryCode ||
          detail.scoreCategory ||
          detail.categoryType ||
          detail.itemCategory ||
          detail.type ||
          ''
      ).toLowerCase();
      if (['basic', 'plus', 'minus'].includes(rawCode)) return rawCode;
      if (rawCode === '1') return 'basic';
      if (rawCode === '2') return 'plus';
      if (rawCode === '3') return 'minus';
      const categoryText = [
        detail.categoryName,
        detail.scoreCategoryName,
        detail.category,
        detail.categoryTypeName,
        detail.typeName,
      ]
        .filter(Boolean)
        .join(' ');
      if (categoryText.includes('减分')) return 'minus';
      if (categoryText.includes('加分')) return 'plus';
      if (categoryText.includes('基础')) return 'basic';
      return '';
    },
    getScoreCategoryDetailsByList(details, categoryCode) {
      return (details || []).filter(item => this.getScoreDetailCategoryCode(item) === categoryCode);
    },
    getScoreCategoryDetails(categoryCode) {
      return this.getScoreCategoryDetailsByList(this.currentScore.details || [], categoryCode);
    },
    isScoreCategoryExpanded(categoryCode) {
      return this.expandedScoreCategoryKeys.includes(categoryCode);
    },
    toggleScoreCategory(row) {
      const categoryCode = row.categoryCode;
      this.expandedScoreCategoryKeys = this.isScoreCategoryExpanded(categoryCode)
        ? []
        : [categoryCode];
    },
    handleScoreCategoryExpandChange(row, expandedRows) {
      const expandedCategoryCodes = (expandedRows || []).map(item => item.categoryCode);
      this.expandedScoreCategoryKeys = expandedCategoryCodes.includes(row.categoryCode)
        ? [row.categoryCode]
        : [];
    },
    parseScoreOptions(value) {
      if (!value) return [];
      const normalize = list =>
        list.map(item => ({
          ...item,
          label: item.label || item.optionName || item.value,
          value: item.value || item.optionName || item.label,
          score: item.score,
        }));
      if (Array.isArray(value)) return normalize(value);
      try {
        const options = JSON.parse(value);
        return Array.isArray(options) ? normalize(options) : [];
      } catch (error) {
        return [];
      }
    },
    scoreOptionChange(detail, optionValue) {
      const option = this.parseScoreOptions(detail.optionsJson).find(
        item => item.value === optionValue
      );
      const score = Number(option?.score || 0);
      detail.selfScore = score;
      this.calculateScore(this.currentScore);
    },
    handleScoreDetailScoreInput(detail, prop, value) {
      const normalized = String(value || '')
        .replace(/[^\d.]/g, '')
        .replace(/^\./, '')
        .replace(/(\..*)\./g, '$1')
        .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
      if (prop === 'reviewScore' && normalized !== '') {
        const maxScore = this.getScoreItemFullScore(detail);
        if (Number(normalized) > maxScore) {
          detail[prop] = String(maxScore);
          this.$message.warning(
            `${detail.itemName || '评分项目'}复评得分不能高于配置的最高得分${this.formatScoreValue(
              maxScore
            )}分`
          );
          this.calculateScore(this.currentScore);
          return;
        }
      }
      detail[prop] = normalized;
      this.calculateScore(this.currentScore);
    },
    handleScoreDecimalInput(prop, value) {
      const normalized = String(value || '')
        .replace(/[^\d.]/g, '')
        .replace(/^\./, '')
        .replace(/(\..*)\./g, '$1')
        .replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
      this.currentScore[prop] = normalized;
    },
    handleScoreAttachmentUploadSuccess(file) {
      const index = this.scoreAttachmentFiles.findIndex(
        item => item.uid === file.uid || item.url === file.url
      );
      const attachment = {
        ...file,
        size: file.size || '',
        uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
        uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
      };
      if (index > -1) {
        this.scoreAttachmentFiles.splice(index, 1, {
          ...this.scoreAttachmentFiles[index],
          ...attachment,
        });
        return;
      }
      this.scoreAttachmentFiles.push(attachment);
    },
    isMinusScoreDetail(detail = {}) {
      return this.getScoreDetailCategoryCode(detail) === 'minus';
    },
    isBasicScoreDetail(detail = {}) {
      return this.getScoreDetailCategoryCode(detail) === 'basic';
    },
    getSignedScore(detail, prop) {
      const score = Number(detail[prop] || 0);
      return this.isMinusScoreDetail(detail) ? -score : score;
    },
    getScoreItemFullScore(detail = {}) {
      const itemScore = Number(detail.score);
      if (Number.isFinite(itemScore) && itemScore > 0) return itemScore;
      const optionScores = this.parseScoreOptions(detail.optionsJson)
        .map(item => Number(item.score))
        .filter(score => Number.isFinite(score));
      return optionScores.length ? Math.max(...optionScores) : 0;
    },
    getScoreFullMark(details = []) {
      return details.reduce((sum, item) => {
        if (!this.isBasicScoreDetail(item)) return sum;
        return sum + this.getScoreItemFullScore(item);
      }, 0);
    },
    matchCreditStandard(score, scoreRate) {
      const standards = (score.standards || [])
        .filter(item => item && item.creditLevel)
        .sort((a, b) => Number(b.scoreRateLower || 0) - Number(a.scoreRateLower || 0));
      return standards.find(standard => {
        const lower = Number(standard.scoreRateLower);
        const upper = Number(standard.scoreRateUpper);
        if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false;
        return scoreRate >= lower && scoreRate <= upper;
      });
    },
    calculateCreditLimitByStandard(standard, scoreRate) {
      if (!standard) return '';
      const lowerLimit = Number(standard.creditLimitLower);
      const upperLimit = Number(standard.creditLimitUpper);
      if (!Number.isFinite(lowerLimit) || !Number.isFinite(upperLimit)) return '';
      const scoreRateIncrease = Number(standard.scoreRateIncrease || 0);
      const creditLimitIncrease = Number(standard.creditLimitIncrease || 0);
      if (scoreRateIncrease <= 0 || creditLimitIncrease <= 0) {
        return Number(Math.min(lowerLimit, upperLimit).toFixed(2));
      }
      const steps = Math.max(
        0,
        Math.floor((scoreRate - Number(standard.scoreRateLower || 0)) / scoreRateIncrease)
      );
      const limit = lowerLimit + steps * creditLimitIncrease;
      return Number(Math.min(limit, upperLimit).toFixed(2));
    },
    calculateScore(score) {
      const details = score.details || [];
      const basicDetails = this.getScoreCategoryDetailsByList(details, 'basic');
      const plusDetails = this.getScoreCategoryDetailsByList(details, 'plus');
      const minusDetails = this.getScoreCategoryDetailsByList(details, 'minus');
      const sumScore = (list, prop) =>
        list.reduce((sum, item) => sum + Number(item[prop] || 0), 0);
      const selfScore =
        sumScore(basicDetails, 'selfScore') +
        sumScore(plusDetails, 'selfScore') -
        sumScore(minusDetails, 'selfScore');
      const reviewScore =
        sumScore(basicDetails, 'reviewScore') +
        sumScore(plusDetails, 'reviewScore') -
        sumScore(minusDetails, 'reviewScore');
      const hasReviewScore = details.some(
        item =>
          item.reviewScore !== undefined && item.reviewScore !== null && item.reviewScore !== ''
      );
      const finalScore = hasReviewScore ? reviewScore : selfScore;
      const fullMark = this.getScoreFullMark(details);
      const scoreRate =
        fullMark > 0 ? Math.max(0, Math.min(100, (finalScore / fullMark) * 100)) : 0;
      const standard = this.matchCreditStandard(score, scoreRate);
      const hasStandards = (score.standards || []).some(item => item && item.creditLevel);
      score.selfScore = Number(selfScore.toFixed(2));
      score.reviewScore = Number(reviewScore.toFixed(2));
      score.finalScore = Number(finalScore.toFixed(2));
      score.scoreRate = Number(scoreRate.toFixed(2));
      if (hasStandards) {
        score.creditLevel = standard?.creditLevel || '';
        score.maxCreditLimit = standard
          ? this.calculateCreditLimitByStandard(standard, scoreRate)
          : '';
      }
      return score;
    },
    hasIncompleteBasicScoreDetail(details = []) {
      return this.getScoreCategoryDetailsByList(details, 'basic').some(item => !item.selectedOption);
    },
    finishScore(score) {
      if (!score.details || !score.details.length) {
        this.$message.warning('请先读取评分量化表');
        return;
      }
      if (this.hasIncompleteBasicScoreDetail(score.details)) {
        this.$message.warning('请完整选择基础得分项评分明细选项');
        return;
      }
      this.calculateScore(score);
      score.selfStatus = '已完成';
      score.reviewStatus = '已完成';
      this.$message.success('评分已完成');
    },
    saveScoreDetail() {
      if (!this.validateScoreForm(false)) return;
      this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || []));
      this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles);
      this.calculateScore(this.currentScore);
      const score = JSON.parse(JSON.stringify(this.currentScore));
      if (this.scoreRecordIndex > -1) {
        this.archiveForm.scores.splice(this.scoreRecordIndex, 1, score);
      } else {
        this.archiveForm.scores.push(score);
        this.handleScoreCurrentChange(this.scorePageCount);
      }
      this.syncArchiveCreditFromScores();
      this.activeTab = 'scores';
      this.scoreBox = false;
      this.saveScoreArchive();
    },
    saveScoreArchive() {
      if (!this.archiveForm.id) {
        this.$message.success('评分记录已添加，请保存客商档案后生效');
        return;
      }
      submit(this.normalizeArchive()).then(() => {
        this.$message.success('评分记录已保存');
      });
    },
    confirmReviewScore() {
      if (!this.validateScoreForm(true)) return;
      this.currentScore.selfStatus = '已完成';
      this.currentScore.reviewStatus = '已完成';
      this.saveScoreDetail();
      this.$message.success('复评确认成功');
    },
    validateScoreForm(requireComplete) {
      if (!this.currentScore.scoreDate) {
        this.$message.warning('请选择评定日期');
        return false;
      }
      if (!this.currentScore.applyCreditLimit) {
        this.$message.warning('请输入拟申请总资金使用额度');
        return false;
      }
      if (Number(this.currentScore.applyCreditLimit) < 0) {
        this.$message.warning('拟申请总资金使用额度不能小于0');
        return false;
      }
      if (!this.currentScore.quantificationId) {
        this.$message.warning('请选择评分量化表');
        return false;
      }
      if (!this.currentScore.details || !this.currentScore.details.length) {
        this.$message.warning('当前评分量化表未配置评分项目');
        return false;
      }
      const overMaxReviewScore = this.currentScore.details.find(item => {
        if (item.reviewScore === undefined || item.reviewScore === null || item.reviewScore === '') {
          return false;
        }
        return Number(item.reviewScore) > this.getScoreItemFullScore(item);
      });
      if (overMaxReviewScore) {
        this.$message.warning(
          `${
            overMaxReviewScore.itemName || '评分项目'
          }复评得分不能高于配置的最高得分${this.formatScoreValue(
            this.getScoreItemFullScore(overMaxReviewScore)
          )}分`
        );
        return false;
      }
      if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) {
        this.$message.warning('请完整选择基础得分项评分明细选项');
        return false;
      }
      return true;
    },
    handleScoreCurrentChange(currentPage) {
      const page = Number(currentPage) || 1;
      this.scorePage.currentPage = Math.min(Math.max(page, 1), this.scorePageCount);
    },
    handleScoreSizeChange(pageSize) {
      this.scorePage.pageSize = Number(pageSize) || 3;
      this.handleScoreCurrentChange(1);
    },
    formatScoreValue(value) {
      return value === undefined || value === null || value === '' ? '' : value;
    },
    rowDel(row) {
      this.$confirm('仅草稿状态客商可删除，确定删除该数据?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => remove(row.id))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
        });
    },
    handleDelete() {
      if (this.selectionList.length === 0) {
        this.$message.warning('请选择至少一条数据');
        return;
      }
      if (this.selectionList.some(item => item.approvalStatus !== 'draft')) {
        this.$message.warning('仅草稿状态客商可删除');
        return;
      }
      this.$confirm('确定删除所选草稿客商?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => remove(this.ids))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
          this.selectionClear();
        });
    },
    handleSubmitApproval(row) {
      getDetail(row.id).then(res => {
        const archive = this.normalizeDetail(res.data.data);
        archive.qualificationAttachments = archive.qualificationAttachments || '';
        if (!this.validateFormalForApproval(archive)) return;
        this.$confirm('是否提交客商准入审批?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(() => submitApproval(row.id))
          .then(() => {
            this.onLoad(this.page);
            this.$message({ type: 'success', message: '提交成功!' });
          });
      });
    },
    handleWithdrawApproval(row) {
      this.$confirm('是否确认撤回该客商审批?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => withdrawApproval(row.id))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '已撤回至草稿状态!' });
        });
    },
    handleApprove(row) {
      this.$confirm('是否确认审核通过?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => approve(row.id))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '审核通过!' });
        });
    },
    handleReject(row) {
      this.$confirm('是否确认审核不通过?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => reject(row.id))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
        });
    },
    handleStatus(row) {
      const nextStatus = row.status === 1 ? 2 : 1;
      const actionName = nextStatus === 1 ? '启用' : '停用';
      this.$confirm(`是否确认${actionName}该客商?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => changeStatus(row.id, nextStatus))
        .then(() => {
          this.onLoad(this.page);
          this.$message({ type: 'success', message: '操作成功!' });
        });
    },
    searchReset() {
      this.query = this.isAdmin ? {} : { deptName: this.getCurrentDeptName() };
      this.onLoad(this.page);
    },
    searchChange(params, done) {
      this.query = this.buildQuery(params);
      this.page.currentPage = 1;
      this.onLoad(this.page);
      done();
    },
    selectionChange(list) {
      this.selectionList = list;
    },
    selectionClear() {
      this.selectionList = [];
      this.$refs.crud.toggleSelection();
    },
    currentChange(currentPage) {
      this.page.currentPage = currentPage;
    },
    sizeChange(pageSize) {
      this.page.pageSize = pageSize;
    },
    refreshChange() {
      this.onLoad(this.page, this.query);
    },
    onLoad(page, params = {}) {
      if (!this.deptSearchInitialized) return;
      this.loading = true;
      getList(page.currentPage, page.pageSize, this.buildQuery(params))
        .then(res => {
          const pageData = res.data.data;
          this.page.total = pageData.total;
          this.data = pageData.records;
          this.selectionClear();
        })
        .finally(() => {
          this.loading = false;
        });
    },
    handleExport() {
      this.$confirm('是否导出客商档案数据?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        NProgress.start();
        exportBlob(
          '/blade-transport/customer-archive/export-customer-archive',
          this.buildExportParams(),
          { feedback: true }
        )
          .then(res => {
            downloadXls(res.data, `客商档案${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
          })
          .finally(() => {
            NProgress.done();
          });
      });
    },
    buildExportParams() {
      return {
        ...this.buildQuery(),
        ids: this.ids,
        [this.website.tokenHeader]: getToken(),
      };
    },
    buildQuery(params = {}) {
      return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
    },
    getCurrentDeptName() {
      const currentDeptId = this.userInfo.deptId || this.userInfo.dept_id;
      const currentDept = this.deptOptions.find(
        item => String(item.value) === String(currentDeptId)
      );
      return currentDept?.rawLabel || this.userInfo.deptName || this.userInfo.dept_name || '';
    },
  },
};
</script>

<style lang="scss" scoped>
.customer-archive-page {
  :deep(.el-table th .cell),
  :deep(.el-table td .cell) {
    white-space: nowrap;
  }
}

.archive-form {
  :deep(.el-input-number),
  :deep(.el-select),
  :deep(.el-cascader),
  :deep(.el-date-editor) {
    width: 100%;
  }
}

business-term-field,
.archive-form__address {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.business-term-field {
  :deep(.el-date-editor) {
    flex: 1;
    min-width: 0;
  }

  :deep(.el-checkbox) {
    flex: 0 0 auto;
    margin-right: 0;
  }
}

.archive-form__address {
  .el-input,
  :deep(.el-cascader) {
    flex: 1;
    min-width: 0;
  }

  :deep(.el-cascader) {
    flex: 0 0 36%;
  }
}

.section-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}

.archive-rule-alert {
  margin-bottom: 12px;
}

.archive-tabs {
  margin-top: 18px;
}

.score-list-table {
  width: 100%;
}

.score-detail-form {
  padding: 8px 36px 0;

  :deep(.el-form-item) {
    margin-bottom: 18px;
  }

  :deep(.el-form-item__label) {
    color: #70757a;
    font-size: 15px;
    font-weight: 600;
  }

  :deep(.el-input),
  :deep(.el-select),
  :deep(.el-date-editor) {
    width: 230px;
  }

  :deep(.el-col:nth-child(2) .el-form-item) {
    justify-content: flex-end;
  }
}

.score-detail-form__range {
  display: flex;
  align-items: center;
  gap: 14px;

  :deep(.el-date-editor) {
    width: 230px;
  }
}

.score-category-table {
  width: calc(100% - 72px);
  margin: 8px 36px 0;

  :deep(.el-table__body td) {
    height: 56px;
    color: #303133;
  }

  :deep(.el-table__body tr) {
    cursor: pointer;
  }
}

.score-category-table__name {
  display: inline-block;
  min-width: 96px;
  margin-right: 10px;
  font-weight: 700;
}

.score-category-table__arrow {
  font-size: 18px;
  font-weight: 700;
  color: #303133;
  transition: transform 0.2s ease;

  &.is-expanded {
    transform: rotate(90deg);
  }
}

.score-category-table {
  :deep(.score-category-table__expand-column) {
    width: 0 !important;
    padding: 0 !important;
    border-right: 0;

    .cell {
      display: none;
    }
  }

  :deep(.el-table__expanded-cell) {
    padding: 0;
    background-color: #fff;
  }
}

.score-standard-cell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 104px;
  padding: 8px 24px;
}

.score-standard-cell__label {
  margin-bottom: 12px;
  color: #303133;
}

.score-detail-table--inline {
  margin-top: 0;

  :deep(.el-table__header th) {
    height: 46px;
    background-color: #f5f7fa;
    color: #303133;
    font-weight: 700;
  }

  :deep(.el-table__body td) {
    min-height: 112px;
    color: #303133;
  }

  :deep(.el-select),
  :deep(.el-input),
  :deep(.el-select) {
    width: 100%;
  }
}

// 客商材料、变更记录已改用全局 <section-card> 组件，无需额外 margin-top

.score-list-table,
.contact-table,
.receipt-table,
.invoice-table {
  :deep(.el-table__header th) {
    background-color: #f5f7fa;
  }
}

.contact-form,
.receipt-form,
.invoice-form {
  max-width: 620px;

  :deep(.el-form-item) {
    margin-bottom: 16px;
  }

  :deep(.el-input) {
    width: 100%;
  }
}

.invoice-form {
  max-width: none;
}

.contact-form__address {
  display: flex;
  gap: 10px;
  width: 100%;

  .el-input {
    flex: 1;
    min-width: 0;
  }

  .el-input:first-child {
    flex: 0 0 38%;
  }
}

.contact-form__compact-field {
  :deep(.el-input) {
    width: 60%;
  }
}

.contact-dialog__footer-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.receipt-form {
  :deep(.el-input),
  :deep(.el-select) {
    width: 60%;
  }
}

.receipt-dialog__footer-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.invoice-dialog__footer-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.contact-form__map-toolbar {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;

  .el-input {
    flex: 1;
  }
}

.contact-form__map {
  width: 100%;
  height: 460px;
  border: 1px solid #dcdfe6;
}

.contact-form__map-info {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  min-height: 22px;
  margin-top: 10px;
  color: #606266;
  font-size: 13px;
}

.invoice-form__address {
  display: flex;
  gap: 10px;
  width: 100%;

  .el-input {
    flex: 1;
    min-width: 0;
  }

  .el-input:first-child {
    flex: 0 0 38%;
  }
}

.score-detail-table {
  width: 100%;
  margin-top: 12px;
}

:deep(.score-detail-dialog .el-dialog__body) {
  max-height: 74vh;
  overflow: auto;
  padding: 18px 20px 22px;
}

:deep(.score-detail-dialog .el-dialog__footer) {
  text-align: center;

  .el-button {
    min-width: 160px;
    height: 42px;
    margin: 0 18px;
  }
}

.score-material-card {
  :deep(.vehicle-attachment-upload) {
    width: auto;
  }

  :deep(.el-upload-list) {
    display: none;
  }
}

.score-material-table {
  width: 100%;
}

:deep(.archive-dialog .el-dialog__body) {
  max-height: 72vh;
  overflow: auto;
}

:deep(.contact-dialog .el-dialog__body) {
  max-height: 70vh;
  overflow: auto;
}

:deep(.receipt-dialog .el-dialog__body) {
  max-height: 70vh;
  overflow: auto;
}

:deep(.invoice-dialog .el-dialog__body) {
  max-height: 72vh;
  overflow: auto;
}
</style>
