5401 lines
192 KiB
Vue
5401 lines
192 KiB
Vue
<template>
|
||
<basic-container class="customer-archive-page">
|
||
<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 #customerCode="{ row }">
|
||
<el-button type="primary" link @click="viewArchive(row)">
|
||
{{ row.customerCode }}
|
||
</el-button>
|
||
</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': isBusinessLicenseExpiringSoon(row) }">
|
||
{{ formatBusinessLicenseValidPeriod(row) }}
|
||
</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 #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') &&
|
||
['draft', 'reviewing'].includes(row.approvalStatus)
|
||
"
|
||
@click="handleWithdrawApproval(row)"
|
||
>
|
||
撤回
|
||
</el-link>
|
||
<el-link
|
||
type="success"
|
||
v-if="hasPermission('customer_archive_approve') && row.approvalStatus === 'reviewing'"
|
||
@click="handleApprove(row)"
|
||
>
|
||
审核通过
|
||
</el-link>
|
||
<el-link
|
||
type="warning"
|
||
v-if="hasPermission('customer_archive_reject') && row.approvalStatus === 'reviewing'"
|
||
@click="handleReject(row)"
|
||
>
|
||
审核不通过
|
||
</el-link>
|
||
<el-link
|
||
type="primary"
|
||
v-if="hasPermission('customer_archive_status')"
|
||
@click="handleStatus(row)"
|
||
>
|
||
{{ row.status === 1 ? '停用' : '启用' }}
|
||
</el-link>
|
||
<el-link
|
||
type="danger"
|
||
v-if="hasPermission('customer_archive_delete') && row.approvalStatus === 'draft'"
|
||
@click="rowDel(row)"
|
||
>
|
||
删除
|
||
</el-link>
|
||
</template>
|
||
</avue-crud>
|
||
<empty-pagination
|
||
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="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
|
||
@input="
|
||
archiveForm.unifiedCreditCode = String(
|
||
archiveForm.unifiedCreditCode || ''
|
||
).toUpperCase()
|
||
"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="客商名称" prop="fullName">
|
||
<el-input
|
||
v-model="archiveForm.fullName"
|
||
placeholder="请输入"
|
||
maxlength="100"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="客商分类" prop="customerType">
|
||
<el-select
|
||
v-model="archiveForm.customerType"
|
||
multiple
|
||
placeholder="请选择"
|
||
filterable
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="item in customerTypeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="企业法人" prop="legalPerson">
|
||
<el-input v-model="archiveForm.legalPerson" maxlength="10" show-word-limit />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="经营范围" prop="businessScope" required>
|
||
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
|
||
<el-option
|
||
v-for="item in businessScopeOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item
|
||
label="营业期限"
|
||
prop="businessEndDate"
|
||
:required="archiveForm.businessTermType === '固定期限'"
|
||
:rules="businessEndDateRules"
|
||
>
|
||
<div class="business-term-field">
|
||
<el-date-picker
|
||
v-model="archiveForm.businessEndDate"
|
||
type="date"
|
||
value-format="YYYY-MM-DD"
|
||
:disabled="archiveForm.businessTermType === '长期'"
|
||
@change="handleBusinessEndDateChange"
|
||
/>
|
||
<el-checkbox
|
||
:model-value="archiveForm.businessTermType === '长期'"
|
||
@change="handleNoFixedTermChange"
|
||
>
|
||
无固定期限
|
||
</el-checkbox>
|
||
</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="助记码" prop="mnemonicCode">
|
||
<el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="客户简称" prop="shortName">
|
||
<el-input
|
||
v-model="archiveForm.shortName"
|
||
placeholder="请输入"
|
||
maxlength="20"
|
||
show-word-limit
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="所在地区" prop="registeredDetailAddress" required>
|
||
<div class="archive-form__address">
|
||
<el-cascader
|
||
ref="registeredRegionCascader"
|
||
v-model="archiveForm.registeredRegionPath"
|
||
:options="regionOptions"
|
||
:props="regionCascaderProps"
|
||
:placeholder="archiveForm.registeredRegionName || '请选择省市区'"
|
||
clearable
|
||
filterable
|
||
@change="handleRegisteredRegionChange"
|
||
/>
|
||
</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-row>
|
||
</section-card>
|
||
|
||
<section-card title="联系信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="客商负责人" prop="principal">
|
||
<el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="联系电话" prop="contactPhone">
|
||
<el-input v-model="archiveForm.contactPhone" maxlength="20" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="所属组织" prop="deptName">
|
||
<el-cascader
|
||
v-model="deptCascaderValue"
|
||
:options="deptTree"
|
||
:props="deptCascaderProps"
|
||
clearable
|
||
filterable
|
||
placeholder="请选择 所属组织"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6"></el-col>
|
||
</el-row>
|
||
</section-card>
|
||
|
||
<section-card title="财务/信用信息">
|
||
<el-row :gutter="18">
|
||
<el-col :span="6">
|
||
<el-form-item label="注册资金(万元)" prop="registeredCapital">
|
||
<el-input
|
||
v-model="archiveForm.registeredCapital"
|
||
maxlength="18"
|
||
@input="value => handleDecimalInput('registeredCapital', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="开票税点(%)" prop="invoiceTaxRate">
|
||
<el-input
|
||
v-model="archiveForm.invoiceTaxRate"
|
||
maxlength="5"
|
||
@input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="客户等级" prop="customerLevel">
|
||
<el-input v-model="archiveForm.customerLevel" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="最大资金使用额度(万元)" prop="maxCreditLimit">
|
||
<el-input
|
||
v-model="archiveForm.maxCreditLimit"
|
||
maxlength="18"
|
||
disabled
|
||
@input="value => handleDecimalInput('maxCreditLimit', value)"
|
||
/>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="申请总资金使用额度(万元)" prop="applyCreditLimit">
|
||
<el-input v-model="archiveForm.applyCreditLimit" maxlength="18" disabled />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-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 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="操作" width="135" fixed="right" align="center">
|
||
<template #default="{ row }">
|
||
<el-link type="primary" @click="openScore(row.__raw, row.__rawIndex)">
|
||
详情
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<empty-pagination
|
||
:page="scorePaginationPage"
|
||
always-show
|
||
@size-change="handleScoreSizeChange"
|
||
@current-change="handleScoreCurrentChange"
|
||
/>
|
||
</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="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">
|
||
<template #default="{ row }">
|
||
<div
|
||
class="qualification-file-name-cell"
|
||
:title="row.originalName || row.name || '-'"
|
||
>
|
||
<span
|
||
class="qualification-file-name"
|
||
:class="{ 'is-disabled': !row.url }"
|
||
@click="row.url && previewQualificationFile(row)"
|
||
>
|
||
{{ row.originalName || row.name || '-' }}
|
||
</span>
|
||
</div>
|
||
</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>
|
||
|
||
<el-dialog
|
||
v-model="changeRecordDetailVisible"
|
||
title="变更记录详情"
|
||
append-to-body
|
||
destroy-on-close
|
||
width="1100px"
|
||
top="10px"
|
||
class="change-record-detail-dialog"
|
||
>
|
||
<div v-if="changeRecordDetail" class="change-record-detail-meta">
|
||
<span>变更日期:{{ changeRecordDetail.changeTime || '-' }}</span>
|
||
<span>变更账号:{{ changeRecordDetail.changeUserName || '-' }}</span>
|
||
</div>
|
||
<el-table :data="changeRecordDetailRows" border :show-overflow-tooltip="false">
|
||
<el-table-column prop="field" label="变更字段" min-width="180" />
|
||
<el-table-column
|
||
prop="before"
|
||
label="变更前"
|
||
min-width="360"
|
||
class-name="change-record-detail-value"
|
||
/>
|
||
<el-table-column
|
||
prop="after"
|
||
label="变更后"
|
||
min-width="500"
|
||
class-name="change-record-detail-value"
|
||
/>
|
||
</el-table>
|
||
<el-empty
|
||
v-if="!changeRecordDetailRows.length"
|
||
description="暂无变更内容"
|
||
:image-size="60"
|
||
/>
|
||
<template #footer>
|
||
<el-button type="primary" @click="changeRecordDetailVisible = false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<div class="archive-form__footer">
|
||
<!-- 次要:独立页返回 / 只读关闭 / 弹窗取消 -->
|
||
<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 label="开户行" prop="bankName">
|
||
<el-input v-model="receiptForm.bankName" maxlength="100" />
|
||
</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-input v-model="invoiceForm.bankName" maxlength="100" />
|
||
</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="200">
|
||
<template #default="{ row }">
|
||
<el-input
|
||
v-if="!isInternalCustomer"
|
||
:model-value="row.deptNames"
|
||
disabled
|
||
/>
|
||
<el-select
|
||
v-else
|
||
v-model="row.deptIdList"
|
||
multiple
|
||
collapse-tags
|
||
collapse-tags-tooltip
|
||
filterable
|
||
clearable
|
||
:disabled="readonly"
|
||
placeholder="请选择所属部门"
|
||
@change="() => handleInvoiceContactDeptChange(row)"
|
||
>
|
||
<el-option
|
||
v-for="item in invoiceDeptOptions"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</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="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-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.finalScore)" disabled />
|
||
</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 :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="临时申请额度">
|
||
<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 :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 :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="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 :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="currentScore.quantificationId"
|
||
ref="scoreCategoryTable"
|
||
:data="scoreCategoryRows"
|
||
border
|
||
:show-header="false"
|
||
row-key="categoryCode"
|
||
:expand-row-keys="expandedScoreCategoryKeys"
|
||
class="score-category-table"
|
||
@row-click="toggleScoreCategory"
|
||
@expand-change="handleScoreCategoryExpandChange"
|
||
>
|
||
<el-table-column type="expand" width="1" class-name="score-category-table__expand-column">
|
||
<template #default="{ row }">
|
||
<el-table
|
||
:data="getScoreCategoryDetails(row.categoryCode)"
|
||
border
|
||
class="score-detail-table score-detail-table--inline"
|
||
max-height="520"
|
||
>
|
||
<el-table-column label="评分项目" prop="itemName" width="150" align="center" />
|
||
<el-table-column label="评分标准" min-width="430">
|
||
<template #default="{ row: detail }">
|
||
<div class="score-standard-cell">
|
||
<template v-if="isScoreTypeDetail(detail)">
|
||
<el-input
|
||
:model-value="detail.scoreInput"
|
||
inputmode="decimal"
|
||
:disabled="readonly"
|
||
@input="value => handleScoreValueInput(detail, value)"
|
||
>
|
||
<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 label="复评得分" width="150" align="center">
|
||
<template #default="{ row: detail }">
|
||
<el-input
|
||
v-model="detail.reviewScore"
|
||
maxlength="10"
|
||
inputmode="decimal"
|
||
:disabled="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>复评:{{ 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">
|
||
<template #default="{ row }">
|
||
{{ row.originalName || row.name || '-' }}
|
||
</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>复评:{{ formatScoreValue(currentScore.reviewScore) }}分</span>
|
||
</div>
|
||
<el-button @click="scoreBox = false">取消</el-button>
|
||
<el-button type="primary" plain v-if="!readonly" @click="saveScoreDetail">保存</el-button>
|
||
<el-button
|
||
v-if="!readonly && 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,
|
||
getChangeRecordList,
|
||
submit,
|
||
submitApproval,
|
||
withdrawApproval,
|
||
approve,
|
||
reject,
|
||
changeStatus,
|
||
remove,
|
||
getScoreTemplate,
|
||
recognizeBusinessLicenseOcr,
|
||
} from '@/api/vehicle/customer-archive';
|
||
import {
|
||
getList as getCreditScoreQuantificationList,
|
||
getDetail as getCreditScoreQuantificationDetail,
|
||
} from '@/api/vehicle/credit-score-quantification';
|
||
import { getDictionary } from '@/api/system/dictbiz';
|
||
import { getDeptTree } from '@/api/system/dept';
|
||
import { getLazyTree } from '@/api/base/region';
|
||
import { exportBlob } from '@/api/common';
|
||
import { downloadFileByUrl, downloadXls } from '@/utils/util';
|
||
import { getToken } from '@/utils/auth';
|
||
import { getUploadHeaders } from '@/utils/upload';
|
||
import { normalizeSearchRangeParams } from '@/utils/search-range';
|
||
import { ArrowRight } from '@element-plus/icons-vue';
|
||
import { 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';
|
||
|
||
const createTimeRangeMap = {
|
||
createTimeRange: ['createTimeStart', 'createTimeEnd'],
|
||
};
|
||
|
||
const qualificationViewerPlugins = [
|
||
imagePlugin(),
|
||
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
|
||
textPlugin(),
|
||
fallbackPlugin(),
|
||
];
|
||
|
||
export default {
|
||
components: {
|
||
SectionCard,
|
||
ArrowRight,
|
||
ElImageViewer,
|
||
OpenFileViewer,
|
||
PdfPreview,
|
||
},
|
||
data() {
|
||
const nonNegativeLabelMap = {
|
||
invoiceTaxRate: '开票税点',
|
||
registeredCapital: '注册资金',
|
||
maxCreditLimit: '最大资金使用额度',
|
||
applyCreditLimit: '申请总资金使用额度',
|
||
};
|
||
const validateNonNegative = (rule, value, callback) => {
|
||
if (value === undefined || value === null || value === '') {
|
||
callback();
|
||
} else if (Number(value) < 0) {
|
||
callback(new Error(`${nonNegativeLabelMap[rule.field] || '数值'}不能小于0`));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
const validateInvoiceTaxRate = (rule, value, callback) => {
|
||
if (value === undefined || value === null || value === '') {
|
||
callback();
|
||
} else if (Number(value) < 0 || Number(value) > 13) {
|
||
callback(new Error('开票税点必须在0到13之间'));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
const validateUnifiedCreditCode = (rule, value, callback) => {
|
||
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) {
|
||
callback();
|
||
return;
|
||
}
|
||
if (code.length !== 18 || ![...code].every(char => charset.includes(char))) {
|
||
callback(new Error('统一社会信用代码必须为18位有效字符'));
|
||
return;
|
||
}
|
||
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) {
|
||
callback(new Error('统一社会信用代码校验码错误'));
|
||
return;
|
||
}
|
||
callback();
|
||
};
|
||
const validateRegisteredAddress = (rule, value, callback) => {
|
||
const regionName = (this.archiveForm.registeredRegionName || '').trim();
|
||
const detailAddress = String(value || '').trim();
|
||
if (!regionName) {
|
||
callback(new Error('请选择省市区'));
|
||
} else if (!detailAddress) {
|
||
callback(new Error('请输入详细地址'));
|
||
} else if (detailAddress.length > 255) {
|
||
callback(new Error('详细地址最多255个字符'));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
return {
|
||
form: {},
|
||
query: {},
|
||
deptSearchInitialized: false,
|
||
loading: true,
|
||
data: [],
|
||
page: {
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
currentPage: 1,
|
||
total: 0,
|
||
},
|
||
selectionList: [],
|
||
archiveBox: false,
|
||
scoreBox: false,
|
||
contactBox: false,
|
||
receiptBox: false,
|
||
invoiceBox: false,
|
||
readonly: false,
|
||
originalAccessType: '',
|
||
activeTab: 'scores',
|
||
archiveForm: this.emptyArchive(),
|
||
currentScore: { details: [] },
|
||
scoreRecordIndex: -1,
|
||
scoreAttachmentFiles: [],
|
||
expandedScoreCategoryKeys: [],
|
||
scoreQuantificationLoading: false,
|
||
scoreQuantificationOptions: [],
|
||
scorePage: {
|
||
currentPage: 1,
|
||
pageSize: 3,
|
||
pageSizes: [3, 10, 20, 50],
|
||
},
|
||
contactPage: {
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
},
|
||
receiptPage: {
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
},
|
||
invoicePage: {
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
pageSizes: [10, 20, 50, 100],
|
||
},
|
||
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 },
|
||
],
|
||
businessScopeOptions: [],
|
||
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' },
|
||
},
|
||
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' }],
|
||
contactPhone: [
|
||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||
{ pattern: /^1\d{10}$/, message: '联系电话必须为11位手机号', trigger: 'blur' },
|
||
],
|
||
deptName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||
invoiceTaxRate: [{ validator: validateInvoiceTaxRate, trigger: 'blur' }],
|
||
registeredCapital: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
maxCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
applyCreditLimit: [{ validator: validateNonNegative, trigger: 'blur' }],
|
||
registeredDetailAddress: [
|
||
{ validator: validateRegisteredAddress, trigger: ['change', 'blur'] },
|
||
],
|
||
businessScope: [
|
||
{
|
||
required: true,
|
||
type: 'array',
|
||
min: 1,
|
||
message: '请选择经营范围',
|
||
trigger: 'change',
|
||
},
|
||
],
|
||
remark: [{ max: 500, message: '备注最多500个字符', trigger: 'blur' }],
|
||
},
|
||
contactRules: {
|
||
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
|
||
contactPhone: [
|
||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||
{ pattern: /^1\d{10}$/, message: '手机号必须为11位手机号', trigger: 'blur' },
|
||
],
|
||
email: [{ type: 'email', message: '请输入正确的邮箱', trigger: 'blur' }],
|
||
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' }],
|
||
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: 280,
|
||
column: [
|
||
{
|
||
label: '客商编号',
|
||
prop: 'customerCode',
|
||
slot: true,
|
||
search: true,
|
||
searchOrder: 10,
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 140,
|
||
},
|
||
{
|
||
label: '客商简称',
|
||
prop: 'shortName',
|
||
search: true,
|
||
searchOrder: 2,
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 150,
|
||
},
|
||
{
|
||
label: '客商名称',
|
||
prop: 'fullName',
|
||
search: true,
|
||
searchOrder: 9,
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 220,
|
||
overHidden: true,
|
||
},
|
||
{
|
||
label: '客商分类',
|
||
prop: 'customerType',
|
||
search: true,
|
||
searchType: 'select',
|
||
searchOrder: 7,
|
||
searchValue: '',
|
||
searchPlaceholder: '请输入',
|
||
minWidth: 140,
|
||
dicData: [{ label: '全部', value: '' }],
|
||
},
|
||
{
|
||
label: '客商类型',
|
||
prop: 'customerKind',
|
||
type: 'select',
|
||
minWidth: 120,
|
||
dicData: [
|
||
{ label: '外部客商', value: 'external' },
|
||
{ label: '内部组织', value: 'internal' },
|
||
],
|
||
},
|
||
{
|
||
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: '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: 'status',
|
||
slot: true,
|
||
type: 'select',
|
||
search: true,
|
||
searchOrder: 6,
|
||
searchValue: '',
|
||
dataType: 'number',
|
||
minWidth: 100,
|
||
dicData: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '启用', value: 1 },
|
||
{ label: '停用', value: 2 },
|
||
],
|
||
clearable: true,
|
||
},
|
||
],
|
||
},
|
||
};
|
||
},
|
||
created() {
|
||
this.initDeptTree();
|
||
this.initRegionOptions();
|
||
this.initBusinessDictionaries();
|
||
this.initScoreQuantificationOptions();
|
||
if (this.isArchivePage) {
|
||
const readonly = this.$route.query.view === '1' || this.$route.query.view === 'true';
|
||
this.openArchive(this.$route.query.id ? { id: this.$route.query.id } : null, readonly);
|
||
}
|
||
},
|
||
computed: {
|
||
...mapGetters(['permission', 'userInfo']),
|
||
isAdmin() {
|
||
const authority = this.userInfo.authority || '';
|
||
return authority.includes('admin');
|
||
},
|
||
permissionList() {
|
||
return {
|
||
addBtn: false,
|
||
};
|
||
},
|
||
isArchivePage() {
|
||
return this.$route.path === '/vehicle/customer-archive/form';
|
||
},
|
||
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',
|
||
};
|
||
},
|
||
// 所属组织级联值: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.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';
|
||
},
|
||
invoiceDeptOptions() {
|
||
return this.flattenDept(this.deptTree).map(item => ({
|
||
label: item.rawLabel,
|
||
value: String(item.value),
|
||
}));
|
||
},
|
||
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 || [];
|
||
},
|
||
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);
|
||
const selfScore = details.reduce((sum, item) => sum + Number(item.selfScore || 0), 0);
|
||
const reviewScore = details.reduce((sum, item) => sum + Number(item.reviewScore || 0), 0);
|
||
return {
|
||
...category,
|
||
index: index + 1,
|
||
selfScore: Number(selfScore.toFixed(2)),
|
||
reviewScore: Number(reviewScore.toFixed(2)),
|
||
};
|
||
});
|
||
return rows;
|
||
},
|
||
},
|
||
watch: {
|
||
'archiveForm.registeredDetailAddress'() {
|
||
this.$nextTick(() => this.checkOverflow('registeredDetailInput', 'registeredDetailOverflow'));
|
||
},
|
||
'invoiceForm.registeredDetailAddress'() {
|
||
this.$nextTick(() => this.checkOverflow('invoiceDetailInput', 'invoiceDetailOverflow'));
|
||
},
|
||
},
|
||
methods: {
|
||
hasPermission(code) {
|
||
return this.isAdmin || this.validData(this.permission[code], false);
|
||
},
|
||
// 检测输入框文本是否溢出,仅溢出时才允许 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: '固定期限',
|
||
networkFreightPlatform: null,
|
||
guangxiTop100: null,
|
||
registeredRegionPath: [],
|
||
registeredRegionName: '',
|
||
registeredDetailAddress: '',
|
||
registeredAddress: '',
|
||
contacts: [],
|
||
receiptAccounts: [],
|
||
invoices: [],
|
||
scores: [],
|
||
changeRecords: [],
|
||
};
|
||
},
|
||
getDetailList(type) {
|
||
const listMap = {
|
||
contact: this.archiveForm.contacts,
|
||
receipt: this.archiveForm.receiptAccounts,
|
||
invoice: this.archiveForm.invoices,
|
||
};
|
||
return listMap[type] || [];
|
||
},
|
||
getDetailPage(type) {
|
||
const pageMap = {
|
||
contact: this.contactPage,
|
||
receipt: this.receiptPage,
|
||
invoice: this.invoicePage,
|
||
};
|
||
return pageMap[type];
|
||
},
|
||
getDetailPageCount(type) {
|
||
const page = this.getDetailPage(type);
|
||
return Math.max(Math.ceil(this.getDetailList(type).length / page.pageSize), 1);
|
||
},
|
||
buildDetailPaginationPage(type) {
|
||
return {
|
||
...this.getDetailPage(type),
|
||
total: this.getDetailList(type).length,
|
||
};
|
||
},
|
||
buildDetailTableData(type) {
|
||
const page = this.getDetailPage(type);
|
||
const start = (page.currentPage - 1) * page.pageSize;
|
||
return this.getDetailList(type)
|
||
.slice(start, start + page.pageSize)
|
||
.map((item, index) => ({
|
||
...item,
|
||
__raw: item,
|
||
__rawIndex: start + index,
|
||
__index: start + index + 1,
|
||
}));
|
||
},
|
||
handleDetailCurrentChange(type, currentPage) {
|
||
const page = this.getDetailPage(type);
|
||
const nextPage = Number(currentPage) || 1;
|
||
page.currentPage = Math.min(Math.max(nextPage, 1), this.getDetailPageCount(type));
|
||
},
|
||
handleDetailSizeChange(type, pageSize) {
|
||
const page = this.getDetailPage(type);
|
||
page.pageSize = Number(pageSize) || 10;
|
||
this.handleDetailCurrentChange(type, 1);
|
||
},
|
||
resetDetailPagination() {
|
||
this.handleScoreCurrentChange(1);
|
||
this.handleDetailCurrentChange('contact', 1);
|
||
this.handleDetailCurrentChange('receipt', 1);
|
||
this.handleDetailCurrentChange('invoice', 1);
|
||
},
|
||
loadChangeRecords() {
|
||
if (!this.archiveForm.id) return;
|
||
const page = this.changeRecordPage;
|
||
getChangeRecordList(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: '客商编号',
|
||
shortName: '客商简称',
|
||
fullName: '客商名称',
|
||
customerType: '客商分类',
|
||
customerKind: '客商类型',
|
||
customerNature: '客户性质',
|
||
guangxiTop100: '是否广西百强',
|
||
unifiedCreditCode: '统一信用代码',
|
||
deptName: '所属组织',
|
||
approvedTime: '审核通过时间',
|
||
status: '状态',
|
||
businessTermType: '营业期限类型',
|
||
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: '',
|
||
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('registeredDetailAddress');
|
||
return;
|
||
}
|
||
this.$nextTick(() => {
|
||
this.archiveForm.registeredRegionName = this.getRegisteredRegionLabels(value).join('');
|
||
this.$refs.archiveForm?.validateField('registeredDetailAddress');
|
||
});
|
||
},
|
||
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() {
|
||
this.loadDictOptions('nature_of_client', 'customerNatureOptions', [], options => {
|
||
const customerNatureColumn = this.findColumn(this.option.column, 'customerNature');
|
||
customerNatureColumn.dicData = [{ label: '全部', value: '' }, ...options];
|
||
});
|
||
this.loadDictOptions('customer_type', 'customerTypeOptions', ['客户', '承运商'], options => {
|
||
const customerTypeColumn = this.findColumn(this.option.column, 'customerType');
|
||
customerTypeColumn.dicData = [{ label: '全部', value: '' }, ...options];
|
||
});
|
||
this.loadDictOptions('scope_of_business', 'businessScopeOptions');
|
||
this.loadDictOptions('customer_attachment_types', 'qualificationTypeOptions', [], () => {
|
||
this.sortQualificationFiles();
|
||
});
|
||
},
|
||
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;
|
||
},
|
||
setDefaultTopLevelDept() {
|
||
const deptIds = Array.isArray(this.archiveForm.deptId)
|
||
? this.archiveForm.deptId
|
||
: this.archiveForm.deptId
|
||
? [this.archiveForm.deptId]
|
||
: [];
|
||
const firstTopLevelDept = this.deptTree[0];
|
||
if (!this.isArchivePage || this.$route.query.id || deptIds.length || !firstTopLevelDept) {
|
||
return;
|
||
}
|
||
this.onDeptChange([firstTopLevelDept.value]);
|
||
},
|
||
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 '无固定期限';
|
||
return row.businessEndDate || '';
|
||
},
|
||
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 = {}) {
|
||
if (row.businessTermType === '长期' || !row.businessEndDate) return false;
|
||
const endDate = this.$dayjs(row.businessEndDate);
|
||
if (!endDate.isValid()) return false;
|
||
const today = this.$dayjs().startOf('day');
|
||
const expireSoonDate = today.add(30, 'day');
|
||
return !endDate.isAfter(expireSoonDate, 'day');
|
||
},
|
||
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();
|
||
},
|
||
saveReceipt() {
|
||
this.$refs.receiptForm.validate(valid => {
|
||
if (!valid) return;
|
||
const receipt = this.normalizeReceipt(this.receiptForm);
|
||
if (this.receiptIndex > -1) {
|
||
this.archiveForm.receiptAccounts.splice(this.receiptIndex, 1, receipt);
|
||
} else {
|
||
this.archiveForm.receiptAccounts.push(receipt);
|
||
this.handleDetailCurrentChange('receipt', this.getDetailPageCount('receipt'));
|
||
}
|
||
this.receiptBox = false;
|
||
});
|
||
},
|
||
deleteReceipt(index) {
|
||
this.$confirm('确定删除该收款信息?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => {
|
||
this.archiveForm.receiptAccounts.splice(index, 1);
|
||
this.handleDetailCurrentChange('receipt', this.receiptPage.currentPage);
|
||
})
|
||
.catch(() => {});
|
||
},
|
||
normalizeInvoice(invoice = {}) {
|
||
const registeredDetailAddress =
|
||
invoice.registeredDetailAddress ||
|
||
(invoice.registeredRegionName ? '' : invoice.registeredAddress) ||
|
||
'';
|
||
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) {
|
||
const ids = Array.isArray(row.deptIdList) ? row.deptIdList.map(item => String(item)) : [];
|
||
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?.result || {});
|
||
this.$message.success('营业执照识别完成');
|
||
})
|
||
.catch(() => {
|
||
this.$message.warning('营业执照上传成功,自动识别失败,请手动填写工商信息');
|
||
})
|
||
.finally(() => loading.close());
|
||
},
|
||
getBusinessLicenseOcrValue(result, labels) {
|
||
const words = result?.words_result || result?.wordsResult || result || {};
|
||
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 code = this.getBusinessLicenseOcrValue(result, [
|
||
'统一社会信用代码',
|
||
'社会信用代码',
|
||
'注册号',
|
||
]);
|
||
const name = this.getBusinessLicenseOcrValue(result, ['单位名称', '企业名称', '名称']);
|
||
const legalPerson = this.getBusinessLicenseOcrValue(result, ['法定代表人', '法人']);
|
||
const address = this.getBusinessLicenseOcrValue(result, ['地址', '住所']);
|
||
const capital = this.getBusinessLicenseOcrValue(result, ['注册资本']);
|
||
const term = this.getBusinessLicenseOcrValue(result, ['营业期限', '有效期']);
|
||
const termStartDate = this.getBusinessLicenseOcrValue(result, ['有效期起始日期']);
|
||
const scope = this.getBusinessLicenseOcrValue(result, ['经营范围']);
|
||
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
|
||
if (name) this.archiveForm.fullName = name;
|
||
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];
|
||
}
|
||
this.applyBusinessTermRecognition(term);
|
||
if (this.isNoFixedBusinessTerm(term) || this.isNoFixedBusinessTerm(termStartDate)) {
|
||
this.handleNoFixedTermChange(true);
|
||
}
|
||
if (scope) {
|
||
const matchedScopes = this.businessScopeOptions
|
||
.filter(item => scope.includes(item.label) || scope.includes(item.value))
|
||
.map(item => item.value);
|
||
if (matchedScopes.length) this.archiveForm.businessScope = matchedScopes;
|
||
}
|
||
this.$nextTick(() => {
|
||
[
|
||
'unifiedCreditCode',
|
||
'fullName',
|
||
'legalPerson',
|
||
'registeredCapital',
|
||
'businessEndDate',
|
||
'businessScope',
|
||
'registeredDetailAddress',
|
||
].forEach(prop => this.$refs.archiveForm?.validateField(prop));
|
||
});
|
||
},
|
||
applyBusinessTermRecognition(term) {
|
||
if (!term) return;
|
||
if (/长期|永久/.test(term)) {
|
||
this.archiveForm.businessTermType = '长期';
|
||
this.archiveForm.businessEndDate = '';
|
||
return;
|
||
}
|
||
const match = term.match(/(\d{4}[年./-]\d{1,2}[月./-]\d{1,2})/);
|
||
if (!match) return;
|
||
const date = match[1]
|
||
.replace(/[年./]/g, '-')
|
||
.replace('月', '-')
|
||
.replace('日', '');
|
||
this.archiveForm.businessTermType = '固定期限';
|
||
this.archiveForm.businessEndDate = date;
|
||
},
|
||
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 businessScope = this.splitValue(detail.businessScope);
|
||
const registeredDetailAddress =
|
||
detail.registeredDetailAddress ||
|
||
(detail.registeredRegionName ? '' : detail.registeredAddress) ||
|
||
'';
|
||
const archive = {
|
||
...this.emptyArchive(),
|
||
...detail,
|
||
deptId: this.normalizeDeptIds(detail),
|
||
customerType: this.splitValue(detail.customerType),
|
||
customerKind: detail.customerKind || 'external',
|
||
invoiceTaxRate: this.normalizeOptionalAmount(detail.invoiceTaxRate),
|
||
registeredCapital: this.normalizeOptionalAmount(detail.registeredCapital),
|
||
maxCreditLimit: this.normalizeOptionalAmount(detail.maxCreditLimit),
|
||
applyCreditLimit: this.normalizeOptionalAmount(detail.applyCreditLimit),
|
||
businessScope,
|
||
businessTermType: detail.businessTermType || '固定期限',
|
||
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: '',
|
||
scoreDate: this.$dayjs().format('YYYY-MM-DD'),
|
||
applyCreditLimit: this.archiveForm.applyCreditLimit || '',
|
||
maxCreditLimit: '',
|
||
creditLevel: '',
|
||
standards: [],
|
||
tempApplyCreditLimit: '',
|
||
tempCreditStartDate: '',
|
||
tempCreditEndDate: '',
|
||
remark: '',
|
||
selfStatus: '未完成',
|
||
reviewStatus: '未完成',
|
||
proofAttachments: '',
|
||
attachments: [],
|
||
details: [],
|
||
...score,
|
||
quantificationId: score.quantificationId ? String(score.quantificationId) : '',
|
||
applyCreditLimit: this.normalizeOptionalAmount(
|
||
score.applyCreditLimit === undefined
|
||
? this.archiveForm.applyCreditLimit
|
||
: score.applyCreditLimit
|
||
),
|
||
maxCreditLimit: this.normalizeOptionalAmount(score.maxCreditLimit),
|
||
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;
|
||
},
|
||
applyArchiveCreditFromScores(archive) {
|
||
const latestScore = this.getLatestCreditScore(archive.scores || []);
|
||
archive.customerLevel = latestScore?.creditLevel || archive.customerLevel || '';
|
||
archive.maxCreditLimit = latestScore?.maxCreditLimit || archive.maxCreditLimit || '';
|
||
archive.applyCreditLimit = archive.maxCreditLimit;
|
||
return archive;
|
||
},
|
||
syncArchiveCreditFromScores() {
|
||
this.applyArchiveCreditFromScores(this.archiveForm);
|
||
},
|
||
openArchivePage(row) {
|
||
const isEdit = Boolean(row?.id);
|
||
this.$router.push({
|
||
path: '/vehicle/customer-archive/form',
|
||
query: isEdit ? { id: row.id, name: '编辑客商档案' } : { name: '新增客商档案' },
|
||
});
|
||
},
|
||
viewArchive(row) {
|
||
this.$router.push({
|
||
path: '/vehicle/customer-archive/form',
|
||
query: { id: row.id, name: '查看客商档案', view: '1' },
|
||
});
|
||
},
|
||
closeArchive() {
|
||
this.$router.push('/vehicle/customer-archive');
|
||
},
|
||
openArchive(row, readonly = false) {
|
||
this.readonly = readonly;
|
||
this.activeTab = 'scores';
|
||
this.resetDetailPagination();
|
||
this.resetChangeRecordPage();
|
||
if (row && row.id) {
|
||
getDetail(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 = [];
|
||
this.archiveBox = true;
|
||
this.loadChangeRecords();
|
||
});
|
||
return;
|
||
}
|
||
this.archiveForm = this.emptyArchive();
|
||
this.originalAccessType = '';
|
||
this.qualificationFiles = [];
|
||
this.qualificationUploadFiles = [];
|
||
this.ocrQualificationUploadFiles = [];
|
||
this.selectedQualificationFiles = [];
|
||
this.archiveBox = true;
|
||
},
|
||
resetArchive() {
|
||
this.readonly = false;
|
||
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();
|
||
},
|
||
// 客商材料不做提交校验,仅在页面上提示未上传项
|
||
validateFormalForApproval(archive) {
|
||
if (archive.accessType !== 'formal') return true;
|
||
const hasCompletedScore = (archive.scores || []).some(
|
||
item => item.selfStatus === '已完成' || item.reviewStatus === '已完成' || item.finalScore
|
||
);
|
||
if (!hasCompletedScore) {
|
||
this.$message.warning('正式客商提交审核前必须完成信用评分');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
normalizeArchive() {
|
||
const archive = JSON.parse(JSON.stringify(this.archiveForm));
|
||
if (archive.businessTermType === '长期') {
|
||
archive.businessEndDate = null;
|
||
}
|
||
const invoiceTaxRate = this.normalizeOptionalAmount(archive.invoiceTaxRate);
|
||
const registeredCapital = this.normalizeOptionalAmount(archive.registeredCapital);
|
||
archive.invoiceTaxRate = invoiceTaxRate === '' ? null : invoiceTaxRate;
|
||
archive.registeredCapital = registeredCapital === '' ? null : registeredCapital;
|
||
archive.customerType = Array.isArray(archive.customerType)
|
||
? archive.customerType.join(',')
|
||
: archive.customerType;
|
||
archive.businessScope = Array.isArray(archive.businessScope)
|
||
? archive.businessScope.join(',')
|
||
: archive.businessScope;
|
||
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);
|
||
archive.receiptAccounts = (archive.receiptAccounts || [])
|
||
.map(item => this.normalizeReceipt(item))
|
||
.filter(item => item.accountName || item.accountHolderName || item.bankAccount);
|
||
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;
|
||
},
|
||
saveArchive() {
|
||
this.$refs.archiveForm.validate(valid => {
|
||
if (!valid) {
|
||
this.$message.warning('请先完整填写必填项');
|
||
return;
|
||
}
|
||
submit(this.normalizeArchive()).then(() => {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
});
|
||
});
|
||
},
|
||
saveAndSubmitArchive() {
|
||
this.$refs.archiveForm.validate(valid => {
|
||
if (!valid) {
|
||
this.$message.warning('请先完整填写必填项');
|
||
return;
|
||
}
|
||
const archive = this.normalizeArchive();
|
||
// 仅提交时落变更记录;保存不记录
|
||
archive.recordChange = true;
|
||
if (!this.validateFormalForApproval(archive)) return;
|
||
submit(archive).then(res => {
|
||
const data = res.data.data;
|
||
const id = (data && typeof data === 'object' ? data.id : data) || this.archiveForm.id;
|
||
if (!id) {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '保存成功,请在列表提交审批' });
|
||
return;
|
||
}
|
||
submitApproval(id).then(() => {
|
||
this.closeArchive();
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '提交成功!' });
|
||
});
|
||
});
|
||
});
|
||
},
|
||
addScore() {
|
||
this.scoreRecordIndex = -1;
|
||
this.currentScore = this.normalizeScore({});
|
||
this.scoreAttachmentFiles = [];
|
||
this.expandedScoreCategoryKeys = [];
|
||
this.scoreBox = true;
|
||
if (!this.scoreQuantificationOptions.length) {
|
||
this.initScoreQuantificationOptions();
|
||
}
|
||
},
|
||
openScore(row, index = -1) {
|
||
this.scoreRecordIndex = index;
|
||
this.currentScore = this.normalizeScore(JSON.parse(JSON.stringify(row || {})));
|
||
this.scoreAttachmentFiles = JSON.parse(JSON.stringify(this.currentScore.attachments || []));
|
||
this.expandedScoreCategoryKeys = [];
|
||
this.scoreBox = true;
|
||
const loadOptions = this.scoreQuantificationOptions.length
|
||
? Promise.resolve()
|
||
: this.initScoreQuantificationOptions();
|
||
loadOptions.then(() =>
|
||
this.ensureScoreQuantificationOption(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,
|
||
scoreDate: this.currentScore.scoreDate,
|
||
applyCreditLimit: this.currentScore.applyCreditLimit,
|
||
maxCreditLimit: '',
|
||
creditLevel: '',
|
||
tempApplyCreditLimit: this.currentScore.tempApplyCreditLimit,
|
||
tempCreditStartDate: this.currentScore.tempCreditStartDate,
|
||
tempCreditEndDate: this.currentScore.tempCreditEndDate,
|
||
remark: this.currentScore.remark,
|
||
attachments: this.scoreAttachmentFiles,
|
||
standards: quantificationData.standards || scoreTemplate.standards || [],
|
||
details: this.normalizeScoreTemplateDetails(scoreTemplate),
|
||
});
|
||
if (!template.details || !template.details.length) {
|
||
this.$message.warning('当前评分量化表未配置评分项目');
|
||
}
|
||
this.currentScore = template;
|
||
this.expandedScoreCategoryKeys = [];
|
||
this.calculateScore(this.currentScore);
|
||
});
|
||
},
|
||
normalizeScoreTemplateDetails(template = {}) {
|
||
const categoryDetails = (template.categories || []).flatMap(category =>
|
||
(category.items || []).map(item => ({
|
||
...item,
|
||
categoryCode: category.categoryCode,
|
||
categoryName: category.categoryName,
|
||
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 generatedLabel = isScoreOption
|
||
? `${changeLabel}${item.changeValue || ''}${item.changeUnit || ''}${scoreLabel}${
|
||
item.score || ''
|
||
}分`
|
||
: '';
|
||
const label = item.label || item.optionName || item.value || generatedLabel;
|
||
return {
|
||
...item,
|
||
label,
|
||
value: item.value || item.optionName || item.label || generatedLabel,
|
||
score:
|
||
item.scoreType === 'subtract' && item.score !== undefined
|
||
? -Math.abs(Number(item.score))
|
||
: item.score,
|
||
};
|
||
});
|
||
if (Array.isArray(value)) return normalize(value);
|
||
try {
|
||
const options = JSON.parse(value);
|
||
return Array.isArray(options) ? normalize(options) : [];
|
||
} catch (error) {
|
||
return [];
|
||
}
|
||
},
|
||
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 stepScore = Math.abs(Number(rule.score || 0));
|
||
if (!Number.isFinite(stepScore)) return null;
|
||
|
||
const projectScore = this.getScoreItemFullScore(detail);
|
||
const signedStep = rule.scoreType === 'subtract' ? -stepScore : stepScore;
|
||
const calculatedScore = Number(detail.score || 0) + distance * signedStep;
|
||
return Number(Math.min(Math.max(calculatedScore, 0), projectScore).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.$message.warning(`输入值${increase ? '不能小于' : '不能大于'}基准数值${base}`);
|
||
this.calculateScore(this.currentScore);
|
||
return;
|
||
}
|
||
detail.selfScore = this.getScoreTypeCalculatedScore(detail);
|
||
this.calculateScore(this.currentScore);
|
||
},
|
||
scoreOptionChange(detail, optionValue) {
|
||
const option = this.parseScoreOptions(detail.optionsJson).find(
|
||
item => item.value === optionValue
|
||
);
|
||
const score = Number(option?.score || 0);
|
||
detail.selfScore = score;
|
||
this.calculateScore(this.currentScore);
|
||
},
|
||
handleScoreDetailScoreInput(detail, prop, value) {
|
||
const normalized = String(value || '')
|
||
.replace(/[^\d.]/g, '')
|
||
.replace(/^\./, '')
|
||
.replace(/(\..*)\./g, '$1')
|
||
.replace(/^(\d+)(\.\d{0,2})?.*$/, '$1$2');
|
||
if (prop === 'reviewScore' && normalized !== '') {
|
||
const maxScore = this.getScoreItemFullScore(detail);
|
||
if (Number(normalized) > maxScore) {
|
||
detail[prop] = String(maxScore);
|
||
this.$message.warning(
|
||
`${detail.itemName || '评分项目'}复评得分不能高于配置的最高得分${this.formatScoreValue(
|
||
maxScore
|
||
)}分`
|
||
);
|
||
this.calculateScore(this.currentScore);
|
||
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));
|
||
return optionScores.length ? Math.max(...optionScores) : 0;
|
||
},
|
||
getScoreFullMark(details = []) {
|
||
return details.reduce((sum, item) => {
|
||
if (!this.isBasicScoreDetail(item)) return sum;
|
||
return sum + this.getScoreItemFullScore(item);
|
||
}, 0);
|
||
},
|
||
logReviewScoreSummary() {
|
||
const details = this.currentScore.details || [];
|
||
console.log('[评分详情] 复评评分结果', {
|
||
复评总分: this.currentScore.reviewScore,
|
||
总分: this.getScoreFullMark(details),
|
||
得分率: `${this.currentScore.scoreRate}%`,
|
||
匹配到的信用等级: this.currentScore.creditLevel || '',
|
||
});
|
||
},
|
||
matchCreditStandard(score, scoreRate) {
|
||
const standards = (score.standards || [])
|
||
.filter(item => item && item.creditLevel)
|
||
.sort((a, b) => Number(b.scoreRateLower || 0) - Number(a.scoreRateLower || 0));
|
||
return standards.find(standard => {
|
||
const lower = Number(standard.scoreRateLower);
|
||
const upper = Number(standard.scoreRateUpper);
|
||
if (!Number.isFinite(lower) || !Number.isFinite(upper)) return false;
|
||
return scoreRate >= lower && scoreRate <= upper;
|
||
});
|
||
},
|
||
calculateCreditLimitByStandard(standard, scoreRate) {
|
||
if (!standard) return '';
|
||
const lowerLimit = Number(standard.creditLimitLower);
|
||
const upperLimit = Number(standard.creditLimitUpper);
|
||
if (!Number.isFinite(lowerLimit) || !Number.isFinite(upperLimit)) return '';
|
||
const scoreRateIncrease = Number(standard.scoreRateIncrease || 0);
|
||
const creditLimitIncrease = Number(standard.creditLimitIncrease || 0);
|
||
if (scoreRateIncrease <= 0 || creditLimitIncrease <= 0) {
|
||
return Number(Math.min(lowerLimit, upperLimit).toFixed(2));
|
||
}
|
||
const steps = Math.max(
|
||
0,
|
||
Math.floor((scoreRate - Number(standard.scoreRateLower || 0)) / scoreRateIncrease)
|
||
);
|
||
const limit = lowerLimit + steps * creditLimitIncrease;
|
||
return Number(Math.min(limit, upperLimit).toFixed(2));
|
||
},
|
||
calculateScore(score) {
|
||
const details = score.details || [];
|
||
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 selfScore =
|
||
sumScore(basicDetails, 'selfScore') +
|
||
sumScore(plusDetails, 'selfScore') -
|
||
sumScore(minusDetails, 'selfScore');
|
||
const reviewScore =
|
||
sumScore(basicDetails, 'reviewScore') +
|
||
sumScore(plusDetails, 'reviewScore') -
|
||
sumScore(minusDetails, 'reviewScore');
|
||
const hasReviewScore = details.some(
|
||
item =>
|
||
item.reviewScore !== undefined && item.reviewScore !== null && item.reviewScore !== ''
|
||
);
|
||
const finalScore = hasReviewScore ? reviewScore : selfScore;
|
||
const fullMark = this.getScoreFullMark(details);
|
||
const scoreRate =
|
||
fullMark > 0 ? Math.max(0, Math.min(100, (finalScore / fullMark) * 100)) : 0;
|
||
const standard = this.matchCreditStandard(score, scoreRate);
|
||
const hasStandards = (score.standards || []).some(item => item && item.creditLevel);
|
||
score.selfScore = Number(selfScore.toFixed(2));
|
||
score.reviewScore = Number(reviewScore.toFixed(2));
|
||
score.finalScore = Number(finalScore.toFixed(2));
|
||
score.scoreRate = Number(scoreRate.toFixed(2));
|
||
if (hasStandards) {
|
||
score.creditLevel = standard?.creditLevel || '';
|
||
score.maxCreditLimit = standard
|
||
? this.calculateCreditLimitByStandard(standard, scoreRate)
|
||
: '';
|
||
}
|
||
return score;
|
||
},
|
||
hasIncompleteBasicScoreDetail(details = []) {
|
||
return this.getScoreCategoryDetailsByList(details, 'basic').some(item => {
|
||
if (this.isScoreTypeDetail(item)) {
|
||
const input = String(item.scoreInput ?? '').trim();
|
||
return !input || !Number.isFinite(Number(input)) || item.selfScore === '';
|
||
}
|
||
return !item.selectedOption;
|
||
});
|
||
},
|
||
finishScore(score) {
|
||
if (!score.details || !score.details.length) {
|
||
this.$message.warning('请先读取评分量化表');
|
||
return;
|
||
}
|
||
if (this.hasIncompleteBasicScoreDetail(score.details)) {
|
||
this.$message.warning('请完整填写基础得分项评分明细');
|
||
return;
|
||
}
|
||
this.calculateScore(score);
|
||
score.selfStatus = '已完成';
|
||
score.reviewStatus = '已完成';
|
||
this.$message.success('评分已完成');
|
||
},
|
||
saveScoreDetail() {
|
||
if (!this.validateScoreForm(false)) return;
|
||
this.currentScore.attachments = JSON.parse(JSON.stringify(this.scoreAttachmentFiles || []));
|
||
this.currentScore.proofAttachments = this.stringifyAttachments(this.scoreAttachmentFiles);
|
||
this.calculateScore(this.currentScore);
|
||
const score = JSON.parse(JSON.stringify(this.currentScore));
|
||
if (this.scoreRecordIndex > -1) {
|
||
this.archiveForm.scores.splice(this.scoreRecordIndex, 1, score);
|
||
} else {
|
||
this.archiveForm.scores.push(score);
|
||
this.handleScoreCurrentChange(1);
|
||
}
|
||
this.syncArchiveCreditFromScores();
|
||
this.activeTab = 'scores';
|
||
this.scoreBox = false;
|
||
this.saveScoreArchive();
|
||
},
|
||
saveScoreArchive() {
|
||
if (!this.archiveForm.id) {
|
||
this.$message.success('评分记录已添加,请保存客商档案后生效');
|
||
return;
|
||
}
|
||
submit(this.normalizeArchive()).then(() => {
|
||
this.$message.success('评分记录已保存');
|
||
});
|
||
},
|
||
confirmReviewScore() {
|
||
if (!this.validateScoreForm(true)) return;
|
||
this.currentScore.selfStatus = '已完成';
|
||
this.currentScore.reviewStatus = '已完成';
|
||
this.saveScoreDetail();
|
||
this.$message.success('复评确认成功');
|
||
},
|
||
validateScoreForm(requireComplete) {
|
||
if (!this.currentScore.scoreDate) {
|
||
this.$message.warning('请选择评定日期');
|
||
return false;
|
||
}
|
||
if (!this.currentScore.applyCreditLimit) {
|
||
this.$message.warning('请输入拟申请总资金使用额度');
|
||
return false;
|
||
}
|
||
if (Number(this.currentScore.applyCreditLimit) < 0) {
|
||
this.$message.warning('拟申请总资金使用额度不能小于0');
|
||
return false;
|
||
}
|
||
if (!this.currentScore.quantificationId) {
|
||
this.$message.warning('请选择评分量化表');
|
||
return false;
|
||
}
|
||
if (!this.currentScore.details || !this.currentScore.details.length) {
|
||
this.$message.warning('当前评分量化表未配置评分项目');
|
||
return false;
|
||
}
|
||
const 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;
|
||
}
|
||
const overMaxReviewScore = this.currentScore.details.find(item => {
|
||
if (
|
||
item.reviewScore === undefined ||
|
||
item.reviewScore === null ||
|
||
item.reviewScore === ''
|
||
) {
|
||
return false;
|
||
}
|
||
return Number(item.reviewScore) > this.getScoreItemFullScore(item);
|
||
});
|
||
if (overMaxReviewScore) {
|
||
this.$message.warning(
|
||
`${
|
||
overMaxReviewScore.itemName || '评分项目'
|
||
}复评得分不能高于配置的最高得分${this.formatScoreValue(
|
||
this.getScoreItemFullScore(overMaxReviewScore)
|
||
)}分`
|
||
);
|
||
return false;
|
||
}
|
||
if (requireComplete && this.hasIncompleteBasicScoreDetail(this.currentScore.details)) {
|
||
this.$message.warning('请完整填写基础得分项评分明细');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
handleScoreCurrentChange(currentPage) {
|
||
const page = Number(currentPage) || 1;
|
||
this.scorePage.currentPage = Math.min(Math.max(page, 1), this.scorePageCount);
|
||
},
|
||
handleScoreSizeChange(pageSize) {
|
||
this.scorePage.pageSize = Number(pageSize) || 3;
|
||
this.handleScoreCurrentChange(1);
|
||
},
|
||
formatScoreValue(value) {
|
||
return value === undefined || value === null || value === '' || value === -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.validateFormalForApproval(archive)) return;
|
||
this.$confirm('是否提交客商准入审批?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => submitApproval(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '提交成功!' });
|
||
});
|
||
});
|
||
},
|
||
handleWithdrawApproval(row) {
|
||
this.$confirm('是否确认撤回该客商审批?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => withdrawApproval(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '已撤回至草稿状态!' });
|
||
});
|
||
},
|
||
handleApprove(row) {
|
||
this.$confirm('是否确认审核通过?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => approve(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '审核通过!' });
|
||
});
|
||
},
|
||
handleReject(row) {
|
||
this.$confirm('是否确认审核不通过?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => reject(row.id))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
});
|
||
},
|
||
handleStatus(row) {
|
||
const nextStatus = row.status === 1 ? 2 : 1;
|
||
const actionName = nextStatus === 1 ? '启用' : '停用';
|
||
this.$confirm(`是否确认${actionName}该客商?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
})
|
||
.then(() => changeStatus(row.id, nextStatus))
|
||
.then(() => {
|
||
this.onLoad(this.page);
|
||
this.$message({ type: 'success', message: '操作成功!' });
|
||
});
|
||
},
|
||
searchReset() {
|
||
this.query = {};
|
||
this.onLoad(this.page);
|
||
},
|
||
searchChange(params, done) {
|
||
// 搜索区所属组织级联返回的是 ID 路径数组,取末级 ID 转成部门名称传给后端(与列表接口 deptName 字段一致)
|
||
if (Array.isArray(params.deptName)) {
|
||
const path = params.deptName.filter(
|
||
item => item !== undefined && item !== null && item !== ''
|
||
);
|
||
const deptId = path.length ? path[path.length - 1] : '';
|
||
params.deptName = deptId
|
||
? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || ''
|
||
: '';
|
||
}
|
||
this.query = this.buildQuery(params);
|
||
this.page.currentPage = 1;
|
||
this.onLoad(this.page);
|
||
done();
|
||
},
|
||
selectionChange(list) {
|
||
this.selectionList = list;
|
||
},
|
||
selectionClear() {
|
||
this.selectionList = [];
|
||
this.$refs.crud?.toggleSelection();
|
||
},
|
||
currentChange(currentPage) {
|
||
this.page.currentPage = currentPage;
|
||
},
|
||
sizeChange(pageSize) {
|
||
this.page.pageSize = pageSize;
|
||
},
|
||
refreshChange() {
|
||
this.onLoad(this.page, this.query);
|
||
},
|
||
onLoad(page, params = {}) {
|
||
if (!this.deptSearchInitialized) return;
|
||
this.loading = true;
|
||
getList(page.currentPage, page.pageSize, this.buildQuery(params))
|
||
.then(res => {
|
||
const pageData = res.data.data;
|
||
this.page.total = pageData.total;
|
||
this.data = pageData.records;
|
||
this.selectionClear();
|
||
})
|
||
.finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
handleExport() {
|
||
this.$confirm('是否导出客商档案数据?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}).then(() => {
|
||
NProgress.start();
|
||
exportBlob(
|
||
'/blade-transport/customer-archive/export-customer-archive',
|
||
this.buildExportParams(),
|
||
{ feedback: true }
|
||
)
|
||
.then(res => {
|
||
downloadXls(res.data, `客商档案${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`);
|
||
})
|
||
.finally(() => {
|
||
NProgress.done();
|
||
});
|
||
});
|
||
},
|
||
buildExportParams() {
|
||
return {
|
||
...this.buildQuery(),
|
||
ids: this.ids,
|
||
[this.website.tokenHeader]: getToken(),
|
||
};
|
||
},
|
||
buildQuery(params = {}) {
|
||
return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap);
|
||
},
|
||
},
|
||
};
|
||
</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);
|
||
}
|
||
|
||
.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: nowrap;
|
||
|
||
:deep(.el-date-editor) {
|
||
flex: 0 0 140px;
|
||
width: 140px;
|
||
min-width: 0;
|
||
}
|
||
|
||
:deep(.el-checkbox) {
|
||
flex: 0 0 auto;
|
||
margin-right: 0;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.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-file-name-cell {
|
||
display: block;
|
||
width: 100%;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-align: left;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.qualification-file-name {
|
||
display: block;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
vertical-align: middle;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
color: #606266;
|
||
transition: color 0.2s ease;
|
||
|
||
&:hover {
|
||
color: #409eff;
|
||
}
|
||
|
||
&.is-disabled {
|
||
cursor: default;
|
||
color: #606266;
|
||
|
||
&:hover {
|
||
color: #606266;
|
||
}
|
||
}
|
||
}
|
||
|
||
.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: 14px;
|
||
}
|
||
|
||
: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) {
|
||
//min-height: 112px;
|
||
color: #303133;
|
||
}
|
||
|
||
:deep(.el-select),
|
||
:deep(.el-input),
|
||
:deep(.el-select) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
// 客商材料、变更记录已改用全局 <section-card> 组件,无需额外 margin-top
|
||
|
||
.score-list-table,
|
||
.contact-table,
|
||
.receipt-table,
|
||
.invoice-table {
|
||
:deep(.el-table__header th) {
|
||
background-color: #f5f7fa;
|
||
}
|
||
}
|
||
|
||
.contact-form,
|
||
.receipt-form,
|
||
.invoice-form {
|
||
max-width: 620px;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
:deep(.el-input) {
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.invoice-form {
|
||
max-width: none;
|
||
}
|
||
|
||
.invoice-contact-table {
|
||
width: 100%;
|
||
|
||
:deep(.el-input),
|
||
:deep(.el-select) {
|
||
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-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;
|
||
}
|
||
|
||
:deep(.change-record-detail-dialog .el-dialog__body) {
|
||
max-height: 65vh;
|
||
overflow: auto;
|
||
}
|
||
|
||
:deep(.change-record-detail-dialog .change-record-detail-value .cell) {
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.change-record-detail-meta {
|
||
display: flex;
|
||
gap: 32px;
|
||
margin-bottom: 16px;
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.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: 14px !important;
|
||
}
|
||
</style>
|