332 lines
9.3 KiB
Vue
332 lines
9.3 KiB
Vue
<template>
|
||
<div v-loading="loading" class="credit-archive">
|
||
<div class="toolbar">
|
||
<span class="toolbar-label">年报年度:</span>
|
||
<el-select
|
||
v-model="reportYear"
|
||
size="small"
|
||
clearable
|
||
placeholder="默认最近一年"
|
||
style="width: 160px;"
|
||
@change="loadData"
|
||
>
|
||
<el-option
|
||
v-for="y in yearOptions"
|
||
:key="y"
|
||
:label="`${y} 年`"
|
||
:value="y"
|
||
/>
|
||
</el-select>
|
||
<el-button
|
||
type="text"
|
||
icon="el-icon-refresh"
|
||
style="margin-left: 12px;"
|
||
@click="resetGraph"
|
||
>重置图谱</el-button>
|
||
<span class="toolbar-tip">提示:点击一级分支可展开/收起明细,单分支最多展示 {{ maxNodes }} 个节点</span>
|
||
</div>
|
||
<div ref="graph" class="graph-container" />
|
||
|
||
<!-- 节点详情弹窗 -->
|
||
<el-dialog
|
||
:visible.sync="detailVisible"
|
||
:title="detailTitle"
|
||
width="560px"
|
||
append-to-body
|
||
>
|
||
<el-descriptions v-if="detailData" :column="1" border size="small">
|
||
<el-descriptions-item
|
||
v-for="(value, key) in detailData"
|
||
:key="key"
|
||
:label="fieldLabel(key)"
|
||
>{{ value || '—' }}</el-descriptions-item>
|
||
</el-descriptions>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { getCreditArchive } from '@/api/comprehensive'
|
||
|
||
const MAX_NODES = 20
|
||
const CURRENT_YEAR = new Date().getFullYear()
|
||
|
||
const FIELD_LABEL_MAP = {
|
||
name: '姓名',
|
||
entName: '企业名称',
|
||
uniscid: '统一社会信用代码',
|
||
certType: '证件类型',
|
||
subConAm: '认缴出资额',
|
||
subConProp: '认缴出资比例(%)',
|
||
respForm: '责任形式',
|
||
position: '职务',
|
||
lerepsign: '法定代表人标志',
|
||
tel: '联系电话',
|
||
email: '电子邮箱',
|
||
invid: '股东ID',
|
||
outinvid: '对外投资ID',
|
||
lmid: '联络员ID'
|
||
}
|
||
|
||
const BRANCH_DEFS = [
|
||
{ key: 'shareholders', label: '股东信息', color: '#5470c6' },
|
||
{ key: 'legalPersons', label: '法定代表人', color: '#91cc75' },
|
||
{ key: 'investments', label: '对外投资', color: '#fac858' },
|
||
{ key: 'contacts', label: '联络人', color: '#ee6666' }
|
||
]
|
||
|
||
export default {
|
||
name: 'CreditArchive',
|
||
props: {
|
||
pripid: {
|
||
type: String,
|
||
required: true
|
||
},
|
||
entName: {
|
||
type: String,
|
||
default: ''
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
chart: null,
|
||
reportYear: null,
|
||
yearOptions: this.buildYearOptions(),
|
||
maxNodes: MAX_NODES,
|
||
archiveData: {
|
||
shareholders: [],
|
||
legalPersons: [],
|
||
investments: [],
|
||
contacts: []
|
||
},
|
||
truncated: {
|
||
shareholders: false,
|
||
investments: false,
|
||
contacts: false
|
||
},
|
||
branches: BRANCH_DEFS,
|
||
detailVisible: false,
|
||
detailTitle: '',
|
||
detailData: null
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadData()
|
||
window.addEventListener('resize', this.resize)
|
||
},
|
||
beforeDestroy() {
|
||
window.removeEventListener('resize', this.resize)
|
||
if (this.chart) {
|
||
this.chart.dispose()
|
||
this.chart = null
|
||
}
|
||
},
|
||
methods: {
|
||
buildYearOptions() {
|
||
const list = []
|
||
for (let y = CURRENT_YEAR; y >= CURRENT_YEAR - 9; y--) {
|
||
list.push(y)
|
||
}
|
||
return list
|
||
},
|
||
fieldLabel(key) {
|
||
return FIELD_LABEL_MAP[key] || key
|
||
},
|
||
async loadData() {
|
||
if (!this.pripid) return
|
||
this.loading = true
|
||
try {
|
||
const res = await getCreditArchive({
|
||
pripid: this.pripid,
|
||
reportYear: this.reportYear || undefined
|
||
})
|
||
const data = (res && res.data) || res || {}
|
||
this.archiveData = {
|
||
shareholders: data.shareholders || [],
|
||
legalPersons: data.legalPersons || [],
|
||
investments: data.investments || [],
|
||
contacts: data.contacts || []
|
||
}
|
||
this.truncated = data.truncated || { shareholders: false, investments: false, contacts: false }
|
||
// Q5:首次加载用后端返回的 reportYear 作为默认选中
|
||
if (!this.reportYear && data.reportYear) {
|
||
this.reportYear = data.reportYear
|
||
}
|
||
this.$nextTick(() => this.renderGraph())
|
||
} catch (e) {
|
||
this.$message.error('信用档案数据加载失败')
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
renderGraph() {
|
||
if (!this.$refs.graph) return
|
||
if (!this.chart) {
|
||
this.chart = this.$echarts.init(this.$refs.graph)
|
||
this.chart.on('click', params => this.handleNodeClick(params))
|
||
}
|
||
|
||
const centerName = this.entName || this.archiveData.entName || '主体'
|
||
const centerNode = {
|
||
id: 'center',
|
||
name: centerName,
|
||
symbolSize: 80,
|
||
category: 0,
|
||
fixed: true,
|
||
x: 0,
|
||
y: 0,
|
||
itemStyle: { color: '#409EFF' },
|
||
label: { fontWeight: 'bold' }
|
||
}
|
||
const branchNodes = this.branches.map((b, i) => ({
|
||
id: b.key,
|
||
name: `${b.label} (${(this.archiveData[b.key] || []).length})`,
|
||
symbolSize: 50,
|
||
category: i + 1,
|
||
itemStyle: { color: b.color }
|
||
}))
|
||
const branchLinks = this.branches.map(b => ({
|
||
source: 'center',
|
||
target: b.key,
|
||
lineStyle: { color: '#aaa', width: 2 }
|
||
}))
|
||
|
||
this.chart.setOption({
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: params => {
|
||
if (params.dataType === 'edge') return ''
|
||
return params.name
|
||
}
|
||
},
|
||
legend: [{
|
||
data: this.branches.map(b => b.label),
|
||
bottom: 0
|
||
}],
|
||
series: [{
|
||
type: 'graph',
|
||
layout: 'force',
|
||
roam: true,
|
||
draggable: true,
|
||
label: { show: true, position: 'right', formatter: '{b}' },
|
||
force: { repulsion: 400, edgeLength: 120 },
|
||
categories: [
|
||
{ name: '主体' },
|
||
...this.branches.map(b => ({ name: b.label }))
|
||
],
|
||
data: [centerNode, ...branchNodes],
|
||
links: branchLinks,
|
||
lineStyle: { color: 'source', curveness: 0.1 }
|
||
}]
|
||
}, true)
|
||
},
|
||
handleNodeClick(params) {
|
||
// 点击中心节点 / 边 → 忽略
|
||
if (!params.data || params.dataType === 'edge') return
|
||
const branch = this.branches.find(b => b.key === params.data.id)
|
||
if (branch) {
|
||
this.toggleBranch(branch)
|
||
return
|
||
}
|
||
// 二级节点点击 → 弹出明细
|
||
if (params.data.rawData) {
|
||
const parentKey = String(params.data.id).split('-')[0]
|
||
const parentBranch = this.branches.find(b => b.key === parentKey)
|
||
this.detailTitle = parentBranch ? `${parentBranch.label}详情` : '详情'
|
||
this.detailData = params.data.rawData
|
||
this.detailVisible = true
|
||
}
|
||
},
|
||
toggleBranch(branch) {
|
||
const list = this.archiveData[branch.key] || []
|
||
if (!list.length) {
|
||
this.$message.info(`暂无${branch.label}`)
|
||
return
|
||
}
|
||
const option = this.chart.getOption()
|
||
const series = option.series[0]
|
||
const prefix = branch.key + '-'
|
||
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))
|
||
} 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({
|
||
id: nodeId,
|
||
name: this.getNodeName(branch.key, item),
|
||
symbolSize: 30,
|
||
category: categoryIndex,
|
||
itemStyle: { color: branch.color, opacity: 0.7 },
|
||
rawData: item
|
||
})
|
||
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`
|
||
series.data.push({
|
||
id: moreId,
|
||
name: `更多... (共 ${list.length}${list.length > MAX_NODES ? '+' : ''} 项)`,
|
||
symbolSize: 26,
|
||
category: categoryIndex,
|
||
itemStyle: { color: branch.color, opacity: 0.4 }
|
||
})
|
||
series.links.push({ source: branch.key, target: moreId })
|
||
}
|
||
}
|
||
this.chart.setOption(option, true)
|
||
},
|
||
getNodeName(key, item) {
|
||
if (key === 'investments') return item.entName || '—'
|
||
if (key === 'shareholders') return item.name || '—'
|
||
return item.name || '—'
|
||
},
|
||
resetGraph() {
|
||
this.renderGraph()
|
||
},
|
||
resize() {
|
||
if (this.chart) this.chart.resize()
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.credit-archive {
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10px 12px;
|
||
background: #fafafa;
|
||
border: 1px solid #ebeef5;
|
||
border-bottom: none;
|
||
|
||
.toolbar-label {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
margin-right: 6px;
|
||
}
|
||
.toolbar-tip {
|
||
margin-left: auto;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
}
|
||
.graph-container {
|
||
width: 100%;
|
||
height: 600px;
|
||
border: 1px solid #ebeef5;
|
||
background: #fff;
|
||
}
|
||
}
|
||
</style>
|