d4ae6d521a
Co-authored-by: Cursor <cursoragent@cursor.com>
6719 lines
244 KiB
Vue
6719 lines
244 KiB
Vue
<template>
|
||
<basic-container class="customer-archive-page" :class="{ 'is-public-view': isPublicViewPage }">
|
||
<avue-crud
|
||
v-if="!isArchivePage"
|
||
: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="openArchivePage()"
|
||
>新增
|
||
</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 #header>
|
||
<el-tabs
|
||
v-model="query.expireStatus"
|
||
type="card"
|
||
class="customer-archive-page__expiry-tabs"
|
||
@tab-change="handleExpireChange"
|
||
>
|
||
<el-tab-pane
|
||
v-for="item in expiryTagOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:name="item.value"
|
||
/>
|
||
</el-tabs>
|
||
</template>
|
||
<template #customerCode="{ row }">
|
||
<el-button type="primary" link @click="viewArchive(row)">
|
||
{{ row.customerCode }}
|
||
</el-button>
|
||
</template>
|
||
<template #customerRisk="{ row }">
|
||
<el-tooltip
|
||
v-if="customerRowRiskName(row) && customerRowRiskName(row) !== '无风险'"
|
||
placement="top"
|
||
effect="dark"
|
||
popper-class="customer-risk-popper"
|
||
>
|
||
<template #content>
|
||
<div>{{ row.hcsRiskDetail || customerRowRiskName(row) }}</div>
|
||
</template>
|
||
<el-tag
|
||
:type="customerRowRisky(row) ? 'danger' : row.hcsRiskLevel === '1' ? 'warning' : 'success'"
|
||
class="status-text"
|
||
>
|
||
{{ customerRowRiskName(row) }}
|
||
</el-tag>
|
||
</el-tooltip>
|
||
<el-tag
|
||
v-else-if="customerRowRiskName(row)"
|
||
type="success"
|
||
class="status-text"
|
||
>
|
||
{{ customerRowRiskName(row) }}
|
||
</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
<template #accessType="{ row }">
|
||
<el-tag
|
||
class="status-text"
|
||
:class="
|
||
row.accessType === 'formal' ? 'archive-tag--deep-blue' : 'archive-tag--light-blue'
|
||
"
|
||
>
|
||
{{ accessTypeMap[row.accessType] || '临时客商' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #businessEndDate="{ row }">
|
||
<span :class="{ 'business-license-expiring': isDateExpiringSoon(row.businessEndDate, row.businessTermType === '长期') }">
|
||
{{ formatBusinessLicenseValidPeriod(row) }}
|
||
</span>
|
||
</template>
|
||
<template #nextEvaluateDate="{ row }">
|
||
<span :class="{ 'business-license-expiring': isDateExpiringSoon(row.nextEvaluateDate) }">
|
||
{{ row.nextEvaluateDate || '-' }}
|
||
</span>
|
||
</template>
|
||
<template #fundUseRate="{ row }">
|
||
{{ formatFundUseRate(row) }}
|
||
</template>
|
||
<template #fundUseRisk="{ row }">
|
||
<el-tag v-if="row.fundUseRisk === 'high'" type="danger">
|
||
{{ row.fundUseRiskName || '高风险' }}
|
||
</el-tag>
|
||
<el-tag v-else-if="row.fundUseRisk === 'medium'" type="warning">
|
||
{{ row.fundUseRiskName || '中风险' }}
|
||
</el-tag>
|
||
<span v-else class="fund-use-risk-none">-</span>
|
||
</template>
|
||
<template #fundUseRisk-header>
|
||
<span class="fund-use-risk-header">
|
||
资金使用额度风险
|
||
<el-tooltip placement="top" effect="dark">
|
||
<template #content>
|
||
<div>中风险:客户资金额度使用率 ≥ 80%</div>
|
||
<div>高风险:客户资金额度使用率 ≥ 90%</div>
|
||
</template>
|
||
<el-icon class="fund-use-risk-help">
|
||
<el-icon-question-filled />
|
||
</el-icon>
|
||
</el-tooltip>
|
||
</span>
|
||
</template>
|
||
<template #approvalStatus="{ row }">
|
||
<el-tag
|
||
class="status-text"
|
||
:class="{
|
||
'archive-tag--deep-blue': row.approvalStatus === 'approved',
|
||
'archive-tag--light-blue': row.approvalStatus === 'reviewing',
|
||
}"
|
||
:type="
|
||
['approved', 'reviewing'].includes(row.approvalStatus)
|
||
? undefined
|
||
: approvalStatusMap[row.approvalStatus]?.type || 'info'
|
||
"
|
||
>
|
||
{{ approvalStatusMap[row.approvalStatus]?.label || '草稿' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #status="{ row }">
|
||
<el-tag :type="row.status === 1 ? 'primary' : 'info'" class="status-text">
|
||
{{ row.status === 1 ? '启用' : '停用' }}
|
||
</el-tag>
|
||
</template>
|
||
<template #kingdeeStatus="{ row }">
|
||
<el-tag v-if="row.kingdeeStatus === 'synced'" type="success">已推送</el-tag>
|
||
<el-tag v-else-if="row.kingdeeStatus === 'exist'" type="primary">金蝶已有</el-tag>
|
||
<el-tooltip
|
||
v-else-if="row.kingdeeStatus === 'failed'"
|
||
:content="row.kingdeeFailReason || '推送金蝶失败,请重试'"
|
||
placement="top"
|
||
>
|
||
<el-tag type="danger">推送失败</el-tag>
|
||
</el-tooltip>
|
||
<el-tag v-else type="info">未推送</el-tag>
|
||
</template>
|
||
<template #menu="{ row }">
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('customer_archive_view')"
|
||
@click="viewArchive(row)"
|
||
>
|
||
查看
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('customer_archive_edit') && row.approvalStatus !== 'reviewing'"
|
||
@click="openArchivePage(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') &&
|
||
row.approvalStatus === 'reviewing'
|
||
"
|
||
@click="handleWithdrawApproval(row)"
|
||
>
|
||
撤回
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="
|
||
hasPermission('customer_type_re_cert') &&
|
||
row.approvalStatus === 'reviewing' &&
|
||
row.reviewStatus !== '已完成'
|
||
"
|
||
@click="openReview(row)"
|
||
>
|
||
复评
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="
|
||
mkEnabled &&
|
||
hasPermission('customer_archive_approve') &&
|
||
['reviewing', 'approved'].includes(row.approvalStatus)
|
||
"
|
||
@click="handleMkAudit(row)"
|
||
>
|
||
{{ row.approvalStatus === 'approved' ? '流程' : '审核' }}
|
||
</el-link>
|
||
<el-link
|
||
type="success"
|
||
v-if="
|
||
!mkEnabled &&
|
||
hasPermission('customer_archive_approve') &&
|
||
row.approvalStatus === 'reviewing' &&
|
||
row.reviewStatus === '已完成'
|
||
"
|
||
@click="handleApprove(row)"
|
||
>
|
||
审核通过
|
||
</el-link>
|
||
<el-link
|
||
type="warning"
|
||
v-if="
|
||
!mkEnabled &&
|
||
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="primary"
|
||
:disabled="kingdeeSyncingId === row.id"
|
||
v-if="
|
||
hasPermission('customer_archive_sync') &&
|
||
row.approvalStatus === 'approved' &&
|
||
!['synced', 'exist'].includes(row.kingdeeStatus)
|
||
"
|
||
@click="handleSyncKingdee(row)"
|
||
>
|
||
{{ kingdeeSyncingId === row.id ? '推送中…' : '同步金蝶' }}
|
||
</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
|
||
v-if="!isArchivePage"
|
||
:page="page"
|
||
@size-change="sizeChange"
|
||
@current-change="currentChange"
|
||
@load="onLoad(page, query)"
|
||
/>
|
||
|
||
<component
|
||
:is="archiveContainer"
|
||
v-if="archiveBox"
|
||
v-bind="archiveContainerProps"
|
||
@update:model-value="archiveBox = $event"
|
||
@closed="resetArchive"
|
||
>
|
||
<div v-if="isArchivePage" class="archive-page-form__title">
|
||
{{ dialogTitle }}
|
||
</div>
|
||
<el-form
|
||
ref="archiveForm"
|
||
:model="archiveForm"
|
||
:rules="formRules"
|
||
label-position="right"
|
||
label-width="calc(6em + 24px)"
|
||
:disabled="readonly"
|
||
class="archive-form dialog-form-label-fixed"
|
||
>
|
||
<section-card title="基础信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="客商编号" prop="customerCode">
|
||
<el-input
|
||
v-model="archiveForm.customerCode"
|
||
maxlength="20"
|
||
show-word-limit
|
||
placeholder="请输入,留空则自动生成"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="供应商编码" prop="supplierCode">
|
||
<el-input
|
||
v-model="archiveForm.supplierCode"
|
||
maxlength="64"
|
||
show-word-limit
|
||
disabled
|
||
placeholder="金蝶供应商编码,系统自动获取"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="客商类型" prop="customerKind">
|
||
<el-select
|
||
v-model="archiveForm.customerKind"
|
||
placeholder="请选择"
|
||
filterable
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="item in customerKindOptions"
|
||
: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="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="guangxiTop100">
|
||
<el-select v-model="archiveForm.guangxiTop100" placeholder="请选择" clearable>
|
||
<el-option
|
||
v-for="item in guangxiTop100Options"
|
||
: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
|
||
:disabled="kingdeeLocked && Boolean(archiveForm.id)"
|
||
@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
|
||
:disabled="kingdeeLocked"
|
||
@blur="checkArchiveNameRisk"
|
||
/>
|
||
<el-tooltip
|
||
v-if="archiveNameRisky"
|
||
placement="top"
|
||
effect="dark"
|
||
popper-class="customer-risk-popper"
|
||
>
|
||
<template #content>
|
||
<div>{{ archiveNameRiskTip || '该客商为高风险/黑名单客商' }}</div>
|
||
</template>
|
||
<div class="archive-name-risk">{{ archiveNameRiskLabel }}</div>
|
||
</el-tooltip>
|
||
</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
|
||
:disabled="kingdeeLocked"
|
||
/>
|
||
</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="6">
|
||
<el-form-item label="所在地区" prop="registeredRegionName" 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"
|
||
/>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="详细地址" prop="registeredDetailAddress" required>
|
||
<el-tooltip
|
||
:content="archiveForm.registeredDetailAddress"
|
||
placement="top"
|
||
:disabled="!registeredDetailOverflow"
|
||
style="display: block; width: 100%"
|
||
>
|
||
<el-input
|
||
ref="registeredDetailInput"
|
||
v-model="archiveForm.registeredDetailAddress"
|
||
maxlength="255"
|
||
placeholder="请输入详细地址"
|
||
@input="checkOverflow('registeredDetailInput', 'registeredDetailOverflow')"
|
||
/>
|
||
</el-tooltip>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="网络货运平台" prop="networkFreightPlatform">
|
||
<el-select
|
||
v-model="archiveForm.networkFreightPlatform"
|
||
placeholder="请选择"
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="item in networkFreightPlatformOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item
|
||
label="营业期限"
|
||
prop="businessEndDate"
|
||
:required="archiveForm.businessTermType === '固定期限'"
|
||
:rules="businessEndDateRules"
|
||
>
|
||
<div class="business-term-field">
|
||
<el-date-picker
|
||
v-model="archiveForm.businessStartDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="开始日期"
|
||
/>
|
||
<span class="business-term-separator">至</span>
|
||
<el-date-picker
|
||
v-model="archiveForm.businessEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="截止日期"
|
||
: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="registeredCapital">
|
||
<el-input
|
||
v-model="archiveForm.registeredCapital"
|
||
maxlength="18"
|
||
:disabled="kingdeeLocked"
|
||
@input="value => handleDecimalInput('registeredCapital', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="实缴资本(万元)" prop="paidInCapital">
|
||
<el-input
|
||
v-model="archiveForm.paidInCapital"
|
||
maxlength="18"
|
||
:disabled="kingdeeLocked"
|
||
@input="value => handleDecimalInput('paidInCapital', value)"
|
||
/>
|
||
</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-col :span="6">
|
||
<el-form-item label="已使用额度(万元)" prop="usedCreditLimit">
|
||
<el-input
|
||
v-model="archiveForm.usedCreditLimit"
|
||
maxlength="18"
|
||
:disabled="readonly || Boolean(archiveForm.id)"
|
||
placeholder="新建可录历史已使用额度"
|
||
@input="value => handleDecimalInput('usedCreditLimit', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="经营范围" prop="businessScope" class="archive-form__remark">
|
||
<el-input
|
||
v-model="archiveForm.businessScope"
|
||
type="textarea"
|
||
:rows="4"
|
||
maxlength="2000"
|
||
show-word-limit
|
||
placeholder="可通过营业执照 OCR 自动识别,也可手动填写"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="备注" prop="remark" class="archive-form__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>
|
||
|
||
<section-card class="archive-detail-card">
|
||
<el-tabs v-model="activeTab" class="archive-tabs" type="card">
|
||
<el-tab-pane label="评分表" name="scores">
|
||
<div class="tab-table-toolbar" v-if="!readonly">
|
||
<el-button type="primary" plain icon="el-icon-plus" @click="addScore"
|
||
>添加评分记录</el-button
|
||
>
|
||
</div>
|
||
<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 v-if="archiveForm.id" 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="210" align="center">
|
||
<template #header>
|
||
<span>最大资金使用额度(万元)</span>
|
||
</template>
|
||
<template #default="{ row }">
|
||
<span>{{ formatScoreValue(row.maxCreditLimit) }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column min-width="220" align="center">
|
||
<template #header>
|
||
<span>拟申请总资金使用额(万元)</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="220" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<div class="score-list-table__actions">
|
||
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
|
||
详情
|
||
</el-link>
|
||
<el-link
|
||
v-if="canReviewScore(row)"
|
||
type="primary"
|
||
@click="startReviewScore(row.__raw, row.__rawIndex)"
|
||
>
|
||
复评
|
||
</el-link>
|
||
<el-link
|
||
v-if="showScoreDelete"
|
||
type="danger"
|
||
@click="deleteScore(row.__rawIndex)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<empty-pagination
|
||
:page="scorePaginationPage"
|
||
always-show
|
||
@size-change="handleScoreSizeChange"
|
||
@current-change="handleScoreCurrentChange"
|
||
/>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="联系人" name="contacts">
|
||
<div class="tab-table-toolbar" v-if="!readonly">
|
||
<el-button type="primary" plain icon="el-icon-plus" @click="openContact()"
|
||
>新增联系人</el-button
|
||
>
|
||
</div>
|
||
<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"
|
||
fixed="right"
|
||
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)"
|
||
/>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="收款信息" name="receiptAccounts">
|
||
<div class="tab-table-toolbar" v-if="!readonly">
|
||
<el-button type="primary" plain icon="el-icon-plus" @click="openReceipt()"
|
||
>新增收款信息</el-button
|
||
>
|
||
</div>
|
||
<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="cnapsCode" min-width="160" />
|
||
<el-table-column label="备注" prop="remark" min-width="180" />
|
||
<el-table-column
|
||
label="操作"
|
||
width="130"
|
||
fixed="right"
|
||
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)"
|
||
/>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="发票信息" name="invoices">
|
||
<div class="tab-table-toolbar" v-if="!readonly">
|
||
<el-button type="primary" plain icon="el-icon-plus" @click="openInvoice()"
|
||
>新增发票信息</el-button
|
||
>
|
||
</div>
|
||
<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="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="邮箱" min-width="180">
|
||
<template #default="{ row }">
|
||
<span>{{ formatInvoiceEmails(row) || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
label="操作"
|
||
width="130"
|
||
fixed="right"
|
||
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)"
|
||
/>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</section-card>
|
||
|
||
<section-card>
|
||
<template #title>
|
||
<span>客商材料</span>
|
||
<span v-if="missingQualificationText" class="qualification-missing-tip">
|
||
{{ missingQualificationText }}
|
||
</span>
|
||
</template>
|
||
<template #extra>
|
||
<div class="section-actions">
|
||
<el-button type="primary" plain icon="el-icon-download" @click="downloadAttachments"
|
||
>批量下载</el-button
|
||
>
|
||
<vehicle-attachment-upload
|
||
v-if="!readonly"
|
||
v-model="ocrQualificationUploadFiles"
|
||
:multiple="true"
|
||
:limit="20"
|
||
:max-size="500"
|
||
button-text="OCR上传识别"
|
||
:show-tip="false"
|
||
:show-file-list="false"
|
||
@success="handleOcrQualificationUploadSuccess"
|
||
/>
|
||
</div>
|
||
</template>
|
||
<el-table
|
||
:data="qualificationFiles"
|
||
border
|
||
@selection-change="handleQualificationSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="55" align="center" />
|
||
<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
|
||
@change="sortQualificationFiles"
|
||
>
|
||
<el-option
|
||
v-for="item in qualificationTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column
|
||
label="文件名称"
|
||
min-width="180"
|
||
align="left"
|
||
show-overflow-tooltip
|
||
class-name="is-ellipsis-link"
|
||
>
|
||
<template #default="{ row }">
|
||
<span
|
||
class="attachment-file-name"
|
||
:class="{ 'is-disabled': !row.url }"
|
||
role="button"
|
||
tabindex="0"
|
||
@click="row.url && previewQualificationFile(row)"
|
||
@keydown.enter="row.url && previewQualificationFile(row)"
|
||
@keydown.space.prevent="row.url && previewQualificationFile(row)"
|
||
>{{ row.originalName || row.name || '-' }}</span>
|
||
</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>
|
||
<div v-if="!readonly" class="qualification-upload-bar">
|
||
<vehicle-attachment-upload
|
||
v-model="qualificationUploadFiles"
|
||
:multiple="true"
|
||
:limit="20"
|
||
accept=".pdf,.bmp,.jpeg,.png,.jpg,.doc,.docx,.ppt,.pptx,.xlsx,.xls,.eml,.msg,.zip"
|
||
:max-size="500"
|
||
button-text="上传附件"
|
||
:show-file-list="false"
|
||
:show-uploading="true"
|
||
@success="handleQualificationUploadSuccess"
|
||
/>
|
||
</div>
|
||
</section-card>
|
||
|
||
<el-dialog
|
||
v-model="qualificationDocumentPreviewVisible"
|
||
:title="qualificationPreviewFile.name || '附件预览'"
|
||
append-to-body
|
||
destroy-on-close
|
||
width="90%"
|
||
top="4vh"
|
||
>
|
||
<pdf-preview
|
||
v-if="
|
||
qualificationDocumentPreviewVisible &&
|
||
qualificationPreviewFile.url &&
|
||
qualificationPreviewFile.isPdf
|
||
"
|
||
:source="qualificationPreviewFile.url"
|
||
@error="handleQualificationPreviewError"
|
||
/>
|
||
<open-file-viewer
|
||
v-else-if="qualificationDocumentPreviewVisible && qualificationPreviewFile.url"
|
||
:file="qualificationPreviewFile.url"
|
||
:file-name="qualificationPreviewFile.name"
|
||
:mime-type="qualificationPreviewFile.mimeType"
|
||
width="100%"
|
||
height="72vh"
|
||
fit="contain"
|
||
theme="auto"
|
||
locale="zh-CN"
|
||
:toolbar="qualificationViewerToolbar"
|
||
:plugins="qualificationViewerPlugins"
|
||
@unsupported="handleQualificationPreviewUnsupported"
|
||
@error="handleQualificationPreviewError"
|
||
/>
|
||
</el-dialog>
|
||
<el-image-viewer
|
||
v-if="qualificationImagePreviewVisible"
|
||
:url-list="qualificationImagePreviewUrls"
|
||
:initial-index="qualificationImagePreviewIndex"
|
||
@close="qualificationImagePreviewVisible = false"
|
||
/>
|
||
|
||
<section-card v-if="archiveForm.id" title="变更记录">
|
||
<el-table :data="changeRecordPage.records" border :show-overflow-tooltip="false">
|
||
<el-table-column
|
||
label="序号"
|
||
type="index"
|
||
width="90"
|
||
align="center"
|
||
:index="changeRecordIndex"
|
||
/>
|
||
<el-table-column label="变更日期" prop="changeTime" min-width="170" sortable />
|
||
<el-table-column label="变更内容" min-width="560">
|
||
<template #default="{ row }">
|
||
<el-tooltip placement="top" :show-after="200">
|
||
<template #content>
|
||
<div class="change-record-content-tooltip">{{ formatChangeContent(row) }}</div>
|
||
</template>
|
||
<span class="change-record-content-cell">{{ formatChangeContent(row) }}</span>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="变更账号" prop="changeUserName" min-width="160" />
|
||
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="openChangeRecordDetail(row)">详情</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<empty-pagination
|
||
:page="changeRecordPage"
|
||
always-show
|
||
@size-change="handleChangeRecordSizeChange"
|
||
@current-change="handleChangeRecordCurrentChange"
|
||
/>
|
||
</section-card>
|
||
|
||
<change-record-detail-dialog
|
||
v-model="changeRecordDetailVisible"
|
||
:rows="changeRecordDetailRows"
|
||
/>
|
||
|
||
<div class="archive-form__footer" v-if="!isPublicViewPage">
|
||
<!-- 次要:独立页返回 / 只读关闭 / 弹窗取消 -->
|
||
<el-button @click="closeArchive">{{ readonly ? '关闭' : '取消' }}</el-button>
|
||
<el-button type="primary" plain v-if="!readonly" @click="saveArchive">保存</el-button>
|
||
<el-button type="primary" v-if="!readonly" @click="saveAndSubmitArchive">提交</el-button>
|
||
</div>
|
||
</component>
|
||
|
||
<el-dialog
|
||
:title="contactDialogTitle"
|
||
append-to-body
|
||
v-model="contactBox"
|
||
width="680px"
|
||
class="contact-dialog"
|
||
@closed="resetContact"
|
||
@opened="syncContactRegionPath"
|
||
>
|
||
<section-card>
|
||
<el-form
|
||
ref="contactForm"
|
||
:model="contactForm"
|
||
:rules="contactRules"
|
||
label-position="right"
|
||
label-width="auto"
|
||
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="branchName" class="contact-form__compact-field">
|
||
<el-cascader
|
||
v-model="contactBranchCascaderValue"
|
||
:options="deptTree"
|
||
:props="deptCascaderProps"
|
||
clearable
|
||
filterable
|
||
placeholder="请选择所属分公司/事业部"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="省市区" prop="regionName" class="contact-form__compact-field">
|
||
<el-cascader
|
||
ref="contactRegionCascader"
|
||
v-model="contactForm.regionPath"
|
||
:options="regionOptions"
|
||
:props="regionCascaderProps"
|
||
:placeholder="contactForm.regionName || '请选择省市区'"
|
||
clearable
|
||
filterable
|
||
@change="handleContactRegionChange"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="详细地址" prop="detailAddress" class="contact-form__compact-field">
|
||
<el-input
|
||
v-model="contactForm.detailAddress"
|
||
maxlength="255"
|
||
placeholder="请输入详细地址"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="备注" prop="remark" class="contact-form__compact-field">
|
||
<el-input
|
||
v-model="contactForm.remark"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="200"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section-card>
|
||
<template #footer>
|
||
<div class="contact-dialog__footer-actions">
|
||
<el-button @click="contactBox = false">取消</el-button>
|
||
<el-button type="primary" plain @click="saveContact">保存</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
:title="receiptDialogTitle"
|
||
append-to-body
|
||
v-model="receiptBox"
|
||
width="640px"
|
||
class="receipt-dialog"
|
||
@closed="resetReceipt"
|
||
>
|
||
<section-card>
|
||
<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 prop="bankName">
|
||
<template #label>
|
||
<span class="receipt-bank-label">
|
||
开户行
|
||
<el-tooltip placement="top" effect="dark" content="请填写银行全称,系统将自动匹配金蝶档案带出联行号">
|
||
<el-icon class="receipt-bank-help">
|
||
<el-icon-question-filled />
|
||
</el-icon>
|
||
</el-tooltip>
|
||
</span>
|
||
</template>
|
||
<el-input v-model="receiptForm.bankName" maxlength="100" @blur="fetchReceiptCnapsCode" />
|
||
</el-form-item>
|
||
<el-form-item label="联行号" prop="cnapsCode">
|
||
<el-input v-model="receiptForm.cnapsCode" maxlength="32" />
|
||
</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"
|
||
type="textarea"
|
||
:rows="2"
|
||
maxlength="500"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-form>
|
||
</section-card>
|
||
<template #footer>
|
||
<div class="receipt-dialog__footer-actions">
|
||
<el-button @click="receiptBox = false">取消</el-button>
|
||
<el-button type="primary" plain @click="saveReceipt">保存</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"
|
||
placeholder="默认客商统一社会信用代码,可编辑"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="注册地址" prop="registeredRegionName">
|
||
<el-cascader
|
||
ref="invoiceRegisteredRegionCascader"
|
||
v-model="invoiceForm.registeredRegionPath"
|
||
:options="regionOptions"
|
||
:props="regionCascaderProps"
|
||
:placeholder="invoiceForm.registeredRegionName || '默认客商地址 可编辑'"
|
||
clearable
|
||
filterable
|
||
@change="handleInvoiceRegisteredRegionChange"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="开户行名称" prop="bankName">
|
||
<el-autocomplete
|
||
v-model="invoiceForm.bankName"
|
||
:fetch-suggestions="queryBankNameSuggestions"
|
||
clearable
|
||
maxlength="100"
|
||
placeholder="请选择或输入开户行名称"
|
||
class="invoice-bank-autocomplete"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="企业全称" prop="invoiceTitle">
|
||
<el-input
|
||
v-model="invoiceForm.invoiceTitle"
|
||
maxlength="100"
|
||
placeholder="默认客商名称 可编辑"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item label="详细地址" prop="registeredDetailAddress">
|
||
<el-tooltip
|
||
:content="invoiceForm.registeredDetailAddress"
|
||
placement="top"
|
||
:disabled="!invoiceDetailOverflow"
|
||
style="display: block; width: 100%"
|
||
>
|
||
<el-input
|
||
ref="invoiceDetailInput"
|
||
v-model="invoiceForm.registeredDetailAddress"
|
||
maxlength="255"
|
||
placeholder="默认客商地址 可编辑"
|
||
@input="checkOverflow('invoiceDetailInput', 'invoiceDetailOverflow')"
|
||
/>
|
||
</el-tooltip>
|
||
</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>
|
||
<template #title>联系信息</template>
|
||
<template #extra>
|
||
<el-button v-if="!readonly" type="primary" @click="addInvoiceContact">添加</el-button>
|
||
</template>
|
||
<el-table :data="invoiceForm.contacts || []" border class="invoice-contact-table">
|
||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||
<el-table-column label="联系人" min-width="120">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.contactName" maxlength="30" :disabled="readonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="联系电话" min-width="140">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.contactPhone" maxlength="20" :disabled="readonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="邮箱地址" min-width="180">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.email" maxlength="100" :disabled="readonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="所属部门" min-width="220">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="!isInternalCustomer"
|
||
:model-value="row.deptNames"
|
||
disabled
|
||
/>
|
||
<el-cascader
|
||
v-else
|
||
v-model="row.deptIdList"
|
||
:options="deptTree"
|
||
:props="invoiceDeptCascaderProps"
|
||
collapse-tags
|
||
collapse-tags-tooltip
|
||
filterable
|
||
clearable
|
||
:disabled="readonly"
|
||
placeholder="请选择所属部门"
|
||
@change="() => handleInvoiceContactDeptChange(row)"
|
||
/>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="备注" min-width="160">
|
||
<template #default="{ row }">
|
||
<el-input v-model="row.remark" maxlength="200" :disabled="readonly" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="!readonly" label="操作" width="80" align="center" fixed="right">
|
||
<template #default="{ $index }">
|
||
<el-link type="danger" @click="removeInvoiceContact($index)">删除</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="invoice-dialog__footer-actions">
|
||
<el-button @click="invoiceBox = false">取消</el-button>
|
||
<el-button type="primary" plain @click="saveInvoice">保存</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="评分详情表"
|
||
append-to-body
|
||
v-model="scoreBox"
|
||
width="92%"
|
||
class="score-detail-dialog"
|
||
@closed="resetScoreDialog"
|
||
>
|
||
<section-card>
|
||
<el-form
|
||
ref="scoreForm"
|
||
:model="currentScore"
|
||
label-position="right"
|
||
label-width="88px"
|
||
:disabled="readonly"
|
||
class="score-detail-form"
|
||
>
|
||
<el-row :gutter="42">
|
||
<el-col :span="12">
|
||
<el-form-item label="评分方式" required>
|
||
<el-radio-group
|
||
v-model="currentScore.evaluateMode"
|
||
:disabled="readonly"
|
||
@change="handleEvaluateModeChange"
|
||
>
|
||
<el-radio value="online">线上评估</el-radio>
|
||
<el-radio value="offline">线下评估</el-radio>
|
||
</el-radio-group>
|
||
<span v-if="isOfflineScore" class="score-offline-tip">线下请上传评分表 Excel</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="评定日期" required>
|
||
<el-date-picker
|
||
v-model="currentScore.scoreDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="下次评定日期" required>
|
||
<el-date-picker
|
||
v-model="currentScore.nextEvaluateDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
placeholder="请选择"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<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="String(item.id)"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col v-if="isOfflineScore" :span="12">
|
||
<el-form-item label="评分表">
|
||
<div class="score-offline-actions">
|
||
<el-button
|
||
type="primary"
|
||
plain
|
||
:disabled="!currentScore.quantificationId || readonly"
|
||
@click="downloadScoreTemplate"
|
||
>
|
||
下载模板
|
||
</el-button>
|
||
<el-upload
|
||
:show-file-list="false"
|
||
:disabled="!currentScore.quantificationId || readonly || scoreImporting"
|
||
accept=".xls,.xlsx"
|
||
:http-request="uploadScoreExcel"
|
||
>
|
||
<el-button
|
||
type="primary"
|
||
:loading="scoreImporting"
|
||
:disabled="!currentScore.quantificationId || readonly"
|
||
>
|
||
上传 Excel
|
||
</el-button>
|
||
</el-upload>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="信用等级">
|
||
<el-input :model-value="formatScoreValue(currentScore.creditLevel)" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="自评得分" :required="isOfflineScore">
|
||
<el-input
|
||
v-if="isOfflineScore"
|
||
v-model="currentScore.selfScore"
|
||
maxlength="10"
|
||
:disabled="readonly"
|
||
@input="handleOfflineSelfScoreInput"
|
||
@blur="recalculateOfflineScore"
|
||
>
|
||
<template #suffix>分</template>
|
||
</el-input>
|
||
<el-input v-else :model-value="formatScoreValue(currentScore.selfScore)" disabled>
|
||
<template #suffix>分</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="最终得分">
|
||
<el-input :model-value="formatScoreValue(currentScore.finalScore)" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="拟申请总资金使用额度" required>
|
||
<el-input
|
||
v-model="currentScore.applyCreditLimit"
|
||
maxlength="18"
|
||
@input="value => handleScoreDecimalInput('applyCreditLimit', value)"
|
||
>
|
||
<template #suffix>万元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="最大资金使用额度">
|
||
<el-input :model-value="formatScoreValue(currentScore.maxCreditLimit)" disabled>
|
||
<template #suffix>万元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col v-if="!isOfflineScore" :span="6">
|
||
<el-form-item label="临时申请额度">
|
||
<el-input
|
||
v-model="currentScore.tempApplyCreditLimit"
|
||
maxlength="18"
|
||
@input="value => handleScoreDecimalInput('tempApplyCreditLimit', value)"
|
||
>
|
||
<template #suffix>万元</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col v-if="!isOfflineScore" :span="6">
|
||
<el-form-item label="临时申请额度有效期起">
|
||
<el-date-picker
|
||
v-model="currentScore.tempCreditStartDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col v-if="!isOfflineScore" :span="6">
|
||
<el-form-item label="临时申请额度有效期止">
|
||
<el-date-picker
|
||
v-model="currentScore.tempCreditEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="备注">
|
||
<el-input v-model="currentScore.remark" maxlength="200" :rows="2" type="textarea" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
|
||
<el-table
|
||
v-if="!isOfflineScore && 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="评分项目"
|
||
width="150"
|
||
align="center"
|
||
:show-overflow-tooltip="false"
|
||
>
|
||
<template #default="{ row: detail }">
|
||
<el-tooltip
|
||
:content="detail.itemName"
|
||
placement="top"
|
||
effect="dark"
|
||
append-to="body"
|
||
:show-after="200"
|
||
:disabled="!detail.itemName"
|
||
>
|
||
<span class="score-item-name">{{ detail.itemName }}</span>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="评分标准" min-width="430">
|
||
<template #default="{ row: detail }">
|
||
<div class="score-standard-cell">
|
||
<template v-if="isScoreTypeDetail(detail)">
|
||
<el-input
|
||
:model-value="detail.scoreInput"
|
||
inputmode="decimal"
|
||
:disabled="readonly"
|
||
@input="value => handleScoreValueInput(detail, value)"
|
||
@blur="handleScoreValueBlur(detail)"
|
||
>
|
||
<template #append>{{ getScoreRule(detail)?.changeUnit || '' }}</template>
|
||
</el-input>
|
||
</template>
|
||
<template v-else>
|
||
<!-- <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>
|
||
</template>
|
||
</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(getSignedScore(detail, 'selfScore')) }}分</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column v-if="!isNewScore" label="复评得分" width="150" align="center">
|
||
<template #default="{ row: detail }">
|
||
<el-input
|
||
v-model="detail.reviewScore"
|
||
maxlength="10"
|
||
inputmode="decimal"
|
||
:disabled="
|
||
(!reviewMode && readonly) || !hasPermission('customer_type_re_cert')
|
||
"
|
||
@input="value => handleScoreDetailScoreInput(detail, 'reviewScore', value)"
|
||
>
|
||
<template
|
||
v-if="isMinusScoreDetail(detail) && Number(detail.reviewScore) > 0"
|
||
#prefix
|
||
>
|
||
-
|
||
</template>
|
||
<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>自评:{{ getSignedScore(row, 'selfScore') }}分</span>
|
||
<span v-if="!isNewScore">复评:{{ getSignedScore(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>
|
||
|
||
<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"
|
||
:show-uploading="true"
|
||
@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="文件名"
|
||
min-width="220"
|
||
show-overflow-tooltip
|
||
class-name="is-ellipsis-link"
|
||
>
|
||
<template #default="{ row }">
|
||
<span>{{ row.originalName || row.name || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="附件描述" min-width="220">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="!readonly"
|
||
v-model="row.description"
|
||
placeholder="请输入附件描述"
|
||
maxlength="200"
|
||
/>
|
||
<span v-else>{{ row.description || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<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="danger" @click="deleteScoreAttachment($index)"> 删除 </el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</section-card>
|
||
|
||
<template #footer>
|
||
<div class="score-detail-dialog__footer-actions">
|
||
<div class="score-detail-dialog__total">
|
||
<span>总分合计</span>
|
||
<span>自评:{{ formatScoreValue(currentScore.selfScore) }}分</span>
|
||
<span v-if="!isNewScore">复评:{{ formatScoreValue(currentScore.reviewScore) }}分</span>
|
||
</div>
|
||
<el-button @click="scoreBox = false">取消</el-button>
|
||
<el-button v-if="isNewScore" type="primary" @click="submitSelfScore">提交自评</el-button>
|
||
<el-button type="primary" plain v-if="!readonly && !isNewScore" @click="saveScoreDetail">
|
||
保存
|
||
</el-button>
|
||
<el-button
|
||
v-if="!isNewScore && (!readonly || reviewMode) && hasPermission('customer_type_re_cert')"
|
||
type="primary"
|
||
@click="confirmReviewScore"
|
||
>
|
||
复评确认
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</basic-container>
|
||
</template>
|
||
|
||
<script>
|
||
import { h } from 'vue';
|
||
import { ElCascader } from 'element-plus';
|
||
import {
|
||
getList,
|
||
getDetail,
|
||
getPublicDetail,
|
||
getChangeRecordList,
|
||
getPublicChangeRecordList,
|
||
submit,
|
||
submitApproval,
|
||
withdrawApproval,
|
||
approve,
|
||
reject,
|
||
changeStatus,
|
||
remove,
|
||
getScoreTemplate,
|
||
downloadScoreExcelTemplate,
|
||
importScoreExcel,
|
||
recognizeBusinessLicenseOcr,
|
||
queryKingdeeBank,
|
||
queryKingdeeBankByNumber,
|
||
queryKingdeeCustomer,
|
||
querySupplierCode,
|
||
syncKingdee,
|
||
} from '@/api/vehicle/customer-archive';
|
||
import { getRisk, getRiskByName } from '@/api/business/hcs';
|
||
import { isKingdeeBlack, isRiskyTag, resolveRiskTagName } from '@/utils/risk-display';
|
||
import {
|
||
getList as getCreditScoreQuantificationList,
|
||
getDetail as getCreditScoreQuantificationDetail,
|
||
} from '@/api/vehicle/credit-score-quantification';
|
||
import { getDictionary } from '@/api/system/dictbiz';
|
||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||
import { getDeptTree } from '@/api/system/dept';
|
||
import { getLazyTree } from '@/api/base/region';
|
||
import { exportBlob } from '@/api/common';
|
||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||
import {
|
||
isExcelResponse,
|
||
isBusinessJson,
|
||
parseBlobJson,
|
||
resolveImportErrorMessage,
|
||
} from '@/utils/import-excel';
|
||
import { getToken, isAdministrator } from '@/utils/auth';
|
||
import { getUploadHeaders } from '@/utils/upload';
|
||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||
import { ArrowRight } from '@element-plus/icons-vue';
|
||
import { ElImageViewer, ElLoading } from 'element-plus';
|
||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||
import { fallbackPlugin, imagePlugin, officePlugin, textPlugin } from '@open-file-viewer/core';
|
||
import '@open-file-viewer/core/style.css';
|
||
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
||
import { mapGetters } from 'vuex';
|
||
import NProgress from 'nprogress';
|
||
import 'nprogress/nprogress.css';
|
||
import SectionCard from '@/components/section-card/main.vue';
|
||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||
import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue';
|
||
|
||
const customerArchiveListRoute = '/vehicle/customer-archive';
|
||
const customerArchiveFormRoute = '/vehicle/customer-archive/form';
|
||
const customerArchiveListRefreshKey = 'customer-archive-list-refresh';
|
||
/** 客商所属组织固定为顶层组织 */
|
||
const FIXED_OWNER_DEPT_NAME = '广西现代物流集团有限公司';
|
||
/** 暂定:加分/减分合计上限均为 15 分,超出按 15 分计 */
|
||
const HARDCODED_MAX_PLUS_SCORE = 15;
|
||
const HARDCODED_MAX_MINUS_SCORE = 15;
|
||
|
||
const createTimeRangeMap = {
|
||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||
};
|
||
|
||
const qualificationViewerPlugins = [
|
||
imagePlugin(),
|
||
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||
textPlugin(),
|
||
fallbackPlugin(),
|
||
];
|
||
|
||
export default {
|
||
components: {
|
||
SectionCard,
|
||
ChangeRecordDetailDialog,
|
||
ArrowRight,
|
||
ElImageViewer,
|
||
OpenFileViewer,
|
||
PdfPreview,
|
||
},
|
||
props: {
|
||
embeddedPublicId: {
|
||
type: [String, Number],
|
||
default: '',
|
||
},
|
||
},
|
||
data() {
|
||
const nonNegativeLabelMap = {
|
||
invoiceTaxRate: '开票税点',
|
||
registeredCapital: '注册资金',
|
||
paidInCapital: '实缴资本',
|
||
maxCreditLimit: '最大资金使用额度',
|
||
applyCreditLimit: '申请总资金使用额度',
|
||
usedCreditLimit: '已使用额度',
|
||
};
|
||
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 validateUnifiedCreditCode = (rule, value, callback) => {
|
||
const code = String(value || '')
|
||
.trim()
|
||
.toUpperCase();
|
||
if (!code) {
|
||
callback();
|
||
return;
|
||
}
|
||
const error = this.unifiedCreditCodeError(code);
|
||
if (error) {
|
||
callback(new Error(error));
|
||
return;
|
||
}
|
||
callback();
|
||
};
|
||
return {
|
||
form: {},
|
||
query: { expireStatus: 'all' },
|
||
deptSearchInitialized: false,
|
||
loading: true,
|
||
scoreImporting: false,
|
||
data: [],
|
||
archiveNameRisky: false,
|
||
archiveNameRiskTip: '',
|
||
archiveNameRiskLabel: '',
|
||
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,
|
||
reviewMode: false,
|
||
listActivated: false,
|
||
// 表单页实例标记:创建时按路由落定,避免 keep-alive 缓存后随全局 $route 翻转
|
||
isFormPageInstance: false,
|
||
originalAccessType: '',
|
||
activeTab: 'scores',
|
||
archiveForm: this.emptyArchive(),
|
||
kingdeeBlocked: false,
|
||
kingdeeQueryTimer: null,
|
||
kingdeeSyncingId: null,
|
||
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],
|
||
},
|
||
changeRecordPage: {
|
||
currentPage: 1,
|
||
pageSize: 5,
|
||
pageSizes: [5],
|
||
total: 0,
|
||
records: [],
|
||
},
|
||
changeRecordDetailVisible: false,
|
||
changeRecordDetail: null,
|
||
changeRecordDetailRows: [],
|
||
contactIndex: -1,
|
||
contactForm: this.emptyContact(),
|
||
receiptIndex: -1,
|
||
receiptForm: this.emptyReceiptAccount(),
|
||
invoiceIndex: -1,
|
||
invoiceForm: this.emptyInvoice(),
|
||
registeredDetailOverflow: false,
|
||
invoiceDetailOverflow: false,
|
||
qualificationFiles: [],
|
||
qualificationUploadFiles: [],
|
||
ocrQualificationUploadFiles: [],
|
||
selectedQualificationFiles: [],
|
||
qualificationImagePreviewVisible: false,
|
||
qualificationImagePreviewUrls: [],
|
||
qualificationImagePreviewIndex: 0,
|
||
qualificationDocumentPreviewVisible: false,
|
||
qualificationPreviewFile: {},
|
||
qualificationViewerPlugins,
|
||
qualificationViewerToolbar: {
|
||
download: true,
|
||
fullscreen: true,
|
||
print: true,
|
||
rotate: true,
|
||
zoom: true,
|
||
},
|
||
deptTree: [],
|
||
deptOptions: [],
|
||
regionOptions: [],
|
||
customerNatureOptions: [],
|
||
customerTypeOptions: [],
|
||
customerKindOptions: [
|
||
{ label: '外部客商', value: 'external' },
|
||
{ label: '内部组织', value: 'internal' },
|
||
],
|
||
customerKindMap: {
|
||
external: '外部客商',
|
||
internal: '内部组织',
|
||
},
|
||
networkFreightPlatformOptions: [
|
||
{ label: '是', value: 1 },
|
||
{ label: '否', value: 0 },
|
||
],
|
||
guangxiTop100Options: [
|
||
{ label: '是', value: 1 },
|
||
{ label: '否', value: 0 },
|
||
],
|
||
bankListOptions: [],
|
||
qualificationTypeOptions: [],
|
||
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' },
|
||
},
|
||
/** 业务字典 mk_template「开启MK」=1 */
|
||
mkEnabled: false,
|
||
/** 是否已完成 MK 开关加载,避免提交时误判 */
|
||
_mkEnabledResolved: false,
|
||
formRules: {
|
||
customerCode: [{ max: 20, message: '客商编号最多20个字符', trigger: 'blur' }],
|
||
fullName: [{ required: true, message: '请输入客商名称', trigger: 'blur' }],
|
||
customerNature: [{ required: true, message: '请选择客商性质', trigger: 'change' }],
|
||
unifiedCreditCode: [
|
||
{ required: true, message: '请输入统一社会信用代码', trigger: 'blur' },
|
||
{
|
||
validator: validateUnifiedCreditCode,
|
||
trigger: 'blur',
|
||
},
|
||
],
|
||
customerType: [{ required: true, message: '请选择客商分类', trigger: 'change' }],
|
||
legalPerson: [{ required: true, message: '请输入企业法人', trigger: 'blur' }],
|
||
registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
paidInCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
usedCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
businessScope: [{ max: 2000, message: '经营范围最多2000个字符', trigger: 'blur' }],
|
||
registeredRegionName: [{ required: true, message: '请选择省市区', trigger: 'change' }],
|
||
registeredDetailAddress: [
|
||
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||
{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' },
|
||
],
|
||
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' }],
|
||
branchName: [{ required: true, message: '请选择所属分公司/事业部', trigger: 'change' }],
|
||
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' }],
|
||
cnapsCode: [{ 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' }],
|
||
bankName: [{ required: true, message: '请输入开户行名称', trigger: 'blur' }],
|
||
registeredRegionName: [{ required: true, message: '请选择注册地址', trigger: 'change' }],
|
||
registeredDetailAddress: [
|
||
{ required: true, message: '请输入详细地址', trigger: 'blur' },
|
||
{ max: 255, message: '详细地址最多255个字符', trigger: 'blur' },
|
||
],
|
||
bankAccount: [{ required: true, 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: 400,
|
||
column: [
|
||
{
|
||
label: '客商编号',
|
||
prop: 'customerCode',
|
||
slot: true,
|
||
search: true,
|
||
searchOrder: 10,
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 140,
|
||
},
|
||
{
|
||
label: '供应商编码',
|
||
prop: 'supplierCode',
|
||
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: 'customerKind',
|
||
type: 'select',
|
||
minWidth: 120,
|
||
dicData: [
|
||
{ label: '外部客商', value: 'external' },
|
||
{ label: '内部组织', value: 'internal' },
|
||
],
|
||
},
|
||
{
|
||
label: '客商风险',
|
||
prop: 'customerRisk',
|
||
slot: true,
|
||
search: true,
|
||
searchType: 'select',
|
||
searchOrder: 4,
|
||
searchPlaceholder: '请选择',
|
||
minWidth: 150,
|
||
// 选项走业务字典 customer_risk(initBusinessDictionaries 加载:dictKey=筛选值,dictValue=名称),未配置时下拉为空
|
||
dicData: [],
|
||
},
|
||
{
|
||
label: '客户性质',
|
||
prop: 'customerNature',
|
||
type: 'select',
|
||
search: true,
|
||
searchOrder: 5,
|
||
searchValue: '',
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 120,
|
||
dicData: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '有限公司', value: '有限公司' },
|
||
{ label: '个体工商户', value: '个体工商户' },
|
||
],
|
||
},
|
||
{
|
||
label: '是否广西百强',
|
||
prop: 'guangxiTop100',
|
||
type: 'select',
|
||
minWidth: 130,
|
||
dicData: [
|
||
{ label: '是', value: 1 },
|
||
{ label: '否', value: 0 },
|
||
],
|
||
formatter: row => {
|
||
if (row.guangxiTop100 === 1) return '是';
|
||
if (row.guangxiTop100 === 0) return '否';
|
||
return '';
|
||
},
|
||
},
|
||
{
|
||
label: '统一信用代码',
|
||
prop: 'unifiedCreditCode',
|
||
search: true,
|
||
searchOrder: 3,
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 190,
|
||
},
|
||
{
|
||
label: '所属组织',
|
||
prop: 'deptName',
|
||
search: true,
|
||
searchOrder: 1,
|
||
minWidth: 180,
|
||
},
|
||
{
|
||
label: '准入类型',
|
||
prop: 'accessType',
|
||
slot: true,
|
||
type: 'select',
|
||
search: true,
|
||
searchOrder: 8,
|
||
searchPlaceholder: '请选择',
|
||
minWidth: 120,
|
||
dicData: [
|
||
{ label: '临时', value: 'temporary' },
|
||
{ label: '正式', value: 'formal' },
|
||
],
|
||
},
|
||
{
|
||
label: '营业执照有效期',
|
||
prop: 'businessEndDate',
|
||
slot: true,
|
||
minWidth: 140,
|
||
},
|
||
{
|
||
label: '下次评定日期',
|
||
prop: 'nextEvaluateDate',
|
||
slot: true,
|
||
minWidth: 130,
|
||
},
|
||
{
|
||
label: '资金使用情况',
|
||
prop: 'fundUseRate',
|
||
slot: true,
|
||
minWidth: 130,
|
||
},
|
||
{
|
||
label: '资金使用额度风险',
|
||
prop: 'fundUseRisk',
|
||
slot: true,
|
||
headerslot: true,
|
||
width: 200,
|
||
overHidden: false,
|
||
},
|
||
{
|
||
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: 'kingdeeStatus',
|
||
slot: true,
|
||
minWidth: 110,
|
||
},
|
||
{
|
||
label: '状态',
|
||
prop: 'status',
|
||
slot: true,
|
||
type: 'select',
|
||
search: true,
|
||
searchOrder: 6,
|
||
searchValue: '',
|
||
minWidth: 100,
|
||
dicData: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '启用', value: 1 },
|
||
{ label: '停用', value: 2 },
|
||
],
|
||
clearable: true,
|
||
},
|
||
{
|
||
label: '创建人',
|
||
prop: 'createUserName',
|
||
addDisplay: false,
|
||
editDisplay: false,
|
||
minWidth: 120,
|
||
},
|
||
{
|
||
label: '创建时间',
|
||
prop: 'createTime',
|
||
sortable: true,
|
||
type: 'datetime',
|
||
format: 'YYYY-MM-DD HH:mm:ss',
|
||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||
addDisplay: false,
|
||
editDisplay: false,
|
||
minWidth: 170,
|
||
},
|
||
],
|
||
},
|
||
};
|
||
},
|
||
created() {
|
||
if (this.isPublicViewPage) {
|
||
this.readonly = true;
|
||
const id = this.embeddedPublicId || this.$route.query.id;
|
||
if (!id) {
|
||
this.archiveBox = true;
|
||
this.$message.error('缺少客商ID');
|
||
return;
|
||
}
|
||
this.openArchive({ id }, true);
|
||
return;
|
||
}
|
||
this.isFormPageInstance = this.$route.path === customerArchiveFormRoute;
|
||
this.initDeptTree();
|
||
this.initRegionOptions();
|
||
this.initBusinessDictionaries();
|
||
this.initMkEnabled();
|
||
this.initScoreQuantificationOptions();
|
||
if (this.isArchivePage) {
|
||
this.reviewMode = this.$route.query.review === '1';
|
||
const readonly =
|
||
this.$route.query.view === '1' || this.$route.query.view === 'true' || this.reviewMode;
|
||
this.openArchive(this.$route.query.id ? { id: this.$route.query.id } : null, readonly);
|
||
}
|
||
},
|
||
activated() {
|
||
if (this.isFormPageInstance || this.isPublicViewPage) return;
|
||
if (sessionStorage.getItem(customerArchiveListRefreshKey)) {
|
||
sessionStorage.removeItem(customerArchiveListRefreshKey);
|
||
this.onLoad(this.page, this.query);
|
||
this.listActivated = true;
|
||
return;
|
||
}
|
||
if (!this.listActivated) {
|
||
this.listActivated = true;
|
||
return;
|
||
}
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
isAdmin() {
|
||
const authority = this.userInfo.authority || '';
|
||
return isAdministrator(this.userInfo);
|
||
},
|
||
kingdeeLocked() {
|
||
return this.archiveForm.kingdeeStatus === 'exist';
|
||
},
|
||
permissionList() {
|
||
return {
|
||
addBtn: false,
|
||
};
|
||
},
|
||
isArchivePage() {
|
||
return this.isFormPageInstance || this.isPublicViewPage;
|
||
},
|
||
isPublicViewPage() {
|
||
return (
|
||
this.$route.path === '/vehicle/customer-archive/public-view' || !!this.embeddedPublicId
|
||
);
|
||
},
|
||
archiveContainer() {
|
||
return 'div';
|
||
},
|
||
archiveContainerProps() {
|
||
return { class: 'archive-page-form' };
|
||
},
|
||
regionCascaderProps() {
|
||
return {
|
||
value: 'id',
|
||
label: 'title',
|
||
children: 'children',
|
||
emitPath: true,
|
||
};
|
||
},
|
||
deptCascaderProps() {
|
||
return {
|
||
value: 'value',
|
||
label: 'label',
|
||
children: 'children',
|
||
emitPath: true,
|
||
checkStrictly: true,
|
||
expandTrigger: 'click',
|
||
};
|
||
},
|
||
// 发票联系信息所属部门:级联多选,可勾选任意层级
|
||
invoiceDeptCascaderProps() {
|
||
return {
|
||
value: 'value',
|
||
label: 'label',
|
||
children: 'children',
|
||
multiple: true,
|
||
checkStrictly: true,
|
||
emitPath: false,
|
||
expandTrigger: 'click',
|
||
};
|
||
},
|
||
// 所属组织级联值:archiveForm.deptId 只存选中的部门 ID(单选),
|
||
// 级联控件需要根到选中节点的完整路径,两者在此做双向转换——
|
||
// 部门树是异步加载的,依赖 deptTree 可保证树加载完成后回显自动刷新。
|
||
deptCascaderValue: {
|
||
get() {
|
||
const ids = Array.isArray(this.archiveForm.deptId)
|
||
? this.archiveForm.deptId
|
||
: this.archiveForm.deptId
|
||
? [this.archiveForm.deptId]
|
||
: [];
|
||
const currentId = ids.length ? String(ids[ids.length - 1]) : '';
|
||
if (!currentId) return [];
|
||
return this.findDeptPath(currentId) || [currentId];
|
||
},
|
||
set(path) {
|
||
this.onDeptChange(path);
|
||
},
|
||
},
|
||
// 联系人所属分公司/事业部:表单存名称,级联控件用 id 路径
|
||
contactBranchCascaderValue: {
|
||
get() {
|
||
return this.findDeptPathByName(this.contactForm.branchName);
|
||
},
|
||
set(path) {
|
||
const ids = Array.isArray(path)
|
||
? path.filter(item => item !== undefined && item !== null && item !== '')
|
||
: [];
|
||
const deptId = ids.length ? String(ids[ids.length - 1]) : '';
|
||
this.contactForm.branchName = deptId ? this.findDeptLabel(deptId) : '';
|
||
this.$refs.contactForm?.validateField('branchName');
|
||
},
|
||
},
|
||
ids() {
|
||
let ids = [];
|
||
this.selectionList.forEach(ele => {
|
||
ids.push(ele.id);
|
||
});
|
||
return ids.join(',');
|
||
},
|
||
dialogTitle() {
|
||
if (this.reviewMode) return '客商复评';
|
||
if (this.readonly) return '查看客商档案';
|
||
return this.archiveForm.id ? '编辑客商档案' : '新增客商档案';
|
||
},
|
||
businessEndDateRules() {
|
||
return this.archiveForm.businessTermType === '固定期限'
|
||
? [{ required: true, message: '请选择营业期限截止日', trigger: 'change' }]
|
||
: [];
|
||
},
|
||
contactDialogTitle() {
|
||
return this.contactIndex > -1 ? '编辑联系人' : '新增联系人';
|
||
},
|
||
receiptDialogTitle() {
|
||
return this.receiptIndex > -1 ? '编辑收款信息' : '新增收款信息';
|
||
},
|
||
invoiceDialogTitle() {
|
||
return this.invoiceIndex > -1 ? '编辑发票信息' : '新增发票信息';
|
||
},
|
||
isInternalCustomer() {
|
||
return this.archiveForm.customerKind === 'internal';
|
||
},
|
||
isTemporaryToFormal() {
|
||
return (
|
||
Boolean(this.archiveForm.id) &&
|
||
this.originalAccessType === 'temporary' &&
|
||
this.archiveForm.accessType === 'formal'
|
||
);
|
||
},
|
||
missingQualificationText() {
|
||
const missing = this.getMissingQualificationTypes();
|
||
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||
},
|
||
uploadHeaders() {
|
||
return getUploadHeaders();
|
||
},
|
||
scoreList() {
|
||
return this.archiveForm.scores || [];
|
||
},
|
||
showScoreDelete() {
|
||
if (this.readonly) return false;
|
||
const status = this.archiveForm.approvalStatus || 'draft';
|
||
// 草稿/驳回可删;审批通过后也允许删除并重建评分表(旧评分逻辑删除保留,新评分审核通过前仍用旧额度)
|
||
return status === 'draft' || status === 'rejected' || status === 'approved';
|
||
},
|
||
isNewScore() {
|
||
return this.scoreRecordIndex < 0 && !this.reviewMode;
|
||
},
|
||
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.sortScoresByLatest(this.scoreList)
|
||
.slice(start, start + this.scorePage.pageSize)
|
||
.map(({ score, index }, pageIndex) => ({
|
||
...score,
|
||
__raw: score,
|
||
__rawIndex: index,
|
||
__index: start + pageIndex + 1,
|
||
maxCreditLimit: score.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: '减分项目' },
|
||
];
|
||
const rows = categoryMap.map((category, index) => {
|
||
const details = this.getScoreCategoryDetails(category.categoryCode);
|
||
let selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
||
let reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
||
// 暂定:加分/减分合计最高按 15 分计(分类汇总展示封顶后值)
|
||
if (category.categoryCode === 'plus') {
|
||
selfScore = Math.min(selfScore, HARDCODED_MAX_PLUS_SCORE);
|
||
reviewScore = Math.min(reviewScore, HARDCODED_MAX_PLUS_SCORE);
|
||
}
|
||
if (category.categoryCode === 'minus') {
|
||
selfScore = Math.min(selfScore, HARDCODED_MAX_MINUS_SCORE);
|
||
reviewScore = Math.min(reviewScore, HARDCODED_MAX_MINUS_SCORE);
|
||
}
|
||
return {
|
||
...category,
|
||
index: index + 1,
|
||
selfScore: Number(selfScore.toFixed(2)),
|
||
reviewScore: Number(reviewScore.toFixed(2)),
|
||
};
|
||
});
|
||
return rows;
|
||
},
|
||
isOfflineScore() {
|
||
return String(this.currentScore?.evaluateMode || 'online') === 'offline';
|
||
},
|
||
expiryTagOptions() {
|
||
return [
|
||
{ label: '全部', value: 'all' },
|
||
{ label: '30天内到期', value: 'soon' },
|
||
{ label: '已过期', value: 'expired' },
|
||
];
|
||
},
|
||
},
|
||
watch: {
|
||
$route(to, from) {
|
||
if (this.isFormPageInstance || this.isPublicViewPage) return;
|
||
if (
|
||
to.path === customerArchiveListRoute &&
|
||
from?.path?.includes('/customer-archive/form')
|
||
) {
|
||
sessionStorage.removeItem(customerArchiveListRefreshKey);
|
||
this.onLoad(this.page, this.query);
|
||
}
|
||
},
|
||
'archiveForm.unifiedCreditCode'(val) {
|
||
this.handleKingdeeCreditCodeChange(val);
|
||
},
|
||
'archiveForm.registeredDetailAddress'() {
|
||
this.$nextTick(() => this.checkOverflow('registeredDetailInput', 'registeredDetailOverflow'));
|
||
},
|
||
'invoiceForm.registeredDetailAddress'() {
|
||
this.$nextTick(() => this.checkOverflow('invoiceDetailInput', 'invoiceDetailOverflow'));
|
||
},
|
||
},
|
||
methods: {
|
||
markCustomerArchiveListNeedRefresh() {
|
||
sessionStorage.setItem(customerArchiveListRefreshKey, '1');
|
||
},
|
||
hasPermission(code) {
|
||
if (this.isAdmin) {
|
||
return true;
|
||
}
|
||
const permission = this.permission || {};
|
||
const value = permission[code];
|
||
// 不依赖 Avue validData,避免权限值为 true 以外的真值时被误判
|
||
return value === true || value === 'true' || value === 1 || value === '1';
|
||
},
|
||
mergeSelectOptions(target, values) {
|
||
const list = Array.isArray(values) ? values : values ? [values] : [];
|
||
list.forEach(value => {
|
||
if (value === undefined || value === null || value === '') return;
|
||
if (!this[target].some(item => String(item.value) === String(value))) {
|
||
this[target].push({ label: String(value), value });
|
||
}
|
||
});
|
||
},
|
||
ensurePublicViewOptions(archive = {}) {
|
||
this.mergeSelectOptions('customerNatureOptions', archive.customerNature);
|
||
this.mergeSelectOptions('customerTypeOptions', archive.customerType);
|
||
this.mergeSelectOptions(
|
||
'qualificationTypeOptions',
|
||
(this.qualificationFiles || []).map(item => item.type)
|
||
);
|
||
const deptId = Array.isArray(archive.deptId) ? archive.deptId[0] : archive.deptId;
|
||
if (deptId && archive.deptName) {
|
||
this.deptTree = [
|
||
{
|
||
label: archive.deptName,
|
||
value: String(deptId),
|
||
children: [],
|
||
},
|
||
];
|
||
this.deptOptions = this.flattenDept(this.deptTree);
|
||
}
|
||
(archive.scores || []).forEach(score => {
|
||
if (!score.quantificationId) return;
|
||
const id = String(score.quantificationId);
|
||
if (!this.scoreQuantificationOptions.some(item => String(item.id) === id)) {
|
||
this.scoreQuantificationOptions.push({
|
||
id,
|
||
name: score.quantificationName || score.name || id,
|
||
});
|
||
}
|
||
});
|
||
},
|
||
unifiedCreditCodeError(value) {
|
||
const code = String(value || '')
|
||
.trim()
|
||
.toUpperCase();
|
||
const charset = '0123456789ABCDEFGHJKLMNPQRTUWXY';
|
||
const weights = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
|
||
if (code.length !== 18 || ![...code].every(char => charset.includes(char))) {
|
||
return '统一社会信用代码必须为18位有效字符';
|
||
}
|
||
const sum = [...code.slice(0, 17)].reduce(
|
||
(total, char, index) => total + charset.indexOf(char) * weights[index],
|
||
0
|
||
);
|
||
const checkCode = charset[(31 - (sum % 31)) % 31];
|
||
if (code[17] !== checkCode) {
|
||
return '统一社会信用代码校验码错误';
|
||
}
|
||
return '';
|
||
},
|
||
resetKingdeeQueryState() {
|
||
this.kingdeeBlocked = false;
|
||
clearTimeout(this.kingdeeQueryTimer);
|
||
this.kingdeeQueryTimer = null;
|
||
},
|
||
// 新增态下重新输入统一社会信用代码后,此前金蝶回填的数据即失效,重置回填状态并允许对新代码重新查询
|
||
resetKingdeeBackfill() {
|
||
const form = this.archiveForm;
|
||
form.kingdeeStatus = 'none';
|
||
form.customerCode = '';
|
||
form.supplierCode = '';
|
||
form.fullName = '';
|
||
form.legalPerson = '';
|
||
form.registeredCapital = '';
|
||
form.paidInCapital = '';
|
||
form.registeredDetailAddress = '';
|
||
form.businessScope = '';
|
||
form.businessTermType = '固定期限';
|
||
form.businessStartDate = '';
|
||
form.businessEndDate = '';
|
||
},
|
||
// 新增态下统一社会信用代码录入完成后自动查询金蝶(A902-1),防抖 500ms;
|
||
// 代码一经变化即重新查询(含改回历史代码),不做同码去重
|
||
handleKingdeeCreditCodeChange(val) {
|
||
this.kingdeeBlocked = false;
|
||
if (this.readonly || this.archiveForm.id) return;
|
||
this.clearArchiveNameRisk();
|
||
if (this.archiveForm.kingdeeStatus === 'exist') {
|
||
this.resetKingdeeBackfill();
|
||
}
|
||
const code = String(val || '')
|
||
.trim()
|
||
.toUpperCase();
|
||
if (code.length !== 18 || this.unifiedCreditCodeError(code)) return;
|
||
clearTimeout(this.kingdeeQueryTimer);
|
||
this.kingdeeQueryTimer = setTimeout(() => {
|
||
this.fetchKingdeeCustomer(code);
|
||
this.fetchSupplierCode(code);
|
||
}, 500);
|
||
},
|
||
// 新增态下信用代码录入后即时查询金蝶供应商编码(A901-1)回显,查询失败静默留空,保存/同步时后端兜底落库
|
||
async fetchSupplierCode(code) {
|
||
try {
|
||
const res = await querySupplierCode(code);
|
||
// 防止响应返回时用户已修改代码
|
||
if (String(this.archiveForm.unifiedCreditCode || '').trim().toUpperCase() !== code) return;
|
||
// 后端查不到时 data 为 {}(R 对 null 的空承载兜底),非字符串一律按空处理,避免显示 [object Object]
|
||
const supplierCode = res?.data?.data;
|
||
this.archiveForm.supplierCode = typeof supplierCode === 'string' ? supplierCode : '';
|
||
} catch (error) {
|
||
// 忽略查询异常,编码留空
|
||
}
|
||
},
|
||
async fetchKingdeeCustomer(code) {
|
||
let customer = null;
|
||
try {
|
||
const res = await queryKingdeeCustomer(code);
|
||
customer = res?.data?.data || null;
|
||
} catch (error) {
|
||
this.$message.warning('金蝶查询失败,可继续填写,审批通过后系统将自动复查');
|
||
return;
|
||
}
|
||
// 防止响应返回时用户已修改代码
|
||
if (String(this.archiveForm.unifiedCreditCode || '').trim().toUpperCase() !== code) return;
|
||
// 金蝶不存在(rows 为空,data 为 null 或空对象):静默返回,正常填写,不做提示
|
||
if (!customer || !customer.status) return;
|
||
if (customer.status === 'C') {
|
||
this.applyKingdeeCustomer(customer);
|
||
this.$alert(
|
||
'金蝶已创建该客户,将获取金蝶的工商信息,相关字段已自动回填并锁定。如信用代码有误,可直接修改,系统将重新查询。',
|
||
'提示',
|
||
{
|
||
confirmButtonText: '确定',
|
||
}
|
||
);
|
||
} else {
|
||
this.kingdeeBlocked = true;
|
||
this.$alert('金蝶审核中,请稍后重新添加。', '提示', { confirmButtonText: '确定' });
|
||
}
|
||
},
|
||
applyKingdeeCustomer(customer) {
|
||
const form = this.archiveForm;
|
||
form.kingdeeStatus = 'exist';
|
||
// 客商编号回显金蝶客商编码,与后端同步金蝶时的采纳口径保持一致
|
||
if (customer.number) form.customerCode = customer.number;
|
||
// 工商信息以金蝶为准,直接覆盖(后续字段锁定)
|
||
if (customer.name) form.fullName = customer.name;
|
||
if (customer.name) this.checkArchiveNameRisk();
|
||
if (customer.artificialperson) form.legalPerson = customer.artificialperson;
|
||
if (customer.bizpartnerAddress) form.registeredDetailAddress = customer.bizpartnerAddress;
|
||
if (customer.regcapital) {
|
||
const capital = String(customer.regcapital).replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
||
if (capital) form.registeredCapital = capital[0];
|
||
}
|
||
if (customer.businessterm) this.applyBusinessTermRecognition(customer.businessterm);
|
||
if (customer.businessscope) {
|
||
form.businessScope = String(customer.businessscope).trim();
|
||
}
|
||
if (!form.shortName && customer.simplename) form.shortName = customer.simplename;
|
||
this.$nextTick(() => {
|
||
[
|
||
'unifiedCreditCode',
|
||
'fullName',
|
||
'legalPerson',
|
||
'registeredCapital',
|
||
'paidInCapital',
|
||
'businessStartDate',
|
||
'businessEndDate',
|
||
'businessScope',
|
||
'registeredDetailAddress',
|
||
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
|
||
});
|
||
},
|
||
// 检测输入框文本是否溢出,仅溢出时才允许 tooltip 显示完整地址
|
||
checkOverflow(refName, flag) {
|
||
const inst = this.$refs[refName];
|
||
const el = inst && inst.$el && inst.$el.querySelector('input');
|
||
this[flag] = !!(el && el.scrollWidth > el.clientWidth);
|
||
},
|
||
emptyArchive() {
|
||
return {
|
||
accessType: 'temporary',
|
||
approvalStatus: 'draft',
|
||
status: 1,
|
||
customerKind: 'external',
|
||
deptId: [],
|
||
deptIds: '',
|
||
deptName: '',
|
||
customerType: [],
|
||
businessScope: '',
|
||
businessTermType: '固定期限',
|
||
businessStartDate: '',
|
||
businessEndDate: '',
|
||
registeredCapital: '',
|
||
paidInCapital: '',
|
||
usedCreditLimit: '',
|
||
networkFreightPlatform: null,
|
||
guangxiTop100: null,
|
||
registeredRegionPath: [],
|
||
registeredRegionName: '',
|
||
registeredDetailAddress: '',
|
||
registeredAddress: '',
|
||
contacts: [],
|
||
receiptAccounts: [],
|
||
invoices: [],
|
||
scores: [],
|
||
changeRecords: [],
|
||
kingdeeStatus: 'none',
|
||
kingdeeFailReason: '',
|
||
kingdeeSyncTime: null,
|
||
};
|
||
},
|
||
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);
|
||
},
|
||
loadChangeRecords() {
|
||
if (!this.archiveForm.id) return;
|
||
const page = this.changeRecordPage;
|
||
const requestList = this.isPublicViewPage ? getPublicChangeRecordList : getChangeRecordList;
|
||
requestList(this.archiveForm.id, page.currentPage, page.pageSize).then(res => {
|
||
const data = res.data.data || {};
|
||
page.records = this.replaceNegativeOneWithBlank(data.records || []);
|
||
page.total = Number(data.total || 0);
|
||
page.currentPage = Number(data.current || page.currentPage);
|
||
});
|
||
},
|
||
handleChangeRecordCurrentChange(currentPage) {
|
||
this.changeRecordPage.currentPage = Number(currentPage) || 1;
|
||
this.loadChangeRecords();
|
||
},
|
||
handleChangeRecordSizeChange(pageSize) {
|
||
this.changeRecordPage.pageSize = Number(pageSize) || 5;
|
||
this.changeRecordPage.currentPage = 1;
|
||
this.loadChangeRecords();
|
||
},
|
||
resetChangeRecordPage() {
|
||
this.changeRecordPage.currentPage = 1;
|
||
this.changeRecordPage.total = 0;
|
||
this.changeRecordPage.records = [];
|
||
},
|
||
changeRecordIndex(index) {
|
||
return (this.changeRecordPage.currentPage - 1) * this.changeRecordPage.pageSize + index + 1;
|
||
},
|
||
parseChangeData(value) {
|
||
if (!value || value === '-1') return {};
|
||
if (typeof value === 'object') return this.replaceNegativeOneWithBlank(value);
|
||
const text = String(value).trim();
|
||
if (!text || text === '-1') return {};
|
||
try {
|
||
const parsed = JSON.parse(text);
|
||
return parsed && typeof parsed === 'object'
|
||
? this.replaceNegativeOneWithBlank(parsed)
|
||
: { 变更内容: parsed };
|
||
} catch (error) {
|
||
return text.split(/[;;]/).reduce((data, item) => {
|
||
const match = item.trim().match(/^([^::]+)\s*[::]\s*(.*)$/);
|
||
if (match) data[match[1].trim()] = match[2].trim();
|
||
return data;
|
||
}, {});
|
||
}
|
||
},
|
||
getChangeFieldLabel(field) {
|
||
const fieldLabelMap = {
|
||
accessType: '准入标准',
|
||
approvalStatus: '审核状态',
|
||
currentNode: '当前节点',
|
||
currentProcessor: '当前处理人',
|
||
customerCode: '客商编号',
|
||
supplierCode: '供应商编码',
|
||
shortName: '客商简称',
|
||
fullName: '客商名称',
|
||
customerType: '客商分类',
|
||
customerKind: '客商类型',
|
||
customerNature: '客户性质',
|
||
guangxiTop100: '是否广西百强',
|
||
unifiedCreditCode: '统一信用代码',
|
||
deptName: '所属组织',
|
||
approvedTime: '审核通过时间',
|
||
status: '状态',
|
||
businessTermType: '营业期限类型',
|
||
businessStartDate: '营业期限开始日',
|
||
businessEndDate: '营业期限截止日',
|
||
registeredCapital: '注册资金(万元)',
|
||
paidInCapital: '实缴资本(万元)',
|
||
usedCreditLimit: '已使用额度(万元)',
|
||
businessScope: '经营范围',
|
||
networkFreightPlatform: '网络货运平台',
|
||
registeredRegionName: '注册地址',
|
||
registeredDetailAddress: '详细地址',
|
||
invoiceType: '发票类型',
|
||
qualificationAttachments: '客商材料',
|
||
customerAttachments: '客商材料',
|
||
客商材料: '客商材料',
|
||
};
|
||
if (fieldLabelMap[field]) return fieldLabelMap[field];
|
||
const column = (this.option.column || []).find(item => item.prop === field);
|
||
return column?.label || field;
|
||
},
|
||
normalizeChangeFieldValue(value) {
|
||
if (typeof value !== 'string') return value;
|
||
const text = value.trim();
|
||
if (!text || (!text.startsWith('[') && !text.startsWith('{') && text !== 'null')) {
|
||
return value;
|
||
}
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch (error) {
|
||
return value;
|
||
}
|
||
},
|
||
isEmptyChangeValue(value) {
|
||
const normalized = this.normalizeChangeFieldValue(value);
|
||
if (
|
||
normalized === undefined ||
|
||
normalized === null ||
|
||
normalized === '' ||
|
||
normalized === '-1'
|
||
) {
|
||
return true;
|
||
}
|
||
if (Array.isArray(normalized)) return normalized.length === 0;
|
||
if (typeof normalized === 'object') return Object.keys(normalized).length === 0;
|
||
return false;
|
||
},
|
||
normalizeComparableChangeValue(value) {
|
||
const normalized = this.normalizeChangeFieldValue(value);
|
||
if (this.isEmptyChangeValue(normalized)) return null;
|
||
if (typeof normalized === 'number') return Number.isFinite(normalized) ? normalized : null;
|
||
if (typeof normalized === 'boolean') return normalized;
|
||
if (typeof normalized === 'string') {
|
||
const text = normalized.trim();
|
||
if (/^-?\d+(\.\d+)?$/.test(text)) {
|
||
const number = Number(text);
|
||
return Number.isFinite(number) ? number : text;
|
||
}
|
||
return text;
|
||
}
|
||
if (Array.isArray(normalized)) {
|
||
return normalized.map(item => this.normalizeComparableChangeValue(item));
|
||
}
|
||
if (normalized && typeof normalized === 'object') {
|
||
return Object.keys(normalized)
|
||
.sort()
|
||
.reduce((result, key) => {
|
||
result[key] = this.normalizeComparableChangeValue(normalized[key]);
|
||
return result;
|
||
}, {});
|
||
}
|
||
return normalized;
|
||
},
|
||
isSameChangeValue(beforeValue, afterValue) {
|
||
if (beforeValue === afterValue) return true;
|
||
if (this.isEmptyChangeValue(beforeValue) && this.isEmptyChangeValue(afterValue)) return true;
|
||
return (
|
||
JSON.stringify(this.normalizeComparableChangeValue(beforeValue)) ===
|
||
JSON.stringify(this.normalizeComparableChangeValue(afterValue))
|
||
);
|
||
},
|
||
formatScoreChangeValue(value) {
|
||
const scores = Array.isArray(value) ? value : [value];
|
||
const categoryNames = { basic: '基础得分项', plus: '加分项目', minus: '减分项目' };
|
||
return scores
|
||
.filter(item => item && typeof item === 'object')
|
||
.map((score, index) => {
|
||
const summary = [
|
||
score.scoreDate && `评分日期:${score.scoreDate}`,
|
||
score.selfScore !== undefined && score.selfScore !== ''
|
||
? `自评得分:${this.formatScoreValue(score.selfScore)}`
|
||
: '',
|
||
score.reviewScore !== undefined && score.reviewScore !== ''
|
||
? `复评得分:${this.formatScoreValue(score.reviewScore)}`
|
||
: '',
|
||
score.finalScore !== undefined && score.finalScore !== ''
|
||
? `最终得分:${this.formatScoreValue(score.finalScore)}`
|
||
: '',
|
||
score.creditLevel ? `信用等级:${score.creditLevel}` : '',
|
||
].filter(Boolean);
|
||
const scoreDetails = this.normalizeChangeFieldValue(score.details);
|
||
const details = (Array.isArray(scoreDetails) ? scoreDetails : [])
|
||
.map(detail => {
|
||
const categoryCode = this.getScoreDetailCategoryCode(detail);
|
||
const category = categoryNames[categoryCode] || detail.categoryName || '';
|
||
const itemName = detail.itemName || '评分项目';
|
||
const option = detail.selectedOption || detail.scoreDescription || '';
|
||
const points = [
|
||
detail.selfScore !== undefined && detail.selfScore !== ''
|
||
? `自评${this.formatScoreValue(detail.selfScore)}分`
|
||
: '',
|
||
detail.reviewScore !== undefined && detail.reviewScore !== ''
|
||
? `复评${this.formatScoreValue(detail.reviewScore)}分`
|
||
: '',
|
||
].filter(Boolean);
|
||
if (
|
||
!points.length &&
|
||
detail.score !== undefined &&
|
||
detail.score !== '' &&
|
||
detail.score !== -1 &&
|
||
detail.score !== '-1'
|
||
) {
|
||
points.push(`得分${this.formatScoreValue(detail.score)}分`);
|
||
}
|
||
const label = `${category ? `${category}-` : ''}${itemName}`;
|
||
return `${label}${option ? `:${option}` : ''}${
|
||
points.length ? `(${points.join(',')})` : ''
|
||
}`;
|
||
})
|
||
.filter(Boolean);
|
||
if (details.length) summary.push(`评分明细:${details.join(';')}`);
|
||
return summary.length ? summary.join(';') : `第${index + 1}条评分`;
|
||
})
|
||
.join(';');
|
||
},
|
||
normalizeScoreChangeEntries(value) {
|
||
const normalized = this.normalizeChangeFieldValue(value);
|
||
if (Array.isArray(normalized)) return normalized;
|
||
return normalized && typeof normalized === 'object' ? [normalized] : [];
|
||
},
|
||
formatScoreChangeDiff(beforeValue, afterValue) {
|
||
const beforeEntries = this.normalizeScoreChangeEntries(beforeValue);
|
||
const afterEntries = this.normalizeScoreChangeEntries(afterValue);
|
||
const size = Math.max(beforeEntries.length, afterEntries.length);
|
||
const fields = [
|
||
['scoreDate', '评分日期'],
|
||
['selfScore', '自评得分'],
|
||
['reviewScore', '复评得分'],
|
||
['finalScore', '最终得分'],
|
||
['creditLevel', '信用等级'],
|
||
['details', '评分明细'],
|
||
];
|
||
const formatValue = (field, value) => {
|
||
if (field === 'details') {
|
||
return this.formatScoreChangeValue([{ details: value }]).replace(/^第\d+条评分$/, '空');
|
||
}
|
||
if (value === undefined || value === null || value === '' || value === '-1') return '空';
|
||
return field.endsWith('Score') ? `${this.formatScoreValue(value)}分` : String(value);
|
||
};
|
||
const before = [];
|
||
const after = [];
|
||
for (let index = 0; index < size; index += 1) {
|
||
const left = beforeEntries[index] || {};
|
||
const right = afterEntries[index] || {};
|
||
fields.forEach(([field, label]) => {
|
||
const leftValue = left[field];
|
||
const rightValue = right[field];
|
||
if (this.isSameChangeValue(leftValue, rightValue)) return;
|
||
const prefix = size > 1 ? `第${index + 1}条-${label}` : label;
|
||
before.push(`${prefix}:${formatValue(field, leftValue)}`);
|
||
after.push(`${prefix}:${formatValue(field, rightValue)}`);
|
||
});
|
||
}
|
||
return { before: before.join(';') || '空', after: after.join(';') || '空' };
|
||
},
|
||
formatAttachmentChangeValue(value) {
|
||
const attachments = Array.isArray(value)
|
||
? value
|
||
: value && typeof value === 'object'
|
||
? [value]
|
||
: this.parseAttachments(value);
|
||
const names = attachments
|
||
.map(item => {
|
||
const fileName =
|
||
typeof item === 'string'
|
||
? item
|
||
: item?.originalName || item?.name || item?.fileName || item?.url || item?.link || '';
|
||
const name = String(fileName).trim().split('?')[0].split('/').pop();
|
||
try {
|
||
return decodeURIComponent(name);
|
||
} catch (error) {
|
||
return name;
|
||
}
|
||
})
|
||
.filter(Boolean);
|
||
return names.length ? names.join('、') : '空';
|
||
},
|
||
formatChangeFieldValue(field, value) {
|
||
value = this.normalizeChangeFieldValue(value);
|
||
if (value === undefined || value === null || value === '' || value === '-1') return '空';
|
||
const normalizedField =
|
||
{
|
||
准入类型: 'accessType',
|
||
准入标准: 'accessType',
|
||
审批状态: 'approvalStatus',
|
||
审核状态: 'approvalStatus',
|
||
状态: 'status',
|
||
客户类型: 'customerType',
|
||
客商材料: 'qualificationAttachments',
|
||
是否广西百强: 'guangxiTop100',
|
||
}[field] || field;
|
||
if (['qualificationAttachments', 'customerAttachments'].includes(normalizedField)) {
|
||
return this.formatAttachmentChangeValue(value);
|
||
}
|
||
if (normalizedField === '评分信息' || normalizedField === 'scores') {
|
||
return this.formatScoreChangeValue(value) || '空';
|
||
}
|
||
const valueMap = {
|
||
accessType: { temporary: '临时', formal: '正式' },
|
||
approvalStatus: {
|
||
draft: '草稿',
|
||
reviewing: '审核中',
|
||
approved: '审核通过',
|
||
rejected: '审核不通过',
|
||
},
|
||
status: { 1: '启用', 2: '停用', 0: '停用' },
|
||
customerKind: { external: '外部客商', internal: '内部组织' },
|
||
networkFreightPlatform: { 1: '是', 0: '否' },
|
||
guangxiTop100: { 1: '是', 0: '否' },
|
||
};
|
||
if (Array.isArray(value)) {
|
||
return value.map(item => this.formatChangeFieldValue(normalizedField, item)).join('、');
|
||
}
|
||
if (valueMap[normalizedField]?.[value] !== undefined) {
|
||
return valueMap[normalizedField][value];
|
||
}
|
||
if (normalizedField === 'customerType') {
|
||
return String(value)
|
||
.split(/[,,]/)
|
||
.map(item => ({ customer: '客户', carrier: '承运商' }[item.trim()] || item.trim()))
|
||
.filter(Boolean)
|
||
.join('、');
|
||
}
|
||
if (typeof value === 'object') return JSON.stringify(value);
|
||
return String(value);
|
||
},
|
||
buildChangeRecordDetailRows(row = {}) {
|
||
const beforeData = this.parseChangeData(row.beforeData);
|
||
const afterData = this.parseChangeData(row.afterData);
|
||
const fields = [...new Set([...Object.keys(beforeData), ...Object.keys(afterData)])];
|
||
const rows = fields
|
||
.filter(
|
||
field =>
|
||
field !== '变更内容' && !this.isSameChangeValue(beforeData[field], afterData[field])
|
||
)
|
||
.map(field => ({
|
||
field: this.getChangeFieldLabel(field),
|
||
...(this.isScoreChangeField(field)
|
||
? this.formatScoreChangeDiff(beforeData[field], afterData[field])
|
||
: {
|
||
before: this.formatChangeFieldValue(field, beforeData[field]),
|
||
after: this.formatChangeFieldValue(field, afterData[field]),
|
||
}),
|
||
}))
|
||
.filter(item => item.before !== item.after);
|
||
const content = String(row.changeContent || '').trim();
|
||
if (content) {
|
||
rows.unshift({ field: '变更内容', before: '空', after: content });
|
||
}
|
||
return rows;
|
||
},
|
||
isScoreChangeField(field) {
|
||
return ['评分信息', 'scores'].includes(field);
|
||
},
|
||
openChangeRecordDetail(row) {
|
||
this.changeRecordDetail = row;
|
||
this.changeRecordDetailRows = this.buildChangeRecordDetailRows(row);
|
||
this.changeRecordDetailVisible = true;
|
||
},
|
||
formatChangeContent(row) {
|
||
const rows = this.buildChangeRecordDetailRows(row);
|
||
const content = String(row.changeContent || '').trim();
|
||
const details = rows
|
||
.filter(item => item.field !== '变更内容')
|
||
.map(item => `${item.field}:变更前:${item.before} → 变更后:${item.after}`);
|
||
return [content, ...details].filter(Boolean).join(';');
|
||
},
|
||
emptyContact() {
|
||
return {
|
||
contactName: '',
|
||
contactPhone: '',
|
||
email: '',
|
||
regionPath: [],
|
||
regionName: '',
|
||
detailAddress: '',
|
||
contactAddress: '',
|
||
branchName: '',
|
||
positionName: '',
|
||
isDefault: 0,
|
||
remark: '',
|
||
};
|
||
},
|
||
emptyReceiptAccount() {
|
||
return {
|
||
accountName: '',
|
||
accountHolderName: '',
|
||
bankName: '',
|
||
cnapsCode: '',
|
||
bankAccount: '',
|
||
registeredPhone: '',
|
||
registeredAddress: '',
|
||
taxNo: '',
|
||
isDefault: 0,
|
||
remark: '',
|
||
};
|
||
},
|
||
emptyInvoice() {
|
||
return {
|
||
invoiceTitle: '',
|
||
taxNo: '',
|
||
bankName: '',
|
||
bankAccount: '',
|
||
registeredRegionPath: [],
|
||
registeredRegionName: '',
|
||
registeredDetailAddress: '',
|
||
registeredAddress: '',
|
||
contacts: [],
|
||
isDefault: 0,
|
||
};
|
||
},
|
||
initDeptTree() {
|
||
getDeptTree(this.userInfo.tenantId)
|
||
.then(res => {
|
||
const deptTree = this.excludeExternalOrganization(res.data.data || []);
|
||
this.deptTree = this.normalizeDeptTree(deptTree);
|
||
this.deptOptions = this.flattenDept(this.deptTree);
|
||
this.setDefaultTopLevelDept();
|
||
const deptColumn = this.findColumn(this.option.column, 'deptName');
|
||
deptColumn.renderSearch = scope => this.renderDeptSearch(scope);
|
||
})
|
||
.finally(() => {
|
||
this.deptSearchInitialized = true;
|
||
this.$nextTick(() => this.onLoad(this.page, this.query));
|
||
});
|
||
},
|
||
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, refName = 'registeredRegionCascader') {
|
||
const nodes = this.$refs[refName]?.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('registeredRegionName');
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
this.archiveForm.registeredRegionName = this.getRegisteredRegionLabels(value).join('');
|
||
this.$refs.archiveForm?.validateField('registeredRegionName');
|
||
});
|
||
},
|
||
handleInvoiceRegisteredRegionChange(value) {
|
||
if (!value || !value.length) {
|
||
this.invoiceForm.registeredRegionName = '';
|
||
this.$refs.invoiceForm?.validateField('registeredRegionName');
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
this.invoiceForm.registeredRegionName = this.getRegisteredRegionLabels(
|
||
value,
|
||
'invoiceRegisteredRegionCascader'
|
||
).join('');
|
||
this.$refs.invoiceForm?.validateField('registeredRegionName');
|
||
});
|
||
},
|
||
initBusinessDictionaries() {
|
||
// 客商风险筛选项走业务字典 customer_risk(dictKey=筛选值 blacklist/high/middle/low,dictValue=名称),字典缺失保留固定兜底项
|
||
getDictionary({ code: 'customer_risk' }).then(res => {
|
||
const options = (res.data.data || [])
|
||
.filter(item => item.dictKey !== '-1')
|
||
.map(item => ({ label: item.dictValue, value: item.dictKey }));
|
||
if (options.length) {
|
||
this.findColumn(this.option.column, 'customerRisk').dicData = options;
|
||
}
|
||
});
|
||
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('bank_list', 'bankListOptions');
|
||
this.loadDictOptions('customer_attachment_types', 'qualificationTypeOptions', [], () => {
|
||
this.sortQualificationFiles();
|
||
});
|
||
},
|
||
queryBankNameSuggestions(queryString, callback) {
|
||
const query = String(queryString || '')
|
||
.trim()
|
||
.toLowerCase();
|
||
const options = Array.isArray(this.bankListOptions) ? this.bankListOptions : [];
|
||
const matched = query
|
||
? options.filter(item => String(item.value || '').toLowerCase().includes(query))
|
||
: options;
|
||
callback(matched.map(item => ({ value: item.value })));
|
||
},
|
||
initMkEnabled() {
|
||
this._mkEnabledResolved = false;
|
||
loadMkEnabled()
|
||
.then(enabled => {
|
||
this.mkEnabled = !!enabled;
|
||
this._mkEnabledResolved = true;
|
||
})
|
||
.catch(() => {
|
||
this.mkEnabled = false;
|
||
this._mkEnabledResolved = true;
|
||
});
|
||
},
|
||
initScoreQuantificationOptions() {
|
||
this.scoreQuantificationLoading = true;
|
||
return 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: String(item.id),
|
||
name: item.name || item.configName || item.title || item.id,
|
||
}));
|
||
})
|
||
.finally(() => {
|
||
this.scoreQuantificationLoading = false;
|
||
});
|
||
},
|
||
ensureScoreQuantificationOption(quantificationId) {
|
||
if (!quantificationId) return Promise.resolve();
|
||
const id = String(quantificationId);
|
||
if (this.scoreQuantificationOptions.some(item => String(item.id) === id)) {
|
||
return Promise.resolve();
|
||
}
|
||
return getCreditScoreQuantificationDetail(quantificationId).then(res => {
|
||
const item = res.data.data || {};
|
||
if (!item.id) return;
|
||
this.scoreQuantificationOptions.push({
|
||
id: String(item.id),
|
||
name: item.name || item.configName || item.title || String(item.id),
|
||
});
|
||
});
|
||
},
|
||
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 || []),
|
||
};
|
||
});
|
||
},
|
||
excludeExternalOrganization(tree = []) {
|
||
return tree.reduce((result, item) => {
|
||
const deptName = item.label || item.title || item.deptName || item.name || '';
|
||
if (String(deptName).trim() === '外部组织') return result;
|
||
result.push({
|
||
...item,
|
||
children: this.excludeExternalOrganization(item.children || []),
|
||
});
|
||
return result;
|
||
}, []);
|
||
},
|
||
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;
|
||
},
|
||
findTopLevelDeptByName(name) {
|
||
const targetName = String(name || '').trim();
|
||
if (!targetName) {
|
||
return null;
|
||
}
|
||
return (
|
||
(this.deptTree || []).find(item => {
|
||
const label = String(item.label || item.title || item.deptName || item.name || '').trim();
|
||
return label === targetName;
|
||
}) || null
|
||
);
|
||
},
|
||
/**
|
||
* 客商所属组织固定为顶层「广西现代物流集团有限公司」,表单不可编辑。
|
||
*/
|
||
applyFixedOwnerDept() {
|
||
if (!this.isArchivePage || this.readonly) {
|
||
return;
|
||
}
|
||
const fixedDept =
|
||
this.findTopLevelDeptByName(FIXED_OWNER_DEPT_NAME) || (this.deptTree || [])[0] || null;
|
||
if (!fixedDept) {
|
||
return;
|
||
}
|
||
this.onDeptChange([fixedDept.value]);
|
||
},
|
||
setDefaultTopLevelDept() {
|
||
this.applyFixedOwnerDept();
|
||
},
|
||
onDeptChange(value) {
|
||
// 级联单选:value 为根到选中节点的 ID 路径,取末级作为客商所属组织
|
||
const path = Array.isArray(value)
|
||
? value.filter(item => item !== undefined && item !== null && item !== '')
|
||
: [];
|
||
const deptId = path.length ? String(path[path.length - 1]) : '';
|
||
this.archiveForm.deptId = deptId ? [deptId] : [];
|
||
this.archiveForm.deptIds = deptId;
|
||
this.archiveForm.deptName = deptId ? this.findDeptLabel(deptId) : '';
|
||
this.$refs.archiveForm?.validateField('deptName');
|
||
},
|
||
findDeptPath(id, tree = this.deptTree, parents = []) {
|
||
for (const node of tree || []) {
|
||
const path = [...parents, String(node.value)];
|
||
if (String(node.value) === String(id)) return path;
|
||
const children = node.children || [];
|
||
if (children.length) {
|
||
const matched = this.findDeptPath(id, children, path);
|
||
if (matched) return matched;
|
||
}
|
||
}
|
||
return null;
|
||
},
|
||
findDeptLabel(id, tree = this.deptTree) {
|
||
for (const node of tree || []) {
|
||
if (String(node.value) === String(id)) return node.label;
|
||
const matched = this.findDeptLabel(id, node.children || []);
|
||
if (matched) return matched;
|
||
}
|
||
return '';
|
||
},
|
||
findDeptPathByName(name, tree = this.deptTree, parents = []) {
|
||
const target = String(name || '').trim();
|
||
if (!target) return [];
|
||
for (const node of tree || []) {
|
||
const path = [...parents, String(node.value)];
|
||
if (String(node.label || '').trim() === target) return path;
|
||
const matched = this.findDeptPathByName(name, node.children || [], path);
|
||
if (matched.length) return matched;
|
||
}
|
||
return [];
|
||
},
|
||
getCurrentOrganizationName() {
|
||
const currentDeptId = String(this.userInfo.deptId || this.userInfo.dept_id || '')
|
||
.split(',')
|
||
.map(item => item.trim())
|
||
.filter(Boolean)[0];
|
||
if (currentDeptId) {
|
||
const label = this.findDeptLabel(currentDeptId);
|
||
if (label) return label;
|
||
}
|
||
return this.userInfo.deptName || this.userInfo.dept_name || '';
|
||
},
|
||
findRegionPathByName(options = [], regionName = '', parents = []) {
|
||
const target = String(regionName || '').replace(/\s+/g, '');
|
||
if (!target) return [];
|
||
for (const item of options || []) {
|
||
const nextParents = [...parents, item];
|
||
const nextName = nextParents.map(region => region.title || region.name || '').join('');
|
||
if (nextName === target) {
|
||
return nextParents.map(region => region.id);
|
||
}
|
||
const childPath = this.findRegionPathByName(item.children, target, nextParents);
|
||
if (childPath.length) return childPath;
|
||
}
|
||
return [];
|
||
},
|
||
getContactRegionLabels(value) {
|
||
const nodes = this.$refs.contactRegionCascader?.getCheckedNodes?.() || [];
|
||
if (nodes.length && nodes[0].pathLabels?.length) {
|
||
return nodes[0].pathLabels;
|
||
}
|
||
return (value || [])
|
||
.map(code => this.findRegionOption(this.regionOptions, code)?.title)
|
||
.filter(Boolean);
|
||
},
|
||
handleContactRegionChange(value) {
|
||
if (!value || !value.length) {
|
||
this.contactForm.regionName = '';
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
this.contactForm.regionName = this.getContactRegionLabels(value).join('');
|
||
});
|
||
},
|
||
syncContactRegionPath() {
|
||
if (!this.contactForm.regionName || this.contactForm.regionPath?.length) return;
|
||
const path = this.findRegionPathByName(this.regionOptions, this.contactForm.regionName);
|
||
if (path.length) {
|
||
this.contactForm.regionPath = path;
|
||
}
|
||
},
|
||
renderDeptSearch(scope) {
|
||
// 搜索区所属组织:单选级联(与新增/编辑表单一致)
|
||
// 级联返回的是根到选中节点的 ID 路径数组,提交时取末级 ID 转部门名称
|
||
return h(ElCascader, {
|
||
modelValue: Array.isArray(scope.row?.deptName) ? scope.row.deptName : [],
|
||
'onUpdate:modelValue': value => {
|
||
// 级联单选:modelValue 必须是 ID 路径数组,否则控件内部状态会错乱导致无法再次选中
|
||
if (scope.row) scope.row.deptName = Array.isArray(value) ? value : [];
|
||
},
|
||
options: this.deptTree,
|
||
props: {
|
||
label: 'label',
|
||
value: 'value',
|
||
children: 'children',
|
||
checkStrictly: true,
|
||
expandTrigger: 'click',
|
||
},
|
||
filterable: true,
|
||
clearable: true,
|
||
style: 'width: 100%',
|
||
placeholder: '请选择 所属组织',
|
||
});
|
||
},
|
||
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');
|
||
}
|
||
});
|
||
},
|
||
formatBusinessLicenseValidPeriod(row = {}) {
|
||
if (row.businessTermType === '长期') {
|
||
return row.businessStartDate
|
||
? `${row.businessStartDate} 至 无固定期限`
|
||
: '无固定期限';
|
||
}
|
||
if (row.businessStartDate && row.businessEndDate) {
|
||
return `${row.businessStartDate} 至 ${row.businessEndDate}`;
|
||
}
|
||
return row.businessEndDate || row.businessStartDate || '';
|
||
},
|
||
formatFundUseRate(row = {}) {
|
||
if (row.fundUseRate === undefined || row.fundUseRate === null || row.fundUseRate === '') {
|
||
return '-';
|
||
}
|
||
const rate = Number(row.fundUseRate);
|
||
return Number.isFinite(rate) ? `${rate.toFixed(2)}%` : '-';
|
||
},
|
||
isBusinessLicenseExpiringSoon(row = {}) {
|
||
return this.isDateExpiringSoon(row.businessEndDate, row.businessTermType === '长期');
|
||
},
|
||
/** 日期是否在 30 天内到期(含当天);skip=true 时不提示(如营业执照长期) */
|
||
isDateExpiringSoon(dateValue, skip = false) {
|
||
if (skip || !dateValue) return false;
|
||
const endDate = this.$dayjs(dateValue);
|
||
if (!endDate.isValid()) return false;
|
||
const today = this.$dayjs().startOf('day');
|
||
const expireSoonDate = today.add(30, 'day');
|
||
return !endDate.isAfter(expireSoonDate, 'day');
|
||
},
|
||
handleExpireChange(expireStatus) {
|
||
this.query.expireStatus = expireStatus || 'all';
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
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) {
|
||
// 所属组织为单选级联,只保留第一个部门 ID(历史多选数据自动收敛)
|
||
const value = detail.deptIds || detail.deptId;
|
||
const ids = value
|
||
? String(value)
|
||
.split(',')
|
||
.map(item => item.trim())
|
||
.filter(item => item !== '')
|
||
: [];
|
||
return ids.length ? [ids[0]] : [];
|
||
},
|
||
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('')
|
||
);
|
||
},
|
||
// 省市区 + 详细地址为当前录入口径,registeredAddress 仅作为历史数据兜底
|
||
formatInvoiceRegisteredAddress(row = {}) {
|
||
const address = [row.registeredRegionName, row.registeredDetailAddress]
|
||
.map(item => String(item || '').trim())
|
||
.filter(Boolean)
|
||
.join('');
|
||
return address || row.registeredAddress || '';
|
||
},
|
||
formatInvoiceEmails(row = {}) {
|
||
const contacts = row.contacts || row.__raw?.contacts || [];
|
||
return (contacts || [])
|
||
.map(item => String(item.email || '').trim())
|
||
.filter(Boolean)
|
||
.join(';');
|
||
},
|
||
normalizeContact(contact = {}) {
|
||
const contactAddress =
|
||
contact.contactAddress || contact.address || this.formatContactAddress(contact);
|
||
const regionPath = Array.isArray(contact.regionPath)
|
||
? contact.regionPath
|
||
: this.findRegionPathByName(this.regionOptions, contact.regionName);
|
||
return {
|
||
...this.emptyContact(),
|
||
...contact,
|
||
regionPath,
|
||
contactAddress,
|
||
branchName:
|
||
contact.branchName ||
|
||
contact.deptName ||
|
||
this.getCurrentOrganizationName() ||
|
||
this.archiveForm.deptName ||
|
||
'',
|
||
};
|
||
},
|
||
openContact(row, index = -1) {
|
||
this.contactIndex = index;
|
||
this.contactForm = row
|
||
? this.normalizeContact(row)
|
||
: {
|
||
...this.emptyContact(),
|
||
branchName: this.getCurrentOrganizationName() || this.archiveForm.deptName || '',
|
||
};
|
||
this.contactBox = true;
|
||
this.$nextTick(() => this.syncContactRegionPath());
|
||
},
|
||
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),
|
||
});
|
||
delete contact.regionPath;
|
||
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(() => {});
|
||
},
|
||
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();
|
||
},
|
||
// 开户行失焦后按行名精确匹配金蝶行名行号档案(A905-3),命中自动带出联行号,未命中提示检查
|
||
async fetchReceiptCnapsCode() {
|
||
const bankName = String(this.receiptForm.bankName || '').trim();
|
||
if (!bankName) return;
|
||
try {
|
||
const res = await queryKingdeeBank(bankName);
|
||
// 防止响应返回时用户已修改开户行
|
||
if (String(this.receiptForm.bankName || '').trim() !== bankName) return;
|
||
const bank = res?.data?.data || null;
|
||
if (bank && bank.unionNumber) {
|
||
this.receiptForm.cnapsCode = bank.unionNumber;
|
||
this.$refs.receiptForm?.clearValidate('cnapsCode');
|
||
} else {
|
||
this.$message.warning('未查询到金蝶开户行信息,请检查开户行名称');
|
||
}
|
||
} catch (error) {
|
||
// 忽略查询异常,联行号留空由用户手填
|
||
}
|
||
},
|
||
async saveReceipt() {
|
||
const valid = await this.$refs.receiptForm.validate().catch(() => false);
|
||
if (!valid) return;
|
||
// 保存前按联行号精确校验金蝶行名行号档案(A905-3),查不到不允许保存
|
||
const cnapsCode = String(this.receiptForm.cnapsCode || '').trim();
|
||
let bank = null;
|
||
try {
|
||
const res = await queryKingdeeBankByNumber(cnapsCode);
|
||
bank = res?.data?.data || null;
|
||
} catch (error) {
|
||
this.$message.warning('金蝶联行号校验失败,请稍后重试');
|
||
return;
|
||
}
|
||
if (!bank || !bank.unionNumber) {
|
||
this.$message.warning('未查询到金蝶联行号信息,不能保存,请检查联行号');
|
||
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) ||
|
||
'';
|
||
return {
|
||
...this.emptyInvoice(),
|
||
...invoice,
|
||
registeredRegionPath: Array.isArray(invoice.registeredRegionPath)
|
||
? invoice.registeredRegionPath
|
||
: [],
|
||
registeredDetailAddress,
|
||
registeredAddress: this.formatInvoiceRegisteredAddress({
|
||
...invoice,
|
||
registeredDetailAddress,
|
||
}),
|
||
contacts: (invoice.contacts || []).map(item => this.normalizeInvoiceContact(item)),
|
||
};
|
||
},
|
||
normalizeInvoiceContact(contact = {}) {
|
||
const deptIds = String(contact.deptIds || '')
|
||
.split(/[,,、]/)
|
||
.map(item => item.trim())
|
||
.filter(Boolean);
|
||
const deptIdList = Array.isArray(contact.deptIdList)
|
||
? contact.deptIdList.map(item => String(item)).filter(Boolean)
|
||
: deptIds;
|
||
const contactRow = {
|
||
contactName: contact.contactName || '',
|
||
contactPhone: contact.contactPhone || '',
|
||
email: contact.email || '',
|
||
deptIds: contact.deptIds || deptIdList.join(','),
|
||
deptNames: contact.deptNames || '',
|
||
deptIdList,
|
||
remark: contact.remark || '',
|
||
};
|
||
if (!this.isInternalCustomer) {
|
||
contactRow.deptIds = this.externalInvoiceDeptIds();
|
||
contactRow.deptNames = this.externalInvoiceDeptName();
|
||
contactRow.deptIdList = contactRow.deptIds
|
||
? String(contactRow.deptIds)
|
||
.split(/[,,]/)
|
||
.map(item => item.trim())
|
||
.filter(Boolean)
|
||
: [];
|
||
} else if (!contactRow.deptNames && deptIdList.length) {
|
||
contactRow.deptNames = deptIdList.map(id => this.findDeptLabel(id)).filter(Boolean).join('、');
|
||
}
|
||
return contactRow;
|
||
},
|
||
emptyInvoiceContact() {
|
||
const contact = {
|
||
contactName: '',
|
||
contactPhone: '',
|
||
email: '',
|
||
deptIds: '',
|
||
deptNames: '',
|
||
deptIdList: [],
|
||
remark: '',
|
||
};
|
||
if (!this.isInternalCustomer) {
|
||
contact.deptIds = this.externalInvoiceDeptIds();
|
||
contact.deptNames = this.externalInvoiceDeptName();
|
||
contact.deptIdList = contact.deptIds
|
||
? String(contact.deptIds)
|
||
.split(/[,,]/)
|
||
.map(item => item.trim())
|
||
.filter(Boolean)
|
||
: [];
|
||
}
|
||
return contact;
|
||
},
|
||
externalInvoiceDeptName() {
|
||
return this.archiveForm.fullName || this.archiveForm.deptName || '';
|
||
},
|
||
externalInvoiceDeptIds() {
|
||
if (this.archiveForm.deptIds) return String(this.archiveForm.deptIds);
|
||
if (Array.isArray(this.archiveForm.deptId)) return this.archiveForm.deptId.filter(Boolean).join(',');
|
||
return this.archiveForm.deptId ? String(this.archiveForm.deptId) : '';
|
||
},
|
||
defaultInvoice() {
|
||
const archive = this.archiveForm || {};
|
||
return {
|
||
...this.emptyInvoice(),
|
||
invoiceTitle: archive.fullName || '',
|
||
taxNo: archive.unifiedCreditCode || '',
|
||
registeredRegionPath: Array.isArray(archive.registeredRegionPath)
|
||
? [...archive.registeredRegionPath]
|
||
: [],
|
||
registeredRegionName: archive.registeredRegionName || '',
|
||
registeredDetailAddress: archive.registeredDetailAddress || '',
|
||
registeredAddress: this.formatArchiveAddress(archive),
|
||
contacts: [],
|
||
};
|
||
},
|
||
openInvoice(row, index = -1) {
|
||
this.invoiceIndex = index;
|
||
this.invoiceForm = row ? this.normalizeInvoice(row) : this.defaultInvoice();
|
||
this.invoiceBox = true;
|
||
this.$nextTick(() => {
|
||
this.$nextTick(() => {
|
||
this.checkOverflow('invoiceDetailInput', 'invoiceDetailOverflow');
|
||
});
|
||
});
|
||
},
|
||
resetInvoice() {
|
||
this.invoiceIndex = -1;
|
||
this.invoiceForm = this.emptyInvoice();
|
||
this.$refs.invoiceForm?.clearValidate();
|
||
},
|
||
addInvoiceContact() {
|
||
if (!Array.isArray(this.invoiceForm.contacts)) {
|
||
this.invoiceForm.contacts = [];
|
||
}
|
||
this.invoiceForm.contacts.push(this.emptyInvoiceContact());
|
||
},
|
||
removeInvoiceContact(index) {
|
||
this.invoiceForm.contacts.splice(index, 1);
|
||
},
|
||
handleInvoiceContactDeptChange(row) {
|
||
// emitPath=false 时,多选级联值为选中节点 id 数组
|
||
const rawList = Array.isArray(row.deptIdList) ? row.deptIdList : [];
|
||
const ids = rawList
|
||
.map(item => {
|
||
if (Array.isArray(item)) {
|
||
return item.length ? String(item[item.length - 1]) : '';
|
||
}
|
||
return item !== undefined && item !== null && item !== '' ? String(item) : '';
|
||
})
|
||
.filter(Boolean);
|
||
row.deptIdList = ids;
|
||
row.deptIds = ids.join(',');
|
||
row.deptNames = ids.map(id => this.findDeptLabel(id)).filter(Boolean).join('、');
|
||
},
|
||
isBlankInvoiceContact(contact = {}) {
|
||
return ![
|
||
contact.contactName,
|
||
contact.contactPhone,
|
||
contact.email,
|
||
contact.deptIds,
|
||
contact.deptNames,
|
||
...(Array.isArray(contact.deptIdList) ? contact.deptIdList : []),
|
||
contact.remark,
|
||
].some(item => String(item || '').trim());
|
||
},
|
||
validateInvoiceContacts(contacts = []) {
|
||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
for (let index = 0; index < contacts.length; index += 1) {
|
||
const contact = contacts[index];
|
||
if (this.isBlankInvoiceContact(contact)) continue;
|
||
if (!String(contact.contactName || '').trim()) {
|
||
this.$message.warning(`请填写第${index + 1}行联系人`);
|
||
return false;
|
||
}
|
||
if (!String(contact.contactPhone || '').trim()) {
|
||
this.$message.warning(`请填写第${index + 1}行联系电话`);
|
||
return false;
|
||
}
|
||
const email = String(contact.email || '').trim();
|
||
if (email && !emailPattern.test(email)) {
|
||
this.$message.warning(`第${index + 1}行邮箱格式不正确`);
|
||
return false;
|
||
}
|
||
if (String(contact.remark || '').length > 200) {
|
||
this.$message.warning(`第${index + 1}行备注最多200个字`);
|
||
return false;
|
||
}
|
||
if (this.isInternalCustomer && !(contact.deptIdList || []).length) {
|
||
this.$message.warning(`内部组织客商请选择第${index + 1}行所属部门`);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
},
|
||
saveInvoice() {
|
||
this.$refs.invoiceForm.validate(valid => {
|
||
if (!valid) return;
|
||
const invoice = this.normalizeInvoice(this.invoiceForm);
|
||
if (!this.validateInvoiceContacts(invoice.contacts)) return;
|
||
invoice.contacts = (invoice.contacts || [])
|
||
.filter(item => !this.isBlankInvoiceContact(item))
|
||
.map(item => {
|
||
const contact = { ...item };
|
||
if (this.isInternalCustomer) {
|
||
this.handleInvoiceContactDeptChange(contact);
|
||
}
|
||
delete contact.deptIdList;
|
||
return contact;
|
||
});
|
||
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(),
|
||
originalName: String(item.originalName || item.name || '').trim(),
|
||
name: String(item.originalName || 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) {
|
||
this.addQualificationFile(file);
|
||
},
|
||
handleOcrQualificationUploadSuccess(file) {
|
||
this.addQualificationFile(file);
|
||
const originalName = file.originalName || file.name || '客商材料';
|
||
if (!originalName.includes('营业执照') || !this.isQualificationImage(file)) return;
|
||
this.recognizeBusinessLicense(file.url || file.link || '');
|
||
},
|
||
addQualificationFile(file) {
|
||
const originalName = file.originalName || file.name || '客商材料';
|
||
this.qualificationFiles.push({
|
||
type: this.resolveQualificationType(originalName),
|
||
originalName,
|
||
name: originalName,
|
||
description: '',
|
||
size: this.formatAttachmentSize(file.size),
|
||
uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
|
||
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||
url: file.url || file.link || '',
|
||
files: [file],
|
||
});
|
||
this.sortQualificationFiles();
|
||
},
|
||
recognizeBusinessLicense(url) {
|
||
if (!url) {
|
||
this.$message.warning('营业执照上传成功,未获取到图片地址,无法自动识别');
|
||
return;
|
||
}
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '营业执照识别中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
recognizeBusinessLicenseOcr(url)
|
||
.then(res => {
|
||
this.applyBusinessLicenseRecognition(res?.data?.data || {});
|
||
this.$message.success('营业执照识别完成');
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning('营业执照上传成功,自动识别失败,请手动填写工商信息');
|
||
})
|
||
.finally(() => loading.close());
|
||
},
|
||
normalizeBusinessLicenseOcrFields(result = {}) {
|
||
if (Array.isArray(result)) {
|
||
return Object.fromEntries(
|
||
result
|
||
.filter(item => item && item.key)
|
||
.map(item => [String(item.key).trim(), String(item.value ?? '').trim()])
|
||
.filter(([, value]) => value)
|
||
);
|
||
}
|
||
if (result.fields && typeof result.fields === 'object' && !Array.isArray(result.fields)) {
|
||
return result.fields;
|
||
}
|
||
if (Array.isArray(result.data)) {
|
||
return this.normalizeBusinessLicenseOcrFields(result.data);
|
||
}
|
||
return result;
|
||
},
|
||
getBusinessLicenseOcrValue(result, labels) {
|
||
const fields = this.normalizeBusinessLicenseOcrFields(result);
|
||
for (const label of labels) {
|
||
const value = fields[label];
|
||
const text = value && typeof value === 'object' ? value.words : value;
|
||
if (text !== undefined && text !== null && String(text).trim()) return String(text).trim();
|
||
}
|
||
const words = result?.words_result || result?.wordsResult || {};
|
||
for (const label of labels) {
|
||
const value = words[label];
|
||
const text = value && typeof value === 'object' ? value.words : value;
|
||
if (text !== undefined && text !== null && String(text).trim()) return String(text).trim();
|
||
}
|
||
return '';
|
||
},
|
||
isNoFixedBusinessTerm(value) {
|
||
return String(value || '').replace(/\s/g, '') === '无';
|
||
},
|
||
applyBusinessLicenseRecognition(result = {}) {
|
||
const fields = this.normalizeBusinessLicenseOcrFields(result);
|
||
const code = this.getBusinessLicenseOcrValue(fields, [
|
||
'uscc',
|
||
'统一社会信用代码',
|
||
'社会信用代码',
|
||
'注册号',
|
||
]);
|
||
const name = this.getBusinessLicenseOcrValue(fields, [
|
||
'company_name',
|
||
'单位名称',
|
||
'企业名称',
|
||
'名称',
|
||
]);
|
||
const legalPerson = this.getBusinessLicenseOcrValue(fields, [
|
||
'legal_name',
|
||
'法定代表人',
|
||
'法人',
|
||
]);
|
||
const address = this.getBusinessLicenseOcrValue(fields, ['add', '地址', '住所']);
|
||
const capital = this.getBusinessLicenseOcrValue(fields, ['capital', '注册资本']);
|
||
const paidInCapital = this.getBusinessLicenseOcrValue(fields, ['实缴资本', '实收资本']);
|
||
const term = this.getBusinessLicenseOcrValue(fields, ['period', '营业期限', '有效期']);
|
||
const termStartDate = this.getBusinessLicenseOcrValue(fields, [
|
||
'establishment_date',
|
||
'有效期起始日期',
|
||
'成立日期',
|
||
'核准日期',
|
||
]);
|
||
const scope = this.getBusinessLicenseOcrValue(fields, ['经营范围']);
|
||
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
|
||
if (name) this.archiveForm.fullName = name;
|
||
if (name) this.checkArchiveNameRisk();
|
||
if (legalPerson) this.archiveForm.legalPerson = legalPerson;
|
||
if (address) {
|
||
this.archiveForm.registeredAddress = address;
|
||
if (!this.archiveForm.registeredDetailAddress)
|
||
this.archiveForm.registeredDetailAddress = address;
|
||
}
|
||
if (capital) {
|
||
const normalizedCapital = capital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
||
if (normalizedCapital) this.archiveForm.registeredCapital = normalizedCapital[0];
|
||
}
|
||
if (paidInCapital) {
|
||
const normalizedPaidIn = paidInCapital.replace(/,/g, '').match(/\d+(?:\.\d+)?/);
|
||
if (normalizedPaidIn) this.archiveForm.paidInCapital = normalizedPaidIn[0];
|
||
}
|
||
this.applyBusinessTermRecognition(term, termStartDate);
|
||
if (this.isNoFixedBusinessTerm(term) || this.isNoFixedBusinessTerm(termStartDate)) {
|
||
this.handleNoFixedTermChange(true);
|
||
}
|
||
if (scope) {
|
||
this.archiveForm.businessScope = scope;
|
||
}
|
||
this.$nextTick(() => {
|
||
[
|
||
'unifiedCreditCode',
|
||
'fullName',
|
||
'legalPerson',
|
||
'registeredCapital',
|
||
'paidInCapital',
|
||
'businessStartDate',
|
||
'businessEndDate',
|
||
'businessScope',
|
||
'registeredDetailAddress',
|
||
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
|
||
});
|
||
},
|
||
normalizeOcrDate(value) {
|
||
if (!value) return '';
|
||
const match = String(value).match(/(\d{4})[年./-](\d{1,2})[月./-](\d{1,2})/);
|
||
if (!match) return '';
|
||
const month = String(match[2]).padStart(2, '0');
|
||
const day = String(match[3]).padStart(2, '0');
|
||
return `${match[1]}-${month}-${day}`;
|
||
},
|
||
applyBusinessTermRecognition(term, termStartDate = '') {
|
||
const startFromField = this.normalizeOcrDate(termStartDate);
|
||
if (startFromField) {
|
||
this.archiveForm.businessStartDate = startFromField;
|
||
}
|
||
if (!term) return;
|
||
if (/长期|永久|无固定/.test(term) || this.isNoFixedBusinessTerm(term)) {
|
||
this.archiveForm.businessTermType = '长期';
|
||
this.archiveForm.businessEndDate = '';
|
||
return;
|
||
}
|
||
const dateMatches = String(term).match(/\d{4}[年./-]\d{1,2}[月./-]\d{1,2}/g) || [];
|
||
const startDate = this.normalizeOcrDate(dateMatches[0]) || startFromField;
|
||
const endDate = this.normalizeOcrDate(dateMatches[1]);
|
||
if (startDate) {
|
||
this.archiveForm.businessStartDate = startDate;
|
||
}
|
||
if (endDate) {
|
||
this.archiveForm.businessTermType = '固定期限';
|
||
this.archiveForm.businessEndDate = endDate;
|
||
return;
|
||
}
|
||
if (startDate && dateMatches.length === 1 && !/至|到|-|—|~/.test(term)) {
|
||
// 仅识别到一个日期且无区间分隔时,按截止日处理(兼容旧 OCR 结果)
|
||
this.archiveForm.businessTermType = '固定期限';
|
||
this.archiveForm.businessEndDate = startDate;
|
||
}
|
||
},
|
||
getQualificationFileUrl(file = {}) {
|
||
return file.url || file.link || file.domain || '';
|
||
},
|
||
getQualificationFileExtension(file = {}) {
|
||
const name = String(
|
||
file.originalName || file.name || this.getQualificationFileUrl(file) || ''
|
||
).split('?')[0];
|
||
const index = name.lastIndexOf('.');
|
||
return index > -1 ? name.slice(index + 1).toLowerCase() : '';
|
||
},
|
||
isQualificationImage(file) {
|
||
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(
|
||
this.getQualificationFileExtension(file)
|
||
);
|
||
},
|
||
isQualificationPdf(file) {
|
||
const mimeType = String(file.mimeType || file.contentType || '').toLowerCase();
|
||
return (
|
||
mimeType.includes('application/pdf') || this.getQualificationFileExtension(file) === 'pdf'
|
||
);
|
||
},
|
||
previewQualificationFile(file) {
|
||
const url = this.getQualificationFileUrl(file);
|
||
if (!url) {
|
||
this.$message.warning('附件地址为空,无法预览');
|
||
return;
|
||
}
|
||
if (this.isQualificationImage(file)) {
|
||
this.qualificationImagePreviewUrls = this.qualificationFiles
|
||
.filter(item => this.isQualificationImage(item) && this.getQualificationFileUrl(item))
|
||
.map(item => this.getQualificationFileUrl(item));
|
||
this.qualificationImagePreviewIndex = Math.max(
|
||
this.qualificationImagePreviewUrls.indexOf(url),
|
||
0
|
||
);
|
||
this.qualificationImagePreviewVisible = true;
|
||
return;
|
||
}
|
||
const isPdf = this.isQualificationPdf(file);
|
||
this.qualificationPreviewFile = {
|
||
name: file.originalName || file.name || '附件',
|
||
url,
|
||
mimeType: isPdf ? 'application/pdf' : file.mimeType || file.contentType || '',
|
||
isPdf,
|
||
};
|
||
this.qualificationDocumentPreviewVisible = true;
|
||
},
|
||
handleQualificationPreviewUnsupported() {
|
||
this.$message.warning('当前文件暂不支持在线预览');
|
||
},
|
||
handleQualificationPreviewError() {
|
||
this.$message.error('附件预览失败');
|
||
},
|
||
resolveQualificationType(fileName) {
|
||
const name = String(fileName || '').toLocaleLowerCase();
|
||
const matchedType = [...this.qualificationTypeOptions]
|
||
.filter(item => item?.label || item?.value)
|
||
.sort(
|
||
(a, b) =>
|
||
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
||
)
|
||
.find(item => {
|
||
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||
return typeText && name.includes(typeText);
|
||
});
|
||
if (matchedType?.value) return matchedType.value;
|
||
const otherType = this.qualificationTypeOptions.find(item =>
|
||
String(item.label || item.value || '').includes('其他')
|
||
);
|
||
if (otherType?.value) return otherType.value;
|
||
this.qualificationTypeOptions.push({ label: '其他附件', value: '其他附件' });
|
||
return '其他附件';
|
||
},
|
||
getQualificationTypeOrder(type) {
|
||
const normalizedType = String(type || '').trim();
|
||
const index = this.qualificationTypeOptions.findIndex(
|
||
item =>
|
||
String(item.value || '').trim() === normalizedType ||
|
||
String(item.label || '').trim() === normalizedType
|
||
);
|
||
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
||
},
|
||
sortQualificationFiles() {
|
||
this.qualificationFiles = (this.qualificationFiles || [])
|
||
.map((file, index) => ({ file, index }))
|
||
.sort((a, b) => {
|
||
const orderDifference =
|
||
this.getQualificationTypeOrder(a.file.type) -
|
||
this.getQualificationTypeOrder(b.file.type);
|
||
return orderDifference || a.index - b.index;
|
||
})
|
||
.map(item => item.file);
|
||
},
|
||
getRequiredQualificationTypes(customerType = this.archiveForm.customerType) {
|
||
const customerTypes = Array.isArray(customerType)
|
||
? customerType
|
||
: this.splitValue(customerType);
|
||
const requiredTypes = ['营业执照', '法人身份证'];
|
||
const includesCarrier = customerTypes.some(item =>
|
||
['承运商', 'carrier'].includes(
|
||
String(item || '')
|
||
.trim()
|
||
.toLowerCase()
|
||
)
|
||
);
|
||
if (includesCarrier) requiredTypes.push('运输许可证');
|
||
return requiredTypes;
|
||
},
|
||
isUploadedQualificationType(uploadedType, requiredType) {
|
||
const uploaded = String(uploadedType || '')
|
||
.replace(/\s/g, '')
|
||
.trim();
|
||
if (uploaded === requiredType || uploaded.includes(requiredType)) return true;
|
||
return requiredType === '运输许可证' && /运输.*许可证/.test(uploaded);
|
||
},
|
||
getMissingQualificationTypes(
|
||
files = this.qualificationFiles,
|
||
customerType = this.archiveForm.customerType
|
||
) {
|
||
const uploadedTypes = new Set(
|
||
(files || [])
|
||
.filter(item => item.url)
|
||
.map(item => String(item.type || '').trim())
|
||
.filter(Boolean)
|
||
);
|
||
return this.getRequiredQualificationTypes(customerType).filter(
|
||
requiredType =>
|
||
![...uploadedTypes].some(uploadedType =>
|
||
this.isUploadedQualificationType(uploadedType, requiredType)
|
||
)
|
||
);
|
||
},
|
||
downloadAttachments() {
|
||
const source = this.selectedQualificationFiles.length
|
||
? this.selectedQualificationFiles
|
||
: this.qualificationFiles;
|
||
const files = source.filter(item => item.url);
|
||
if (!files.length) {
|
||
this.$message.warning(
|
||
this.selectedQualificationFiles.length ? '所选附件暂无可下载地址' : '暂无可下载附件'
|
||
);
|
||
return;
|
||
}
|
||
files.forEach((item, index) => {
|
||
window.setTimeout(() => {
|
||
downloadFileByUrl(item.url, item.originalName || item.name || `客商材料${index + 1}`);
|
||
}, index * 300);
|
||
});
|
||
},
|
||
handleQualificationSelectionChange(rows) {
|
||
this.selectedQualificationFiles = rows || [];
|
||
},
|
||
replaceNegativeOneWithBlank(value) {
|
||
if (value === -1 || value === '-1') return '';
|
||
if (Array.isArray(value)) {
|
||
return value.map(item => this.replaceNegativeOneWithBlank(item));
|
||
}
|
||
if (value && typeof value === 'object') {
|
||
return Object.keys(value).reduce((result, key) => {
|
||
result[key] = this.replaceNegativeOneWithBlank(value[key]);
|
||
return result;
|
||
}, {});
|
||
}
|
||
return value;
|
||
},
|
||
normalizeDetail(detail) {
|
||
const registeredDetailAddress =
|
||
detail.registeredDetailAddress ||
|
||
(detail.registeredRegionName ? '' : detail.registeredAddress) ||
|
||
'';
|
||
const archive = {
|
||
...this.emptyArchive(),
|
||
...detail,
|
||
deptId: this.normalizeDeptIds(detail),
|
||
customerType: this.splitValue(detail.customerType),
|
||
customerKind: detail.customerKind || 'external',
|
||
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
||
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
||
paidInCapital: this.normalizeOptionalAmount(detail.paidInCapital),
|
||
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
|
||
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
|
||
usedCreditLimit: this.normalizeOptionalAmount(detail.usedCreditLimit),
|
||
businessScope: detail.businessScope || '',
|
||
businessTermType: detail.businessTermType || '固定期限',
|
||
businessStartDate: detail.businessStartDate || '',
|
||
businessEndDate: detail.businessEndDate || '',
|
||
networkFreightPlatform:
|
||
detail.networkFreightPlatform === 0 || detail.networkFreightPlatform === 1
|
||
? detail.networkFreightPlatform
|
||
: null,
|
||
guangxiTop100:
|
||
detail.guangxiTop100 === 0 || detail.guangxiTop100 === 1 ? detail.guangxiTop100 : null,
|
||
registeredRegionPath: Array.isArray(detail.registeredRegionPath)
|
||
? detail.registeredRegionPath
|
||
: [],
|
||
registeredRegionName: detail.registeredRegionName || '',
|
||
registeredDetailAddress,
|
||
contacts: (detail.contacts || []).map(item =>
|
||
this.normalizeContact({
|
||
...item,
|
||
branchName:
|
||
item.branchName ||
|
||
item.deptName ||
|
||
this.getCurrentOrganizationName() ||
|
||
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: '',
|
||
evaluateMode: 'online',
|
||
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
||
nextEvaluateDate: '',
|
||
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
||
maxCreditLimit: '',
|
||
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||
creditLevel: '',
|
||
standards: [],
|
||
tempApplyCreditLimit: '',
|
||
tempCreditStartDate: '',
|
||
tempCreditEndDate: '',
|
||
remark: '',
|
||
selfStatus: '未完成',
|
||
reviewStatus: '未完成',
|
||
proofAttachments: '',
|
||
attachments: [],
|
||
details: [],
|
||
...score,
|
||
quantificationId: score.quantificationId ? String(score.quantificationId) : '',
|
||
evaluateMode: score.evaluateMode === 'offline' ? 'offline' : 'online',
|
||
nextEvaluateDate: score.nextEvaluateDate || '',
|
||
applyCreditLimit: this.normalizeOptionalAmount(
|
||
score.applyCreditLimit === undefined
|
||
? this.archiveForm.applyCreditLimit
|
||
: score.applyCreditLimit
|
||
),
|
||
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
||
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||
tempApplyCreditLimit: this.normalizeOptionalAmount(score.tempApplyCreditLimit),
|
||
attachments: score.attachments || this.parseAttachments(score.proofAttachments),
|
||
details: (score.details || []).map(detail => ({
|
||
...detail,
|
||
scoreInput: this.normalizeScoreInputValue(detail.scoreInput),
|
||
})),
|
||
};
|
||
},
|
||
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;
|
||
},
|
||
sortScoresByLatest(scores = []) {
|
||
return (scores || [])
|
||
.map((score, index) => ({ score, index }))
|
||
.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;
|
||
});
|
||
},
|
||
getLatestCreditScore(scores = []) {
|
||
return this.sortScoresByLatest(scores).filter(item =>
|
||
String(item.score.creditLevel || '').trim()
|
||
)[0]?.score;
|
||
},
|
||
/** 曾审核通过后:删除/重建评分在再次审核通过前,主表等级与额度保持旧值暂时生效 */
|
||
shouldFreezeEffectiveCredit(archive = this.archiveForm) {
|
||
return Boolean(archive?.approvedTime);
|
||
},
|
||
applyArchiveCreditFromScores(archive) {
|
||
if (this.shouldFreezeEffectiveCredit(archive)) {
|
||
return 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);
|
||
},
|
||
openArchivePage(row) {
|
||
const isEdit = Boolean(row?.id);
|
||
this.$router.push({
|
||
path: customerArchiveFormRoute,
|
||
query: isEdit ? { id: row.id, name: '编辑客商档案' } : { name: '新增客商档案' },
|
||
});
|
||
},
|
||
viewArchive(row) {
|
||
this.$router.push({
|
||
path: customerArchiveFormRoute,
|
||
query: { id: row.id, name: '查看客商档案', view: '1' },
|
||
});
|
||
},
|
||
openReview(row) {
|
||
this.$router.push({
|
||
path: customerArchiveFormRoute,
|
||
query: { id: row.id, name: '客商复评', review: '1' },
|
||
});
|
||
},
|
||
closeArchive() {
|
||
if (this.isPublicViewPage) return;
|
||
this.archiveBox = false;
|
||
this.markCustomerArchiveListNeedRefresh();
|
||
this.$router.$avueRouter?.closeTag?.();
|
||
this.$router.push(customerArchiveListRoute);
|
||
},
|
||
openArchive(row, readonly = false) {
|
||
this.readonly = readonly;
|
||
this.activeTab = 'scores';
|
||
this.resetDetailPagination();
|
||
this.resetChangeRecordPage();
|
||
if (row && row.id) {
|
||
const requestDetail = this.isPublicViewPage ? getPublicDetail : getDetail;
|
||
requestDetail(row.id)
|
||
.then(res => {
|
||
const archive = this.normalizeDetail(res.data.data);
|
||
this.archiveForm = readonly ? this.replaceNegativeOneWithBlank(archive) : archive;
|
||
this.originalAccessType = this.archiveForm.accessType || '';
|
||
const qualificationFiles = this.parseAttachments(
|
||
this.archiveForm.qualificationAttachments
|
||
);
|
||
this.qualificationFiles = readonly
|
||
? this.replaceNegativeOneWithBlank(qualificationFiles)
|
||
: qualificationFiles;
|
||
this.sortQualificationFiles();
|
||
this.qualificationUploadFiles = [];
|
||
this.ocrQualificationUploadFiles = [];
|
||
this.selectedQualificationFiles = [];
|
||
if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm);
|
||
this.applyFixedOwnerDept();
|
||
this.checkArchiveNameRisk();
|
||
this.archiveBox = true;
|
||
this.loadChangeRecords();
|
||
if (this.reviewMode) this.openReviewScore();
|
||
})
|
||
.catch(() => {
|
||
this.archiveBox = true;
|
||
if (this.isPublicViewPage) this.$message.error('客商信息加载失败');
|
||
});
|
||
return;
|
||
}
|
||
this.archiveForm = this.emptyArchive();
|
||
this.originalAccessType = '';
|
||
this.resetKingdeeQueryState();
|
||
this.qualificationFiles = [];
|
||
this.qualificationUploadFiles = [];
|
||
this.ocrQualificationUploadFiles = [];
|
||
this.selectedQualificationFiles = [];
|
||
this.applyFixedOwnerDept();
|
||
this.checkArchiveNameRisk();
|
||
this.archiveBox = true;
|
||
},
|
||
resetArchive() {
|
||
this.readonly = false;
|
||
this.resetKingdeeQueryState();
|
||
this.changeRecordDetailVisible = false;
|
||
this.changeRecordDetail = null;
|
||
this.changeRecordDetailRows = [];
|
||
this.archiveForm = this.emptyArchive();
|
||
this.originalAccessType = '';
|
||
this.qualificationFiles = [];
|
||
this.qualificationUploadFiles = [];
|
||
this.ocrQualificationUploadFiles = [];
|
||
this.selectedQualificationFiles = [];
|
||
this.scoreBox = false;
|
||
this.resetScoreDialog();
|
||
this.resetDetailPagination();
|
||
this.resetChangeRecordPage();
|
||
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();
|
||
},
|
||
// 仅客商分类含「客户」时强制评分;纯承运商不强制。评分记录至多一条。
|
||
validateScoreForApproval(archive) {
|
||
const scores = archive.scores || [];
|
||
if (scores.length > 1) {
|
||
this.$message.warning('评分记录只能有一条');
|
||
return false;
|
||
}
|
||
if (!this.requiresCustomerScore(archive.customerType)) {
|
||
return true;
|
||
}
|
||
if (scores.length !== 1 || scores[0].selfStatus !== '已完成') {
|
||
this.$message.warning('客户提交审批前必须完成评分自评');
|
||
return false;
|
||
}
|
||
if (!scores[0].nextEvaluateDate) {
|
||
this.$message.warning('请填写下次评定日期');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
/** 客商分类是否包含客户(字典值「客户」或历史值 customer) */
|
||
requiresCustomerScore(customerType) {
|
||
const values = Array.isArray(customerType)
|
||
? customerType
|
||
: String(customerType || '')
|
||
.replace(/,/g, ',')
|
||
.split(',');
|
||
return values
|
||
.map(item => String(item || '').trim())
|
||
.filter(Boolean)
|
||
.some(item => item === '客户' || item.toLowerCase() === 'customer');
|
||
},
|
||
normalizeArchive() {
|
||
// 提交前再次固定所属组织,防止被绕过
|
||
this.applyFixedOwnerDept();
|
||
const archive = JSON.parse(JSON.stringify(this.archiveForm));
|
||
if (archive.businessTermType === '长期') {
|
||
archive.businessEndDate = null;
|
||
}
|
||
if (!archive.businessStartDate) {
|
||
archive.businessStartDate = null;
|
||
}
|
||
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
|
||
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
|
||
const paidInCapital = this.normalizeOptionalAmount(archive.paidInCapital);
|
||
const usedCreditLimit = this.normalizeOptionalAmount(archive.usedCreditLimit);
|
||
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
|
||
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
|
||
archive.paidInCapital = paidInCapital === '' ? null : paidInCapital;
|
||
archive.usedCreditLimit = usedCreditLimit === '' ? null : usedCreditLimit;
|
||
archive.customerType = Array.isArray(archive.customerType)
|
||
? archive.customerType.join(',')
|
||
: archive.customerType;
|
||
archive.businessScope = String(archive.businessScope || '').trim();
|
||
archive.networkFreightPlatform =
|
||
archive.networkFreightPlatform === 0 || archive.networkFreightPlatform === 1
|
||
? archive.networkFreightPlatform
|
||
: null;
|
||
archive.guangxiTop100 =
|
||
archive.guangxiTop100 === 0 || archive.guangxiTop100 === 1 ? archive.guangxiTop100 : null;
|
||
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 ? Number(deptIds[0]) : null;
|
||
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 => {
|
||
const contact = this.normalizeContact({
|
||
...item,
|
||
contactAddress: this.formatContactAddress(item),
|
||
});
|
||
delete contact.regionPath;
|
||
return contact;
|
||
})
|
||
.filter(item => item.contactName || item.contactPhone);
|
||
// 主表联系电话改由联系人维护,取第一条有效手机号回填
|
||
const firstContactPhone = (archive.contacts || [])
|
||
.map(item => String(item.contactPhone || '').trim())
|
||
.find(Boolean);
|
||
archive.contactPhone = firstContactPhone || null;
|
||
archive.receiptAccounts = (archive.receiptAccounts || [])
|
||
.map(item => this.normalizeReceipt(item))
|
||
.filter(item => item.accountName || item.accountHolderName || item.bankAccount || item.cnapsCode);
|
||
archive.invoices = (archive.invoices || [])
|
||
.map(item => {
|
||
const invoice = this.normalizeInvoice(item);
|
||
delete invoice.registeredRegionPath;
|
||
invoice.contacts = (invoice.contacts || [])
|
||
.filter(contact => !this.isBlankInvoiceContact(contact))
|
||
.map(contact => {
|
||
const row = { ...contact };
|
||
if (this.isInternalCustomer) {
|
||
this.handleInvoiceContactDeptChange(row);
|
||
}
|
||
delete row.deptIdList;
|
||
return row;
|
||
});
|
||
return invoice;
|
||
})
|
||
.filter(item => item.invoiceTitle || item.taxNo || item.bankAccount);
|
||
archive.scores = (archive.scores || []).map(score => {
|
||
const calculatedScore = this.calculateScore(score);
|
||
const scoreMaxCreditLimit = this.normalizeOptionalAmount(calculatedScore.maxCreditLimit);
|
||
const scoreApplyCreditLimit = this.normalizeOptionalAmount(
|
||
calculatedScore.applyCreditLimit
|
||
);
|
||
calculatedScore.maxCreditLimit = scoreMaxCreditLimit === '' ? null : scoreMaxCreditLimit;
|
||
calculatedScore.applyCreditLimit =
|
||
scoreApplyCreditLimit === '' ? null : scoreApplyCreditLimit;
|
||
return calculatedScore;
|
||
});
|
||
this.applyArchiveCreditFromScores(archive);
|
||
const maxCreditLimit = this.normalizeOptionalAmount(archive.maxCreditLimit);
|
||
const applyCreditLimit = this.normalizeOptionalAmount(archive.applyCreditLimit);
|
||
archive.maxCreditLimit = maxCreditLimit === '' ? null : maxCreditLimit;
|
||
archive.applyCreditLimit = applyCreditLimit === '' ? null : applyCreditLimit;
|
||
return archive;
|
||
},
|
||
validateContactsRequired(archive = this.archiveForm) {
|
||
const contacts = (archive.contacts || []).filter(
|
||
item => String(item.contactName || '').trim() || String(item.contactPhone || '').trim()
|
||
);
|
||
if (!contacts.length) {
|
||
this.$message.warning('请至少维护一条联系人');
|
||
this.activeTab = 'contacts';
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
saveArchive() {
|
||
if (this.kingdeeBlocked) {
|
||
this.$message.warning('金蝶审核中,请稍后重新添加');
|
||
return;
|
||
}
|
||
this.applyFixedOwnerDept();
|
||
this.$refs.archiveForm.validate(valid => {
|
||
if (!valid) {
|
||
this.$message.warning('请先完整填写必填项');
|
||
return;
|
||
}
|
||
if (!this.validateContactsRequired()) return;
|
||
submit(this.normalizeArchive()).then(() => {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
});
|
||
});
|
||
},
|
||
saveAndSubmitArchive() {
|
||
if (this.kingdeeBlocked) {
|
||
this.$message.warning('金蝶审核中,请稍后重新添加');
|
||
return;
|
||
}
|
||
this.applyFixedOwnerDept();
|
||
this.$refs.archiveForm.validate(valid => {
|
||
if (!valid) {
|
||
this.$message.warning('请先完整填写必填项');
|
||
return;
|
||
}
|
||
if (!this.validateContactsRequired()) return;
|
||
const archive = this.normalizeArchive();
|
||
// 仅提交时落变更记录;保存不记录
|
||
archive.recordChange = true;
|
||
if (!this.validateScoreForApproval(archive)) return;
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '提交中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
submit(archive)
|
||
.then(res => {
|
||
const data = res.data.data;
|
||
const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id;
|
||
if (!id) {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '保存成功,请在列表提交审批' });
|
||
return;
|
||
}
|
||
const fullName =
|
||
(data && typeof data === 'object' ? data.fullName : '') ||
|
||
archive.fullName ||
|
||
this.archiveForm.fullName ||
|
||
'';
|
||
return this.submitCustomerApproval(
|
||
id,
|
||
fullName,
|
||
archive.approvalStatus || this.archiveForm.approvalStatus
|
||
).then(() => {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '提交成功!' });
|
||
});
|
||
})
|
||
.finally(() => loading.close());
|
||
});
|
||
},
|
||
/**
|
||
* 列表 / 新增 / 编辑提交共用:
|
||
* 未开启 MK 时仅走本地提交审批(与新增一致,不请求 MK 流程);
|
||
* 开启 MK 时先提交审核流,再更新客商审批状态。
|
||
*/
|
||
submitCustomerApproval(id, fullName = '', approvalStatus = '') {
|
||
const submitLocal = () => submitApproval(id);
|
||
const run = enabled => {
|
||
this.mkEnabled = !!enabled;
|
||
this._mkEnabledResolved = true;
|
||
if (!this.mkEnabled) {
|
||
return submitLocal();
|
||
}
|
||
return submitMkApprovalFlow({
|
||
bizType: 'customer-archive',
|
||
bizId: id,
|
||
subjectName: fullName,
|
||
approvalStatus,
|
||
mkEnabled: true,
|
||
}).then(submitLocal);
|
||
};
|
||
|
||
// 与列表按钮一致:以页面 mkEnabled 为准,未开启时不请求 MK
|
||
if (this._mkEnabledResolved) {
|
||
return run(this.mkEnabled);
|
||
}
|
||
return loadMkEnabled()
|
||
.then(enabled => run(enabled))
|
||
.catch(() => run(false));
|
||
},
|
||
openReviewScore() {
|
||
const scores = this.archiveForm.scores || [];
|
||
if (!scores.length) {
|
||
this.$message.warning('暂无评分记录,无法复评');
|
||
return;
|
||
}
|
||
this.$nextTick(() => this.openScore(scores[0], 0));
|
||
},
|
||
/** 提交后详情页评分表:审批中且未完成复评时可复评 */
|
||
canReviewScore(row = {}) {
|
||
return (
|
||
this.hasPermission('customer_type_re_cert') &&
|
||
this.archiveForm.approvalStatus === 'reviewing' &&
|
||
row.reviewStatus !== '已完成'
|
||
);
|
||
},
|
||
startReviewScore(row, index = 0) {
|
||
if (!row) {
|
||
this.$message.warning('暂无评分记录,无法复评');
|
||
return;
|
||
}
|
||
// 详情只读进入时也要能改复评得分并确认
|
||
this.reviewMode = true;
|
||
this.openScore(row, index);
|
||
},
|
||
addScore() {
|
||
const existingScores = this.archiveForm.scores || [];
|
||
if (existingScores.length) {
|
||
this.$message.warning(
|
||
this.shouldFreezeEffectiveCredit()
|
||
? '请先删除原评分记录后再添加新评分表'
|
||
: '请勿重复添加'
|
||
);
|
||
return;
|
||
}
|
||
this.scoreRecordIndex = -1;
|
||
this.currentScore = this.normalizeScore({ applyCreditLimit: '' });
|
||
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;
|
||
const loadOptions = this.scoreQuantificationOptions.length
|
||
? Promise.resolve()
|
||
: this.initScoreQuantificationOptions();
|
||
loadOptions
|
||
.then(() => this.ensureScoreQuantificationOption(this.currentScore.quantificationId))
|
||
.then(() => this.applyMaxMinusScoreFromQuantification(this.currentScore.quantificationId));
|
||
},
|
||
resetScoreDialog() {
|
||
this.scoreRecordIndex = -1;
|
||
this.currentScore = { details: [] };
|
||
this.scoreAttachmentFiles = [];
|
||
this.expandedScoreCategoryKeys = [];
|
||
},
|
||
handleScoreQuantificationChange(quantificationId) {
|
||
if (!quantificationId) {
|
||
this.currentScore.details = [];
|
||
this.expandedScoreCategoryKeys = [];
|
||
return;
|
||
}
|
||
Promise.allSettled([
|
||
getScoreTemplate(quantificationId),
|
||
getCreditScoreQuantificationDetail(quantificationId),
|
||
]).then(([templateResult, quantificationResult]) => {
|
||
const templateData =
|
||
templateResult.status === 'fulfilled' ? templateResult.value.data.data || {} : {};
|
||
const quantificationData =
|
||
quantificationResult.status === 'fulfilled'
|
||
? quantificationResult.value.data.data || {}
|
||
: {};
|
||
const scoreTemplate =
|
||
templateData.categories?.length || templateData.details?.length
|
||
? templateData
|
||
: quantificationData;
|
||
const template = this.normalizeScore({
|
||
...scoreTemplate,
|
||
quantificationId,
|
||
evaluateMode: this.currentScore.evaluateMode || 'online',
|
||
scoreDate: this.currentScore.scoreDate,
|
||
nextEvaluateDate: this.currentScore.nextEvaluateDate,
|
||
applyCreditLimit: this.currentScore.applyCreditLimit,
|
||
maxCreditLimit: '',
|
||
creditLevel: '',
|
||
maxMinusScore: HARDCODED_MAX_MINUS_SCORE,
|
||
maxPlusScore: HARDCODED_MAX_PLUS_SCORE,
|
||
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
||
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
||
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
||
remark: this.currentScore.remark,
|
||
attachments: this.scoreAttachmentFiles,
|
||
standards: quantificationData.standards || scoreTemplate.standards || [],
|
||
details: this.normalizeScoreTemplateDetails(scoreTemplate),
|
||
});
|
||
if (!template.details || !template.details.length) {
|
||
this.$message.warning('当前评分量化表未配置评分项目');
|
||
}
|
||
this.currentScore = template;
|
||
this.expandedScoreCategoryKeys = [];
|
||
this.calculateScore(this.currentScore);
|
||
});
|
||
},
|
||
applyMaxMinusScoreFromQuantification(quantificationId) {
|
||
if (!quantificationId) return Promise.resolve();
|
||
// 暂定写死加分/减分上限,不再依赖量化表配置值
|
||
this.currentScore.maxMinusScore = HARDCODED_MAX_MINUS_SCORE;
|
||
this.currentScore.maxPlusScore = HARDCODED_MAX_PLUS_SCORE;
|
||
this.calculateScore(this.currentScore);
|
||
return Promise.resolve();
|
||
},
|
||
normalizeScoreTemplateDetails(template = {}) {
|
||
const categoryDetails = (template.categories || []).flatMap(category =>
|
||
(category.items || []).map(item => ({
|
||
...item,
|
||
categoryCode: category.categoryCode,
|
||
categoryName: category.categoryName,
|
||
scoreInput: this.normalizeScoreInputValue(item.scoreInput),
|
||
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,
|
||
changeType: option.changeType,
|
||
changeValue: option.changeValue,
|
||
changeUnit: option.changeUnit,
|
||
scoreType: option.scoreType,
|
||
}))
|
||
),
|
||
}))
|
||
);
|
||
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 || '',
|
||
scoreInput: this.normalizeScoreInputValue(detail.scoreInput),
|
||
};
|
||
});
|
||
},
|
||
normalizeScoreInputValue(value) {
|
||
if (value === undefined || value === null || value === '' || value === -1 || value === '-1') {
|
||
return '';
|
||
}
|
||
return String(value);
|
||
},
|
||
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 => {
|
||
const isScoreOption = item.changeType || item.changeValue || item.changeUnit;
|
||
const changeLabel = item.changeType === 'decrease' ? '每减少' : '每增加';
|
||
const scoreLabel = item.scoreType === 'subtract' ? '减' : '加';
|
||
const rawScore = item.score;
|
||
const generatedLabel = isScoreOption
|
||
? `${changeLabel}${item.changeValue || ''}${item.changeUnit || ''}${scoreLabel}${
|
||
this.isBlankScore(rawScore) ? '' : rawScore
|
||
}分`
|
||
: '';
|
||
const label = item.label || item.optionName || item.value || generatedLabel;
|
||
return {
|
||
...item,
|
||
label,
|
||
value: item.value ?? item.optionName ?? item.label ?? generatedLabel,
|
||
score:
|
||
item.scoreType === 'subtract' && !this.isBlankScore(rawScore)
|
||
? -Math.abs(Number(rawScore))
|
||
: rawScore,
|
||
};
|
||
});
|
||
if (Array.isArray(value)) return normalize(value);
|
||
try {
|
||
const options = JSON.parse(value);
|
||
return Array.isArray(options) ? normalize(options) : [];
|
||
} catch (error) {
|
||
return [];
|
||
}
|
||
},
|
||
isScoreTypeDetail(detail = {}) {
|
||
return String(detail.optionType || '').toLowerCase() === 'score';
|
||
},
|
||
getScoreRule(detail = {}) {
|
||
return this.parseScoreOptions(detail.optionsJson)[0] || null;
|
||
},
|
||
getScoreTypeCalculatedScore(detail = {}) {
|
||
const rule = this.getScoreRule(detail);
|
||
const base = Number(detail.baseValue);
|
||
const input = Number(detail.scoreInput);
|
||
if (!rule || !Number.isFinite(base) || !Number.isFinite(input)) return null;
|
||
|
||
const increase = rule.changeType !== 'decrease';
|
||
const valid = increase ? input >= base : input <= base;
|
||
if (!valid) return null;
|
||
|
||
const distance = increase ? input - base : base - input;
|
||
const changeValue = Number(rule.changeValue);
|
||
const stepSize = Number.isFinite(changeValue) && changeValue > 0 ? changeValue : 1;
|
||
const steps = Math.floor(distance / stepSize + 1e-9);
|
||
const stepScore = Math.abs(Number(rule.score || 0));
|
||
if (!Number.isFinite(stepScore) || steps < 0) return null;
|
||
|
||
// 减分项:按阶梯累计扣分,存正数,总分计算时再减;不做项目满分截断
|
||
if (this.isMinusScoreDetail(detail)) {
|
||
return Number((steps * stepScore).toFixed(2));
|
||
}
|
||
|
||
// 基础/加分项:从项目分值起步,按阶梯加减,限制在 [0, 项目满分]
|
||
const baseScore = Number(detail.score || 0);
|
||
const signedStep = rule.scoreType === 'subtract' ? -stepScore : stepScore;
|
||
const calculatedScore = baseScore + steps * signedStep;
|
||
const projectScore = this.getScoreItemFullScore(detail);
|
||
const upper = Math.max(projectScore, baseScore, 0);
|
||
return Number(Math.min(Math.max(calculatedScore, 0), upper).toFixed(2));
|
||
},
|
||
handleScoreValueInput(detail, value) {
|
||
const raw = String(value ?? '')
|
||
.replace(/[^\d.-]/g, '')
|
||
.replace(/(?!^)-/g, '')
|
||
.replace(/(\..*)\./g, '$1');
|
||
const sign = raw.startsWith('-') ? '-' : '';
|
||
const unsigned = raw.replace(/^-/, '');
|
||
const hasDecimal = unsigned.includes('.');
|
||
const [integerPart, decimalPart = ''] = unsigned.split('.');
|
||
const normalized = `${sign}${integerPart}${hasDecimal ? `.${decimalPart.slice(0, 2)}` : ''}`;
|
||
detail.scoreInput = normalized;
|
||
const rule = this.getScoreRule(detail);
|
||
if (!normalized || normalized === '-' || normalized === '.') {
|
||
detail.selfScore = '';
|
||
this.calculateScore(this.currentScore);
|
||
return;
|
||
}
|
||
const base = Number(detail.baseValue);
|
||
const input = Number(normalized);
|
||
if (!rule || !Number.isFinite(base) || !Number.isFinite(input)) {
|
||
detail.selfScore = '';
|
||
this.calculateScore(this.currentScore);
|
||
return;
|
||
}
|
||
const increase = rule.changeType !== 'decrease';
|
||
const valid = increase ? input >= base : input <= base;
|
||
if (!valid) {
|
||
detail.selfScore = '';
|
||
this.calculateScore(this.currentScore);
|
||
return;
|
||
}
|
||
detail.selfScore = this.getScoreTypeCalculatedScore(detail);
|
||
this.calculateScore(this.currentScore);
|
||
},
|
||
handleScoreValueBlur(detail) {
|
||
const normalized = String(detail.scoreInput ?? '').trim();
|
||
if (!normalized || normalized === '-' || normalized === '.') return;
|
||
const rule = this.getScoreRule(detail);
|
||
const base = Number(detail.baseValue);
|
||
const input = Number(normalized);
|
||
if (!rule || !Number.isFinite(base) || !Number.isFinite(input)) return;
|
||
const increase = rule.changeType !== 'decrease';
|
||
const valid = increase ? input >= base : input <= base;
|
||
if (!valid) {
|
||
this.$message.warning(`输入值${increase ? '不能小于' : '不能大于'}基准数值${base}`);
|
||
}
|
||
},
|
||
scoreOptionChange(detail, optionValue) {
|
||
const option = this.parseScoreOptions(detail.optionsJson).find(
|
||
item => item.value === optionValue
|
||
);
|
||
const rawScore = option?.score;
|
||
detail.selfScore = this.isBlankScore(rawScore) ? 0 : Number(rawScore);
|
||
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');
|
||
// 减分项复评得分存正数扣分,允许填 0(不扣分);不做项目满分截断
|
||
if (prop === 'reviewScore' && normalized !== '' && !this.isMinusScoreDetail(detail)) {
|
||
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);
|
||
if (prop === 'reviewScore') this.logReviewScoreSummary();
|
||
return;
|
||
}
|
||
}
|
||
detail[prop] = normalized;
|
||
this.calculateScore(this.currentScore);
|
||
if (prop === 'reviewScore') this.logReviewScoreSummary();
|
||
},
|
||
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 > 0 ? -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));
|
||
if (!optionScores.length) return 0;
|
||
// 减分项选项分常为负(如 -5),项目满分按绝对值取最大可扣分;避免 0 分被误判为高于“最高得分-5”
|
||
if (this.isMinusScoreDetail(detail)) {
|
||
return Math.max(...optionScores.map(score => Math.abs(score)));
|
||
}
|
||
return Math.max(...optionScores);
|
||
},
|
||
getScoreFullMark(details = []) {
|
||
return details.reduce((sum, item) => {
|
||
if (!this.isBasicScoreDetail(item)) return sum;
|
||
return sum + this.getScoreItemFullScore(item);
|
||
}, 0);
|
||
},
|
||
logReviewScoreSummary() {
|
||
const details = this.currentScore.details || [];
|
||
console.log('[评分详情] 复评评分结果', {
|
||
复评总分: this.currentScore.reviewScore,
|
||
总分: this.getScoreFullMark(details),
|
||
得分率: `${this.currentScore.scoreRate}%`,
|
||
匹配到的信用等级: this.currentScore.creditLevel || '',
|
||
});
|
||
},
|
||
matchCreditStandard(score, scoreRate) {
|
||
// 闭开区间:≥lower 且 <upper;最高档 upper=100 时包含 100(≥90 A;80≤x<90 B;…;x<50 F)
|
||
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;
|
||
if (upper >= 100) {
|
||
return scoreRate >= lower && scoreRate <= upper;
|
||
}
|
||
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 || [];
|
||
details.forEach(detail => {
|
||
if (!this.isScoreTypeDetail(detail) || String(detail.scoreInput ?? '').trim() === '')
|
||
return;
|
||
const calculatedScore = this.getScoreTypeCalculatedScore(detail);
|
||
if (calculatedScore !== null) detail.selfScore = calculatedScore;
|
||
});
|
||
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 rawPlusSelf = sumScore(plusDetails, 'selfScore');
|
||
const rawPlusReview = sumScore(plusDetails, 'reviewScore');
|
||
const rawMinusSelf = sumScore(minusDetails, 'selfScore');
|
||
const rawMinusReview = sumScore(minusDetails, 'reviewScore');
|
||
// 暂定:加分/减分合计最高按 15 分计(未超按实际,超 15 按 15/-15)
|
||
const plusSelf = Math.min(rawPlusSelf, HARDCODED_MAX_PLUS_SCORE);
|
||
const plusReview = Math.min(rawPlusReview, HARDCODED_MAX_PLUS_SCORE);
|
||
const minusSelf = Math.min(rawMinusSelf, HARDCODED_MAX_MINUS_SCORE);
|
||
const minusReview = Math.min(rawMinusReview, HARDCODED_MAX_MINUS_SCORE);
|
||
const computedSelfScore = sumScore(basicDetails, 'selfScore') + plusSelf - minusSelf;
|
||
const reviewScore = sumScore(basicDetails, 'reviewScore') + plusReview - minusReview;
|
||
const hasReviewScore = details.some(
|
||
item => !this.isBlankScore(item.reviewScore)
|
||
);
|
||
// 线下模式允许手工改自评得分:有手工值时优先采用,否则回退明细合计
|
||
const isOffline = String(score.evaluateMode || '') === 'offline';
|
||
const manualSelfScore = Number(score.selfScore);
|
||
const selfScore =
|
||
isOffline && Number.isFinite(manualSelfScore) && String(score.selfScore ?? '').trim() !== ''
|
||
? manualSelfScore
|
||
: computedSelfScore;
|
||
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(Number(selfScore).toFixed(2));
|
||
score.reviewScore = Number(reviewScore.toFixed(2));
|
||
score.finalScore = Number(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;
|
||
},
|
||
isBlankScore(value) {
|
||
return value === undefined || value === null || String(value).trim() === '';
|
||
},
|
||
hasIncompleteBasicScoreDetail(details = []) {
|
||
return this.getScoreCategoryDetailsByList(details, 'basic').some(item => {
|
||
if (this.isScoreTypeDetail(item)) {
|
||
const input = String(item.scoreInput ?? '').trim();
|
||
const inputBlank = input === '' || input === '-' || input === '.';
|
||
return inputBlank || !Number.isFinite(Number(input)) || this.isBlankScore(item.selfScore);
|
||
}
|
||
return this.isBlankScore(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(successMessage) {
|
||
if (!this.validateScoreForm(false)) return;
|
||
this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || []));
|
||
this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles);
|
||
this.currentScore.selfStatus = this.hasIncompleteBasicScoreDetail(this.currentScore.details)
|
||
? '未完成'
|
||
: '已完成';
|
||
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(1);
|
||
}
|
||
this.syncArchiveCreditFromScores();
|
||
this.activeTab = 'scores';
|
||
this.scoreBox = false;
|
||
const pending = this.saveScoreArchive(successMessage);
|
||
if (this.reviewMode && successMessage && pending && pending.then) {
|
||
pending.then(() => this.closeArchive());
|
||
}
|
||
return pending;
|
||
},
|
||
deleteScore(index) {
|
||
const freezeCredit = this.shouldFreezeEffectiveCredit();
|
||
const confirmText = freezeCredit
|
||
? '确定删除该评分记录?删除后可重新添加评分表;原评分数据会保留,新评分再次审核通过前,客商等级与额度仍按原评分暂时生效。'
|
||
: '确定删除该评分记录?';
|
||
this.$confirm(confirmText, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
if (!Array.isArray(this.archiveForm.scores)) this.archiveForm.scores = [];
|
||
this.archiveForm.scores.splice(index, 1);
|
||
if (this.scoreRecordIndex === index) {
|
||
this.scoreBox = false;
|
||
} else if (this.scoreRecordIndex > index) {
|
||
this.scoreRecordIndex -= 1;
|
||
}
|
||
if (!freezeCredit) {
|
||
const latestScore = this.getLatestCreditScore(this.archiveForm.scores);
|
||
if (latestScore) {
|
||
this.syncArchiveCreditFromScores();
|
||
} else {
|
||
this.archiveForm.customerLevel = '';
|
||
this.archiveForm.maxCreditLimit = '';
|
||
this.archiveForm.applyCreditLimit = '';
|
||
}
|
||
}
|
||
this.handleScoreCurrentChange(this.scorePage.currentPage);
|
||
this.saveScoreArchive(
|
||
freezeCredit
|
||
? '评分记录已删除,原等级与额度暂时继续生效'
|
||
: '评分记录已删除',
|
||
'评分记录已删除,请保存客商档案后生效'
|
||
);
|
||
})
|
||
.catch(() => {});
|
||
},
|
||
saveScoreArchive(
|
||
successMessage = '评分记录已保存',
|
||
draftMessage = '评分记录已添加,请保存客商档案后生效'
|
||
) {
|
||
if (!this.archiveForm.id) {
|
||
this.$message.success(draftMessage);
|
||
return Promise.resolve();
|
||
}
|
||
return submit(this.normalizeArchive()).then(() => {
|
||
this.$message.success(successMessage);
|
||
});
|
||
},
|
||
submitSelfScore() {
|
||
if (!this.validateScoreForm(true)) return;
|
||
this.currentScore.selfStatus = '已完成';
|
||
this.currentScore.reviewStatus = '未完成';
|
||
this.saveScoreDetail('自评已提交');
|
||
},
|
||
confirmReviewScore() {
|
||
if (!this.validateScoreForm(true)) return;
|
||
this.currentScore.selfStatus = '已完成';
|
||
this.currentScore.reviewStatus = '已完成';
|
||
this.saveScoreDetail('复评确认成功');
|
||
},
|
||
validateScoreForm(requireComplete) {
|
||
if (!this.currentScore.scoreDate) {
|
||
this.$message.warning('请选择评定日期');
|
||
return false;
|
||
}
|
||
if (!this.currentScore.nextEvaluateDate) {
|
||
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(
|
||
this.isOfflineScore
|
||
? '请先上传评分表 Excel 解析回填明细'
|
||
: '当前评分量化表未配置评分项目'
|
||
);
|
||
return false;
|
||
}
|
||
const invalidScoreInput = this.currentScore.details.find(item => {
|
||
if (!this.isScoreTypeDetail(item)) return false;
|
||
const input = String(item.scoreInput ?? '').trim();
|
||
return input !== '' && this.getScoreTypeCalculatedScore(item) === null;
|
||
});
|
||
if (invalidScoreInput) {
|
||
this.$message.warning(
|
||
`${invalidScoreInput.itemName || '评分项目'}的输入值不符合基准数值限制,请重新填写`
|
||
);
|
||
return false;
|
||
}
|
||
// 减分项允许填 0(不扣分),且按阶梯累计扣分不做项目满分截断
|
||
const overMaxReviewScore = this.currentScore.details.find(item => {
|
||
if (this.isMinusScoreDetail(item)) return false;
|
||
if (
|
||
item.reviewScore === undefined ||
|
||
item.reviewScore === null ||
|
||
String(item.reviewScore).trim() === ''
|
||
) {
|
||
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 === -1 || value === '-1'
|
||
? ''
|
||
: 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.validateContactsRequired(archive)) return;
|
||
if (!this.validateScoreForApproval(archive)) return;
|
||
this.$confirm('是否提交客商准入审批?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() =>
|
||
this.submitCustomerApproval(
|
||
row.id,
|
||
archive.fullName || row.fullName,
|
||
archive.approvalStatus || row.approvalStatus
|
||
)
|
||
)
|
||
.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: '已撤回至草稿状态!' });
|
||
});
|
||
},
|
||
handleMkAudit(row) {
|
||
if (!row?.id) {
|
||
this.$message.warning('客商ID为空,无法打开审核页');
|
||
return;
|
||
}
|
||
getMKApprovalUrl({ bizId: String(row.id) })
|
||
.then(res => {
|
||
const url = res?.data?.data;
|
||
if (!url) {
|
||
this.$message.error('获取审核跳转地址失败');
|
||
return;
|
||
}
|
||
window.open(url, '_blank');
|
||
})
|
||
.catch(() => {
|
||
this.$message.error('获取审核跳转地址失败');
|
||
});
|
||
},
|
||
async handleApprove(row) {
|
||
await this.$confirm('是否确认审核通过?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
});
|
||
const res = await approve(row.id);
|
||
// data=false 表示审核已通过但推送金蝶失败,msg 带具体原因
|
||
if (res.data.data === false) {
|
||
this.$message.warning(res.data.msg);
|
||
} else {
|
||
this.$message.success(res.data.msg || '审核通过!');
|
||
}
|
||
this.onLoad(this.page);
|
||
},
|
||
async handleSyncKingdee(row) {
|
||
if (this.kingdeeSyncingId) return;
|
||
try {
|
||
await this.$confirm('是否确认推送金蝶?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
});
|
||
} catch (exception) {
|
||
return;
|
||
}
|
||
this.kingdeeSyncingId = row.id;
|
||
try {
|
||
const res = await syncKingdee(row.id);
|
||
const archive = res?.data?.data || {};
|
||
if (archive.kingdeeStatus === 'synced') {
|
||
this.$message.success('推送成功,金蝶客户编码与客商编号一致');
|
||
} else if (archive.kingdeeStatus === 'exist') {
|
||
this.$message.info('金蝶已有该客商档案,无需推送');
|
||
} else {
|
||
this.$message.warning(`推送失败:${archive.kingdeeFailReason || '未知原因'}`);
|
||
}
|
||
this.onLoad(this.page);
|
||
} finally {
|
||
this.kingdeeSyncingId = null;
|
||
}
|
||
},
|
||
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 = { expireStatus: this.query.expireStatus || 'all' };
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
searchChange(params, done) {
|
||
// Avue 只回传非空筛选项,被清空的字段不会出现在 params 里,
|
||
// 因此查询条件必须整体重建,不能与上一次的 this.query 合并,否则清空后仍会带上次的值
|
||
const expireStatus = this.query.expireStatus || 'all';
|
||
this.query = this.buildQuery(params, true);
|
||
this.query.expireStatus = expireStatus;
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page, this.query);
|
||
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;
|
||
});
|
||
},
|
||
// 黑名单判定:仅以金蝶黑名单(A801-5)命中为准(失信类别/列入事由任一非空);C101-1 的 hcsBlackStatus 不作为黑名单依据
|
||
customerRowIsBlack(row) {
|
||
return isKingdeeBlack(row.kingdeeBlackCategory, row.kingdeeBlackReason);
|
||
},
|
||
// 客商风险名称(每日定时刷新落库字段):等级与黑名单组合展示(如"低风险、黑名单");内部组织默认无风险;未查询显示"-"
|
||
customerRowRiskName(row) {
|
||
return resolveRiskTagName(row.hcsRiskLevel, this.customerRowIsBlack(row), row.customerKind);
|
||
},
|
||
// 高风险/黑名单判定(与列表筛选口径一致,命中显示红色标签)
|
||
customerRowRisky(row) {
|
||
return isRiskyTag(row.hcsRiskLevel, this.customerRowIsBlack(row));
|
||
},
|
||
// 客商名称风险校验(C101-1,仅展示不作为安全边界):名称失焦/回显时查询,
|
||
// risky 在名称框下展示红色提示,悬浮见风险项明细;查询失败静默不阻断填写,提交链路由后端强校验兜底
|
||
// 清空客商名称下风险提示(信用代码变化后原按旧编号+信用代码查询的结果失效)
|
||
clearArchiveNameRisk() {
|
||
this.archiveNameRisky = false;
|
||
this.archiveNameRiskTip = '';
|
||
this.archiveNameRiskLabel = '';
|
||
},
|
||
checkArchiveNameRisk() {
|
||
this.clearArchiveNameRisk();
|
||
const customerName = String(this.archiveForm.fullName || '').trim();
|
||
if (!customerName) return;
|
||
// 新增客商档案本地反查不到编号:金蝶带出后表单已有客商编号,优先按编号查 C101-1,
|
||
// 并传信用代码供金蝶黑名单(A801-5)兜底;无编号时按名称反查档案
|
||
const customerCode = String(this.archiveForm.customerCode || '').trim();
|
||
const request = customerCode
|
||
? getRisk(customerCode, String(this.archiveForm.unifiedCreditCode || '').trim(), customerName)
|
||
: getRiskByName(customerName);
|
||
request
|
||
.then(res => {
|
||
const risk = (res.data && res.data.data) || {};
|
||
this.archiveNameRisky = risk.risky === true;
|
||
this.archiveNameRiskTip = risk.riskTip || '';
|
||
// 红字与列表标签同口径(risk-display):等级按合同系统返回值翻译,黑名单按金蝶命中标记独立展示,组合不分家
|
||
this.archiveNameRiskLabel = this.archiveNameRisky
|
||
? (resolveRiskTagName(risk.fdRiskLevel, risk.kingdeeBlackHit === true) || '高风险/黑名单') + '客商'
|
||
: '';
|
||
})
|
||
.catch(() => {
|
||
// 忽略风险查询异常,表单正常填写
|
||
});
|
||
},
|
||
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(),
|
||
};
|
||
},
|
||
// replace=true 表示以本次搜索条件为准整体重建(清空字段不会回传,需丢弃旧条件);
|
||
// 翻页、刷新等场景传 false,沿用上一次的查询条件
|
||
buildQuery(params = {}, replace = false) {
|
||
const merged = replace ? { ...params } : { ...this.query, ...params };
|
||
Object.keys(params).forEach(key => {
|
||
const value = params[key];
|
||
if (
|
||
value === '' ||
|
||
value === null ||
|
||
value === undefined ||
|
||
(Array.isArray(value) && value.length === 0)
|
||
) {
|
||
delete merged[key];
|
||
}
|
||
});
|
||
if (Array.isArray(merged.deptName)) {
|
||
const path = merged.deptName.filter(
|
||
item => item !== undefined && item !== null && item !== ''
|
||
);
|
||
const deptId = path.length ? path[path.length - 1] : '';
|
||
merged.deptName = deptId
|
||
? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || ''
|
||
: '';
|
||
if (!merged.deptName) {
|
||
delete merged.deptName;
|
||
}
|
||
}
|
||
const values = normalizeSearchRangeParams(merged, createTimeRangeMap);
|
||
// 状态下拉「全部」会被数字类型转成 0/-1,二次搜索会把列表滤空。
|
||
const status = values.status;
|
||
if (status === 0 || status === '0' || status === -1 || status === '-1') {
|
||
delete values.status;
|
||
}
|
||
if (!values.expireStatus) {
|
||
values.expireStatus = this.query.expireStatus || 'all';
|
||
}
|
||
if (values.expireStatus === 'all') {
|
||
delete values.expireStatus;
|
||
}
|
||
return values;
|
||
},
|
||
handleEvaluateModeChange(mode) {
|
||
this.currentScore.evaluateMode = mode === 'offline' ? 'offline' : 'online';
|
||
if (this.isOfflineScore) {
|
||
this.expandedScoreCategoryKeys = [];
|
||
}
|
||
},
|
||
handleOfflineSelfScoreInput(value) {
|
||
const normalized = String(value || '')
|
||
.replace(/[^\d.-]/g, '')
|
||
.replace(/(?!^)-/g, '')
|
||
.replace(/^\./, '')
|
||
.replace(/(\..*)\./g, '$1')
|
||
.replace(/^(-?\d+)(\.\d{0,2})?.*$/, '$1$2');
|
||
this.currentScore.selfScore = normalized;
|
||
},
|
||
recalculateOfflineScore() {
|
||
if (!this.isOfflineScore) return;
|
||
const selfScore = Number(this.currentScore.selfScore);
|
||
if (!Number.isFinite(selfScore)) return;
|
||
this.currentScore.selfScore = Number(selfScore.toFixed(2));
|
||
const hasReviewScore = (this.currentScore.details || []).some(
|
||
item => !this.isBlankScore(item.reviewScore)
|
||
);
|
||
if (!hasReviewScore) {
|
||
this.currentScore.finalScore = this.currentScore.selfScore;
|
||
}
|
||
const fullMark = this.getScoreFullMark(this.currentScore.details || []);
|
||
const scoreRate =
|
||
fullMark > 0
|
||
? Math.max(0, Math.min(100, (Number(this.currentScore.finalScore) / fullMark) * 100))
|
||
: 0;
|
||
this.currentScore.scoreRate = Number(scoreRate.toFixed(2));
|
||
const standard = this.matchCreditStandard(this.currentScore, scoreRate);
|
||
const hasStandards = (this.currentScore.standards || []).some(item => item && item.creditLevel);
|
||
if (hasStandards) {
|
||
this.currentScore.creditLevel = standard?.creditLevel || '';
|
||
this.currentScore.maxCreditLimit = standard
|
||
? this.calculateCreditLimitByStandard(standard, scoreRate)
|
||
: '';
|
||
}
|
||
},
|
||
downloadScoreTemplate() {
|
||
if (!this.currentScore.quantificationId) {
|
||
this.$message.warning('请先选择评分量化表');
|
||
return;
|
||
}
|
||
NProgress.start();
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '模板下载中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
downloadScoreExcelTemplate(this.currentScore.quantificationId)
|
||
.then(res => {
|
||
downloadXls(
|
||
res.data,
|
||
`客商评分明细模板${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||
);
|
||
})
|
||
.finally(() => {
|
||
loading.close();
|
||
NProgress.done();
|
||
});
|
||
},
|
||
async uploadScoreExcel(option) {
|
||
const file = option?.file;
|
||
if (!file) return;
|
||
if (!this.currentScore.quantificationId) {
|
||
this.$message.warning('请先选择评分量化表');
|
||
return;
|
||
}
|
||
const fileName = String(file.name || '').toLowerCase();
|
||
if (!/\.(xls|xlsx)$/.test(fileName)) {
|
||
this.$message.error('请上传 .xls,.xlsx 标准格式文件');
|
||
return;
|
||
}
|
||
this.scoreImporting = true;
|
||
NProgress.start();
|
||
const loading = ElLoading.service({
|
||
lock: true,
|
||
text: '导入中',
|
||
background: 'rgba(255, 255, 255, 0.7)',
|
||
});
|
||
try {
|
||
const res = await importScoreExcel(this.currentScore.quantificationId, file);
|
||
if (isExcelResponse(res)) {
|
||
downloadXls(
|
||
res.data,
|
||
`客商评分导入失败明细${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
|
||
);
|
||
this.$message.warning('部分数据导入失败,已下载失败明细');
|
||
return;
|
||
}
|
||
const result = isBusinessJson(res?.data) ? res.data : await parseBlobJson(res?.data);
|
||
if (Number(result?.code) !== 200) {
|
||
this.$message.error(result?.msg || '导入失败');
|
||
return;
|
||
}
|
||
const imported = result?.data || {};
|
||
const keepFields = {
|
||
evaluateMode: 'offline',
|
||
scoreDate: this.currentScore.scoreDate,
|
||
nextEvaluateDate: this.currentScore.nextEvaluateDate,
|
||
applyCreditLimit: this.currentScore.applyCreditLimit,
|
||
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
||
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
||
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
||
remark: this.currentScore.remark,
|
||
attachments: this.scoreAttachmentFiles,
|
||
quantificationId: this.currentScore.quantificationId,
|
||
};
|
||
this.currentScore = this.normalizeScore({
|
||
...imported,
|
||
...keepFields,
|
||
standards: imported.standards || this.currentScore.standards || [],
|
||
details: (imported.details || []).map(detail => ({
|
||
...detail,
|
||
scoreInput: this.normalizeScoreInputValue(detail.scoreInput),
|
||
})),
|
||
});
|
||
this.calculateScore(this.currentScore);
|
||
this.$message.success('评分表解析成功,请在证明材料中上传评分表原件');
|
||
} catch (error) {
|
||
const message = await resolveImportErrorMessage(error);
|
||
this.$message.error(message);
|
||
} finally {
|
||
this.scoreImporting = false;
|
||
loading.close();
|
||
NProgress.done();
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.customer-archive-page {
|
||
:deep(.el-table th .cell),
|
||
:deep(.el-table td .cell) {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
// 表头含问号图标,避免被压缩成省略号
|
||
:deep(.el-table th .cell:has(.fund-use-risk-header)) {
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
}
|
||
}
|
||
|
||
.archive-tag--deep-blue {
|
||
--el-tag-bg-color: #409eff;
|
||
--el-tag-border-color: #409eff;
|
||
--el-tag-text-color: #ffffff;
|
||
}
|
||
|
||
.archive-tag--light-blue {
|
||
--el-tag-bg-color: #eef8ff;
|
||
--el-tag-border-color: #b8e1ff;
|
||
--el-tag-text-color: #409eff;
|
||
}
|
||
|
||
.business-license-expiring {
|
||
color: var(--el-color-danger);
|
||
}
|
||
|
||
.customer-archive-page__expiry-tabs {
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.score-offline-tip {
|
||
margin-left: 12px;
|
||
color: #909399;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.score-offline-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
// 风险客商悬浮提示:popper 挂在 body 下,需用 :global 约束宽度
|
||
:global(.customer-risk-popper) {
|
||
max-width: 360px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
// 客商名称框下方的高风险/黑名单提示(对齐表单错误文案形态)
|
||
.archive-name-risk {
|
||
color: var(--el-color-danger);
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
cursor: default;
|
||
}
|
||
|
||
.fund-use-risk-none {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.fund-use-risk-header {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.fund-use-risk-help {
|
||
color: #a8abb2;
|
||
cursor: help;
|
||
font-size: 14px;
|
||
flex: none;
|
||
}
|
||
|
||
.archive-form {
|
||
:deep(.el-form-item__label) {
|
||
flex: 0 0 calc(6em + 24px) !important;
|
||
width: calc(6em + 24px) !important;
|
||
height: auto;
|
||
white-space: normal !important;
|
||
word-break: break-all;
|
||
overflow-wrap: anywhere;
|
||
line-height: 16px;
|
||
}
|
||
|
||
// 锁定表单项 label 与控件垂直居中对齐(避免长 label 折行时视觉错位)
|
||
:deep(.el-form-item) {
|
||
align-items: center;
|
||
}
|
||
|
||
// 必填星号 * 与首行文字垂直居中
|
||
// Element Plus 的 .el-form-item__label 默认 inline-flex + flex-start,
|
||
// 多行 label 时 :before 与文字行盒高度不一致导致错位。
|
||
// 通过 padding 把内容下沉到 label 中线,让 * 视觉中心落在第一行文字中心。
|
||
:deep(.el-form-item.is-required .el-form-item__label) {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
:deep(.el-form-item.is-required .el-form-item__label::before) {
|
||
display: inline-block;
|
||
height: 18px;
|
||
line-height: 18px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
:deep(.el-form-item__content > .el-input),
|
||
:deep(.el-form-item__content > .el-select),
|
||
:deep(.el-form-item__content > .el-cascader),
|
||
:deep(.el-form-item__content > .el-input-number),
|
||
:deep(.el-form-item__content > .el-date-editor) {
|
||
width: 250px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
:deep(.archive-form__remark .el-textarea) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.business-term-field,
|
||
.archive-form__address {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
width: 100%;
|
||
}
|
||
|
||
.business-term-field {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
width: 100%;
|
||
|
||
:deep(.el-date-editor) {
|
||
flex: 0 0 140px;
|
||
width: 140px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.business-term-separator {
|
||
flex: 0 0 auto;
|
||
color: #606266;
|
||
}
|
||
|
||
:deep(.el-checkbox) {
|
||
flex: 0 0 auto;
|
||
margin-right: 0;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.invoice-bank-autocomplete {
|
||
width: 100%;
|
||
}
|
||
|
||
.archive-form__address {
|
||
:deep(.el-cascader) {
|
||
flex: 0 0 240px;
|
||
width: 240px;
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.el-input) {
|
||
flex: 1;
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
|
||
.section-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
justify-content: flex-end;
|
||
|
||
:deep(.vehicle-attachment-upload) {
|
||
width: auto;
|
||
flex: 0 0 auto;
|
||
}
|
||
}
|
||
|
||
.qualification-missing-tip {
|
||
margin-left: 8px;
|
||
color: var(--el-color-danger);
|
||
font-size: 13px;
|
||
font-weight: 400;
|
||
}
|
||
|
||
|
||
.qualification-upload-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-top: 12px;
|
||
margin-bottom: 6px;
|
||
|
||
:deep(.vehicle-attachment-upload) {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
min-width: 0;
|
||
|
||
:deep(.el-upload) {
|
||
flex: 0 0 auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
|
||
:deep(.el-upload__tip) {
|
||
margin-left: 30px;
|
||
color: #909399;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
text-align: left;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
}
|
||
}
|
||
|
||
.archive-detail-card {
|
||
margin-top: 12px;
|
||
}
|
||
.archive-tabs {
|
||
:deep(.el-tabs__header) {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
.tab-table-toolbar {
|
||
margin-top: 16px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.score-list-table {
|
||
width: 100%;
|
||
}
|
||
|
||
.score-detail-form {
|
||
padding: 8px 36px 0;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
:deep(.el-form-item__label) {
|
||
flex: 0 0 108px !important;
|
||
width: 108px !important;
|
||
min-width: 108px !important;
|
||
color: #70757a;
|
||
font-size: 14px;
|
||
white-space: normal !important;
|
||
word-break: break-all;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
:deep(.el-form-item__content > .el-input),
|
||
:deep(.el-form-item__content > .el-select),
|
||
:deep(.el-form-item__content > .el-date-editor) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.score-category-table {
|
||
width: calc(100% - 72px);
|
||
margin: 8px 36px 16px;
|
||
|
||
: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) {
|
||
color: #303133;
|
||
}
|
||
|
||
:deep(.score-item-name) {
|
||
display: block;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
: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;
|
||
}
|
||
}
|
||
|
||
.score-list-table__actions {
|
||
display: inline-flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.contact-form,
|
||
.receipt-form,
|
||
.invoice-form {
|
||
max-width: 620px;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
:deep(.el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.invoice-form {
|
||
max-width: none;
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-cascader),
|
||
:deep(.el-select),
|
||
:deep(.el-autocomplete) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.el-cascader .el-input),
|
||
:deep(.el-autocomplete .el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.invoice-contact-table {
|
||
width: 100%;
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select),
|
||
:deep(.el-cascader) {
|
||
width: 100%;
|
||
}
|
||
|
||
:deep(.el-cascader .el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.contact-form__compact-field {
|
||
:deep(.el-input),
|
||
:deep(.el-cascader) {
|
||
width: 60%;
|
||
}
|
||
}
|
||
|
||
.contact-dialog__footer-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
}
|
||
|
||
.receipt-form {
|
||
:deep(.el-input),
|
||
:deep(.el-select) {
|
||
width: 60%;
|
||
}
|
||
|
||
.receipt-bank-label {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.receipt-bank-help {
|
||
color: #a8abb2;
|
||
cursor: help;
|
||
font-size: 14px;
|
||
flex: none;
|
||
}
|
||
}
|
||
|
||
.receipt-dialog__footer-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
}
|
||
|
||
.invoice-dialog__footer-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
}
|
||
|
||
.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;
|
||
|
||
.score-detail-dialog__footer-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.score-detail-dialog__total {
|
||
display: flex;
|
||
flex: 1;
|
||
align-items: center;
|
||
gap: 16px;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
text-align: left;
|
||
|
||
span:first-child {
|
||
color: #409eff;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.score-detail-dialog__footer-actions .el-button {
|
||
min-width: 160px;
|
||
height: 42px;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
:global(.score-detail-dialog .score-detail-dialog__footer-actions) {
|
||
display: flex;
|
||
align-items: center;
|
||
width: 100%;
|
||
gap: 12px;
|
||
}
|
||
|
||
:global(.score-detail-dialog .score-detail-dialog__total) {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-right: auto;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
:global(.score-detail-dialog .score-detail-dialog__total span:first-child) {
|
||
color: #409eff;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.score-material-card {
|
||
:deep(.vehicle-attachment-upload) {
|
||
width: auto;
|
||
}
|
||
|
||
:deep(.el-upload-list) {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.score-material-table {
|
||
width: 100%;
|
||
}
|
||
|
||
.change-record-content-cell {
|
||
display: block;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.change-record-content-tooltip {
|
||
max-width: 900px;
|
||
max-height: 320px;
|
||
overflow: auto;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.customer-archive-page.is-public-view {
|
||
:deep(.archive-page-form .archive-form__footer) {
|
||
left: 0;
|
||
}
|
||
}
|
||
|
||
.archive-page-form {
|
||
min-height: 100%;
|
||
margin-bottom: 60px;
|
||
}
|
||
.archive-form__footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
.archive-page-form & {
|
||
position: fixed;
|
||
right: 0;
|
||
left: 230px;
|
||
bottom: 0;
|
||
z-index: 10;
|
||
margin: 0;
|
||
padding: 12px 24px;
|
||
border-top: 1px solid #eff1f7;
|
||
background: #fff;
|
||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
}
|
||
|
||
:global(.avue--collapse .archive-page-form .archive-form__footer) {
|
||
left: 60px;
|
||
}
|
||
|
||
:global(.avue-layout--horizontal .archive-page-form .archive-form__footer) {
|
||
left: 0;
|
||
}
|
||
|
||
: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;
|
||
}
|
||
.el-col {
|
||
margin-bottom: 0;
|
||
}
|
||
.el-form-item {
|
||
//margin-bottom: 24px !important;
|
||
}
|
||
</style>
|