调整 合同、运力、客商、项目

This commit is contained in:
2026-09-15 11:59:55 +08:00
parent 79aa4f9fae
commit 6a87066097
15 changed files with 1857 additions and 957 deletions
+151 -408
View File
@@ -79,11 +79,12 @@
资金使用额度风险
<el-tooltip placement="top" effect="dark">
<template #content>
<div>中风险80% 客户资金额度使用率 &lt; 90%</div>
<div>中风险客户资金额度使用率 80%</div>
<div>高风险客户资金额度使用率 90%</div>
<div>使用率 = (累计付款金额 - 累计收票金额) / 最大资金使用额度 × 100%</div>
</template>
<i class="el-icon-question fund-use-risk-help" />
<el-icon class="fund-use-risk-help">
<el-icon-question-filled />
</el-icon>
</el-tooltip>
</span>
</template>
@@ -927,6 +928,7 @@
width="680px"
class="contact-dialog"
@closed="resetContact"
@opened="syncContactRegionPath"
>
<section-card>
<el-form
@@ -934,7 +936,7 @@
:model="contactForm"
:rules="contactRules"
label-position="right"
label-width="110px"
label-width="auto"
class="contact-form"
>
<el-form-item label="联系人姓名" prop="contactName" class="contact-form__compact-field">
@@ -946,30 +948,41 @@
<el-form-item label="邮箱" prop="email" class="contact-form__compact-field">
<el-input v-model="contactForm.email" maxlength="100" />
</el-form-item>
<el-form-item label="地址" prop="detailAddress">
<div class="contact-form__address">
<el-input
v-model="contactForm.regionName"
readonly
maxlength="100"
placeholder="行政区划自动带出"
/>
<el-input
v-model="contactForm.detailAddress"
readonly
maxlength="255"
placeholder="点击后弹出地图组件"
suffix-icon="el-icon-location"
@click="handleContactMapPick"
/>
</div>
<el-form-item 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="500"
maxlength="200"
show-word-limit
/>
</el-form-item>
@@ -983,52 +996,6 @@
</template>
</el-dialog>
<el-dialog
title="地图选择行政区划"
append-to-body
v-model="contactMapBox"
width="920px"
class="contact-form__map-dialog"
@opened="initContactAmap"
>
<div class="contact-form__map-toolbar">
<el-input
v-model="contactMapKeyword"
clearable
placeholder="输入地址关键词"
@keyup.enter="searchContactMapKeyword"
/>
<el-button
type="primary"
icon="el-icon-search"
:loading="contactMapLoading"
@click="searchContactMapKeyword"
>
搜索
</el-button>
</div>
<div class="map-picker-content">
<map-search-results :results="contactMapSearchResults" @select="selectContactMapSearchResult" />
<div ref="contactAmap" class="contact-form__map"></div>
</div>
<div class="contact-form__map-info">
<span>{{ contactMapStatus }}</span>
<span v-if="contactMapSelected.regionName">
行政区划:{{ contactMapSelected.regionName }}
</span>
</div>
<template #footer>
<el-button @click="contactMapBox = false">取消</el-button>
<el-button
type="primary"
:disabled="!contactMapSelected.longitude"
@click="confirmContactMapPick"
>
确定
</el-button>
</template>
</el-dialog>
<el-dialog
:title="receiptDialogTitle"
append-to-body
@@ -1554,9 +1521,6 @@ const createTimeRangeMap = {
createTimeRange: ['createTimeStart', 'createTimeEnd'],
};
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
let amapLoader;
const qualificationViewerPlugins = [
imagePlugin(),
officePlugin({ pdf: { workerSrc: pdfWorkerSrc, useFetchData: true } }),
@@ -1695,16 +1659,6 @@ export default {
changeRecordDetailRows: [],
contactIndex: -1,
contactForm: this.emptyContact(),
contactMapBox: false,
contactMapLoading: false,
contactMapKeyword: '',
contactMapStatus: '可搜索地址或点击地图选点',
contactMapSelected: {},
contactMapSearchResults: [],
contactMapTarget: 'contact',
contactAmap: null,
contactAmapMarker: null,
contactAmapGeocoder: null,
receiptIndex: -1,
receiptForm: this.emptyReceiptAccount(),
invoiceIndex: -1,
@@ -1814,6 +1768,7 @@ export default {
{ 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' }],
},
@@ -1980,7 +1935,9 @@ export default {
label: '资金使用额度风险',
prop: 'fundUseRisk',
slot: true,
minWidth: 150,
headerslot: true,
width: 200,
overHidden: false,
},
{
label: '审批状态',
@@ -2097,6 +2054,20 @@ export default {
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 => {
@@ -2650,11 +2621,9 @@ export default {
contactName: '',
contactPhone: '',
email: '',
regionPath: [],
regionName: '',
regionCode: '',
detailAddress: '',
longitude: '',
latitude: '',
contactAddress: '',
branchName: '',
positionName: '',
@@ -2948,6 +2917,67 @@ export default {
}
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 转部门名称
@@ -3071,11 +3101,20 @@ export default {
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.archiveForm.deptName || '',
branchName:
contact.branchName ||
contact.deptName ||
this.getCurrentOrganizationName() ||
this.archiveForm.deptName ||
'',
};
},
openContact(row, index = -1) {
@@ -3084,9 +3123,10 @@ export default {
? this.normalizeContact(row)
: {
...this.emptyContact(),
branchName: this.archiveForm.deptName || '',
branchName: this.getCurrentOrganizationName() || this.archiveForm.deptName || '',
};
this.contactBox = true;
this.$nextTick(() => this.syncContactRegionPath());
},
resetContact() {
this.contactIndex = -1;
@@ -3100,6 +3140,7 @@ export default {
...this.contactForm,
contactAddress: this.formatContactAddress(this.contactForm),
});
delete contact.regionPath;
if (this.contactIndex > -1) {
this.archiveForm.contacts.splice(this.contactIndex, 1, contact);
} else {
@@ -3121,279 +3162,6 @@ export default {
})
.catch(() => {});
},
handleContactMapPick() {
this.contactMapTarget = 'contact';
this.contactMapKeyword = this.contactForm.detailAddress || this.contactForm.regionName || '';
this.contactMapSelected = this.buildContactMapSelection({
lng: this.contactForm.longitude,
lat: this.contactForm.latitude,
address: this.contactForm.detailAddress,
regionName: this.contactForm.regionName,
regionCode: this.contactForm.regionCode,
});
this.contactMapStatus = this.contactMapSelected.longitude
? '已加载当前选点,可重新选点'
: '可搜索地址或点击地图选点';
this.contactMapBox = true;
},
loadAmap() {
if (window.AMap && window.AMap.Map) {
return Promise.resolve();
}
if (!amapLoader) {
amapLoader = new Promise((resolve, reject) => {
window._AMapSecurityConfig = {
securityJsCode: AMAP_SECURITY_CODE,
};
const script = document.createElement('script');
script.src = `https://webapi.amap.com/maps?v=2.0&key=${AMAP_KEY}`;
script.async = true;
script.onload = resolve;
script.onerror = () => reject(new Error('高德地图组件加载失败'));
document.body.appendChild(script);
});
}
return amapLoader;
},
initContactAmap() {
this.loadAmap()
.then(() => {
this.$nextTick(() => {
if (!this.contactAmap) {
const center = this.toLngLat(
this.contactMapSelected.longitude || 116.40769,
this.contactMapSelected.latitude || 39.89945
);
this.contactAmap = new window.AMap.Map(this.$refs.contactAmap, {
center,
zoom: this.contactMapSelected.longitude ? 14 : 11,
});
window.AMap.plugin(['AMap.ToolBar', 'AMap.Geocoder'], () => {
this.contactAmap.addControl(new window.AMap.ToolBar());
if (!this.contactAmapGeocoder) {
this.contactAmapGeocoder = new window.AMap.Geocoder();
}
});
this.contactAmap.on('click', event => this.pickContactMapPoint(event.lnglat));
} else if (typeof this.contactAmap.resize === 'function') {
this.contactAmap.resize();
}
if (this.contactMapSelected.longitude) {
this.renderContactMapMarker(
this.toLngLat(this.contactMapSelected.longitude, this.contactMapSelected.latitude)
);
} else {
this.clearContactMapMarker();
}
});
})
.catch(() => {
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
this.contactMapBox = false;
});
},
searchContactMapKeyword() {
const keyword = String(this.contactMapKeyword || '').trim();
if (!keyword) {
this.$message.warning('请输入地址关键词');
return;
}
this.loadAmap()
.then(() => {
this.contactMapLoading = true;
this.ensureContactAmapGeocoder()
.then(() => this.runAmapGeocode('location', keyword))
.then(result => {
this.contactMapSearchResults = (result.geocodes || []).map((item, index) => ({
id: item.id || index,
name: item.formattedAddress || keyword,
address: item.formattedAddress || keyword,
location: item.location,
}));
const point = this.resolveMapPoint(result);
if (!point) {
this.contactMapStatus = '未找到匹配地址';
this.$message.warning('地图搜索无匹配地址');
return;
}
this.pickContactMapPoint(point, keyword);
})
.catch(() => {
this.contactMapStatus = '地图搜索失败';
this.$message.error('地图搜索失败,请稍后重试');
})
.finally(() => {
this.contactMapLoading = false;
});
})
.catch(() => {
this.$message.error('高德地图组件加载失败,请稍后重试或重新打开弹窗');
});
},
selectContactMapSearchResult(item) {
if (item && item.location) {
this.pickContactMapPoint(item.location, item.address || item.name || '');
}
},
pickContactMapPoint(lnglat, keyword) {
const longitude = this.getPointLng(lnglat);
const latitude = this.getPointLat(lnglat);
if (longitude === undefined || latitude === undefined) {
this.$message.warning('选点坐标无效');
return;
}
const point = this.toLngLat(longitude, latitude);
this.renderContactMapMarker(point);
this.contactMapSelected = this.buildContactMapSelection({
lng: longitude,
lat: latitude,
address: keyword,
});
this.contactMapStatus = '正在反查地址...';
this.ensureContactAmapGeocoder()
.then(() => this.runAmapGeocode('address', point))
.then(result => {
const address = this.resolveMapAddress(result);
this.contactMapSelected = {
...this.contactMapSelected,
detailAddress: address.detailAddress || this.contactMapSelected.detailAddress,
regionName: address.regionName || this.contactMapSelected.regionName,
regionCode: address.regionCode || this.contactMapSelected.regionCode,
};
this.contactMapKeyword = this.contactMapSelected.detailAddress || this.contactMapKeyword;
this.contactMapStatus = this.contactMapSelected.detailAddress || '已选点,可确认回填';
})
.catch(() => {
this.contactMapStatus = '反查地址失败';
this.$message.error('反查地址失败,请重新选点');
});
},
renderContactMapMarker(point) {
if (!this.contactAmap || !window.AMap) return;
this.clearContactMapMarker();
this.contactAmapMarker = new window.AMap.Marker({
position: point,
});
this.contactAmapMarker.setMap(this.contactAmap);
this.contactAmap.setCenter(point);
},
clearContactMapMarker() {
if (this.contactAmapMarker) {
this.contactAmapMarker.setMap(null);
this.contactAmapMarker = null;
}
},
confirmContactMapPick() {
if (!this.contactMapSelected.longitude) {
this.$message.warning('请先搜索或点击地图完成选点');
return;
}
this.contactForm.detailAddress =
this.contactMapSelected.detailAddress || this.contactForm.detailAddress;
this.contactForm.regionName =
this.contactMapSelected.regionName || this.contactForm.regionName;
this.contactForm.regionCode =
this.contactMapSelected.regionCode || this.contactForm.regionCode;
this.contactForm.longitude = this.contactMapSelected.longitude;
this.contactForm.latitude = this.contactMapSelected.latitude;
this.$refs.contactForm?.validateField('detailAddress');
this.contactMapBox = false;
},
buildContactMapSelection({ lng, lat, address, regionName, regionCode }) {
const longitude = this.formatCoordinate(lng);
const latitude = this.formatCoordinate(lat);
return {
longitude,
latitude,
detailAddress: address || '',
regionName: regionName || '',
regionCode: regionCode || '',
};
},
formatCoordinate(value) {
if (value === undefined || value === null || value === '') {
return '';
}
const numberValue = Number(value);
return Number.isFinite(numberValue) ? numberValue.toFixed(6) : '';
},
toLngLat(lng, lat) {
return new window.AMap.LngLat(Number(lng), Number(lat));
},
getPointLng(point) {
if (!point) return undefined;
if (typeof point.getLng === 'function') return point.getLng();
return point.lng ?? point.lon;
},
getPointLat(point) {
if (!point) return undefined;
if (typeof point.getLat === 'function') return point.getLat();
return point.lat;
},
ensureContactAmapGeocoder() {
if (this.contactAmapGeocoder) {
return Promise.resolve(this.contactAmapGeocoder);
}
return new Promise((resolve, reject) => {
if (!window.AMap || !window.AMap.plugin) {
reject(new Error('高德地图组件未就绪'));
return;
}
window.AMap.plugin(['AMap.Geocoder'], () => {
try {
this.contactAmapGeocoder = new window.AMap.Geocoder();
resolve(this.contactAmapGeocoder);
} catch (error) {
reject(error);
}
});
});
},
runAmapGeocode(action, input) {
return new Promise((resolve, reject) => {
const timer = window.setTimeout(() => {
reject(new Error('高德地图请求超时'));
}, 10000);
const done = (status, result) => {
window.clearTimeout(timer);
if (status === 'complete' && result) {
resolve(result);
return;
}
reject(new Error('高德地图请求失败'));
};
try {
if (action === 'location') {
this.contactAmapGeocoder.getLocation(input, done);
} else {
this.contactAmapGeocoder.getAddress(input, done);
}
} catch (error) {
window.clearTimeout(timer);
reject(error);
}
});
},
resolveMapPoint(result) {
if (!result) return null;
const geocode = Array.isArray(result.geocodes) ? result.geocodes[0] : null;
if (geocode && geocode.location) return geocode.location;
if (result.location) return result.location;
if (result.lnglat) return result.lnglat;
if (result.getLng || result.lng || result.lon) return result;
return null;
},
resolveMapAddress(result = {}) {
const regeocode = result.regeocode || {};
const component = regeocode.addressComponent || {};
const city = Array.isArray(component.city) ? '' : component.city;
const regionName = [component.province, city, component.district].filter(Boolean).join('');
return {
detailAddress: regeocode.formattedAddress || '',
regionName,
regionCode: component.adcode || '',
};
},
normalizeReceipt(receipt = {}) {
return {
...this.emptyReceiptAccount(),
@@ -4020,7 +3788,12 @@ export default {
contacts: (detail.contacts || []).map(item =>
this.normalizeContact({
...item,
branchName: item.branchName || item.deptName || detail.deptName || '',
branchName:
item.branchName ||
item.deptName ||
this.getCurrentOrganizationName() ||
detail.deptName ||
'',
})
),
receiptAccounts: (detail.receiptAccounts || []).map(item => this.normalizeReceipt(item)),
@@ -4226,12 +3999,14 @@ export default {
delete archive.registeredRegionPath;
archive.qualificationAttachments = this.stringifyAttachments(this.qualificationFiles);
archive.contacts = (archive.contacts || [])
.map(item =>
this.normalizeContact({
.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))
@@ -5048,6 +4823,12 @@ export default {
: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 {
@@ -5073,13 +4854,15 @@ export default {
.fund-use-risk-header {
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.fund-use-risk-help {
margin-left: 4px;
color: #a8abb2;
cursor: help;
font-size: 14px;
flex: none;
}
.archive-form {
@@ -5417,23 +5200,9 @@ export default {
}
}
.contact-form__address {
display: flex;
gap: 10px;
width: 100%;
.el-input {
flex: 1;
min-width: 0;
}
.el-input:first-child {
flex: 0 0 38%;
}
}
.contact-form__compact-field {
:deep(.el-input) {
:deep(.el-input),
:deep(.el-cascader) {
width: 60%;
}
}
@@ -5463,32 +5232,6 @@ export default {
gap: 12px;
}
.contact-form__map-toolbar {
display: flex;
gap: 10px;
margin-bottom: 12px;
.el-input {
flex: 1;
}
}
.contact-form__map {
width: 100%;
height: 460px;
border: 1px solid #dcdfe6;
}
.contact-form__map-info {
display: flex;
justify-content: space-between;
gap: 16px;
min-height: 22px;
margin-top: 10px;
color: #606266;
font-size: 13px;
}
.score-detail-table {
width: 100%;
margin-top: 12px;