信用档案法人节点新增相关企业信息;新增经营异常、严重违法信息

This commit is contained in:
zhouxy 2026-06-08 17:30:15 +08:00
parent 88b2ec4dcb
commit edac46da8c
2 changed files with 285 additions and 41 deletions

View File

@ -231,6 +231,21 @@ export function getCreditArchive(params) {
})
}
/**
* 根据人员姓名/证件号查询其担任的相关企业信用档案图谱三级节点扩展
* @param {Object} params
* @param {string} params.name 姓名必填
* @param {string} [params.cerno] 证件号可选精确匹配
* @param {string} [params.excludePripid] 排除的主体pripid可选
*/
export function getPersonRelatedEnts(params) {
return request({
url: '/eBaseinfo/getPersonRelatedEnts',
method: 'get',
params
})
}
/**
* 信用修复预警统计
* @param data

View File

@ -8,7 +8,7 @@
style="margin-left: 12px;"
@click="resetGraph"
>重置图谱</el-button>
<span class="toolbar-tip">提示点击一级分支可展开/收起明细单分支最多展示 {{ maxNodes }} 个节点</span>
<span class="toolbar-tip">提示点击一级分支可展开/收起明细法定代表人二级节点再次点击可查看相关企业单分支最多展示 {{ maxNodes }} 个节点</span>
</div>
<div ref="graph" class="graph-container" />
@ -24,17 +24,16 @@
v-for="(value, key) in detailData"
:key="key"
:label="fieldLabel(key)"
>{{ value || '—' }}</el-descriptions-item>
>{{ formatDetailValue(key, value) }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script>
import { getCreditArchive } from '@/api/comprehensive'
import { getCreditArchive, getPersonRelatedEnts } from '@/api/comprehensive'
const MAX_NODES = 20
// 2026 2025
const DEFAULT_YEAR = new Date().getFullYear() - 1
const FIELD_LABEL_MAP = {
@ -51,16 +50,31 @@ const FIELD_LABEL_MAP = {
email: '电子邮箱',
invid: '股东ID',
outinvid: '对外投资ID',
lmid: '联络员ID'
lmid: '联络员ID',
specause: '列入原因',
abntime: '列入日期',
decorg: '列入决定机关',
remexcpres: '移出原因',
remdate: '移出日期',
status: '状态'
}
const BRANCH_DEFS = [
{ key: 'shareholders', label: '股东信息', color: '#5470c6' },
{ key: 'legalPersons', label: '法定代表人', color: '#91cc75' },
{ key: 'investments', label: '对外投资', color: '#fac858' },
{ key: 'contacts', label: '联络人', color: '#ee6666' }
{ key: 'contacts', label: '联络人', color: '#ee6666' },
{ key: 'abnormals', label: '经营异常名录', color: '#fc8452' },
{ key: 'seriousIllegals', label: '严重违法失信', color: '#ea4335' }
]
const ROLE_LABEL_MAP = {
LEGAL_PERSON: '法人',
PRINCIPAL_PERSON: '主要人员',
SHAREHOLDER: '股东',
OPERATOR: '经营者'
}
export default {
name: 'CreditArchive',
props: {
@ -83,12 +97,16 @@ export default {
shareholders: [],
legalPersons: [],
investments: [],
contacts: []
contacts: [],
abnormals: [],
seriousIllegals: []
},
truncated: {
shareholders: false,
investments: false,
contacts: false
contacts: false,
abnormals: false,
seriousIllegals: false
},
branches: BRANCH_DEFS,
detailVisible: false,
@ -111,6 +129,15 @@ export default {
fieldLabel(key) {
return FIELD_LABEL_MAP[key] || key
},
formatDetailValue(key, value) {
if (key === 'status') {
return value === 'IN' ? '在列' : value === 'OUT' ? '已移出' : (value || '—')
}
if (key === 'lerepsign') {
return value === '1' ? '法定代表人' : (value || '—')
}
return value || '—'
},
async loadData() {
if (!this.pripid) return
this.loading = true
@ -124,9 +151,17 @@ export default {
shareholders: data.shareholders || [],
legalPersons: data.legalPersons || [],
investments: data.investments || [],
contacts: data.contacts || []
contacts: data.contacts || [],
abnormals: data.abnormals || [],
seriousIllegals: data.seriousIllegals || []
}
this.truncated = data.truncated || {
shareholders: false,
investments: false,
contacts: false,
abnormals: false,
seriousIllegals: false
}
this.truncated = data.truncated || { shareholders: false, investments: false, contacts: false }
this.$nextTick(() => this.renderGraph())
} catch (e) {
this.$message.error('信用档案数据加载失败')
@ -153,13 +188,23 @@ export default {
itemStyle: { color: '#409EFF' },
label: { fontWeight: 'bold' }
}
const branchNodes = this.branches.map((b, i) => ({
const branchNodes = this.branches.map((b, i) => {
const list = this.archiveData[b.key] || []
const count = list.length
const isTruncated = this.truncated[b.key]
const label = count === 0
? b.label
: isTruncated
? `${b.label} (${MAX_NODES}+)`
: `${b.label} (${count})`
return {
id: b.key,
name: `${b.label} (${(this.archiveData[b.key] || []).length})`,
name: label,
symbolSize: 50,
category: i + 1,
itemStyle: { color: b.color }
}))
}
})
const branchLinks = this.branches.map(b => ({
source: 'center',
target: b.key,
@ -169,10 +214,7 @@ export default {
this.chart.setOption({
tooltip: {
trigger: 'item',
formatter: params => {
if (params.dataType === 'edge') return ''
return params.name
}
formatter: params => this.formatTooltip(params)
},
legend: [{
data: this.branches.map(b => b.label),
@ -195,22 +237,81 @@ export default {
}]
}, true)
},
formatTooltip(params) {
if (params.dataType === 'edge') return ''
const data = params.data
if (!data) return params.name
//
if (data.targetPripid && data.rawData) {
const ent = data.rawData
const roles = (ent.roles || []).map(r => ROLE_LABEL_MAP[r] || r).join('、')
return `${ent.entName || '—'}<br/>角色:${roles}<br/>经营状态:${ent.regstateCn || '—'}`
}
// - /
if (data.rawData && data.parentKey) {
const raw = data.rawData
const branchKey = data.parentKey
if (branchKey === 'abnormals' || branchKey === 'seriousIllegals') {
const lines = []
if (raw.specause) lines.push(`列入原因:${raw.specause}`)
if (raw.abntime) lines.push(`列入日期:${raw.abntime}`)
if (raw.decorg) lines.push(`列入决定机关:${raw.decorg}`)
if (raw.status === 'OUT') {
if (raw.remexcpres) lines.push(`移出原因:${raw.remexcpres}`)
if (raw.remdate) lines.push(`移出日期:${raw.remdate}`)
}
lines.push(`状态:${raw.status === 'IN' ? '在列' : '已移出'}`)
return lines.join('<br/>')
}
}
return params.name
},
handleNodeClick(params) {
// /
if (!params.data || params.dataType === 'edge') return
const branch = this.branches.find(b => b.key === params.data.id)
const data = params.data
// toggleBranch
const branch = this.branches.find(b => b.key === data.id)
if (branch) {
this.toggleBranch(branch)
return
}
//
if (params.data.rawData) {
const parentKey = String(params.data.id).split('-')[0]
// tab
if (String(data.id).startsWith('rel-')) {
const pripid = data.targetPripid
if (pripid) {
const routeUrl = this.$router.resolve({
path: '/comprehensive/details',
query: { pripid }
}).href
window.open(routeUrl, '_blank')
}
return
}
//
if (data.rawData) {
const parentKey = String(data.id).split('-')[0]
//
if (parentKey === 'legalPersons') {
this.togglePersonRelatedEnts(data)
return
}
// / /
this.openDetailDialog(parentKey, data.rawData)
}
},
openDetailDialog(parentKey, rawData) {
const parentBranch = this.branches.find(b => b.key === parentKey)
this.detailTitle = parentBranch ? `${parentBranch.label}详情` : '详情'
this.detailData = params.data.rawData
this.detailData = rawData
this.detailVisible = true
}
},
toggleBranch(branch) {
const list = this.archiveData[branch.key] || []
@ -224,26 +325,35 @@ export default {
const hasChild = series.data.some(n => String(n.id).startsWith(prefix))
if (hasChild) {
//
series.data = series.data.filter(n => !String(n.id).startsWith(prefix))
series.links = series.links.filter(l => !String(l.target).startsWith(prefix))
// rel-
series.data = series.data.filter(n => {
const id = String(n.id)
return !id.startsWith(prefix) && !(n._parentPrefix && n._parentPrefix.startsWith(prefix))
})
series.links = series.links.filter(l => {
const target = String(l.target)
return !target.startsWith(prefix) && !(l._parentPrefix && l._parentPrefix.startsWith(prefix))
})
} else {
// Q11 MAX_NODES "..."
const displayList = list.slice(0, MAX_NODES)
const categoryIndex = this.branches.indexOf(branch) + 1
displayList.forEach((item, i) => {
const nodeId = `${branch.key}-${i}`
series.data.push({
const nodeData = {
id: nodeId,
name: this.getNodeName(branch.key, item),
name: this.getNodeName(branch.key, item, i),
symbolSize: 30,
category: categoryIndex,
itemStyle: { color: branch.color, opacity: 0.7 },
rawData: item
})
itemStyle: {
color: branch.color,
opacity: this.getAbnormalOpacity(branch.key, item)
},
rawData: item,
parentKey: branch.key
}
series.data.push(nodeData)
series.links.push({ source: branch.key, target: nodeId })
})
//
const isTruncated = this.truncated[branch.key] || list.length > MAX_NODES
if (isTruncated) {
const moreId = `${branch.key}-more`
@ -259,11 +369,130 @@ export default {
}
this.chart.setOption(option, true)
},
getNodeName(key, item) {
getNodeName(key, item, index) {
if (key === 'investments') return item.entName || '—'
if (key === 'shareholders') return item.name || '—'
if (key === 'contacts') return item.name || '—'
if (key === 'legalPersons') return item.name || '—'
if (key === 'abnormals') {
return this.getAbnormalNodeName(item, '经营异常', index)
}
if (key === 'seriousIllegals') {
return this.getAbnormalNodeName(item, '严重违法', index)
}
return item.name || '—'
},
getAbnormalNodeName(item, fallbackPrefix, index) {
const cause = item.specause
const time = item.abntime
if (cause) {
const truncatedCause = cause.length > 10 ? cause.substring(0, 10) + '...' : cause
if (time) {
const dateStr = this.formatDateToYM(time)
return `${truncatedCause} (${dateStr})`
}
return truncatedCause
}
return `${fallbackPrefix} ${index != null ? index + 1 : ''}`.trim()
},
getAbnormalOpacity(branchKey, item) {
if (branchKey === 'abnormals' || branchKey === 'seriousIllegals') {
return item.status === 'OUT' ? 0.4 : 0.7
}
return 0.7
},
formatDateToYM(dateVal) {
if (!dateVal) return ''
const d = new Date(dateVal)
if (isNaN(d.getTime())) return String(dateVal).substring(0, 7)
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
return `${y}-${m}`
},
async togglePersonRelatedEnts(personNode) {
const prefix = `rel-${personNode.id}-`
const option = this.chart.getOption()
const series = option.series[0]
const hasChild = series.data.some(n => String(n.id).startsWith(prefix))
//
if (hasChild) {
series.data = series.data.filter(n => !String(n.id).startsWith(prefix))
series.links = series.links.filter(l => !String(l.target).startsWith(prefix))
this.chart.setOption(option, true)
return
}
//
const person = personNode.rawData
this.loading = true
try {
const res = await getPersonRelatedEnts({
name: person.name,
cerno: person.cerno,
excludePripid: this.pripid
})
const data = (res && res.data) || res || {}
const list = data.ents || []
if (!list.length) {
this.$message.info('未查询到该人员的其他相关企业')
return
}
const truncated = !!data.truncated
const displayList = list.slice(0, MAX_NODES)
const categoryIndex = this.branches.findIndex(b => b.key === 'legalPersons') + 1
displayList.forEach((ent, i) => {
const nodeId = `${prefix}${i}`
const nodeData = {
id: nodeId,
name: this.buildRelatedEntLabel(ent),
symbolSize: 22,
category: categoryIndex,
itemStyle: { color: '#91cc75', opacity: 0.5 },
targetPripid: ent.pripid,
rawData: ent,
_parentPrefix: 'legalPersons-'
}
series.data.push(nodeData)
series.links.push({
source: personNode.id,
target: nodeId,
lineStyle: { type: 'dashed', color: '#91cc75' },
_parentPrefix: 'legalPersons-'
})
})
if (truncated) {
const moreId = `${prefix}more`
series.data.push({
id: moreId,
name: `更多... (共 ${displayList.length}+ 项)`,
symbolSize: 20,
category: categoryIndex,
itemStyle: { color: '#91cc75', opacity: 0.3 },
_parentPrefix: 'legalPersons-'
})
series.links.push({
source: personNode.id,
target: moreId,
lineStyle: { type: 'dashed' },
_parentPrefix: 'legalPersons-'
})
}
this.chart.setOption(option, true)
} catch (e) {
this.$message.error('查询相关企业失败')
} finally {
this.loading = false
}
},
buildRelatedEntLabel(ent) {
const name = ent.entName || '—'
const roles = (ent.roles || []).map(r => ROLE_LABEL_MAP[r] || r)
if (roles.length) {
return `${name}(${roles.join('/')})`
}
return name
},
resetGraph() {
this.renderGraph()
},