This commit is contained in:
2026-01-15 18:03:13 +08:00
commit ad794a1312
670 changed files with 92362 additions and 0 deletions

238
src/ui/CQYGL3F8E/README.md Normal file
View File

@@ -0,0 +1,238 @@
# CQYGL3F8E 人企关系加强版模块
## 概述
CQYGL3F8E 是人企关系加强版模块,提供全面的企业关联分析功能。该模块通过拆分功能,将原本的单一组件分解为三个独立的子模块,每个子模块专注于特定类型的企业关联信息展示。
## 模块结构
### 主模块
- **文件位置**: `src/ui/CQYGL3F8E/index.vue`
- **功能**: 整合三个子模块,提供完整的人企关系分析视图
- **API ID**: `QYGL3F8E`
### 子模块
#### 1. 投资企业记录 (Investment)
- **文件位置**: `src/ui/CQYGL3F8E/components/Investment.vue`
- **API ID**: `CQYGL3F8E_Investment`
- **功能**: 展示用户作为股东、历史股东、法人、历史法人的企业记录
- **数据来源**: 过滤 `relationship` 字段包含 `["sh", "his_sh", "lp", "his_lp"]` 的企业
#### 2. 高管任职记录 (SeniorExecutive)
- **文件位置**: `src/ui/CQYGL3F8E/components/SeniorExecutive.vue`
- **API ID**: `CQYGL3F8E_SeniorExecutive`
- **功能**: 展示用户作为高管、历史高管的企业任职记录
- **数据来源**: 过滤 `relationship` 字段包含 `["tm", "his_tm"]` 的企业
#### 3. 涉诉风险 (Lawsuit)
- **文件位置**: `src/ui/CQYGL3F8E/components/Lawsuit.vue`
- **API ID**: `CQYGL3F8E_Lawsuit`
- **功能**: 展示存在涉诉风险的企业信息
- **数据来源**: 过滤 `lawsuitInfo` 字段包含有效涉诉数据的企业
#### 4. 对外投资历史 (InvestHistory)
- **文件位置**: `src/ui/CQYGL3F8E/components/InvestHistory.vue`
- **API ID**: `CQYGL3F8E_InvestHistory`
- **功能**: 展示企业的对外投资历史记录
- **数据来源**: `invest_history` 字段
#### 5. 融资历史 (FinancingHistory)
- **文件位置**: `src/ui/CQYGL3F8E/components/FinancingHistory.vue`
- **API ID**: `CQYGL3F8E_FinancingHistory`
- **功能**: 展示企业的融资历史记录
- **数据来源**: `financing_history` 字段
#### 6. 行政处罚 (Punishment)
- **文件位置**: `src/ui/CQYGL3F8E/components/Punishment.vue`
- **API ID**: `CQYGL3F8E_Punishment`
- **功能**: 展示企业的行政处罚记录
- **数据来源**: `punishment_info` 字段
#### 7. 经营异常 (Abnormal)
- **文件位置**: `src/ui/CQYGL3F8E/components/Abnormal.vue`
- **API ID**: `CQYGL3F8E_Abnormal`
- **功能**: 展示企业的经营异常记录
- **数据来源**: `abnormal_info` 字段
#### 8. 欠税公告 (OwnTax)
- **文件位置**: `src/ui/CQYGL3F8E/components/OwnTax.vue`
- **API ID**: `CQYGL3F8E_OwnTax`
- **功能**: 展示企业的欠税公告记录
- **数据来源**: `own_tax` 字段
#### 9. 税收违法 (TaxContravention)
- **文件位置**: `src/ui/CQYGL3F8E/components/TaxContravention.vue`
- **API ID**: `CQYGL3F8E_TaxContravention`
- **功能**: 展示企业的税收违法记录
- **数据来源**: `tax_contravention` 字段
## 数据拆分逻辑
### 数据源结构
```javascript
{
data: {
apiID: 'QYGL3F8E',
data: {
items: [
{
orgName: '企业名称',
relationship: ['sh', 'tm'], // 关系类型
lawsuitInfo: { ... }, // 涉诉信息
basicInfo: { ... }, // 基本信息
stockHolderItem: { ... }, // 持股信息
staffList: { ... } // 人员列表
}
]
}
}
}
```
### 拆分规则
#### 投资企业记录
- **过滤条件**: `relationship` 包含投资类关系
- **关系类型**: `["sh", "his_sh", "lp", "his_lp"]`
- **包含字段**: 完整的企业信息,包括持股详情
#### 高管任职记录
- **过滤条件**: `relationship` 包含高管类关系
- **关系类型**: `["tm", "his_tm"]`
- **包含字段**: 完整的企业信息,重点关注任职信息
#### 涉诉风险
- **过滤条件**: `lawsuitInfo` 包含有效涉诉数据
- **检查字段**:
- `lawsuitInfo.entout.data` (非空对象)
- `lawsuitInfo.sxbzxr.data.sxbzxr` (非空数组)
- `lawsuitInfo.xgbzxr.data.xgbzxr` (非空数组)
- **包含字段**: 涉诉企业和总数统计
#### 对外投资历史
- **数据来源**: `invest_history.items` 数组
- **包含字段**: 投资企业信息、持股比例、注册资本等
#### 融资历史
- **数据来源**: `financing_history.items` 数组
- **包含字段**: 融资轮次、融资金额、投资者、新闻信息等
#### 行政处罚
- **数据来源**: `punishment_info.items` 数组
- **包含字段**: 处罚类型、处罚金额、处罚原因、处罚部门等
#### 经营异常
- **数据来源**: `abnormal_info.items` 数组
- **包含字段**: 异常原因、列入/移出日期、相关部门等
#### 欠税公告
- **数据来源**: `own_tax.items` 数组
- **包含字段**: 欠税金额、税务类型、欠税税种、纳税人信息、税务机关等
#### 税收违法
- **数据来源**: `tax_contravention.items` 数组
- **包含字段**: 案件性质、违法ID、税务机关、发布时间、纳税人名称等
## 工具函数
### simpleSplitter.js
位置: `src/ui/CQYGL3F8E/utils/simpleSplitter.js`
#### 主要函数
- `splitCQYGL3F8EForTabs(reportData)`: 数据拆分主函数
- `getRelationshipText(relation)`: 获取关系文本描述
- `getRelationshipClass(relation)`: 获取关系样式类
- `getStatusClass(status)`: 获取企业状态样式类
- `formatCapital(capital, currency)`: 格式化资本金额
- `formatDate(dateStr)`: 格式化日期显示
## 集成配置
### BaseReport.vue 配置
```javascript
// 导入拆分函数
import { splitCQYGL3F8EForTabs } from '@/ui/CQYGL3F8E/utils/simpleSplitter.js';
// 数据处理
const processedReportData = computed(() => {
let data = reportData.value;
// ... 其他拆分
data = splitCQYGL3F8EForTabs(data);
return data;
});
// 功能映射
const featureMap = {
QYGL3F8E: {
name: "人企关系加强版",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/index.vue")),
remark: '人企关系加强版提供全面的企业关联分析,包括投资企业记录、高管任职记录和涉诉风险等多维度信息。'
},
CQYGL3F8E_Investment: {
name: "投资企业记录",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Investment.vue")),
},
CQYGL3F8E_SeniorExecutive: {
name: "高管任职记录",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/SeniorExecutive.vue")),
},
CQYGL3F8E_Lawsuit: {
name: "涉诉风险",
component: defineAsyncComponent(() => import("@/ui/CQYGL3F8E/components/Lawsuit.vue")),
}
};
```
## 使用方式
### 完整视图
访问 `QYGL3F8E` 将显示完整的人企关系分析,包含所有九个子模块。
### 独立子模块
- 访问 `CQYGL3F8E_Investment` 仅显示投资企业记录
- 访问 `CQYGL3F8E_SeniorExecutive` 仅显示高管任职记录
- 访问 `CQYGL3F8E_Lawsuit` 仅显示涉诉风险
- 访问 `CQYGL3F8E_InvestHistory` 仅显示对外投资历史
- 访问 `CQYGL3F8E_FinancingHistory` 仅显示融资历史
- 访问 `CQYGL3F8E_Punishment` 仅显示行政处罚
- 访问 `CQYGL3F8E_Abnormal` 仅显示经营异常
- 访问 `CQYGL3F8E_OwnTax` 仅显示欠税公告
- 访问 `CQYGL3F8E_TaxContravention` 仅显示税收违法
## 特性
### 1. 数据过滤
- 基于关系类型智能过滤企业数据
- 支持多种关系类型的组合展示
### 2. 展开式详情
- 企业卡片支持点击展开查看详细信息
- 包含持股信息、基本信息、联系方式等
### 3. 状态标识
- 企业状态颜色编码(存续、注销、吊销等)
- 关系类型标签展示
### 4. 数据格式化
- 资本金额自动转换为万元单位
- 日期格式化显示
- 持股比例可视化进度条
### 5. 响应式设计
- 支持移动端和桌面端
- 自适应布局和交互
## 注意事项
1. **数据完整性**: 拆分后的数据保持原始结构的完整性
2. **性能优化**: 使用 `defineAsyncComponent` 实现组件懒加载
3. **错误处理**: 对缺失数据进行安全处理,避免渲染错误
4. **样式一致性**: 保持与整体设计系统的视觉一致性
## 更新历史
- **v1.0.0**: 初始版本,支持基本的企业关联信息展示
- **v2.0.0**: 模块拆分重构,支持独立子模块访问
- **v2.1.0**: 优化数据处理逻辑,增强错误处理能力
- **v2.2.0**: 新增欠税公告和税收违法模块,完善企业风险分析功能

View File

@@ -0,0 +1,273 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/jyyc.png" alt="经营异常" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">经营异常</span>
</div>
<!-- 按异常企业分组显示 -->
<div v-if="groupedAbnormal.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedAbnormal" :key="groupIndex" class="abnormal-group">
<!-- 异常企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 异常企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.companyName }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">异常企业</span>
<span class="text-sm text-gray-600">异常记录: <span class="ml-1 font-medium text-[#333333]">{{
group.totalAbnormals }}</span></span>
</div>
</div>
<!-- 异常记录列表 -->
<div class="space-y-3">
<div v-for="(abnormal, index) in group.abnormals" :key="index"
class="abnormal-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 异常卡片 - 可点击展开 -->
<div class="cursor-pointer relative"
@click="toggleAbnormalExpand(abnormal.putReason, 'abnormal', index)">
<!-- 异常原因 -->
<div class="px-4 py-2">
<div class="font-medium text-[#333333]">{{ abnormal.putReason || '经营异常' }}</div>
</div>
<!-- 异常信息 -->
<div class="px-4">
<!-- 状态标签 -->
<div class="mb-2">
<span class="text-sm bg-[#1FBE5D1A] rounded py-1 px-2 text-[#1FBE5D]">
已移出
</span>
<span v-if="abnormal.removeDepartment"
class="text-sm bg-primary-light rounded py-1 px-2 text-primary ml-1">
{{ abnormal.removeDepartment }}
</span>
</div>
<!-- 列入部门 -->
<div class="text-sm text-gray-600 mb-2">
<span>{{ abnormal.putDepartment || '—' }}</span>
</div>
<!-- 列入和移出日期 -->
<div class="text-sm text-gray-600 mb-2 flex items-center gap-4">
<div>
<span class="text-gray-500">列入</span>
<span>{{ formatDate(abnormal.putDate) }}</span>
</div>
<div v-if="abnormal.removeDate">
<span class="text-gray-500">移出</span>
<span>{{ formatDate(abnormal.removeDate) }}</span>
</div>
</div>
<!-- 移出部门 -->
<div v-if="abnormal.removeDepartment" class="text-sm text-gray-600">
<span class="text-gray-500">移出部门</span>
<span>{{ abnormal.removeDepartment }}</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isAbnormalExpanded(abnormal.putReason, 'abnormal', index) }" />
</div>
</div>
<!-- 异常详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isAbnormalExpanded(abnormal.putReason, 'abnormal', index),
'max-h-none opacity-100': isAbnormalExpanded(abnormal.putReason, 'abnormal', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 异常状态 -->
<span class="text-base text-[#666666] flex-shrink-0">异常状态</span>
<span class="text-base font-medium text-[#333333] break-words">{{
getStatusText(abnormal.removeDate) }}</span>
<!-- 列入原因 -->
<span class="text-base text-[#666666] flex-shrink-0">列入原因</span>
<span class="text-base font-medium text-[#333333] break-words">{{ abnormal.putReason || '-'
}}</span>
<!-- 列入部门 -->
<span class="text-base text-[#666666] flex-shrink-0">列入部门</span>
<span class="text-base font-medium text-[#333333] break-words">{{ abnormal.putDepartment || '-'
}}</span>
<!-- 列入日期 -->
<span class="text-base text-[#666666] flex-shrink-0">列入日期</span>
<span class="text-base font-medium text-[#333333] break-words">{{ formatDate(abnormal.putDate)
}}</span>
<!-- 移出日期 -->
<template v-if="abnormal.removeDate">
<span class="text-base text-[#666666] flex-shrink-0">移出日期</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatDate(abnormal.removeDate) }}</span>
</template>
<!-- 移出原因 -->
<template v-if="abnormal.removeReason">
<span class="text-base text-[#666666] flex-shrink-0">移出原因</span>
<span class="text-base font-medium text-[#333333] break-words">{{ abnormal.removeReason
}}</span>
</template>
<!-- 移出部门 -->
<template v-if="abnormal.removeDepartment">
<span class="text-base text-[#666666] flex-shrink-0">移出部门</span>
<span class="text-base font-medium text-[#333333] break-words">{{ abnormal.removeDepartment
}}</span>
</template>
<!-- 持续时长 -->
<span class="text-base text-[#666666] flex-shrink-0">持续时长</span>
<span class="text-base font-medium text-[#333333] break-words">{{
calculateDuration(abnormal.putDate,
abnormal.removeDate) }} </span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无异常记录时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2"></div>
暂无经营异常记录
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="经营异常展示申请人相关企业的经营异常情况,包括异常类型、异常原因、异常时间和移出情况等。数据来源于国家企业信用信息公示系统等权威渠道。异常状态分为异常中和已移出,已移出的异常风险相对较低。建议重点关注未移出的经营异常记录,特别是严重违法违规类型的异常。" />
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const abnormalInfo = computed(() => props.data?.items || [])
// 按异常企业分组
const groupedAbnormal = computed(() => {
const groups = {}
abnormalInfo.value.forEach(abnormal => {
const companyName = abnormal.companyName || '未知企业'
if (!groups[companyName]) {
groups[companyName] = {
companyName,
abnormals: [],
totalAbnormals: 0
}
}
groups[companyName].abnormals.push(abnormal)
groups[companyName].totalAbnormals++
})
return Object.values(groups)
})
// 用于跟踪展开的企业卡片
const expandedAbnormal = ref({})
// 切换展开/收起企业详情
const toggleAbnormalExpand = (abnormalId, listType, index) => {
const uniqueKey = `${abnormalId}_${listType}_${index}`
expandedAbnormal.value[uniqueKey] = !expandedAbnormal.value[uniqueKey]
}
// 检查企业是否展开
const isAbnormalExpanded = (abnormalId, listType, index) => {
const uniqueKey = `${abnormalId}_${listType}_${index}`
return !!expandedAbnormal.value[uniqueKey]
}
// 格式化日期显示
const formatDate = (dateStr) => {
if (!dateStr) return '—'
return dateStr
}
// 计算异常持续天数
const calculateDuration = (startDateStr, endDateStr) => {
if (!startDateStr) return '—'
const start = new Date(startDateStr)
const end = endDateStr ? new Date(endDateStr) : new Date() // 如果没有结束日期,则计算到今天
if (isNaN(start.getTime()) || isNaN(end.getTime())) return '—'
const diffTime = Math.abs(end.getTime() - start.getTime())
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return diffDays
}
// 获取状态文本
const getStatusText = (removeDate) => {
if (removeDate) {
return '已移出'
} else {
return '异常中'
}
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const abnormalList = abnormalInfo.value || [];
// 没有经营异常 -> 无风险 -> 100分
if (abnormalList.length === 0) {
return 100;
}
// 有经营异常 -> 中风险 -> 60分
return 60;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,265 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/rzls.png" alt="融资历史" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">融资历史</span>
</div>
<!-- 按融资企业分组显示 -->
<div v-if="groupedFinancing.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedFinancing" :key="groupIndex" class="financing-group">
<!-- 融资企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 融资企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.companyName }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">融资企业</span>
<span class="text-sm text-gray-600">融资轮次: <span class="ml-1 font-medium text-[#333333]">{{
group.totalFinancings }}</span></span>
</div>
</div>
<!-- 融资记录列表 -->
<div class="space-y-3">
<div v-for="(financing, index) in group.financings" :key="index"
class="financing-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 融资卡片 - 可点击展开 -->
<div class="cursor-pointer relative"
@click="toggleFinancingExpand(financing.companyName, 'financingHistory', index)">
<!-- 企业名称和轮次 -->
<div class="px-4 py-2 border-b border-[#0000000A] flex items-center justify-between">
<div class="font-medium text-[#333333] flex-1 truncate">{{ financing.companyName }}</div>
<span v-if="financing.round"
class="px-2 py-1 text-sm rounded font-medium flex-shrink-0 ml-2 bg-blue-50 text-blue-600">
{{ financing.round }}
</span>
</div>
<!-- 融资信息 -->
<div class="px-4 py-2">
<!-- 投资方 -->
<div class="text-sm text-gray-600 mb-2">
<span>投资方</span>
<span>{{ financing.investorName || '—' }}</span>
</div>
<!-- 披露时间 -->
<div class="text-sm text-gray-600 mb-2">
<span>披露</span>
<span>{{ formatDate(financing.pubTime) }}</span>
</div>
<!-- 融资金额 -->
<div class="text-sm mb-2">
<span class="text-gray-600">融资金额</span>
<span class="font-bold text-primary">{{ financing.money || '—' }}</span>
</div>
<!-- 底部标签行 -->
<div class="flex items-center gap-2 flex-wrap">
<!-- 估值 -->
<span v-if="financing.value" class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
估值: {{ financing.value }}
</span>
<!-- 股权 -->
<span v-if="financing.share" class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
股权: {{ financing.share }}
</span>
<!-- 有新闻标签 -->
<span v-if="financing.newsTitle" class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
有新闻
</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isFinancingExpanded(financing.companyName, 'financingHistory', index) }" />
</div>
</div>
<!-- 融资详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isFinancingExpanded(financing.companyName, 'financingHistory', index),
'max-h-none opacity-100': isFinancingExpanded(financing.companyName, 'financingHistory', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 轮次 -->
<span class="text-base text-[#666666] flex-shrink-0">轮次</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.round || '-' }}</span>
<!-- 投资方 -->
<span class="text-base text-[#666666] flex-shrink-0">投资方</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.investorName || '-'
}}</span>
<!-- 披露时间 -->
<span class="text-base text-[#666666] flex-shrink-0">披露时间</span>
<span class="text-base font-medium text-[#333333] break-words">{{ formatDate(financing.pubTime)
}}</span>
<!-- 融资金额 -->
<span class="text-base text-[#666666] flex-shrink-0">融资金额</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.money || '-' }}</span>
<!-- 企业估值 -->
<template v-if="financing.value">
<span class="text-base text-[#666666] flex-shrink-0">估值</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.value }}</span>
</template>
<!-- 股权 -->
<template v-if="financing.share">
<span class="text-base text-[#666666] flex-shrink-0">股权</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.share }}</span>
</template>
<!-- 新闻标题 -->
<template v-if="financing.newsTitle">
<span class="text-base text-[#666666] flex-shrink-0">新闻标题</span>
<span class="text-base font-medium text-[#333333] break-words">{{ financing.newsTitle }}</span>
</template>
<!-- 新闻链接 -->
<template v-if="financing.newsUrl">
<span class="text-base text-[#666666] flex-shrink-0">新闻链接</span>
<a :href="financing.newsUrl" target="_blank" rel="noopener noreferrer"
class="text-base font-medium text-blue-500 hover:underline break-words">
查看原文
</a>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无融资历史时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2">📈</div>
暂无融资历史记录
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="融资历史展示申请人相关企业的融资情况,包括融资轮次、融资金额、投资机构和融资时间等信息。数据来源于公开的融资信息和投资机构披露。融资历史可以反映企业的成长轨迹、资金实力和市场认可度。一般而言,有多轮融资经历的企业风险相对较低。建议关注融资阶段、投资机构的知名度和融资间隔时间。" />
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const financingHistory = computed(() => props.data?.items || [])
// 按融资企业分组
const groupedFinancing = computed(() => {
const groups = {}
financingHistory.value.forEach(financing => {
const companyName = financing.companyName || '未知企业'
if (!groups[companyName]) {
groups[companyName] = {
companyName,
financings: [],
totalFinancings: 0
}
}
groups[companyName].financings.push(financing)
groups[companyName].totalFinancings++
})
return Object.values(groups)
})
// 用于跟踪展开的企业卡片
const expandedFinancing = ref({})
// 切换展开/收起企业详情
const toggleFinancingExpand = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
expandedFinancing.value[uniqueKey] = !expandedFinancing.value[uniqueKey]
}
// 检查企业是否展开
const isFinancingExpanded = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
return !!expandedFinancing.value[uniqueKey]
}
// 格式化日期显示 (处理时间戳和日期字符串)
const formatDate = (dateInput) => {
if (!dateInput) return '—'
// 如果是时间戳(毫秒),转换为日期字符串
if (typeof dateInput === 'number' && dateInput > 1000000000000) {
return new Date(dateInput).toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
})
}
// 如果已经是日期字符串,直接返回
return dateInput
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const history = financingHistory.value || [];
// 没有融资历史 -> 无风险 -> 100分
if (history.length === 0) {
return 100;
}
// 有融资历史 -> 低风险 -> 90分
return 90;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,337 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/dwtzls.png" alt="对外投资历史" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">对外投资历史</span>
</div>
<!-- 按投资企业分组显示 -->
<div v-if="groupedInvestments.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedInvestments" :key="groupIndex" class="investment-group">
<!-- 投资企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 投资企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.investorCompany }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">投资企业</span>
<span class="text-sm text-gray-600">投资项目: <span class="ml-1 font-medium text-[#333333]">{{
group.totalInvestments }}</span></span>
</div>
</div>
<!-- 被投资企业列表 -->
<div class="space-y-3">
<div v-for="(investment, index) in group.investments" :key="index"
class="company-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 企业卡片 - 可点击展开 -->
<div class="cursor-pointer relative"
@click="toggleCompanyExpand(investment.name, 'investHistory', index)">
<!-- 企业名称和状态 -->
<div class="px-4 py-2 border-b border-[#0000000A] flex items-center justify-between">
<div class="font-medium text-[#333333] flex-1 truncate">{{ investment.name }}</div>
<span v-if="investment.regStatus" class="px-2 py-1 text-sm rounded font-medium flex-shrink-0 ml-2"
:class="getStatusClass(investment.regStatus)">
{{ investment.regStatus }}
</span>
</div>
<!-- 企业信息 -->
<div class="px-4 py-2">
<div class="flex items-center gap-2 text-sm text-gray-600 flex-1">
<span class="px-2 py-0.5 rounded bg-[#2B79EE1A] text-[#2B79EE] text-sm">
{{ getPersonTypeText(investment.personType) }}法人
</span>
<span>成立: {{ formatDate(investment.estiblishTime) }}</span>
</div>
<div class="text-sm text-gray-600 mt-2">
注册资本: {{ formatCapital(investment.regCapital) }}
</div>
<div class="flex items-center gap-1">
<span class="text-sm text-gray-600">持股比例:</span>
<span class="text-base font-bold text-primary">{{ investment.percent || '—' }}</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isCompanyExpanded(investment.name, 'investHistory', index) }" />
</div>
</div>
<!-- 企业详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isCompanyExpanded(investment.name, 'investHistory', index),
'max-h-none opacity-100': isCompanyExpanded(investment.name, 'investHistory', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 持股比例 -->
<span class="text-base text-[#666666] flex-shrink-0">持股比例</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.percent || '-'
}}</span>
<!-- 投资金额 -->
<template v-if="investment.amount">
<span class="text-base text-[#666666] flex-shrink-0">投资金额</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatAmount(investment.amount) }}</span>
</template>
<!-- 企业评分 -->
<template v-if="investment.pencertileScore">
<span class="text-base text-[#666666] flex-shrink-0">企业评分</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatScore(investment.pencertileScore) }}</span>
</template>
<!-- 法人类型 -->
<span class="text-base text-[#666666] flex-shrink-0">法人类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{
getPersonTypeText(investment.personType) }}</span>
<!-- 退出时间 -->
<template v-if="investment.withdrawalTime">
<span class="text-base text-[#666666] flex-shrink-0">退出时间</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatDate(investment.withdrawalTime) }}</span>
</template>
<!-- 省份 -->
<template v-if="investment.base">
<span class="text-base text-[#666666] flex-shrink-0">省份</span>
<span class="text-base font-medium text-[#333333] break-words">{{
getProvinceName(investment.base) }}</span>
</template>
<!-- 企业状态 -->
<span class="text-base text-[#666666] flex-shrink-0">企业状态</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.regStatus || '-'
}}</span>
<!-- 统一社会信用代码 -->
<template v-if="investment.creditCode">
<span class="text-base text-[#666666] flex-shrink-0">统一社会信用代码</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.creditCode
}}</span>
</template>
<!-- 企业类型 -->
<template v-if="investment.orgType">
<span class="text-base text-[#666666] flex-shrink-0">企业类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.orgType }}</span>
</template>
<!-- 行业 -->
<template v-if="investment.category">
<span class="text-base text-[#666666] flex-shrink-0">行业</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.category }}</span>
</template>
<!-- 注册资本 -->
<span class="text-base text-[#666666] flex-shrink-0">注册资本</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatCapital(investment.regCapital) }}</span>
<!-- 成立日期 -->
<span class="text-base text-[#666666] flex-shrink-0">成立日期</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatDate(investment.estiblishTime) }}</span>
<!-- 法人代表 -->
<template v-if="investment.legalPersonName">
<span class="text-base text-[#666666] flex-shrink-0">法人代表</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.legalPersonName
}}</span>
</template>
<!-- 经营范围 -->
<template v-if="investment.business_scope">
<span class="text-base text-[#666666] flex-shrink-0">经营范围</span>
<span class="text-base font-medium text-[#333333] break-words">{{ investment.business_scope
}}</span>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无投资历史时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2">💰</div>
暂无对外投资历史
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="对外投资历史展示申请人相关企业的对外投资情况,包括被投资企业名称、投资金额、投资比例和投资时间等。数据来源于工商登记信息和公开披露信息。对外投资可以反映企业的扩张能力、投资策略和资金实力。多元化投资组合通常表明企业具有较强的风险管控能力。建议关注投资集中度、被投资企业的行业分布和投资回报情况。" />
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const investHistory = computed(() => props.data?.items || [])
// 按投资企业分组
const groupedInvestments = computed(() => {
const groups = {}
investHistory.value.forEach(investment => {
const investorCompany = investment.companyName || '未知企业'
if (!groups[investorCompany]) {
groups[investorCompany] = {
investorCompany,
investments: [],
totalInvestments: 0
}
}
groups[investorCompany].investments.push(investment)
groups[investorCompany].totalInvestments++
})
return Object.values(groups)
})
// 用于跟踪展开的企业卡片
const expandedCompanies = ref({})
// 切换展开/收起企业详情
const toggleCompanyExpand = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
expandedCompanies.value[uniqueKey] = !expandedCompanies.value[uniqueKey]
}
// 检查企业是否展开
const isCompanyExpanded = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
return !!expandedCompanies.value[uniqueKey]
}
// 获取企业状态对应的样式
const getStatusClass = (status) => {
if (!status) return 'bg-gray-100 text-gray-500'
if (status.includes('注销') || status.includes('吊销')) {
return 'bg-red-50 text-red-600'
} else if (status.includes('存续') || status.includes('在营')) {
return 'bg-blue-50 text-blue-600'
} else if (status.includes('筹建') || status.includes('新设')) {
return 'bg-green-50 text-green-600'
} else {
return 'bg-yellow-50 text-yellow-600'
}
}
// 格式化日期显示
const formatDate = (dateStr) => {
if (!dateStr) return '—'
// 如果是时间戳,转换为日期
if (typeof dateStr === 'number') {
return new Date(dateStr).toLocaleDateString('zh-CN')
}
return dateStr
}
// 格式化资本金额显示
const formatCapital = (capital) => {
if (!capital) return '—'
return capital
}
// 获取法人类型文本
const getPersonTypeText = (personType) => {
if (personType === 1) return '自然人'
if (personType === 2) return '企业法人'
return '—'
}
// 格式化金额显示
const formatAmount = (amount) => {
if (!amount) return '—'
return amount
}
// 格式化评分显示
const formatScore = (score) => {
if (!score) return '—'
return `${score}/10000`
}
// 获取省份名称
const getProvinceName = (base) => {
const provinceMap = {
'bj': '北京',
'sh': '上海',
'gz': '广州',
'sz': '深圳',
'hz': '杭州',
'cd': '成都',
'wh': '武汉',
'nj': '南京',
'tj': '天津',
'xa': '西安'
}
return provinceMap[base] || base || '—'
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const history = investHistory.value || [];
// 没有投资历史 -> 无风险 -> 100分
if (history.length === 0) {
return 100;
}
// 有投资历史 -> 低风险 -> 90分
return 90;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,335 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden">
<div v-if="investCompanies.length > 0" class="space-y-3">
<div v-for="(company, index) in investCompanies" :key="index"
class="company-wrapper bg-[#F9F9F9] rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE] ">
<!-- 企业卡片 - 可点击展开 -->
<div class="cursor-pointer relative" @click="toggleCompanyExpand(company.orgName, 'invest', index)">
<!-- 企业名称 - 蓝色渐变背景 -->
<div class="px-4 py-2 flex items-center border border-[#0000000A]">
<div class="font-medium text-[#333333] truncate">{{ company.orgName }}</div>
</div>
<!-- 关系标签 - 靠左排列无背景 -->
<div class="px-4 py-1.5 flex items-center">
<div class="flex flex-wrap gap-1">
<span v-for="(relation, idx) in company.relationship" :key="idx" class="px-1.5 py-0.5 text-sm rounded"
:class="getRelationshipClass(relation)">
{{ getRelationshipText(relation) }}
</span>
</div>
</div>
<!-- 企业状态注册资本注册时间 -->
<div
class="px-4 py-2 flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2 text-sm text-gray-500">
<div class="flex items-center gap-3">
<span v-if="company.basicInfo && company.basicInfo.regStatus"
class="px-1.5 py-0.5 rounded-full inline-block" :class="getStatusClass(company.basicInfo.regStatus)">
{{ company.basicInfo.regStatus }}
</span>
<span v-if="company.basicInfo && company.basicInfo.regCapital">
{{ formatCapital(company.basicInfo.regCapital, company.basicInfo.regCapitalCurrency) }}
</span>
</div>
</div>
<!-- 展开指示器 - 放在右下角 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isCompanyExpanded(company.orgName, 'invest', index) }" />
</div>
</div>
<!-- 企业详情抽屉 - 投资企业记录 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isCompanyExpanded(company.orgName, 'invest', index),
'max-h-none opacity-100': isCompanyExpanded(company.orgName, 'invest', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="relative">
<!-- 持股信息 - 如果有的话 -->
<div v-if="company.stockHolderItem && Object.keys(company.stockHolderItem).length > 0"
class="mb-6 relative">
<LTitle title="持股信息" />
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3 p-4">
<span class="text-base text-[#666666]">股东名称</span>
<span class="text-base font-medium text-[#333333]">{{ company.stockHolderItem.orgHolderName || '-'
}}</span>
<span class="text-base text-[#666666]">股东类型</span>
<span class="text-base font-medium text-[#333333]">{{ company.stockHolderItem.orgHolderType || '-'
}}</span>
<span class="text-base text-[#666666]">出资金额</span>
<span class="text-base font-medium text-[#333333]">{{
formatSubscriptAmt(company.stockHolderItem.subscriptAmt) }}</span>
<span class="text-base text-[#666666]">持股比例</span>
<div class="flex items-center gap-2">
<span class="text-base font-medium text-[#333333]">{{ formatRate(company.stockHolderItem.investRate)
}}</span>
<span v-if="parseFloat(company.stockHolderItem.investRate) > 0"
class="text-sm px-2 py-0.5 rounded bg-primary-light text-primary">
{{ getInvestRateDesc(company.stockHolderItem.investRate) }}
</span>
</div>
<span class="text-base text-[#666666]">出资时间</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(company.stockHolderItem.investDate)
}}</span>
</div>
</div>
<!-- 基本信息 -->
<div v-if="company.basicInfo" class="mb-6 relative">
<LTitle title="基本信息" />
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3 p-4">
<span class="text-base text-[#666666]">企业状态</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regStatus || '-' }}</span>
<span class="text-base text-[#666666]">统一社会信用代码</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.creditCode || '-' }}</span>
<span class="text-base text-[#666666]">注册号</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regNumber || '-' }}</span>
<span class="text-base text-[#666666]">企业类型</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.entType || '-' }}</span>
<span class="text-base text-[#666666]">行业</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.industry || '-' }}</span>
<span class="text-base text-[#666666]">注册资本</span>
<span class="text-base font-medium text-[#333333]">{{ formatCapital(company.basicInfo.regCapital,
company.basicInfo.regCapitalCurrency) }}</span>
<span class="text-base text-[#666666]">成立日期</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(company.basicInfo.estiblishTime)
}}</span>
<span class="text-base text-[#666666]">登记机关</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regInstitute || '-' }}</span>
<span class="text-base text-[#666666]">核准日期</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(company.basicInfo.approvedTime)
}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2">📊</div>
暂无投资企业记录
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="投资企业记录展示申请人相关企业的对外投资情况,包括投资企业名称、投资金额、投资比例和投资时间等。数据来源于工商登记信息和公开披露信息。投资组合可以反映企业的投资策略、风险偏好和资金实力。高度集中的投资或跨行业投资可能存在特定风险。建议关注投资集中度、投资表现和目标企业的经营状况。" />
</template>
<script setup>
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const investCompanies = computed(() => props.data || [])
// 用于跟踪展开的企业卡片
const expandedCompanies = ref({})
// 切换展开/收起企业详情
const toggleCompanyExpand = (companyId, listType, index) => {
// 创建唯一标识符,包含企业名称、列表类型和索引
const uniqueKey = `${companyId}_${listType}_${index}`
expandedCompanies.value[uniqueKey] = !expandedCompanies.value[uniqueKey]
}
// 检查企业是否展开
const isCompanyExpanded = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
return !!expandedCompanies.value[uniqueKey]
}
// 获取关系文本
const getRelationshipText = relation => {
return relationshipMap[relation]?.text || relation
}
// 获取关系样式类
const getRelationshipClass = relation => {
return relationshipMap[relation]?.color || ''
}
// 关系映射表
const relationshipMap = {
sh: { text: '股东', color: 'bg-primary-light text-primary', icon: '👑' },
his_sh: { text: '曾任股东', color: 'bg-primary-light text-primary', icon: '🕒👑' },
lp: { text: '法人', color: 'bg-green-100 text-green-700', icon: '⚖️' },
his_lp: { text: '曾任法人', color: 'bg-green-50 text-green-600', icon: '🕒⚖️' },
tm: { text: '高管', color: 'bg-purple-100 text-purple-700', icon: '👔' },
his_tm: { text: '曾任高管', color: 'bg-purple-50 text-purple-600', icon: '🕒👔' },
}
// 获取企业状态对应的样式
const getStatusClass = status => {
if (!status) return 'bg-gray-100 text-gray-500'
if (status.includes('注销') || status.includes('吊销')) {
return 'bg-red-50 text-red-600'
} else if (status.includes('存续') || status.includes('在营')) {
return 'bg-[#2B79EE1A] text-[#2B79EE]'
} else if (status.includes('筹建') || status.includes('新设')) {
return 'bg-green-50 text-green-600'
} else {
return 'bg-yellow-50 text-yellow-600'
}
}
// 格式化日期显示
const formatDate = dateStr => {
if (!dateStr) return '-'
return dateStr
}
// 格式化资本金额显示
const formatCapital = (capital, currency) => {
if (!capital) return '-'
// 检查是否包含"万"字或需要显示为万元
let unit = ''
let value = parseFloat(capital)
// 处理原始数据中可能带有的单位
if (typeof capital === 'string' && capital.includes('万')) {
unit = '万'
// 提取数字部分
const numMatch = capital.match(/[\d.]+/)
value = numMatch ? parseFloat(numMatch[0]) : 0
} else if (value >= 10000) {
// 大额数字转换为万元显示
value = value / 10000
unit = '万'
}
// 格式化数字,保留两位小数(如果有小数部分)
const formattedValue = value.toLocaleString('zh-CN', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})
return `${formattedValue}${unit} ${currency || '人民币'}`
}
// 格式化占比
const formatRate = rate => {
if (!rate) return '-'
// 移除末尾不必要的0和小数点
const formatted = parseFloat(rate)
.toFixed(2)
.replace(/\.?0+$/, '')
return `${formatted}%`
}
// 格式化出资金额(特别处理,统一使用万元单位)
const formatSubscriptAmt = amount => {
if (!amount) return '-'
let value = parseFloat(amount)
// 处理原始数据中可能带有的单位
if (typeof amount === 'string') {
// 提取数字部分
const numMatch = amount.match(/[\d.]+/)
value = numMatch ? parseFloat(numMatch[0]) : 0
}
// 如果数值大于等于10000则转换为万元
if (value >= 10000) {
value = value / 10000
}
// 格式化数字,保留两位小数(如果有小数部分)
const formattedValue = value.toLocaleString('zh-CN', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})
return `${formattedValue} 万元`
}
// 获取持股比例对应的颜色样式
const getInvestRateClass = rate => {
if (!rate) return 'bg-gray-200'
const value = parseFloat(rate)
if (value >= 50) return 'bg-blue-500' // 控股或全资
if (value >= 20) return 'bg-blue-400' // 重要股东
if (value >= 10) return 'bg-blue-300' // 主要股东
return 'bg-blue-200' // 一般股东
}
// 获取持股比例的描述
const getInvestRateDesc = rate => {
if (!rate) return ''
const value = parseFloat(rate)
if (value >= 50) return '控股股东'
if (value >= 20) return '重要股东'
if (value >= 10) return '主要股东'
if (value > 0) return '一般股东'
return ''
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const companies = investCompanies.value || [];
// 没有投资企业 -> 无风险 -> 100分
if (companies.length === 0) {
return 100;
}
// 有投资企业 -> 低风险 -> 90分
return 90;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,289 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/xzcf.png" alt="行政处罚" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">行政处罚</span>
</div>
<!-- 按处罚企业分组显示 -->
<div v-if="groupedPunishments.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedPunishments" :key="groupIndex" class="punishment-group">
<!-- 处罚企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 处罚企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.companyName }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">被处罚企业</span>
<span class="text-sm text-gray-600">处罚记录: <span class="ml-1 font-medium text-[#333333]">{{
group.totalPunishments }}</span></span>
</div>
</div>
<!-- 处罚记录列表 -->
<div class="space-y-3">
<div v-for="(punishment, index) in group.punishments" :key="index"
class="punishment-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 处罚卡片 - 可点击展开 -->
<div class="cursor-pointer relative"
@click="togglePunishmentExpand(punishment.punishNumber, 'punishment', index)">
<!-- 处罚名称和处罚类型 -->
<div class="px-4 py-2 border-b border-[#0000000A] flex items-center justify-between">
<div class="font-medium text-[#333333] flex-1 truncate">
{{ punishment.punishName || punishment.punishNumber || '行政处罚决定' }}
</div>
<span v-if="punishment.type"
class="px-2 py-1 text-sm rounded font-medium flex-shrink-0 ml-2 bg-blue-50 text-blue-600">
{{ punishment.type }}
</span>
</div>
<!-- 处罚信息 -->
<div class="px-4 py-2">
<!-- 处罚单位 -->
<div class="text-sm text-gray-600 mb-2">
<span>处罚单位</span>
<span>{{ punishment.departmentName || '—' }}</span>
</div>
<!-- 决定日期 -->
<div class="text-sm text-gray-600 mb-2">
<span>决定日期</span>
<span>{{ formatDate(punishment.decisionDate) }}</span>
</div>
<!-- 处罚金额 -->
<div class="text-sm mb-2">
<span class="text-gray-600">处罚金额</span>
<span class="font-bold text-primary">{{ formatPecuniary(punishment.pecuniary) }}</span>
</div>
<!-- 底部标签行 -->
<div class="flex items-center gap-2 flex-wrap">
<!-- 处罚事由 -->
<span v-if="punishment.reason" class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
{{ punishment.reason }}
</span>
<!-- 处罚状态 -->
<span v-if="punishment.punishStatus"
class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
{{ punishment.punishStatus }}
</span>
<!-- 法人 -->
<span v-if="punishment.legalPersonName"
class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
法人: {{ punishment.legalPersonName }}
</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isPunishmentExpanded(punishment.punishNumber, 'punishment', index) }" />
</div>
</div>
<!-- 处罚详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isPunishmentExpanded(punishment.punishNumber, 'punishment', index),
'max-h-none opacity-100': isPunishmentExpanded(punishment.punishNumber, 'punishment', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 处罚类型 -->
<span class="text-base text-[#666666] flex-shrink-0">处罚类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.type || '-' }}</span>
<!-- 处罚单位 -->
<span class="text-base text-[#666666] flex-shrink-0">处罚单位</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.departmentName || '-'
}}</span>
<!-- 决定日期 -->
<span class="text-base text-[#666666] flex-shrink-0">决定日期</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatDate(punishment.decisionDate) }}</span>
<!-- 处罚金额 -->
<span class="text-base text-[#666666] flex-shrink-0">处罚金额</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatPecuniary(punishment.pecuniary) }}</span>
<!-- 处罚事由 -->
<span class="text-base text-[#666666] flex-shrink-0">处罚事由</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.reason || '-'
}}</span>
<!-- 处罚内容 -->
<template v-if="punishment.content">
<span class="text-base text-[#666666] flex-shrink-0">处罚内容</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.content }}</span>
</template>
<!-- 处罚依据 -->
<template v-if="punishment.evidence">
<span class="text-base text-[#666666] flex-shrink-0">处罚依据</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.evidence }}</span>
</template>
<!-- 决定文书号 -->
<span class="text-base text-[#666666] flex-shrink-0">决定文书号</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.punishNumber || '-'
}}</span>
<!-- 法定代表人 -->
<template v-if="punishment.legalPersonName">
<span class="text-base text-[#666666] flex-shrink-0">法定代表人</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.legalPersonName
}}</span>
</template>
<!-- 处罚状态 -->
<template v-if="punishment.punishStatus">
<span class="text-base text-[#666666] flex-shrink-0">处罚状态</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.punishStatus
}}</span>
</template>
<!-- 数据来源 -->
<template v-if="punishment.source">
<span class="text-base text-[#666666] flex-shrink-0">数据来源</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.source }}</span>
</template>
<!-- 备注 -->
<template v-if="punishment.remark">
<span class="text-base text-[#666666] flex-shrink-0">备注</span>
<span class="text-base font-medium text-[#333333] break-words">{{ punishment.remark }}</span>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无处罚记录时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2"></div>
暂无行政处罚记录
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="行政处罚展示申请人相关企业受到的行政处罚情况,包括处罚类型、处罚原因、处罚金额和处罚时间等信息。数据来源于各级政府部门的行政处罚公示信息。处罚类型包括罚款、警告、停业整顿等,处罚金额和频率可以反映企业的合规经营情况。建议重点关注近期和高金额的行政处罚记录,评估其对企业经营的影响。" />
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const punishments = computed(() => props.data?.items || [])
// 按处罚企业分组
const groupedPunishments = computed(() => {
const groups = {}
punishments.value.forEach(punishment => {
const companyName = punishment.companyName || '未知企业'
if (!groups[companyName]) {
groups[companyName] = {
companyName,
punishments: [],
totalPunishments: 0
}
}
groups[companyName].punishments.push(punishment)
groups[companyName].totalPunishments++
})
return Object.values(groups)
})
// 用于跟踪展开的企业卡片
const expandedPunishments = ref({})
// 切换展开/收起企业详情
const togglePunishmentExpand = (punishmentId, listType, index) => {
const uniqueKey = `${punishmentId}_${listType}_${index}`
expandedPunishments.value[uniqueKey] = !expandedPunishments.value[uniqueKey]
}
// 检查企业是否展开
const isPunishmentExpanded = (punishmentId, listType, index) => {
const uniqueKey = `${punishmentId}_${listType}_${index}`
return !!expandedPunishments.value[uniqueKey]
}
// 格式化日期显示
const formatDate = (dateStr) => {
if (!dateStr) return '—'
return dateStr
}
// 格式化处罚金额
const formatPecuniary = (pecuniary) => {
if (!pecuniary) return '—'
const value = parseFloat(pecuniary)
if (isNaN(value)) return '—'
return `${value.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 })} 元`
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const punishmentList = punishments.value || [];
// 没有行政处罚 -> 无风险 -> 100分
if (punishmentList.length === 0) {
return 100;
}
// 有行政处罚 -> 高风险 -> 40分
return 40;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,307 @@
<template>
<!-- 高管任职记录 -->
<div class="card shadow-sm rounded-xl overflow-hidden">
<div v-if="managerPositions.length > 0" class="space-y-3">
<div v-for="(company, index) in managerPositions" :key="index"
class="company-wrapper bg-[#F9F9F9] rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE] ">
<!-- 企业卡片 - 可点击展开 -->
<div class="cursor-pointer relative" @click="toggleCompanyExpand(company.orgName, 'manager', index)">
<!-- 企业名称 - 蓝色渐变背景 -->
<div class="px-4 py-2 flex items-center border border-[#0000000A]">
<div class="font-medium text-[#333333] truncate">{{ company.orgName }}</div>
</div>
<!-- 关系标签 - 靠左排列无背景 -->
<div class="px-4 py-1.5 flex items-center">
<div class="flex flex-wrap gap-1">
<span v-for="(relation, idx) in company.relationship" :key="idx" class="px-1.5 py-0.5 text-sm rounded"
:class="getRelationshipClass(relation)">
{{ getRelationshipText(relation) }}
</span>
</div>
</div>
<!-- 企业状态注册资本注册时间 -->
<div
class="px-4 py-2 flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2 text-sm text-gray-500">
<div class="flex items-center gap-3">
<span v-if="company.basicInfo && company.basicInfo.regStatus"
class="px-1.5 py-0.5 rounded-full inline-block" :class="getStatusClass(company.basicInfo.regStatus)">
{{ company.basicInfo.regStatus }}
</span>
<span v-if="company.basicInfo && company.basicInfo.regCapital">
{{ formatCapital(company.basicInfo.regCapital, company.basicInfo.regCapitalCurrency) }}
</span>
</div>
</div>
<!-- 展开指示器 - 放在右下角 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isCompanyExpanded(company.orgName, 'manager', index) }" />
</div>
</div>
<!-- 企业详情抽屉 - 高管任职记录 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isCompanyExpanded(company.orgName, 'manager', index),
'max-h-none opacity-100': isCompanyExpanded(company.orgName, 'manager', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="relative">
<!-- 任职信息 -->
<div v-if="company.relationship && company.relationship.length > 0" class="mb-6 relative">
<LTitle title="任职信息" />
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3 p-4">
<span class="text-base text-[#666666]">担任职务</span>
<span class="text-base font-medium text-[#333333]">{{ getManagerPositionDesc(company.relationship)
}}</span>
<template v-if="company.staffList && company.staffList.result && company.staffList.result.length > 0">
<span class="text-base text-[#666666]">公司职位</span>
<div class="flex flex-wrap gap-1">
<template v-for="(staff, staffIdx) in company.staffList.result" :key="staffIdx">
<template v-if="staff.typeJoin && staff.typeJoin.length > 0">
<span v-for="(position, posIdx) in staff.typeJoin" :key="posIdx"
class="text-sm px-2 py-0.5 rounded bg-primary-light text-primary">
{{ position }}
</span>
</template>
</template>
</div>
</template>
</div>
</div>
<!-- 基本信息 -->
<div v-if="company.basicInfo" class="mb-6 relative">
<LTitle title="基本信息" />
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3 p-4">
<span class="text-base text-[#666666]">企业状态</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regStatus || '-' }}</span>
<span class="text-base text-[#666666]">统一社会信用代码</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.creditCode || '-' }}</span>
<span class="text-base text-[#666666]">注册号</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regNumber || '-' }}</span>
<span class="text-base text-[#666666]">企业类型</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.entType || '-' }}</span>
<span class="text-base text-[#666666]">行业</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.industry || '-' }}</span>
<span class="text-base text-[#666666]">注册资本</span>
<span class="text-base font-medium text-[#333333]">{{ formatCapital(company.basicInfo.regCapital,
company.basicInfo.regCapitalCurrency) }}</span>
<span class="text-base text-[#666666]">成立日期</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(company.basicInfo.estiblishTime)
}}</span>
<span class="text-base text-[#666666]">登记机关</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regInstitute || '-' }}</span>
<span class="text-base text-[#666666]">核准日期</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(company.basicInfo.approvedTime)
}}</span>
</div>
</div>
<!-- 联系方式 -->
<div v-if="company.basicInfo" class="mb-6 relative">
<LTitle title="联系方式" />
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3 p-4">
<span class="text-base text-[#666666]">电话</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.phone || '-' }}</span>
<span class="text-base text-[#666666]">邮箱</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.email || '-' }}</span>
<span class="text-base text-[#666666]">网址</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.website || '-' }}</span>
<span class="text-base text-[#666666]">注册地址</span>
<span class="text-base font-medium text-[#333333]">{{ company.basicInfo.regAddress || '-' }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2">👔</div>
暂无高管任职记录
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="高管任职记录展示申请人在相关企业中担任的管理职务,包括职务类型、任职时间和企业信息等。数据来源于工商登记信息和公开披露信息。高管职务包括董事长、总经理、监事等重要职位,可以反映申请人的管理经验和企业资源。多个高管职务可能表明较强的商业能力,但也可能存在连带风险。建议关注任职企业的经营状况和行业风险。" />
</template>
<script setup>
import LRemark from '@/components/LRemark.vue'
import LTitle from '@/components/LTitle.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
// 获取高管任职记录(高管、历史高管)
const managerPositions = computed(() => {
return props.data || []
})
onMounted(() => {
setTimeout(() => {
}, 1000)
})
// 用于跟踪展开的企业卡片
const expandedCompanies = ref({})
// 切换展开/收起企业详情
const toggleCompanyExpand = (companyId, listType, index) => {
// 创建唯一标识符,包含企业名称、列表类型和索引
const uniqueKey = `${companyId}_${listType}_${index}`
expandedCompanies.value[uniqueKey] = !expandedCompanies.value[uniqueKey]
}
// 检查企业是否展开
const isCompanyExpanded = (companyId, listType, index) => {
const uniqueKey = `${companyId}_${listType}_${index}`
return !!expandedCompanies.value[uniqueKey]
}
// 关系映射表
const relationshipMap = {
sh: { text: '股东', color: 'bg-primary-light text-primary', icon: '👑' },
his_sh: { text: '曾任股东', color: 'bg-primary-light text-primary', icon: '🕒👑' },
lp: { text: '法人', color: 'bg-green-100 text-green-700', icon: '⚖️' },
his_lp: { text: '曾任法人', color: 'bg-green-50 text-green-600', icon: '🕒⚖️' },
tm: { text: '高管', color: 'bg-purple-100 text-purple-700', icon: '👔' },
his_tm: { text: '曾任高管', color: 'bg-purple-50 text-purple-600', icon: '🕒👔' },
}
// 获取关系文本
const getRelationshipText = relation => {
return relationshipMap[relation]?.text || relation
}
// 获取关系样式类
const getRelationshipClass = relation => {
return relationshipMap[relation]?.color || ''
}
// 高管类关系
const managerRelations = ['tm', 'his_tm']
// 获取企业状态对应的样式
const getStatusClass = status => {
if (!status) return 'bg-gray-100 text-gray-500'
if (status.includes('注销') || status.includes('吊销')) {
return 'bg-red-50 text-red-600'
} else if (status.includes('存续') || status.includes('在营')) {
return 'bg-[#2B79EE1A] text-[#2B79EE]'
} else if (status.includes('筹建') || status.includes('新设')) {
return 'bg-green-50 text-green-600'
} else {
return 'bg-yellow-50 text-yellow-600'
}
}
// 格式化日期显示
const formatDate = dateStr => {
if (!dateStr) return '-'
return dateStr
}
// 格式化资本金额显示
const formatCapital = (capital, currency) => {
if (!capital) return '-'
// 检查是否包含"万"字或需要显示为万元
let unit = ''
let value = parseFloat(capital)
// 处理原始数据中可能带有的单位
if (typeof capital === 'string' && capital.includes('万')) {
unit = '万'
// 提取数字部分
const numMatch = capital.match(/[\d.]+/)
value = numMatch ? parseFloat(numMatch[0]) : 0
} else if (value >= 10000) {
// 大额数字转换为万元显示
value = value / 10000
unit = '万'
}
// 格式化数字,保留两位小数(如果有小数部分)
const formattedValue = value.toLocaleString('zh-CN', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})
return `${formattedValue}${unit} ${currency || '人民币'}`
}
// 获取高管职位的描述
const getManagerPositionDesc = relations => {
if (!relations || relations.length === 0) return '-'
const positions = []
relations.forEach(relation => {
if (managerRelations.includes(relation)) {
positions.push(getRelationshipText(relation))
}
})
return positions.length > 0 ? positions.join('、') : '-'
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const positions = managerPositions.value || [];
// 没有高管任职记录 -> 无风险 -> 100分
if (positions.length === 0) {
return 100;
}
// 有高管任职记录 -> 低风险 -> 90分
return 90;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,265 @@
<template>
<div class="">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/qsgg.png" alt="欠税公告" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">欠税公告</span>
</div>
<!-- 按企业分组显示 -->
<div v-if="groupedOwnTax.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedOwnTax" :key="groupIndex" class="own-tax-group">
<!-- 欠税企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 欠税企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.companyName }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">欠税企业</span>
<span class="text-sm text-gray-600">欠税记录: <span class="ml-1 font-medium text-[#333333]">{{
group.totalOwnTax }}</span></span>
</div>
</div>
<!-- 欠税记录列表 -->
<div class="space-y-3">
<div v-for="(tax, index) in group.ownTaxes" :key="index"
class="tax-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 欠税卡片 - 可点击展开 -->
<div class="cursor-pointer relative" @click="toggleTaxExpand(tax.taxIdNumber, 'ownTax', index)">
<!-- 税务类型标签 -->
<div class="px-4 py-2 border-b border-[#0000000A]">
<div class="flex items-center gap-2">
<span v-if="tax.type" class="px-2 py-1 text-sm rounded font-medium bg-blue-50 text-blue-600">
{{ tax.type }}
</span>
<span class="font-medium text-[#333333]">{{ tax.taxCategory || '企业所得税' }}</span>
</div>
</div>
<!-- 欠税信息 -->
<div class="px-4">
<!-- 纳税人 -->
<div class="text-sm text-gray-600 mb-2">
<span>纳税人</span>
<span>{{ tax.name || '—' }}</span>
</div>
<!-- 发布日期 -->
<div class="text-sm text-gray-600 mb-2">
<span>发布</span>
<span>{{ formatDate(tax.publishDate) }}</span>
</div>
<!-- 欠税金额 -->
<div class="text-sm mb-2">
<span class="text-gray-600">欠税金额</span>
<span class="font-bold text-primary">{{ tax.ownTaxAmount || '—' }}</span>
</div>
<!-- 识别号和纳税人类型 -->
<div class="flex items-center gap-2 flex-wrap mb-2">
<span class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
识别号: {{ tax.taxIdNumber || '—' }}
</span>
<span class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
纳税人类型: {{ tax.taxpayerType || '—' }}
</span>
<span v-if="tax.legalpersonName" class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
法人: {{ tax.legalpersonName }}
</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isTaxExpanded(tax.taxIdNumber, 'ownTax', index) }" />
</div>
</div>
<!-- 欠税详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isTaxExpanded(tax.taxIdNumber, 'ownTax', index),
'max-h-none opacity-100': isTaxExpanded(tax.taxIdNumber, 'ownTax', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 纳税人名称 -->
<span class="text-base text-[#666666] flex-shrink-0">纳税人名称</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.name || '-' }}</span>
<!-- 纳税人识别号 -->
<span class="text-base text-[#666666] flex-shrink-0">纳税人识别号</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.taxIdNumber || '-' }}</span>
<!-- 纳税人类型 -->
<span class="text-base text-[#666666] flex-shrink-0">纳税人类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.taxpayerType || '-'
}}</span>
<!-- 税务类型 -->
<template v-if="tax.type">
<span class="text-base text-[#666666] flex-shrink-0">税务类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.type }}</span>
</template>
<!-- 欠税税种 -->
<template v-if="tax.taxCategory">
<span class="text-base text-[#666666] flex-shrink-0">欠税税种</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.taxCategory }}</span>
</template>
<!-- 发布时间 -->
<span class="text-base text-[#666666] flex-shrink-0">发布时间</span>
<span class="text-base font-medium text-[#333333] break-words">{{ formatDate(tax.publishDate)
}}</span>
<!-- 欠税金额 -->
<span class="text-base text-[#666666] flex-shrink-0">欠税金额</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.ownTaxAmount || '-'
}}</span>
<!-- 欠税余额 -->
<template v-if="tax.ownTaxBalance">
<span class="text-base text-[#666666] flex-shrink-0">欠税余额</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.ownTaxBalance }}</span>
</template>
<!-- 新发生欠税余额 -->
<template v-if="tax.newOwnTaxBalance">
<span class="text-base text-[#666666] flex-shrink-0">新发生欠税余额</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.newOwnTaxBalance }}</span>
</template>
<!-- 法人或负责人 -->
<template v-if="tax.legalpersonName">
<span class="text-base text-[#666666] flex-shrink-0">法人或负责人</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.legalpersonName }}</span>
</template>
<!-- 注册类型 -->
<template v-if="tax.regType">
<span class="text-base text-[#666666] flex-shrink-0">注册类型</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.regType }}</span>
</template>
<!-- 经营地点 -->
<template v-if="tax.location">
<span class="text-base text-[#666666] flex-shrink-0">经营地点</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.location }}</span>
</template>
<!-- 税务机关 -->
<template v-if="tax.department">
<span class="text-base text-[#666666] flex-shrink-0">税务机关</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.department }}</span>
</template>
<!-- 证件号码 -->
<template v-if="tax.personIdNumber">
<span class="text-base text-[#666666] flex-shrink-0">证件号码</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.personIdNumber }}</span>
</template>
<!-- 证件名称 -->
<template v-if="tax.personIdName">
<span class="text-base text-[#666666] flex-shrink-0">证件名称</span>
<span class="text-base font-medium text-[#333333] break-words">{{ tax.personIdName }}</span>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无欠税记录时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2">💰</div>
暂无欠税公告记录
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const ownTaxData = computed(() => props.data?.items || [])
// 按企业分组
const groupedOwnTax = computed(() => {
const groups = {}
ownTaxData.value.forEach(tax => {
const companyName = tax.name || '未知企业'
if (!groups[companyName]) {
groups[companyName] = {
companyName,
ownTaxes: [],
totalOwnTax: 0
}
}
groups[companyName].ownTaxes.push(tax)
groups[companyName].totalOwnTax++
})
return Object.values(groups)
})
// 用于跟踪展开的欠税卡片
const expandedTax = ref({})
// 切换展开/收起欠税详情
const toggleTaxExpand = (taxId, listType, index) => {
const uniqueKey = `${taxId}_${listType}_${index}`
expandedTax.value[uniqueKey] = !expandedTax.value[uniqueKey]
}
// 检查欠税是否展开
const isTaxExpanded = (taxId, listType, index) => {
const uniqueKey = `${taxId}_${listType}_${index}`
return !!expandedTax.value[uniqueKey]
}
// 格式化日期显示
const formatDate = (dateStr) => {
if (!dateStr) return '—'
// 如果是时间戳,转换为日期
if (typeof dateStr === 'number') {
return new Date(dateStr).toLocaleDateString('zh-CN')
}
return dateStr
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,208 @@
<template>
<div class="">
<div class="border border-[#EEEEEE] p-4 rounded-xl">
<div class="flex items-center mb-3">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/sswf.png" alt="税收违法" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">税收违法</span>
</div>
<!-- 按企业分组显示 -->
<div v-if="groupedTaxContraventions.length > 0" class="space-y-4">
<div v-for="(group, groupIndex) in groupedTaxContraventions" :key="groupIndex" class="tax-contravention-group">
<!-- 违法企业容器 -->
<div class="bg-[#F5F5F5] rounded-lg p-3">
<!-- 违法企业标题 -->
<div class="mb-3">
<div class="flex items-center border-b border-[#0000000A] pb-2">
<div class="text-[#333333] font-medium">{{ group.companyName }}</div>
</div>
<div class="flex items-center justify-between pt-2">
<span class="text-sm text-gray-600">违法企业</span>
<span class="text-sm text-gray-600">违法记录: <span class="ml-1 font-medium text-[#333333]">{{
group.totalContraventions }}</span></span>
</div>
</div>
<!-- 违法记录列表 -->
<div class="space-y-3">
<div v-for="(contravention, index) in group.contraventions" :key="index"
class="contravention-wrapper bg-white rounded-lg overflow-hidden shadow-sm border border-[#EEEEEE]">
<!-- 违法卡片 - 可点击展开 -->
<div class="cursor-pointer relative"
@click="toggleContraventionExpand(contravention.id, 'taxContravention', index)">
<!-- 企业名称和案件性质 -->
<div class="px-4 py-2 border-b border-[#0000000A] flex items-center justify-between">
<div class="font-medium text-[#333333] flex-1">
<span v-if="contravention.case_type"
class="px-2 py-1 text-sm rounded font-medium flex-shrink-0 mr-2 bg-[#EB3C3C1A] text-[#EB3C3C] border border-[#EB3C3C]">
{{ contravention.case_type }}
</span>
<span>{{ contravention.taxpayer_name || group.companyName }}</span>
</div>
</div>
<!-- 违法信息 -->
<div class="px-4 py-2">
<!-- 违法ID和发布日期 -->
<div class="flex items-center gap-4 text-sm text-gray-600 mb-2">
<div>
<span>违法ID</span>
<span>{{ contravention.id || '—' }}</span>
</div>
<div>
<span>发布</span>
<span>{{ formatDate(contravention.publish_time) }}</span>
</div>
</div>
<!-- 案件编号 -->
<div class="text-sm mb-2">
<span class="text-gray-600">案件编号</span>
<span class="font-bold text-primary">#{{ contravention.id || '—' }}</span>
</div>
<!-- 税务机关 -->
<div v-if="contravention.department" class="flex items-center gap-2 flex-wrap mb-2">
<span class="text-sm bg-primary-light rounded py-1 px-2 text-primary">
税务机关: {{ contravention.department }}
</span>
</div>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-2 flex items-center text-sm text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isContraventionExpanded(contravention.id, 'taxContravention', index) }" />
</div>
</div>
<!-- 违法详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isContraventionExpanded(contravention.id, 'taxContravention', index),
'max-h-none opacity-100': isContraventionExpanded(contravention.id, 'taxContravention', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="grid grid-cols-[max-content_1fr] gap-y-3 p-4">
<!-- 违法ID -->
<span class="text-base text-[#666666] flex-shrink-0">违法ID</span>
<span class="text-base font-medium text-[#333333] break-words">#{{ contravention.id || '-'
}}</span>
<!-- 纳税人名称 -->
<span class="text-base text-[#666666] flex-shrink-0">纳税人名称</span>
<span class="text-base font-medium text-[#333333] break-words">{{ contravention.taxpayer_name ||
'-' }}</span>
<!-- 案件性质 -->
<template v-if="contravention.case_type">
<span class="text-base text-[#666666] flex-shrink-0">案件性质</span>
<span class="text-base font-medium text-[#333333] break-words">{{ contravention.case_type
}}</span>
</template>
<!-- 发布时间 -->
<span class="text-base text-[#666666] flex-shrink-0">发布时间</span>
<span class="text-base font-medium text-[#333333] break-words">{{
formatDate(contravention.publish_time) }}</span>
<!-- 所属税务机关 -->
<template v-if="contravention.department">
<span class="text-base text-[#666666] flex-shrink-0">所属税务机关</span>
<span class="text-base font-medium text-[#333333] break-words">{{ contravention.department
}}</span>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无税收违法记录时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2"></div>
暂无税收违法记录
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({ items: [], total: 0 }),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
const taxContraventionData = computed(() => props.data?.items || [])
// 按企业分组
const groupedTaxContraventions = computed(() => {
const groups = {}
taxContraventionData.value.forEach(contravention => {
const companyName = contravention.taxpayer_name || '未知企业'
if (!groups[companyName]) {
groups[companyName] = {
companyName,
contraventions: [],
totalContraventions: 0
}
}
groups[companyName].contraventions.push(contravention)
groups[companyName].totalContraventions++
})
return Object.values(groups)
})
// 用于跟踪展开的违法卡片
const expandedContravention = ref({})
// 切换展开/收起违法详情
const toggleContraventionExpand = (contraventionId, listType, index) => {
const uniqueKey = `${contraventionId}_${listType}_${index}`
expandedContravention.value[uniqueKey] = !expandedContravention.value[uniqueKey]
}
// 检查违法是否展开
const isContraventionExpanded = (contraventionId, listType, index) => {
const uniqueKey = `${contraventionId}_${listType}_${index}`
return !!expandedContravention.value[uniqueKey]
}
// 格式化日期显示
const formatDate = (dateStr) => {
if (!dateStr) return '—'
// 如果是时间戳,转换为日期
if (typeof dateStr === 'number') {
return new Date(dateStr).toLocaleDateString('zh-CN')
}
return dateStr
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,239 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden">
<!-- 标题 -->
<div class="border border-[#EEEEEE] rounded-xl">
<div class="flex items-center mb-3 p-4">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/swfx.png" alt="税务风险" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">税务风险</span>
</div>
<!-- 统计总结头部 -->
<div class="">
<!-- 欠税公告统计 -->
<div class="mb-4">
<LTitle title="欠税公告统计" type="red" class="mb-4" />
<div class="grid grid-cols-2 gap-3 p-4">
<div class="bg-[#EB3C3C1A] rounded-lg p-4 border border-[#EB3C3C8F] text-center">
<div class="text-xl font-bold text-[#EB3C3C] mb-1">{{ ownTaxStats.companyCount }}<span
class=" font-normal text-[#EB3C3C] ml-1"></span></div>
<div class="text-sm text-gray-600">涉及企业</div>
</div>
<div class="bg-[#EB3C3C1A] rounded-lg p-4 border border-[#EB3C3C8F] text-center">
<div class="text-xl font-bold text-[#EB3C3C] mb-1">{{ ownTaxStats.recordCount }}<span
class="font-normal text-[#EB3C3C] ml-1"></span></div>
<div class="text-sm text-gray-600">欠税记录</div>
</div>
<div class="col-span-1 bg-[#EB3C3C1A] rounded-lg p-4 border border-[#EB3C3C8F] text-center">
<div class="text-xl font-bold text-[#EB3C3C] mb-1">{{ ownTaxStats.totalAmount }}<span
class=" font-normal text-[#EB3C3C] ml-1"></span></div>
<div class="text-sm text-gray-600">欠税总额</div>
</div>
</div>
</div>
<!-- 税收违法统计 -->
<div>
<LTitle title="税收违法统计" type="red" class="mb-2" />
<div class="grid grid-cols-2 gap-3 p-4">
<div class="bg-[#EB3C3C1A] rounded-lg p-4 border border-[#EB3C3C8F] text-center">
<div class="text-xl font-bold text-[#EB3C3C] mb-1">{{ taxContraventionStats.companyCount
}}<span class="font-normal text-[#EB3C3C] ml-1"></span></div>
<div class="text-sm text-gray-600">涉及企业</div>
</div>
<div class="bg-[#EB3C3C1A] rounded-lg p-4 border border-[#EB3C3C8F] text-center">
<div class="text-xl font-bold text-[#EB3C3C] mb-1">{{ taxContraventionStats.recordCount
}}<span class="font-normal text-[#EB3C3C] ml-1"></span></div>
<div class="text-sm text-gray-600">违法记录</div>
</div>
</div>
</div>
</div>
</div>
<!-- 欠税公告 -->
<OwnTax :data="ownTaxData" class="mt-4" />
<!-- 税收违法 -->
<TaxContravention class="mt-4" :data="taxContraventionData" />
<!-- 温馨提示 -->
</div>
<LRemark
content="税务风险展示申请人相关企业的税务相关风险信息,包括欠税公告和税收违法记录。欠税公告数据来源于国家税务总局和各地方税务局的欠税公告,税收违法数据来源于国家税务总局和各地方税务局的违法案件公示。税务风险记录可能对企业信用评级、经营资质和融资能力产生负面影响。建议关注欠税金额、违法性质和整改情况,及时了解企业的税务合规状况。" />
</template>
<script setup>
import { computed } from 'vue'
import OwnTax from './OwnTax.vue'
import TaxContravention from './TaxContravention.vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({}),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
// 获取欠税公告数据
const ownTaxData = computed(() => {
const ownTaxCompanies = props.data?.items?.filter((item) => {
const ownTax = item?.own_tax || {};
return ownTax.total > 0 && ownTax.items && ownTax.items.length > 0;
}) || [];
// 提取所有欠税公告记录
const allOwnTax = []
ownTaxCompanies.forEach(company => {
if (company.own_tax?.items) {
company.own_tax.items.forEach(tax => {
allOwnTax.push({
...tax,
companyName: company.orgName,
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
return { items: allOwnTax, total: allOwnTax.length }
})
// 获取税收违法数据
const taxContraventionData = computed(() => {
const taxContraventionCompanies = props.data?.items?.filter((item) => {
const taxContravention = item?.tax_contravention || {};
return taxContravention.total > 0 && taxContravention.items && taxContravention.items.length > 0;
}) || [];
// 提取所有税收违法记录
const allTaxContravention = []
taxContraventionCompanies.forEach(company => {
if (company.tax_contravention?.items) {
company.tax_contravention.items.forEach(contravention => {
allTaxContravention.push({
...contravention,
companyName: company.orgName,
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
return { items: allTaxContravention, total: allTaxContravention.length }
})
// 欠税公告统计
const ownTaxStats = computed(() => {
const ownTaxCompanies = props.data?.items?.filter((item) => {
const ownTax = item?.own_tax || {};
return ownTax.total > 0 && ownTax.items && ownTax.items.length > 0;
}) || [];
let totalAmount = 0;
let recordCount = 0;
ownTaxCompanies.forEach(company => {
if (company.own_tax?.items) {
company.own_tax.items.forEach(tax => {
recordCount++;
// 尝试解析欠税金额
const amount = parseFloat(tax.ownTaxAmount) || 0;
totalAmount += amount;
});
}
});
return {
companyCount: ownTaxCompanies.length,
recordCount,
totalAmount: totalAmount > 0 ? `${totalAmount.toFixed(2)}` : '—'
};
});
// 税收违法统计
const taxContraventionStats = computed(() => {
const taxContraventionCompanies = props.data?.items?.filter((item) => {
const taxContravention = item?.tax_contravention || {};
return taxContravention.total > 0 && taxContravention.items && taxContravention.items.length > 0;
}) || [];
let recordCount = 0;
taxContraventionCompanies.forEach(company => {
if (company.tax_contravention?.items) {
company.tax_contravention.items.forEach(contravention => {
recordCount++;
});
}
});
return {
companyCount: taxContraventionCompanies.length,
recordCount
};
});
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const ownTaxList = ownTaxData.value || [];
const taxContraventionList = taxContraventionData.value || [];
// 没有税务风险 -> 无风险 -> 100分
if (ownTaxList.length === 0 && taxContraventionList.length === 0) {
return 100;
}
// 有税务风险 -> 高风险 -> 40分
return 40;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style scoped>
/* 添加过渡效果 */
.company-wrapper {
overflow: hidden;
}
/* 添加阴影悬停效果 */
.company-wrapper>div:first-child {
transform-origin: top;
}
/* 确保展开有足够的空间 */
.max-h-\[2000px\] {
max-height: 2000px;
}
</style>

167
src/ui/CQYGL3F8E/index.vue Normal file
View File

@@ -0,0 +1,167 @@
<script setup>
import Investment from "./components/Investment.vue";
import { useRiskNotifier } from '@/composables/useRiskNotifier';
import SeniorExecutive from "./components/SeniorExecutive.vue";
import Lawsuit from "./components/Lawsuit.vue";
import TaxRisk from "./components/TaxRisk/index.vue";
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({})
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
});
const { data } = props;
// 投资类关系
const investRelations = ["sh", "his_sh", "lp", "his_lp"];
// 高管类关系
const managerRelations = ["tm", "his_tm"];
const totalCompanies = computed(() => {
return data?.items?.length || 0;
});
// 获取投资企业记录(股东、历史股东、法人、历史法人)
const investCompanies = computed(() => {
const items = data?.items || [];
return items.filter((item) => {
const relationships = item?.relationship || [];
return relationships.some((r) => investRelations.includes(r));
});
});
// 获取高管任职记录(高管、历史高管)
const managerPositions = computed(() => {
const items = data?.items || [];
return items.filter((item) => {
const relationships = item?.relationship || [];
return relationships.some((r) => managerRelations.includes(r));
});
});
// 获取有涉诉风险的企业
const lawsuitCompanies = computed(() => {
const items = data?.items || [];
return items.filter((item) => {
const lawsuit = item?.lawsuitInfo || {};
return (
(lawsuit.entout && lawsuit.entout.data && Object.keys(lawsuit.entout.data).length > 0) ||
(lawsuit.sxbzxr && lawsuit.sxbzxr.data && lawsuit.sxbzxr.data.sxbzxr && lawsuit.sxbzxr.data.sxbzxr.length > 0) ||
(lawsuit.xgbzxr && lawsuit.xgbzxr.data && lawsuit.xgbzxr.data.xgbzxr && lawsuit.xgbzxr.data.xgbzxr.length > 0)
);
});
});
// 获取有税务风险的企业(欠税公告或税收违法)
const taxRiskCompanies = computed(() => {
const items = data?.items || [];
return items.filter((item) => {
const ownTax = item?.own_tax || {};
const taxContravention = item?.tax_contravention || {};
return (ownTax.total > 0 && ownTax.items && ownTax.items.length > 0) ||
(taxContravention.total > 0 && taxContravention.items && taxContravention.items.length > 0);
});
});
// 判断是否有风险(人企关系有数据就算有风险)
const hasRisk = computed(() => {
return Object.keys(props.data || {}).length > 0;
});
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
// 检查是否有数据
if (!props.data || Object.keys(props.data).length === 0) {
return 100; // 无数据视为最安全
}
// 获取各项风险数据
const lawsuitCount = lawsuitCompanies.value.length;
const taxRiskCount = taxRiskCompanies.value.length;
const investCount = investCompanies.value.length;
const managerCount = managerPositions.value.length;
// 计算风险评分
let score = 100; // 初始分数
// 涉诉风险每个涉诉企业扣15分
if (lawsuitCount > 0) {
score -= Math.min(lawsuitCount * 15, 60);
}
// 税务风险每个税务风险企业扣10分
if (taxRiskCount > 0) {
score -= Math.min(taxRiskCount * 10, 40);
}
// 投资企业每个投资企业扣2分投资本身不算高风险
if (investCount > 0) {
score -= Math.min(investCount * 2, 20);
}
// 高管任职每个高管任职扣3分高管本身不算高风险
if (managerCount > 0) {
score -= Math.min(managerCount * 3, 20);
}
// 确保分数在0-100范围内
return Math.max(0, Math.min(100, score));
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
hasRisk,
riskScore
});
</script>
<template>
<div class="pb-8">
<!-- 投资企业记录 -->
<Investment :data="investCompanies" />
<!-- 高管任职记录 -->
<SeniorExecutive :data="managerPositions" />
<!-- 涉诉风险 -->
<Lawsuit :data="{ lawsuitCompanies: lawsuitCompanies }" />
<!-- 税务风险 -->
<TaxRisk :data="taxRiskCompanies" />
</div>
</template>
<style scoped>
/* 添加过渡效果 */
.company-wrapper {
overflow: hidden;
}
/* 添加阴影悬停效果 */
.company-wrapper>div:first-child {
transform-origin: top;
}
/* 确保展开有足够的空间 */
.max-h-\[2000px\] {
max-height: 2000px;
}
</style>

View File

@@ -0,0 +1,302 @@
/**
* CQYGL3F8E企业关联数据拆分工具
* 将企业关联数据拆分为投资企业记录、高管任职记录和涉诉风险三个独立模块
*/
/**
* 拆分CQYGL3F8E数据为多个独立的tab模块
* @param {Array} reportData - 报告数据数组
* @returns {Array} 拆分后的数据数组
*/
export function splitCQYGL3F8EForTabs(reportData) {
const result = []
reportData.forEach(item => {
if (item.data?.apiID === 'QYGL3F8E') {
// 将QYGL3F8E拆分成多个独立的tab
const qyglData = item.data.data
const baseTimestamp = item.data.timestamp
// 投资类关系
const investRelations = ["sh", "his_sh", "lp", "his_lp"]
// 高管类关系
const managerRelations = ["tm", "his_tm"]
// 获取投资企业记录(股东、历史股东、法人、历史法人)
const investCompanies = (qyglData?.items || []).filter((item) => {
const relationships = item?.relationship || []
return relationships.some((r) => investRelations.includes(r))
})
// 获取高管任职记录(高管、历史高管)
const managerPositions = (qyglData?.items || []).filter((item) => {
const relationships = item?.relationship || []
return relationships.some((r) => managerRelations.includes(r))
})
// 获取有涉诉风险的企业
const lawsuitCompanies = (qyglData?.items || []).filter((item) => {
const lawsuit = item?.lawsuitInfo || {}
return (
(lawsuit.entout && lawsuit.entout.data && Object.keys(lawsuit.entout.data).length > 0) ||
(lawsuit.sxbzxr && lawsuit.sxbzxr.data && lawsuit.sxbzxr.data.sxbzxr && lawsuit.sxbzxr.data.sxbzxr.length > 0) ||
(lawsuit.xgbzxr && lawsuit.xgbzxr.data && lawsuit.xgbzxr.data.xgbzxr && lawsuit.xgbzxr.data.xgbzxr.length > 0)
)
})
// 1. 投资企业记录模块
result.push({
data: {
apiID: 'CQYGL3F8E_Investment',
data: investCompanies,
success: true,
timestamp: baseTimestamp
}
})
// 2. 高管任职记录模块
result.push({
data: {
apiID: 'CQYGL3F8E_SeniorExecutive',
data: managerPositions,
success: true,
timestamp: baseTimestamp
}
})
// 3. 涉诉风险模块
result.push({
data: {
apiID: 'CQYGL3F8E_Lawsuit',
data: {
lawsuitCompanies: lawsuitCompanies,
totalCompanies: qyglData?.items?.length || 0
},
success: true,
timestamp: baseTimestamp
}
})
// 4. 对外投资历史模块 - 从所有企业中收集投资历史
const allInvestHistory = []
qyglData?.items?.forEach(company => {
if (company.invest_history?.items) {
company.invest_history.items.forEach(investment => {
allInvestHistory.push({
...investment,
companyName: company.orgName, // 添加企业名称
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
result.push({
data: {
apiID: 'CQYGL3F8E_InvestHistory',
data: { items: allInvestHistory, total: allInvestHistory.length },
success: true,
timestamp: baseTimestamp
}
})
// 5. 融资历史模块 - 从所有企业中收集融资历史
const allFinancingHistory = []
qyglData?.items?.forEach(company => {
if (company.financing_history?.items) {
company.financing_history.items.forEach(financing => {
allFinancingHistory.push({
...financing,
companyName: company.orgName, // 添加企业名称
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
result.push({
data: {
apiID: 'CQYGL3F8E_FinancingHistory',
data: { items: allFinancingHistory, total: allFinancingHistory.length },
success: true,
timestamp: baseTimestamp
}
})
// 6. 行政处罚模块 - 从所有企业中收集行政处罚
const allPunishmentInfo = []
qyglData?.items?.forEach(company => {
if (company.punishment_info?.items) {
company.punishment_info.items.forEach(punishment => {
allPunishmentInfo.push({
...punishment,
companyName: company.orgName, // 添加企业名称
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
result.push({
data: {
apiID: 'CQYGL3F8E_Punishment',
data: { items: allPunishmentInfo, total: allPunishmentInfo.length },
success: true,
timestamp: baseTimestamp
}
})
// 7. 经营异常模块 - 从所有企业中收集经营异常
const allAbnormalInfo = []
qyglData?.items?.forEach(company => {
if (company.abnormal_info?.items) {
company.abnormal_info.items.forEach(abnormal => {
allAbnormalInfo.push({
...abnormal,
companyName: company.orgName, // 添加企业名称
companyInfo: {
orgName: company.orgName,
relationship: company.relationship,
basicInfo: company.basicInfo
}
})
})
}
})
result.push({
data: {
apiID: 'CQYGL3F8E_Abnormal',
data: { items: allAbnormalInfo, total: allAbnormalInfo.length },
success: true,
timestamp: baseTimestamp
}
})
// 8. 税务风险模块 - 包含欠税公告和税收违法
const taxRiskCompanies = (qyglData?.items || []).filter((item) => {
const ownTax = item?.own_tax || {};
const taxContravention = item?.tax_contravention || {};
return (ownTax.total > 0 && ownTax.items && ownTax.items.length > 0) ||
(taxContravention.total > 0 && taxContravention.items && taxContravention.items.length > 0);
});
result.push({
data: {
apiID: 'CQYGL3F8E_TaxRisk',
data: { items: taxRiskCompanies },
success: true,
timestamp: baseTimestamp
}
})
} else {
// 其他数据直接添加
result.push(item)
}
})
return result
}
/**
* 获取关系文本描述
* @param {string} relation - 关系代码
* @returns {string} 关系文本
*/
export function getRelationshipText(relation) {
const relationshipMap = {
sh: '股东',
his_sh: '曾任股东',
lp: '法人',
his_lp: '曾任法人',
tm: '高管',
his_tm: '曾任高管'
}
return relationshipMap[relation] || relation
}
/**
* 获取关系样式类
* @param {string} relation - 关系代码
* @returns {string} 样式类名
*/
export function getRelationshipClass(relation) {
const relationshipMap = {
sh: 'bg-blue-100 text-blue-700',
his_sh: 'bg-blue-50 text-blue-600',
lp: 'bg-green-100 text-green-700',
his_lp: 'bg-green-50 text-green-600',
tm: 'bg-purple-100 text-purple-700',
his_tm: 'bg-purple-50 text-purple-600'
}
return relationshipMap[relation] || 'bg-gray-100 text-gray-600'
}
/**
* 获取企业状态对应的样式类
* @param {string} status - 企业状态
* @returns {string} 样式类名
*/
export function getStatusClass(status) {
if (!status) return 'bg-gray-100 text-gray-500'
if (status.includes('注销') || status.includes('吊销')) {
return 'bg-red-50 text-red-600'
} else if (status.includes('存续') || status.includes('在营')) {
return 'bg-green-50 text-green-600'
} else if (status.includes('筹建') || status.includes('新设')) {
return 'bg-blue-50 text-blue-600'
} else {
return 'bg-yellow-50 text-yellow-600'
}
}
/**
* 格式化资本金额显示
* @param {string|number} capital - 资本金额
* @param {string} currency - 货币类型
* @returns {string} 格式化后的金额
*/
export function formatCapital(capital, currency) {
if (!capital) return '—'
let unit = ''
let value = parseFloat(capital)
// 处理原始数据中可能带有的单位
if (typeof capital === 'string' && capital.includes('万')) {
unit = '万'
const numMatch = capital.match(/[\d.]+/)
value = numMatch ? parseFloat(numMatch[0]) : 0
} else if (value >= 10000) {
// 大额数字转换为万元显示
value = value / 10000
unit = '万'
}
// 格式化数字,保留两位小数(如果有小数部分)
const formattedValue = value.toLocaleString('zh-CN', {
minimumFractionDigits: 0,
maximumFractionDigits: 2,
})
return `${formattedValue}${unit} ${currency || '人民币'}`
}
/**
* 格式化日期显示
* @param {string} dateStr - 日期字符串
* @returns {string} 格式化后的日期
*/
export function formatDate(dateStr) {
if (!dateStr) return '—'
return dateStr
}