提交 6d19a446 authored 作者: coderBryanFu's avatar coderBryanFu

feat:更新数据资源库

流水线 #488 已通过 于阶段
in 1 分 39 秒
...@@ -189,7 +189,8 @@ export function getDeepMiningSelect(data) { ...@@ -189,7 +189,8 @@ export function getDeepMiningSelect(data) {
export function getDeepMiningIndustry(params) { export function getDeepMiningIndustry(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/chain/getChainInfo`, // url: `/api/chain/getChainInfo`,
url: `/api/chain/getAllChain`,
params params
}); });
} }
...@@ -204,7 +205,7 @@ export function getDeepMiningIndustry(params) { ...@@ -204,7 +205,7 @@ export function getDeepMiningIndustry(params) {
export function getDeepMiningIndustryFishbone(params) { export function getDeepMiningIndustryFishbone(params) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/chain/getChainFishbone`, url: `/api/chain/getChainNodes`,
params params
}); });
} }
......
...@@ -11,10 +11,11 @@ export function getStatCount(params) { ...@@ -11,10 +11,11 @@ export function getStatCount(params) {
} }
// 分类接口 // 分类接口
export function getStatSort() { export function getStatSort(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/marketsearchHome/statSort` url: `/api/marketsearchHome/statSort`,
params
}) })
} }
...@@ -39,6 +40,18 @@ export function getStatNum(params) { ...@@ -39,6 +40,18 @@ export function getStatNum(params) {
}) })
} }
// 查询关税税率
/**
* @param {byYorM}
*/
export function getSearchTariff(params) {
return request({
method: 'GET',
url: `/api/marketsearchDetails/getSearchTariff`,
params
})
}
// 制裁领域分布 // 制裁领域分布
/** /**
* @param {years} * @param {years}
...@@ -178,6 +191,14 @@ export function getSearchBlurb(params) { ...@@ -178,6 +191,14 @@ export function getSearchBlurb(params) {
}) })
} }
// 获取原文
export function getOriginalUrl(params) {
return request({
method: 'GET',
url: `/api/marketsearchDetails/getOriginalUrl/${params.id}`,
})
}
// 获取相关事件 // 获取相关事件
export function getRelatedEvents(params) { export function getRelatedEvents(params) {
return request({ return request({
......
...@@ -113,7 +113,7 @@ service.interceptors.response.use( ...@@ -113,7 +113,7 @@ service.interceptors.response.use(
} }
// 特殊处理:风险信号管理页面接口偶发 500,不弹出提示 // 特殊处理:风险信号管理页面接口偶发 500,不弹出提示
// 覆盖接口:/api/riskSignal/getCountInfo | /api/riskSignal/getDailyCount | /api/riskSignal/pageQuery // 覆盖接口:/api/riskSignal/baseInfo | /api/riskSignal/pageQuery
try { try {
const errUrl = String(error?.config?.url || '') const errUrl = String(error?.config?.url || '')
if (error?.response?.status === 500 && errUrl.includes('/api/riskSignal/')) { if (error?.response?.status === 500 && errUrl.includes('/api/riskSignal/')) {
......
import request from "@/api/request.js"; import request from "@/api/request.js";
// 基本统计信息 /** 风险信号管理页:统计 + 日历热力数据(原 getCountInfo + getDailyCount) */
export function getCountInfo() { export function getRiskSignalBaseInfo() {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/riskSignal/getCountInfo`, url: `/api/riskSignal/baseInfo`
}) });
}
// 每日统计信息
export function getDailyCount() {
return request({
method: 'GET',
url: `/api/riskSignal/getDailyCount`,
})
} }
// 按条件分页查询风险信号信息 // 按条件分页查询风险信号信息
export function getPageQuery(data) { export function getPageQuery(data) {
return request({ return request({
method: 'POST', method: 'POST',
url: `/api/riskSignal/pageQuery`, url: `/api/riskSignal/PageLimit`,
data: data data: data
}) })
} }
...@@ -53,6 +53,25 @@ export function getThinkTankPolicyIndustryChange(params) { ...@@ -53,6 +53,25 @@ export function getThinkTankPolicyIndustryChange(params) {
}); });
} }
/**
* 智库概览-数量变化趋势(按领域统计)
* GET /thinkTankReport/domainStats
* @param {{ startDate: string, endDate: string }} params
*/
export function getThinkTankReportDomainStats(params) {
return request({
method: "GET",
url: `/api/thinkTankReport/domainStats`,
params: {
startDate: params.startDate,
endDate: params.endDate
},
// 与 policyIndustryChange 一致:无数据年份可能返回 400/500,避免走全局错误提示
validateStatus: (status) =>
(status >= 200 && status < 300) || status === 400 || status === 500
});
}
// 政策建议领域分布 // 政策建议领域分布
export function getThinkTankPolicyIndustry(params) { export function getThinkTankPolicyIndustry(params) {
return request({ return request({
......
差异被折叠。
...@@ -3,7 +3,10 @@ ...@@ -3,7 +3,10 @@
<div class="icon"> <div class="icon">
<img src="./tip-icon.svg" alt=""> <img src="./tip-icon.svg" alt="">
</div> </div>
<div class="text text-tip-2 text-primary-50-clor">{{ tipText }}</div> <div class="text text-tip-2 text-primary-50-clor">
<div v-if="ellipsis" :title="tipText" class="one-line-ellipsis">{{ tipText }}</div>
<div v-else>{{ tipText }}</div>
</div>
</div> </div>
</template> </template>
...@@ -23,7 +26,10 @@ const props = defineProps({ ...@@ -23,7 +26,10 @@ const props = defineProps({
type: String, type: String,
default: '2023.1至2025.12' default: '2023.1至2025.12'
}, },
ellipsis: {
type: Boolean,
default: false
}
}) })
const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`) const tipText = computed(() => props.text || `数据来源:${props.dataSource},数据时间:${props.dataTime}`)
...@@ -48,5 +54,9 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource} ...@@ -48,5 +54,9 @@ const tipText = computed(() => props.text || `数据来源:${props.dataSource}
height: 100%; height: 100%;
} }
} }
.text {
width: 20px;
flex: auto;
}
} }
</style> </style>
\ No newline at end of file
...@@ -6,6 +6,7 @@ const MarketAccessCase = () => import('@/views/marketAccessRestrictions/marketAc ...@@ -6,6 +6,7 @@ const MarketAccessCase = () => import('@/views/marketAccessRestrictions/marketAc
const MarketSingleCaseLayout = () => import('@/views/marketAccessRestrictions/singleCaseLayout/index.vue') const MarketSingleCaseLayout = () => import('@/views/marketAccessRestrictions/singleCaseLayout/index.vue')
const MarketSingleCaseOverview = () => import('@/views/marketAccessRestrictions/singleCaseLayout/overview/index.vue') const MarketSingleCaseOverview = () => import('@/views/marketAccessRestrictions/singleCaseLayout/overview/index.vue')
const MarketSingleCaseDeepdig = () => import('@/views/marketAccessRestrictions/singleCaseLayout/deepdig/index.vue') const MarketSingleCaseDeepdig = () => import('@/views/marketAccessRestrictions/singleCaseLayout/deepdig/index.vue')
const MarketSingleReportOriginal = () => import('@/views/marketAccessRestrictions/pages/reportOriginal.vue')
const marketAccessRestrictionsRoutes = [ const marketAccessRestrictionsRoutes = [
// 市场准入限制首页 // 市场准入限制首页
...@@ -42,7 +43,6 @@ const marketAccessRestrictionsRoutes = [ ...@@ -42,7 +43,6 @@ const marketAccessRestrictionsRoutes = [
} }
] ]
}, },
{ {
path: "/marketSingleCaseLayout", path: "/marketSingleCaseLayout",
name: "MarketSingleCaseLayout", name: "MarketSingleCaseLayout",
...@@ -67,7 +67,12 @@ const marketAccessRestrictionsRoutes = [ ...@@ -67,7 +67,12 @@ const marketAccessRestrictionsRoutes = [
} }
] ]
}, },
{
path: "reportOriginal",
name: "MarketSingleReportOriginal",
component: MarketSingleReportOriginal,
meta: { noTitle: true }
}
] ]
export default marketAccessRestrictionsRoutes export default marketAccessRestrictionsRoutes
\ No newline at end of file
...@@ -18,7 +18,12 @@ import RelationChart from '@/components/base/RelationChart/index.vue' ...@@ -18,7 +18,12 @@ import RelationChart from '@/components/base/RelationChart/index.vue'
</el-radio-group> </el-radio-group>
</div> </div>
<div class="chart-box"> <div class="chart-box">
<RelationChart @line-click="handleClickLine" @node-click="handleClickNode" :is-vertical-chart="isVerticalChart" :graph-data="graphData" /> <RelationChart
@line-click="handleClickLine"
@node-click="handleClickNode"
:is-vertical-chart="isVerticalChart"
:graph-data="graphData"
/>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
...@@ -149,7 +154,15 @@ const graphData = ref({ ...@@ -149,7 +154,15 @@ const graphData = ref({
{ id: "e2-9", text: "e2-9" } { id: "e2-9", text: "e2-9" }
], ],
lines: [ lines: [
{ from: "a", to: "b", text: '从属', fontColor: 'var(--color-orange-100)', color: 'orange', textOffset_x: -20, lineWidth: 5}, {
from: "a",
to: "b",
text: "从属",
fontColor: "var(--color-orange-100)",
color: "orange",
textOffset_x: -20,
lineWidth: 5
},
{ from: "b", to: "b1" }, { from: "b", to: "b1" },
{ from: "b1", to: "b1-1" }, { from: "b1", to: "b1-1" },
{ from: "b1", to: "b1-2" }, { from: "b1", to: "b1-2" },
...@@ -254,18 +267,15 @@ const graphData = ref({ ...@@ -254,18 +267,15 @@ const graphData = ref({
] ]
}); });
const handleClickNode = (value) => { const handleClickNode = value => {
console.log('value', value); console.log("value", value);
alert('我点击了node-'+ value.text) alert("我点击了node-" + value.text);
} };
const handleClickLine = (value) => {
console.log('value', value);
alert('我点击了line-'+ value.text)
}
const handleClickLine = value => {
console.log("value", value);
alert("我点击了line-" + value.text);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -278,4 +288,4 @@ const handleClickLine = (value) => { ...@@ -278,4 +288,4 @@ const handleClickLine = (value) => {
width: 1600px; width: 1600px;
height: 500px; height: 500px;
} }
</style> </style>
\ No newline at end of file
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<TextStyle /> <TextStyle />
<div class="text-title-1-show">通用样式/组件</div> <div class="text-title-1-show">通用样式/组件</div>
<div style="position: relative; min-height: 700px;"> <div style="position: relative; min-height: 700px;">
<el-tabs tabPosition="left" class="tabs-nav-no-wrap left-float-nav-tabs"> <el-tabs tabPosition="left" class="tabs-nav-no-wrap left-float-nav-tabs dev-style-tabs">
<el-tab-pane label="通用" lazy> <el-tab-pane label="通用" lazy>
<common-page /> <common-page />
</el-tab-pane> </el-tab-pane>
...@@ -113,4 +113,10 @@ import WorkingBox from './WorkingBox/index.vue' ...@@ -113,4 +113,10 @@ import WorkingBox from './WorkingBox/index.vue'
.box { .box {
padding-bottom: 20px; padding-bottom: 20px;
} }
.dev-style-tabs {
:deep(.el-tabs__content) {
display: block !important;
}
}
</style> </style>
\ No newline at end of file
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<img src="./assets/icon-star.svg" alt="" class="btn-icon" /> <img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div v-show="activeIndex !== '关系图'" class="graph" id="relGraph"></div>
<RelationChart v-if="activeIndex === '关系图' && graphData.rootId" :graph-data="graphData" />
</div> </div>
</div> </div>
</template> </template>
...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from "vue"; ...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js"; import { getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js";
import "default-passive-events"; import "default-passive-events";
import RelationChart from '@/components/base/RelationChart/index.vue';
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
...@@ -95,6 +97,7 @@ const nodes = ref([]); ...@@ -95,6 +97,7 @@ const nodes = ref([]);
const links = ref([]); const links = ref([]);
const characterInfo = ref({}); const characterInfo = ref({});
const CharacterRelation = ref([]); const CharacterRelation = ref([]);
const graphData = ref({ rootId: '', nodes: [], lines: [] });
const list = ref([ const list = ref([
{ {
...@@ -108,8 +111,8 @@ const list = ref([ ...@@ -108,8 +111,8 @@ const list = ref([
icon: new URL("./assets/icon-force.svg", import.meta.url).href, icon: new URL("./assets/icon-force.svg", import.meta.url).href,
}, },
{ {
value: "树形布局", value: "关系图",
label: "树形布局", label: "关系图",
icon: new URL("./assets/icon-tree.svg", import.meta.url).href, icon: new URL("./assets/icon-tree.svg", import.meta.url).href,
}, },
]); ]);
...@@ -204,6 +207,15 @@ const getCharacterRelationFn = async () => { ...@@ -204,6 +207,15 @@ const getCharacterRelationFn = async () => {
nodes.value = [centerNode]; nodes.value = [centerNode];
links.value = []; links.value = [];
} }
// 构建关系图数据
const gNodes = [{ id: 'center', text: characterInfo.value.name || '' }];
const gLines = [];
CharacterRelation.value.forEach((item, index) => {
gNodes.push({ id: `node_${index}`, text: item.name });
gLines.push({ from: 'center', to: `node_${index}`, text: item.relation || '' });
});
graphData.value = { rootId: 'center', nodes: gNodes, lines: gLines };
}; };
// ========== 渲染图表 ========== // ========== 渲染图表 ==========
......
...@@ -84,7 +84,7 @@ export function useBills() { ...@@ -84,7 +84,7 @@ export function useBills() {
} }
if (filters.selectedTimeRanges.length > 0 && !filters.selectedTimeRanges.includes('all')) { if (filters.selectedTimeRanges.length > 0 && !filters.selectedTimeRanges.includes('all')) {
params.years = filters.selectedTimeRanges params.years = filters.selectedTimeRanges.map(Number)
} }
params.isCN = filters.isCN params.isCN = filters.isCN
......
...@@ -68,10 +68,9 @@ ...@@ -68,10 +68,9 @@
color: 'rgba(59, 65, 75, 1)' color: 'rgba(59, 65, 75, 1)'
}" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large"> }" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large">
<el-table-column prop="rank" label="排名" width="100" align="center" /> <el-table-column prop="rank" label="排名" width="100" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="200" />
<el-table-column prop="totalAmount" label="总捐款" width="150" /> <el-table-column prop="totalAmount" label="总捐款" width="150" />
<el-table-column prop="individualAmount" label="个人捐款" width="150" /> <el-table-column prop="donationYear" label="捐款年份" width="150" />
<el-table-column prop="pacsAmount" label="PACs捐款" width="150" />
</el-table> </el-table>
<div class="table-pagination"> <div class="table-pagination">
<span class="table-pagination-total">共{{ fundTotal }}项</span> <span class="table-pagination-total">共{{ fundTotal }}项</span>
...@@ -109,7 +108,7 @@ ...@@ -109,7 +108,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" alt="" v-else /> src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
...@@ -389,7 +388,9 @@ const handleBillMoreClick = (bill) => { ...@@ -389,7 +388,9 @@ const handleBillMoreClick = (bill) => {
}; };
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const newsTab = ref('history') const newsTab = ref('history')
// 处理图片代理 // 处理图片代理
...@@ -490,24 +491,17 @@ const handleChangeYear = () => { ...@@ -490,24 +491,17 @@ const handleChangeYear = () => {
const yearList = ref([ const yearList = ref([]);
{ const generateYearList = () => {
label: "全部", const currentYear = new Date().getFullYear();
value: 'all' const list = [{ label: "全部", value: 'all' }];
}, for (let i = 0; i < 5; i++) {
{ const year = currentYear - i;
label: "2025", list.push({ label: String(year), value: year });
value: 2025
},
{
label: "2024",
value: 2024
},
{
label: "2023",
value: 2023
} }
]); yearList.value = list;
};
generateYearList();
const CharacterFundSource = ref([]); const CharacterFundSource = ref([]);
const getCharacterFundSourceFn = async () => { const getCharacterFundSourceFn = async () => {
...@@ -516,7 +510,7 @@ const getCharacterFundSourceFn = async () => { ...@@ -516,7 +510,7 @@ const getCharacterFundSourceFn = async () => {
pageSize: 4, pageSize: 4,
currentPage: fundCurrentPage.value - 1, currentPage: fundCurrentPage.value - 1,
}; };
if (selectedOption.value !== '全部') { if (selectedOption.value !== 'all') {
params.year = selectedOption.value; params.year = selectedOption.value;
} }
...@@ -528,10 +522,9 @@ const getCharacterFundSourceFn = async () => { ...@@ -528,10 +522,9 @@ const getCharacterFundSourceFn = async () => {
CharacterFundSource.value = res.data.content.map((item, index) => { CharacterFundSource.value = res.data.content.map((item, index) => {
return { return {
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.companyName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
individualAmount: item.personalDonation, donationYear: item.donationYear
pacsAmount: item.pacsDonation
} }
}); });
...@@ -627,7 +620,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -627,7 +620,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
...@@ -730,7 +724,7 @@ const info = ref(["人物详情", "历史提案", "人物关系" ]); ...@@ -730,7 +724,7 @@ const info = ref(["人物详情", "历史提案", "人物关系" ]);
const infoActive = ref("人物详情"); const infoActive = ref("人物详情");
const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]); const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]);
const numActive = ref("全部"); const numActive = ref("全部");
const selectedOption = ref("全部"); const selectedOption = ref("all");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const currentTag = ref(null) const currentTag = ref(null)
......
...@@ -17,7 +17,8 @@ ...@@ -17,7 +17,8 @@
<img src="./assets/icon-star.svg" alt="" class="btn-icon" /> <img src="./assets/icon-star.svg" alt="" class="btn-icon" />
</div> </div>
<div class="mainBox"> <div class="mainBox">
<div class="graph" id="relGraph"></div> <div v-show="activeIndex !== '关系图'" class="graph" id="relGraph"></div>
<RelationChart v-if="activeIndex === '关系图' && graphData.rootId" :graph-data="graphData" />
</div> </div>
</div> </div>
</template> </template>
...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from 'vue' ...@@ -27,6 +28,7 @@ import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { getCharacterGlobalInfo, getCharacterRelation } from '@/api/characterPage/characterPage.js' import { getCharacterGlobalInfo, getCharacterRelation } from '@/api/characterPage/characterPage.js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import RelationChart from '@/components/base/RelationChart/index.vue'
const route = useRoute() const route = useRoute()
const personId = ref(route.query.personId || 'Y000064') const personId = ref(route.query.personId || 'Y000064')
...@@ -79,6 +81,7 @@ const nodes = ref([]) ...@@ -79,6 +81,7 @@ const nodes = ref([])
const links = ref([]) const links = ref([])
const characterInfo = ref({}) const characterInfo = ref({})
const CharacterRelation = ref([]) const CharacterRelation = ref([])
const graphData = ref({ rootId: '', nodes: [], lines: [] })
const list = ref([ const list = ref([
{ {
...@@ -91,9 +94,9 @@ const list = ref([ ...@@ -91,9 +94,9 @@ const list = ref([
label: '力导向布局', label: '力导向布局',
icon: new URL('./assets/icon-force.svg', import.meta.url).href, icon: new URL('./assets/icon-force.svg', import.meta.url).href,
}, },
{ {
value: '树形布局', value: '关系图',
label: '树形布局', label: '关系图',
icon: new URL('./assets/icon-tree.svg', import.meta.url).href, icon: new URL('./assets/icon-tree.svg', import.meta.url).href,
}, },
]) ])
...@@ -193,6 +196,15 @@ const getCharacterRelationFn = async () => { ...@@ -193,6 +196,15 @@ const getCharacterRelationFn = async () => {
nodes.value = [centerNode] nodes.value = [centerNode]
links.value = [] links.value = []
} }
// 构建关系图数据
const gNodes = [{ id: 'center', text: characterInfo.value.name || '' }]
const gLines = []
CharacterRelation.value.forEach((item, index) => {
gNodes.push({ id: `node_${index}`, text: item.name })
gLines.push({ from: 'center', to: `node_${index}`, text: item.relation || '' })
})
graphData.value = { rootId: 'center', nodes: gNodes, lines: gLines }
} }
const handleChangeLayout = (value) => { const handleChangeLayout = (value) => {
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /> <img src="./assets/type1.png" alt="" v-if="item.remarks === true" />
<img src="./assets/type2.png" alt="" v-else /> <img src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p> <p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p>
...@@ -273,7 +273,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png' ...@@ -273,7 +273,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png' import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { Close } from '@element-plus/icons-vue' import { Close } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const areaList = ref([]) const areaList = ref([])
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const wordloading=ref(false) const wordloading=ref(false)
...@@ -545,7 +547,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -545,7 +547,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
......
...@@ -20,11 +20,15 @@ ...@@ -20,11 +20,15 @@
<button <button
:class="['news-tab', { active: activeTab === 'local' }]" :class="['news-tab', { active: activeTab === 'local' }]"
@click="$emit('update:activeTab', 'local')" @click="$emit('update:activeTab', 'local')"
> 报告</button> > 智库报告</button>
<button <button
:class="['news-tab', { active: activeTab === 'capital' }]" :class="['news-tab', { active: activeTab === 'capital' }]"
@click="$emit('update:activeTab', 'capital')" @click="$emit('update:activeTab', 'capital')"
> 项目</button> > 调查项目</button>
<button
:class="['news-tab', { active: activeTab === 'hearing' }]"
@click="$emit('update:activeTab', 'hearing')"
> 听证会</button>
</div> </div>
<div class="news-sort" ref="sortDropdownRef"> <div class="news-sort" ref="sortDropdownRef">
......
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" alt="" v-else /> src="./assets/type2.png" alt="" v-else />
</div> </div>
<div class="content"> <div class="content" style="cursor: pointer;" @click="gotoNewsDetail(item.newsId)">
<div <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1"> <p v-if="item.remarks === true" class="content-title1">
...@@ -202,7 +202,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png' ...@@ -202,7 +202,9 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png' import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const boxHeight = computed(() => { const boxHeight = computed(() => {
if(characterBasicInfo.value.organizationList==undefined) return '625px' if(characterBasicInfo.value.organizationList==undefined) return '625px'
...@@ -370,7 +372,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -370,7 +372,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
......
...@@ -12,12 +12,12 @@ export const CHARACTER_CONFIG = { ...@@ -12,12 +12,12 @@ export const CHARACTER_CONFIG = {
useImageProxy: false, useImageProxy: false,
headerTagType: "areaTag", headerTagType: "areaTag",
wordCloudTitle: "科技观点", wordCloudTitle: "科技观点",
yearDefault: "全部时间", yearDefault: "全部",
yearBuildMode: "dynamic", yearBuildMode: "dynamic",
yearStaticOptions: [], yearStaticOptions: [],
showFundSource: false, showFundSource: false,
resumeMode: "inline", resumeMode: "inline",
resumeTitle: "职业履历", resumeTitle: "生涯履历",
resumeHeight: "1336px", resumeHeight: "1336px",
companySectionTitle: "实体信息", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
...@@ -51,13 +51,13 @@ export const CHARACTER_CONFIG = { ...@@ -51,13 +51,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "inline", headerTagType: "inline",
wordCloudTitle: "科技观点", wordCloudTitle: "科技观点",
yearDefault: "全部", yearDefault: "全部",
yearBuildMode: "static", yearBuildMode: "dynamic",
yearStaticOptions: ["全部", "2025", "2024", "2023", "2022", "2021", "2020"], yearStaticOptions: [],
showFundSource: true, showFundSource: true,
resumeMode: "inline", resumeMode: "inline",
resumeTitle: "职业履历", resumeTitle: "生涯履历",
resumeHeight: "1556px", resumeHeight: "1556px",
companySectionTitle: "社交媒体", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" }, { label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" }, { label: "现任职位:", key: "positionTitle", type: "text" },
...@@ -89,13 +89,13 @@ export const CHARACTER_CONFIG = { ...@@ -89,13 +89,13 @@ export const CHARACTER_CONFIG = {
headerTagType: "areaTag", headerTagType: "areaTag",
wordCloudTitle: "核心观点", wordCloudTitle: "核心观点",
yearDefault: "全部", yearDefault: "全部",
yearBuildMode: "static", yearBuildMode: "dynamic",
yearStaticOptions: ["全部", "2026", "2025", "2024", "2023", "2022", "2021"], yearStaticOptions: [],
showFundSource: false, showFundSource: false,
resumeMode: "card", resumeMode: "inline",
resumeTitle: "政治履历", resumeTitle: "生涯履历",
resumeHeight: null, resumeHeight: null,
companySectionTitle: "社交媒体", companySectionTitle: "实体信息",
basicInfoFields: [ basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" }, { label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" }, { label: "现任职位:", key: "positionTitle", type: "text" },
......
...@@ -4,6 +4,20 @@ ...@@ -4,6 +4,20 @@
<div class="header"> <div class="header">
<div class="avatar"> <div class="avatar">
<el-avatar :size="160" shape="circle" :src="avatarUrl" /> <el-avatar :size="160" shape="circle" :src="avatarUrl" />
<div class="person-tags">
<div class="person-tag-bg" v-if="characterInfo.party === 'Republican' || characterInfo.party === '共和党'">
<img :src="getTagIconUrl('1')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg" v-else-if="characterInfo.party && characterInfo.party !== 'Republican' && characterInfo.party !== '共和党'">
<img :src="getTagIconUrl('2')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg person-tag-right" v-if="isHouse(characterInfo.congress)">
<img :src="getCongressIconUrl('congress1')" class="tag-icon" alt="" />
</div>
<div class="person-tag-bg person-tag-right" v-else-if="isSenate(characterInfo.congress)">
<img :src="getCongressIconUrl('congress2')" class="tag-icon" alt="" />
</div>
</div>
</div> </div>
<div class="info"> <div class="info">
<p class="name-cn">{{ characterInfo.name }}</p> <p class="name-cn">{{ characterInfo.name }}</p>
...@@ -81,10 +95,9 @@ ...@@ -81,10 +95,9 @@
color: 'rgba(59, 65, 75, 1)' color: 'rgba(59, 65, 75, 1)'
}" :row-class-name="tableRowClassName" :row-style="{ height: '50px' }" size="large"> }" :row-class-name="tableRowClassName" :row-style="{ height: '50px' }" size="large">
<el-table-column prop="rank" label="排名" width="80" align="center" /> <el-table-column prop="rank" label="排名" width="80" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="200" />
<el-table-column prop="totalAmount" label="总捐款" width="150" align="right" /> <el-table-column prop="totalAmount" label="总捐款" width="150" align="right" />
<el-table-column prop="individualAmount" label="个人捐款" width="150" align="right" /> <el-table-column prop="donationYear" label="捐款年份" width="150" align="right" />
<el-table-column prop="pacsAmount" label="PACs捐款" width="150" align="right" />
</el-table> </el-table>
<div class="table-pagination"> <div class="table-pagination">
<span class="table-pagination-total">共{{ fundTotal }}项</span> <span class="table-pagination-total">共{{ fundTotal }}项</span>
...@@ -122,7 +135,7 @@ ...@@ -122,7 +135,7 @@
</div> </div>
<div class="timeline-line-bottom" v-if="idx !== CharacterLatestDynamic.length - 1"></div> <div class="timeline-line-bottom" v-if="idx !== CharacterLatestDynamic.length - 1"></div>
</div> </div>
<div class="content"> <div class="content" :style="{ cursor: item.remarks === false ? 'pointer' : 'default' }" @click="item.remarks === false && gotoNewsDetail(item.newsId)">
<div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> <div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p> <p v-if="item.remarks === true" class="content-title1">{{ item.content }}</p>
<p v-else class="content-title2">{{ item.title }}</p> <p v-else class="content-title2">{{ item.title }}</p>
...@@ -151,7 +164,7 @@ ...@@ -151,7 +164,7 @@
<div class="right"> <div class="right">
<!-- 基本信息 --> <!-- 基本信息 -->
<AnalysisBox title="基本信息" width="520px" :height="boxHeight" :show-all-btn="false" class="right-top" v-if="characterBasicInfo"> <AnalysisBox title="基本信息" width="520px" :show-all-btn="false" class="right-top auto-height-box" v-if="characterBasicInfo">
<div class="main-content"> <div class="main-content">
<div class="baseInfo"> <div class="baseInfo">
<div v-for="field in config.basicInfoFields" :key="field.key" class="baseInfo-item"> <div v-for="field in config.basicInfoFields" :key="field.key" class="baseInfo-item">
...@@ -183,7 +196,7 @@ ...@@ -183,7 +196,7 @@
</div> </div>
<div class="company"> <div class="company">
<div class="company-title">{{ config.companySectionTitle }}</div> <div class="company-title">{{ config.companySectionTitle }}</div>
<div class="company-content"> <div class="company-content" v-if="characterBasicInfo.organizationList && characterBasicInfo.organizationList.length > 0">
<div v-for="item in characterBasicInfo.organizationList" :key="item" <div v-for="item in characterBasicInfo.organizationList" :key="item"
class="company-item"> class="company-item">
<img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" /> <img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" />
...@@ -193,6 +206,7 @@ ...@@ -193,6 +206,7 @@
</div> </div>
</div> </div>
</div> </div>
<el-empty v-else description="暂无数据" :image-size="60" />
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -322,6 +336,7 @@ ...@@ -322,6 +336,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, nextTick } from "vue"; import { ref, computed, onMounted, nextTick } from "vue";
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news.js';
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"; import WordCloudChart from "@/components/base/WordCloundChart/index.vue";
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'; import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue';
import AreaTag from '@/components/base/AreaTag/index.vue'; import AreaTag from '@/components/base/AreaTag/index.vue';
...@@ -350,6 +365,7 @@ const props = defineProps({ ...@@ -350,6 +365,7 @@ const props = defineProps({
}); });
const route = useRoute(); const route = useRoute();
const gotoNewsDetail = useGotoNewsDetail();
const config = computed(() => CHARACTER_CONFIG[Number(props.type)] || CHARACTER_CONFIG[1]); const config = computed(() => CHARACTER_CONFIG[Number(props.type)] || CHARACTER_CONFIG[1]);
const personIdRef = ref(props.personId || route.query.personId || "Y000064"); const personIdRef = ref(props.personId || route.query.personId || "Y000064");
...@@ -383,6 +399,26 @@ const typeIcon2 = computed(() => typeAssets.value.type2 || ''); ...@@ -383,6 +399,26 @@ const typeIcon2 = computed(() => typeAssets.value.type2 || '');
const resumeIcon01 = computed(() => typeAssets.value.icon01 || ''); const resumeIcon01 = computed(() => typeAssets.value.icon01 || '');
const resumeIcon02 = computed(() => typeAssets.value.icon02 || ''); const resumeIcon02 = computed(() => typeAssets.value.icon02 || '');
// 政党/国会图标(同 technologyFigures/PersonTable 逻辑)
const getTagIconUrl = (tag) => {
const icons = import.meta.glob('../../../technologyFigures/assets/images/header-icon*.png', { eager: true, as: 'url' });
const iconPath = `../../../technologyFigures/assets/images/header-icon${tag}.png`;
return icons[iconPath] || '';
};
const getCongressIconUrl = (type) => {
const icons = import.meta.glob('../../../technologyFigures/assets/images/congress*.svg', { eager: true, as: 'url' });
const iconPath = `../../../technologyFigures/assets/images/${type}.svg`;
return icons[iconPath] || '';
};
const isHouse = (text) => {
if (!text) return false;
return text.includes('众议院') || text.toLowerCase().includes('house');
};
const isSenate = (text) => {
if (!text) return false;
return text.includes('参议院') || text.toLowerCase().includes('senate');
};
// 子组件 // 子组件
import CharacterRelationships from '../techLeader/components/characterRelationships/index.vue'; import CharacterRelationships from '../techLeader/components/characterRelationships/index.vue';
import RelevantSituation from '../techLeader/components/relevantSituation/index.vue'; import RelevantSituation from '../techLeader/components/relevantSituation/index.vue';
...@@ -438,12 +474,17 @@ const fundCurrentPage = ref(1); ...@@ -438,12 +474,17 @@ const fundCurrentPage = ref(1);
const fundPageSize = ref(4); const fundPageSize = ref(4);
const fundTotal = ref(0); const fundTotal = ref(0);
const selectedOption = ref("all"); const selectedOption = ref("all");
const yearList = ref([ const yearList = ref([]);
{ label: "全部", value: "all" }, const generateYearList = () => {
{ label: "2025", value: 2025 }, const currentYear = new Date().getFullYear();
{ label: "2024", value: 2024 }, const list = [{ label: "全部", value: "all" }];
{ label: "2023", value: 2023 } for (let i = 0; i < 5; i++) {
]); const year = currentYear - i;
list.push({ label: String(year), value: year });
}
yearList.value = list;
};
generateYearList();
const isChecked = ref(false); const isChecked = ref(false);
const related = ref('N'); const related = ref('N');
...@@ -577,7 +618,8 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -577,7 +618,8 @@ const getCharacterLatestDynamicFn = async () => {
time: item.time, time: item.time,
industryList: item.industryList || ["人工智能"], industryList: item.industryList || ["人工智能"],
orgName: item.orgName, orgName: item.orgName,
remarks: item.remarks remarks: item.remarks,
newsId: item.newsId
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
} else { } else {
...@@ -632,10 +674,9 @@ const getCharacterFundSourceFn = async () => { ...@@ -632,10 +674,9 @@ const getCharacterFundSourceFn = async () => {
fundTotal.value = res.data.totalElements; fundTotal.value = res.data.totalElements;
CharacterFundSource.value = res.data.content.map((item, index) => ({ CharacterFundSource.value = res.data.content.map((item, index) => ({
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.companyName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
individualAmount: item.personalDonation, donationYear: item.donationYear
pacsAmount: item.pacsDonation
})); }));
} }
} catch (error) { } } catch (error) { }
...@@ -754,13 +795,41 @@ onMounted(() => { ...@@ -754,13 +795,41 @@ onMounted(() => {
width: 160px; width: 160px;
height: 160px; height: 160px;
margin-right: 24px; margin-right: 24px;
overflow: hidden; position: relative;
flex-shrink: 0;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
/* 政党/国会标签 */
.person-tags {
display: flex;
justify-content: center;
gap: 6px;
margin-top: -23px;
width: 160px;
position: relative;
z-index: 1;
}
.person-tag-bg {
width: 36px;
height: 36px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.tag-icon {
width: 28px;
height: 28px;
object-fit: contain;
}
} }
.info { .info {
......
<template> <template>
<div class="character-page"> <div class="character-page">
<img src="./assets/images/background.png" alt="" class="bg" /> <img src="./assets/images/background.png" alt="" class="bg" />
<ModuleHeader />
<!-- 主要内容 --> <!-- 主要内容 -->
<div class="main"> <div class="main">
<unified-character :type="type" :person-id="personId" /> <unified-character :type="type" :person-id="personId" />
...@@ -13,7 +12,6 @@ ...@@ -13,7 +12,6 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import UnifiedCharacter from './components/unified/index.vue'; import UnifiedCharacter from './components/unified/index.vue';
import ModuleHeader from '@/components/base/moduleHeader/index.vue';
import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js"; import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js";
......
...@@ -152,7 +152,7 @@ ...@@ -152,7 +152,7 @@
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
<AiPane :aiContent="summarize1" /> <AiPane :aiContent="aiContent.content1" />
</div> </div>
</div> </div>
...@@ -188,7 +188,7 @@ ...@@ -188,7 +188,7 @@
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
<AiPane :aiContent="summarize2" /> <AiPane :aiContent="aiContent.content2" />
</div> </div>
</div> </div>
</div> </div>
...@@ -442,6 +442,7 @@ import setChart from "@/utils/setChart"; ...@@ -442,6 +442,7 @@ import setChart from "@/utils/setChart";
import DefaultIcon2 from "@/assets/icons/default-icon2.png"; import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import tipsTcon from "./assets/images/tips-icon.png"; import tipsTcon from "./assets/images/tips-icon.png";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { getAIReport, getNearYearList } from "@/views/marketAccessRestrictions/utils/index.ts"
import { useGotoNewsDetail } from '@/router/modules/news'; import { useGotoNewsDetail } from '@/router/modules/news';
...@@ -722,17 +723,16 @@ const handleClickPerson = async item => { ...@@ -722,17 +723,16 @@ const handleClickPerson = async item => {
} catch (error) { } } catch (error) { }
}; };
// 获取最近年份列表 const yearList = getNearYearList();
const currentYear = new Date().getFullYear();
const getYearList = (count = 6) => { // 获取AI智能报告
const yearOptions = []; const aiContent = reactive({
for (let i = 0; i < count; i++) { content1: "正在生成...",
const year = currentYear - i; content2: "正在生成...",
yearOptions.push({ label: year.toString(), value: year.toString() }); })
} const onAIReport = (data, key) => {
return yearOptions; getAIReport(data).then(res => { aiContent[key] = res })
}; }
const yearList = getYearList();
// 行政令发布频度 // 行政令发布频度
const chart1Data = ref({ const chart1Data = ref({
...@@ -745,7 +745,6 @@ const box5Params = reactive({ ...@@ -745,7 +745,6 @@ const box5Params = reactive({
proposeName: '', proposeName: '',
loading: false, loading: false,
}) })
const summarize1 = ref()
const handleGetDecreeYearOrder = async () => { const handleGetDecreeYearOrder = async () => {
box5Params.loading = true box5Params.loading = true
try { try {
...@@ -763,61 +762,17 @@ const handleGetDecreeYearOrder = async () => { ...@@ -763,61 +762,17 @@ const handleGetDecreeYearOrder = async () => {
chart1Data.value.dataY = res.data.map(item => { chart1Data.value.dataY = res.data.map(item => {
return item.count; return item.count;
}); });
onChartInterpretation({ type: "柱状图", name: "数量变化趋势", data: res.data }, summarize1) onAIReport({ type: "柱状图", name: "数量变化趋势", data: res.data }, "content1")
} else {
chart1Data.value.dataX = [];
chart1Data.value.dataY = [];
aiContent.content1 = ""
} }
} catch (error) { } catch (error) {
console.error("行政令发布频度error", error); console.error("行政令发布频度error", error);
} }
box5Params.loading = false box5Params.loading = false
}; };
// AI智能总结
const onChartInterpretation = async (text, param) => {
param.value = "正在生成..."
// 👇 新增:超时 + 终止请求(只加这一段)
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST',
headers: {
"X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json',
},
body: JSON.stringify({ text }),
signal: controller.signal // 👇 新增:绑定中断信号
});
clearTimeout(timeout); // 👇 新增:请求成功清除定时器
if (!response.ok) throw new Error(`HTTP 错误 ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let summarize = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1];
}
}
}
param.value = summarize
} catch (err) {
param.value = "系统异常,生成失败";
}
}
const handleBox5 = async () => { const handleBox5 = async () => {
await handleGetDecreeYearOrder(); await handleGetDecreeYearOrder();
...@@ -863,7 +818,6 @@ const box6Params = reactive({ ...@@ -863,7 +818,6 @@ const box6Params = reactive({
proposeName: '', proposeName: '',
loading: false, loading: false,
}); });
const summarize2 = ref()
const handleGetDecreeArea = async () => { const handleGetDecreeArea = async () => {
box6Params.loading = true box6Params.loading = true
try { try {
...@@ -880,7 +834,10 @@ const handleGetDecreeArea = async () => { ...@@ -880,7 +834,10 @@ const handleGetDecreeArea = async () => {
value: item.count value: item.count
}; };
}); });
onChartInterpretation({ type: "环形图", name: "领域分布情况", data: res.data }, summarize2) onAIReport({ type: "环形图", name: "领域分布情况", data: res.data }, "content2")
} else {
chart2Data.value = []
aiContent.content2 = ""
} }
} catch (error) { } catch (error) {
console.error("政令科技领域error", error); console.error("政令科技领域error", error);
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
<div v-for="(subSubItem, subSubIndex) in subItem.slaver" :key="subSubIndex" <div v-for="(subSubItem, subSubIndex) in subItem.slaver" :key="subSubIndex"
class="sub-sub-item"> class="sub-sub-item">
<div class="sub-sub-item-dot">{{ ALPHABET[subSubIndex % 26] }}.</div> <div class="sub-sub-item-dot">{{ ALPHABET[subSubIndex % 26] }}.</div>
<div class="sub-sub-item-word" v-html="subItem.content"></div> <div class="sub-sub-item-word" v-html="subSubItem.content"></div>
</div> </div>
</div> </div>
</div> </div>
...@@ -157,6 +157,7 @@ import ActionButton from '@/components/base/ActionButton/index.vue' ...@@ -157,6 +157,7 @@ import ActionButton from '@/components/base/ActionButton/index.vue'
import DefaultIcon1 from "@/assets/icons/default-icon1.png"; import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png"; import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png" import defaultCom from "@/views/coopRestriction/assets/images/default-icon2.png"
import { onNumToChinese } from "@/views/marketAccessRestrictions/utils/index"
const route = useRoute(); const route = useRoute();
...@@ -185,70 +186,7 @@ const handleGetAreaList = async () => { ...@@ -185,70 +186,7 @@ const handleGetAreaList = async () => {
// 主要指令 // 主要指令
const isHighlight = ref(false); const isHighlight = ref(false);
const commandWord = ref(""); const commandWord = ref("");
const contentList = ref([ const contentList = ref([]);
// {
// content: "建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划建立美国人工智能出口计划",
// slaver: [
// {
// content: '在本命令发布之日起 90 天内,商务部长应与国务卿及科学技术政策办公室(OSTP)主任协商,建立并实施美国人工智能出口计划(计划),以支持美国全栈人工智能出口软件包的开发和部署。'
// },
// {
// content: '商务部长应公开征集由行业主导的联盟提案,以纳入该计划。公开征集要求每项提案必须:',
// slaver: [
// {
// content: '包含一套全栈人工智能技术包,涵盖:',
// slaver: [
// {
// content: 'AI 优化的计算机硬件(如芯片、服务器和加速器)、数据中心存储、云服务和网络,以及这些设备是否以及在多大程度上在美国制造的描述;'
// },
// {
// content: '数据管道和标签系统;'
// },
// {
// content: '人工智能模型与系统;'
// },
// {
// content: '采取措施保障人工智能模型和系统的安全性和网络安全;'
// },
// {
// content: '针对特定用例的人工智能应用(如软件工程、教育、医疗保健、农业或交通运输);'
// }
// ]
// },
// {
// content: '确定出口参与者的具体目标国家或区域集团;'
// },
// {
// content: '描述一个业务和运营模型,以在高层次上说明哪些实体将建设、拥有和运营数据中心及相关基础设施;'
// },
// {
// content: '联邦激励和支持机制请求的细节;'
// },
// {
// content: '遵守所有相关的美国出口管制制度、出境投资法规和终端用户政策,包括美国法典第 50 编第 58 章及商务部工业与安全局的相关指导。'
// }
// ]
// },
// {
// content: '商务部要求提案须在公开征集后不超过 90 天内提交,并应滚动考虑提案纳入项目。'
// },
// {
// content: '商务部长应与国务卿、国防部长、能源部长及 OSTP 主任协商,评估提交的纳入计划提案。商务部长与国务卿、国防部长、能源部长及 OSTP 主任协商后选定的提案,将被指定为优先 AI 出口包,并通过优先访问本命令第 4 节指定的工具予以支持,符合适用法律。'
// }
// ]
// },
// {
// content: "动员联邦融资工具",
// slaver: [
// {
// content: '经济外交行动小组(EDAG),于 2024 年 6 月 21 日总统备忘录中成立,由国务卿主持,并与商务部长和美国贸易代表协商,并根据 2019 年《通过外交倡导美国企业法案》(公共法 116-94 J 部分第七章)第 708 条(CABDA)所述,应协调联邦融资工具的动员,以支持优先的人工智能出口方案。'
// },
// {
// content: '我将根据 CABDA α 第 708 (c) (3) 条授权小企业管理局局长和 OSTP 主任任命各自执行部门和机构的高级官员担任 EDAG 。'
// }
// ]
// }
]);
const ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; const ALPHABET = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
const onMainContentData = async () => { const onMainContentData = async () => {
try { try {
...@@ -257,7 +195,7 @@ const onMainContentData = async () => { ...@@ -257,7 +195,7 @@ const onMainContentData = async () => {
console.log("主要指令", res); console.log("主要指令", res);
if (res && res.code === 200) { if (res && res.code === 200) {
contentList.value = res.data || []; contentList.value = res.data || [];
contentList.value.forEach((item, index) => { item.content = `(${simpleNumToChinese(index + 1)}) ${item.content}` }) contentList.value.forEach((item, index) => { item.content = `(${onNumToChinese(index + 1)}) ${item.content}` })
if (keyword) { if (keyword) {
let word = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); let word = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
contentList.value.forEach(item => { onHighlight(word, item) }) contentList.value.forEach(item => { onHighlight(word, item) })
...@@ -279,30 +217,6 @@ const onHighlight = (word, row) => { ...@@ -279,30 +217,6 @@ const onHighlight = (word, row) => {
row.slaver.forEach(item => { onHighlight(word, item) }) row.slaver.forEach(item => { onHighlight(word, item) })
} }
} }
// 数字转中文(支持 0-99 整数)
const simpleNumToChinese = (num) => {
// 1. 基础校验:只处理 0-99 的整数
if (!Number.isInteger(num) || num < 0 || num > 99) return '100';
// 2. 定义基础字符
const singleChars = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九'];
const tenChar = '十';
// 3. 核心转换逻辑
if (num < 10) {
// 0-9 直接返回对应字符
return singleChars[num];
} else if (num === 10) {
// 10 特殊处理
return tenChar;
} else if (num < 20) {
// 11-19:十 + 个位(如十一、十九)
return tenChar + singleChars[num - 10];
} else {
// 20-99:十位 + 十 + 个位(个位为0则省略,如二十、二十九)
const ten = Math.floor(num / 10); // 十位数字
const unit = num % 10; // 个位数字
return singleChars[ten] + tenChar + (unit === 0 ? '' : singleChars[unit]);
}
}
// 思维导图 // 思维导图
const isTreeDialog = ref(false); const isTreeDialog = ref(false);
......
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
<template #default="{ row }"> <template #default="{ row }">
<div class="entity-name-cell"> <div class="entity-name-cell">
<el-avatar class="avatar" :size="24" :src="row.avatar || defaultIcon" /> <el-avatar class="avatar" :size="24" :src="row.avatar || defaultIcon" />
<div class="name" :title="row.name">{{ row.name }}</div> <div class="name" @click="handleCompClick(row)" :title="row.name">{{ row.name }}</div>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -81,6 +81,8 @@ ...@@ -81,6 +81,8 @@
import { ref, defineProps, defineEmits, computed, watch } from "vue"; import { ref, defineProps, defineEmits, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue"; import { Close } from "@element-plus/icons-vue";
import defaultIcon from "@/assets/icons/default-icon1.png"; import defaultIcon from "@/assets/icons/default-icon1.png";
import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages();
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
...@@ -118,9 +120,9 @@ const tableData = computed(() => { ...@@ -118,9 +120,9 @@ const tableData = computed(() => {
...item, ...item,
name: item.orgName, name: item.orgName,
domains: item.techDomains || [], domains: item.techDomains || [],
equityRatio: item.equityRatio ? (item.equityRatio * 100).toFixed(2) + '%' : '--', equityRatio: item.equityRatio ? (item.equityRatio * 100).toFixed(2) + "%" : "--",
location: '--', location: "--",
revenue: item.revenue || '--' revenue: item.revenue || "--"
})); }));
}); });
...@@ -149,6 +151,13 @@ const getTagStyle = tag => { ...@@ -149,6 +151,13 @@ const getTagStyle = tag => {
const index = Math.abs(hash) % colorPool.length; const index = Math.abs(hash) % colorPool.length;
return colorPool[index]; return colorPool[index];
}; };
// 跳转公司详情页
const handleCompClick = item => {
console.log("item", item);
window.sessionStorage.setItem("curTabName", item.name || item.orgName);
gotoCompanyPages(item.id);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -260,6 +269,7 @@ const getTagStyle = tag => { ...@@ -260,6 +269,7 @@ const getTagStyle = tag => {
.entity-name-cell { .entity-name-cell {
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer;
.avatar { .avatar {
flex: 0 0 24px; flex: 0 0 24px;
margin-right: 9px; margin-right: 9px;
...@@ -347,4 +357,4 @@ const getTagStyle = tag => { ...@@ -347,4 +357,4 @@ const getTagStyle = tag => {
} }
} }
} }
</style> </style>
\ No newline at end of file
...@@ -670,7 +670,9 @@ ...@@ -670,7 +670,9 @@
</el-col> </el-col>
</template> </template>
<template v-if="activeResourceTab === 'commerce'"> <template v-if="activeResourceTab === 'commerce'">
<listPage /> <div class="commerce-wrapper" :style="{ minHeight: '500px' }">
<listPage />
</div>
</template> </template>
</el-row> </el-row>
</div> </div>
...@@ -859,6 +861,7 @@ const checkedTime = ref(["全部时间"]); ...@@ -859,6 +861,7 @@ const checkedTime = ref(["全部时间"]);
// 跳转到单条制裁页面,单独打开一个新页面 // 跳转到单条制裁页面,单独打开一个新页面
const handleTitleClick = item => { const handleTitleClick = item => {
if (!item.summary) return;
window.sessionStorage.setItem("curTabName", `${item.year}-${item.dateStr}${item.title}》`); window.sessionStorage.setItem("curTabName", `${item.year}-${item.dateStr}${item.title}》`);
const route = router.resolve({ const route = router.resolve({
path: "/exportControl/singleSanction", path: "/exportControl/singleSanction",
...@@ -1989,7 +1992,7 @@ const handleMediaClick = item => { ...@@ -1989,7 +1992,7 @@ const handleMediaClick = item => {
position: absolute; position: absolute;
width: 240px; width: 240px;
height: 89px; height: 89px;
top: 30px; top: 12px;
right: -24px; right: -24px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
...@@ -390,6 +390,9 @@ onMounted(async () => { ...@@ -390,6 +390,9 @@ onMounted(async () => {
background-color: #fff; background-color: #fff;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset; box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
&:hover {
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset !important;
}
} }
:deep(.el-input__inner) { :deep(.el-input__inner) {
...@@ -462,6 +465,11 @@ onMounted(async () => { ...@@ -462,6 +465,11 @@ onMounted(async () => {
height: 24px; height: 24px;
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
} }
:deep(.el-checkbox__label) {
font-size: 16px;
color: #666666;
font-weight: 400;
}
.custom-date-picker { .custom-date-picker {
width: 100%; width: 100%;
......
...@@ -105,8 +105,12 @@ ...@@ -105,8 +105,12 @@
</div> </div>
</template> </template>
<!-- <div class="echarts" ref="sanctionCountChartRef"></div> --> <!-- <div class="echarts" ref="sanctionCountChartRef"></div> -->
<EChart :option="sanctionCountChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" <EChart
@chart-click="handleBarChartClick" /> :option="sanctionCountChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handleBarChartClick"
/>
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -140,14 +144,22 @@ ...@@ -140,14 +144,22 @@
<div class="map-wrapper"> <div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div> <div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list"> <div class="rank-list">
<div class="rank-item" v-for="(item, index) in rankData" :key="index" @click="handleClickRankChart(item)"> <div
class="rank-item"
v-for="(item, index) in rankData"
:key="index"
@click="handleClickRankChart(item)"
>
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div> <div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div> <div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg"> <div class="rank-bar-bg">
<div class="rank-bar-fill" :style="{ <div
width: (item.value / maxRankValue) * 100 + '%', class="rank-bar-fill"
background: getBarColor(index) :style="{
}"></div> width: (item.value / maxRankValue) * 100 + '%',
background: getBarColor(index)
}"
></div>
</div> </div>
<div class="rank-value">{{ item.value }}家</div> <div class="rank-value">{{ item.value }}家</div>
</div> </div>
...@@ -184,8 +196,12 @@ ...@@ -184,8 +196,12 @@
</el-select> </el-select>
</template> </template>
<!-- <div class="echarts" ref="domainChartRef"></div> --> <!-- <div class="echarts" ref="domainChartRef"></div> -->
<EChart :option="domainChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" <EChart
@chart-click="handlePieChartClick" /> :option="domainChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handlePieChartClick"
/>
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -217,7 +233,12 @@ ...@@ -217,7 +233,12 @@
</el-select> </el-select>
</template> </template>
<!-- <div class="echarts" ref="typeChartRef"></div> --> <!-- <div class="echarts" ref="typeChartRef"></div> -->
<EChart :option="typeChartOption" autoresize :style="{ height: '300px', padding: '0 20px' }" @chart-click="handlePieChartClick1" /> <EChart
:option="typeChartOption"
autoresize
:style="{ height: '300px', padding: '0 20px' }"
@chart-click="handlePieChartClick1"
/>
<!-- <div class="bottom"> <!-- <div class="bottom">
<div class="ai"> <div class="ai">
<div class="left"> <div class="left">
...@@ -271,7 +292,7 @@ const typeChart = useChartInterpretation(); ...@@ -271,7 +292,7 @@ const typeChart = useChartInterpretation();
const rankChart = useChartInterpretation(); const rankChart = useChartInterpretation();
const route = useRoute(); const route = useRoute();
const router = useRouter() const router = useRouter();
// 实体清单-数据统计-制裁实体类型分布情况 // 实体清单-数据统计-制裁实体类型分布情况
const typeData = ref([]); const typeData = ref([]);
const getTypeCountData = async () => { const getTypeCountData = async () => {
...@@ -640,13 +661,14 @@ const updateMapChart = () => { ...@@ -640,13 +661,14 @@ const updateMapChart = () => {
}; };
mapChartInstance.setOption(option); mapChartInstance.setOption(option);
mapChartInstance.on('click', function (params) { mapChartInstance.on("click", function (params) {
const param = { const param = {
selectedProvince: params.name, selectedProvince: params.name,
selectedDate: regionTime.value === 'all' ? null : JSON.stringify([regionTime.value + '-01-01', regionTime.value + '-12-31']) selectedDate:
} regionTime.value === "all" ? null : JSON.stringify([regionTime.value + "-01-01", regionTime.value + "-12-31"])
};
const route = router.resolve({ const route = router.resolve({
path: '/dataLibrary/dataEntityList', path: "/dataLibrary/dataEntityList",
query: param query: param
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -990,10 +1012,10 @@ const updateTypeChart = () => { ...@@ -990,10 +1012,10 @@ const updateTypeChart = () => {
let data = typeData.value.length let data = typeData.value.length
? [...typeData.value] ? [...typeData.value]
: [ : [
{ value: 50, name: "企业" }, { value: 50, name: "企业" },
{ value: 32, name: "高校" }, { value: 32, name: "高校" },
{ value: 32, name: "科研院所" } { value: 32, name: "科研院所" }
]; ];
// 2. 聚合逻辑:保留前5项,其余合并为“其他” // 2. 聚合逻辑:保留前5项,其余合并为“其他”
data.sort((a, b) => b.value - a.value); data.sort((a, b) => b.value - a.value);
...@@ -1116,55 +1138,60 @@ const initTypeChart = () => { ...@@ -1116,55 +1138,60 @@ const initTypeChart = () => {
const sanTypeId = ref(""); const sanTypeId = ref("");
// 点击制裁实体数量变化情况 // 点击制裁实体数量变化情况
const handleBarChartClick = (val) => { const handleBarChartClick = val => {
console.log('value', val); console.log("value", val);
const params = { const params = {
selectedDate: activeTab.value === 'year' ? JSON.stringify([val.name + '-01-01', val.name + '-12-31']) : JSON.stringify([val.name, val.name]) selectedDate:
} activeTab.value === "year"
? JSON.stringify([val.name + "-01-01", val.name + "-12-31"])
: JSON.stringify([val.name, val.name])
};
const route = router.resolve({ const route = router.resolve({
path: '/dataLibrary/dataEntityList', path: "/dataLibrary/dataEntityList",
query: params query: params
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} };
// 制裁实体各省分布情况 // 制裁实体各省分布情况
const handleClickRankChart = (item) => { const handleClickRankChart = item => {
// console.log('item', item); // console.log('item', item);
const params = { const params = {
selectedProvince: item.name, selectedProvince: item.name,
selectedDate: regionTime.value === 'all' ? null : JSON.stringify([regionTime.value + '-01-01', regionTime.value + '-12-31']) selectedDate:
} regionTime.value === "all" ? null : JSON.stringify([regionTime.value + "-01-01", regionTime.value + "-12-31"])
};
const route = router.resolve({ const route = router.resolve({
path: '/dataLibrary/dataEntityList', path: "/dataLibrary/dataEntityList",
query: params query: params
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} };
// 制裁实体领域分布情况 // 制裁实体领域分布情况
const handlePieChartClick = (val) => { const handlePieChartClick = val => {
console.log('val', val); console.log("val", val);
const params = { const params = {
domains: val.name, domains: val.name,
selectedDate: domainTime.value === 'all' ? null : JSON.stringify([domainTime.value + '-01-01', domainTime.value + '-12-31']) selectedDate:
} domainTime.value === "all" ? null : JSON.stringify([domainTime.value + "-01-01", domainTime.value + "-12-31"])
};
const route = router.resolve({ const route = router.resolve({
path: '/dataLibrary/dataEntityList', path: "/dataLibrary/dataEntityList",
query: params query: params
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} };
// 制裁实体类型分布情况 // 制裁实体类型分布情况
const handlePieChartClick1 = (val) => { const handlePieChartClick1 = val => {
console.log('val', val); console.log("val", val);
const params = { const params = {
selectedEntityType: val.name, selectedEntityType: val.name,
selectedDate: typeTime.value === 'all' ? null : JSON.stringify([typeTime.value + '-01-01', typeTime.value + '-12-31']) selectedDate: typeTime.value === "all" ? null : JSON.stringify([typeTime.value + "-01-01", typeTime.value + "-12-31"])
} };
const route = router.resolve({ const route = router.resolve({
path: '/dataLibrary/dataEntityList', path: "/dataLibrary/dataEntityList",
query: params query: params
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -1233,16 +1260,11 @@ onMounted(() => { ...@@ -1233,16 +1260,11 @@ onMounted(() => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.data-statistics { .data-statistics {
width: 1601px; width: 1601px;
margin: 0 auto; margin: 0 auto;
padding-top: 16px; padding-top: 16px;
padding-bottom: 50px; // padding-bottom: 50px;
.nav { .nav {
width: 100%; width: 100%;
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" /> <el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" />
<div class="filters"> <div class="filters">
<el-checkbox v-model="onlyChina" label="只看中国实体" /> <el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select v-model="order" placeholder="请选择排序方式" style="width: 160px">
<el-option v-for="item in orderOptions" :value="item.value" :key="item.value" :label="item.name" />
</el-select>
</div> </div>
</div> </div>
<div class="main"> <div class="main">
...@@ -180,6 +183,11 @@ import { useGotoCompanyPages } from "@/router/modules/company"; ...@@ -180,6 +183,11 @@ import { useGotoCompanyPages } from "@/router/modules/company";
const gotoCompanyPages = useGotoCompanyPages(); const gotoCompanyPages = useGotoCompanyPages();
const router = useRouter(); const router = useRouter();
const order = ref("asc");
const orderOptions = ref([
{ name: "正序", value: "asc" },
{ name: "倒序", value: "desc" }
]);
// 跳转公司详情页 // 跳转公司详情页
const handleCompClick = item => { const handleCompClick = item => {
console.log("item", item); console.log("item", item);
...@@ -195,7 +203,7 @@ const handleCompClick = item => { ...@@ -195,7 +203,7 @@ const handleCompClick = item => {
}; };
const searchKeyword = ref(""); const searchKeyword = ref("");
const onlyChina = ref(false); const onlyChina = ref(true);
const sanctionTime = ref(""); const sanctionTime = ref("");
const currentPage = ref(1); const currentPage = ref(1);
...@@ -398,7 +406,9 @@ const getExportControlListApi = async () => { ...@@ -398,7 +406,9 @@ const getExportControlListApi = async () => {
endDate, endDate,
keyword: searchKeyword.value || undefined, keyword: searchKeyword.value || undefined,
pageNum: currentPage.value, pageNum: currentPage.value,
pageSize: pageSize.value pageSize: pageSize.value,
sortOrder: order.value
// sortField: "sanctionTime" + order.value
}; };
try { try {
...@@ -422,7 +432,12 @@ const getExportControlListApi = async () => { ...@@ -422,7 +432,12 @@ const getExportControlListApi = async () => {
} }
}; };
watch(onlyChina, () => { // watch(onlyChina, () => {
// currentPage.value = 1;
// getExportControlListApi();
// });
watch([onlyChina, order], () => {
currentPage.value = 1; currentPage.value = 1;
getExportControlListApi(); getExportControlListApi();
}); });
...@@ -512,7 +527,7 @@ const handlToDataLibrary1 = () => { ...@@ -512,7 +527,7 @@ const handlToDataLibrary1 = () => {
} }
.filters { .filters {
// display: flex; display: flex;
// align-items: center; // align-items: center;
.el-checkbox { .el-checkbox {
...@@ -574,6 +589,7 @@ const handlToDataLibrary1 = () => { ...@@ -574,6 +589,7 @@ const handlToDataLibrary1 = () => {
margin-top: 8px; margin-top: 8px;
padding-right: 24px; padding-right: 24px;
box-sizing: border-box; box-sizing: border-box;
grid-column: 1 / -1;
:deep(.el-date-editor) { :deep(.el-date-editor) {
width: 100%; width: 100%;
......
...@@ -236,7 +236,7 @@ const headerNavList = ref([ ...@@ -236,7 +236,7 @@ const headerNavList = ref([
width: 100%; width: 100%;
height: auto; height: auto;
min-height: calc(100% - 148px); min-height: calc(100% - 148px);
background-color: #f7f8f9; // background-color: #f7f8f9;
} }
} }
</style> </style>
...@@ -323,7 +323,7 @@ const getSanctionOverviewList = async () => { ...@@ -323,7 +323,7 @@ const getSanctionOverviewList = async () => {
// 单次制裁-制裁概况-制裁背景 // 单次制裁-制裁概况-制裁背景
const timelinePage = ref(1); const timelinePage = ref(1);
const timelinePageSize = ref(3); const timelinePageSize = ref(5);
const hasMore = ref(true); const hasMore = ref(true);
const getSanctionBackground = async (isLoadMore = false) => { const getSanctionBackground = async (isLoadMore = false) => {
...@@ -750,7 +750,7 @@ onMounted(() => { ...@@ -750,7 +750,7 @@ onMounted(() => {
.left-bottom-content { .left-bottom-content {
padding: 20px 25px 0 25px; padding: 20px 25px 0 25px;
height: calc(100% - 45px); // 减去标题高度 height: calc(100% - 20px); // 减去标题高度
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<div class="left-top"> <div class="left-top">
<AnalysisBox title="基本信息" :showAllBtn="false"> <AnalysisBox title="基本信息" :showAllBtn="false">
<div class="left-top-main"> <div class="left-top-main">
<div class="left-top-main-title">{{ CCLInfo.description }}</div> <div class="left-top-main-title">{{ CCLInfo.name + CCLInfo.description }}</div>
<div class="left-top-main-content"> <div class="left-top-main-content">
<div class="content-item"> <div class="content-item">
<span class="label">法律依据:</span> <span class="label">法律依据:</span>
......
...@@ -5,81 +5,80 @@ ...@@ -5,81 +5,80 @@
* @LastEditTime: 2026-01-07 09:58:04 * @LastEditTime: 2026-01-07 09:58:04
--> -->
<template> <template>
<div class="sanctions-overview"> <div class="sanctions-overview">
<div class="side-nav"> <div class="side-nav">
<div v-for="(item, index) in activeTab" :key="index" class="tab-item" :class="{'active': index === activeIndex}" @click="activeIndex = index"> <div
{{item}} v-for="(item, index) in activeTab"
<span v-if="index === activeIndex" class="arrow"></span> :key="index"
</div> class="tab-item"
</div> :class="{ active: index === activeIndex }"
<div class="content-box"> @click="activeIndex = index"
<introductionPage v-if="activeIndex === 0"></introductionPage> >
<listPage v-if="activeIndex === 1"></listPage> {{ item }}
</div> <span v-if="index === activeIndex" class="arrow"></span>
</div> </div>
</div>
<div class="content-box">
<introductionPage v-if="activeIndex === 0"></introductionPage>
<listPage v-if="activeIndex === 1"></listPage>
</div>
</div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from "vue";
import introductionPage from "./components/introductionPage/index.vue" import introductionPage from "./components/introductionPage/index.vue";
import listPage from "./components/listPage/index.vue" import listPage from "./components/listPage/index.vue";
const activeTab = ref(["CCL清单简介", "CCL清单列表"])
const activeIndex = ref(0)
const activeTab = ref(["CMC清单简介", "CMC清单列表"]);
const activeIndex = ref(0);
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
*{ .sanctions-overview {
margin: 0; width: 1601px;
padding: 0; margin: 0 auto;
} position: relative;
.sanctions-overview{ // min-height: 800px;
width: 1601px; .side-nav {
margin: 0 auto; position: absolute;
position: relative; top: 27px;
// min-height: 800px; right: 100%;
.side-nav { margin-right: 12px;
position: absolute; display: flex;
top: 27px; flex-direction: column;
right: 100%; gap: 16px;
margin-right: 12px; .tab-item {
display: flex; cursor: pointer;
flex-direction: column; padding: 4px 20px;
gap: 16px; border-radius: 22px;
.tab-item { font-size: 16px;
cursor: pointer; font-weight: 400;
padding: 4px 20px; font-family: "Microsoft YaHei";
border-radius: 22px; line-height: 24px;
font-size: 16px; color: rgb(95, 101, 108);
font-weight: 400; white-space: nowrap;
font-family: "Microsoft YaHei"; display: flex;
line-height: 24px; align-items: center;
color: rgb(95, 101, 108);
white-space: nowrap; &.active {
display: flex; background-color: rgb(5, 95, 194);
align-items: center; color: #fff;
&.active { .arrow {
background-color: rgb(5, 95, 194); display: inline-block;
color: #fff; width: 0;
height: 0;
.arrow { border-top: 5px solid transparent;
display: inline-block; border-bottom: 5px solid transparent;
width: 0; border-left: 6px solid #fff;
height: 0; margin-left: 8px;
border-top: 5px solid transparent; }
border-bottom: 5px solid transparent; }
border-left: 6px solid #fff; }
margin-left: 8px; }
} .content-box {
} width: 100%;
} }
}
.content-box {
width: 100%;
}
} }
</style> </style>
...@@ -55,21 +55,42 @@ import icon2Active from "../assets/icons/icon2_active.png"; ...@@ -55,21 +55,42 @@ import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png"; import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png"; import icon3Active from "../assets/icons/icon3_active.png";
import { getCCLInfo } from "@/api/exportControlV2.0.js";
const route = useRoute(); const route = useRoute();
const sanTypeId = ref(""); const sanTypeId = ref("");
onMounted(() => { onMounted(() => {
// 获取路由参数sanTypeId // 获取路由参数sanTypeId
sanTypeId.value = route.query.sanTypeId; sanTypeId.value = route.query.sanTypeId;
console.log("CommercialControlList 页面接收到的 sanTypeId:", sanTypeId.value); console.log("CommercialControlList 页面接收到的 sanTypeId:", sanTypeId.value);
getCCLInfoFn();
}); });
const headerTitle = ref({ const headerTitle = ref({
img: title, img: "",
title: "商业管制清单(CCL)", title: "",
titleEn: "Commercial Control List", titleEn: "",
department: "美国商务部工业与安全局" department: ""
}); });
const getCCLInfoFn = async () => {
try {
const res = await getCCLInfo(route.query.sanTypeId || 13);
if (res && res.code === 200) {
const info = res.data;
headerTitle.value = {
img: info.orgLogoUrl,
title: `${info.name}${info.shortName})`,
titleEn: info.originalName,
department: info.orgName
};
console.log("getCCLInfoFn", info);
}
} catch (error) {
console.error("获取商业管制清单基本信息失败:", error);
}
};
const activeIndex = ref(0); const activeIndex = ref(0);
const headerNavList = ref([ const headerNavList = ref([
......
...@@ -110,7 +110,9 @@ ...@@ -110,7 +110,9 @@
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
</div> </div>
<div class="data-origin-text">进入实体清单的中国实体数量变化趋势,数据来源:美国商务部官网</div> <div class="data-origin-text">
进入SDN清单的中国实体数量变化趋势,数据来源:美国财政部海外资产管理办公室官网
</div>
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
...@@ -169,7 +171,9 @@ ...@@ -169,7 +171,9 @@
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
</div> </div>
<div class="data-origin-text">进入实体清单的中国实体领域分布情况,数据来源:美国商务部官网</div> <div class="data-origin-text">
进入SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
...@@ -208,7 +212,9 @@ ...@@ -208,7 +212,9 @@
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
</div> </div>
<div class="data-origin-text">进入实体清单的中国实体领域分布情况,数据来源:美国商务部官网</div> <div class="data-origin-text">
进入SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
...@@ -240,7 +246,9 @@ ...@@ -240,7 +246,9 @@
<div class="data-origin-icon"> <div class="data-origin-icon">
<img :src="tipsIcon" alt="" /> <img :src="tipsIcon" alt="" />
</div> </div>
<div class="data-origin-text">进入实体清单的中国实体类型分布情况,数据来源:美国商务部官网</div> <div class="data-origin-text">
进入SDN清单的中国实体类型分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
</div> </div>
<div class="ai-pane"> <div class="ai-pane">
<AiButton /> <AiButton />
...@@ -525,7 +533,8 @@ const getDomainNumData = async () => { ...@@ -525,7 +533,8 @@ const getDomainNumData = async () => {
const processedData = processDomainTrendData(res); const processedData = processDomainTrendData(res);
domainNumChartOption.value = getMultiLineChart(processedData); domainNumChartOption.value = getMultiLineChart(processedData);
console.log("获取实体清单-数据统计-processedData:", processedData); console.log("获取实体清单-数据统计-processedData:", processedData);
console.log("获取实体清单-数据统计-domainNumChartOption:", domainNumChartOption.value); console.log("获取实体清单-数据统计-domainNumChartOption:", res);
domainNumChart.interpret({ type: "折线图", name: "制裁实体领域数量变化情况", data: res });
} }
} catch (error) { } catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域数量变化趋势失败:", error); console.error("获取实体清单-数据统计-制裁实体领域数量变化趋势失败:", error);
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<div class="right"> <div class="right">
<AnalysisBox title="投融资限制举措关系图"> <AnalysisBox title="投融资限制举措关系图">
<div class="right-main"> <div class="right-main">
<div class="relation-empty" v-if="selectedSanctionIds.length == 0"> <div class="relation-empty" v-if="emptyOfRelation">
<el-empty :image="emptyImg" :image-size="200"> <el-empty :image="emptyImg" :image-size="200">
<template #description> <template #description>
<div class="empty">请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果</div> <div class="empty">请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果</div>
...@@ -139,7 +139,24 @@ ...@@ -139,7 +139,24 @@
<div class="icon2"></div> <div class="icon2"></div>
</div> </div>
</div> </div>
<RelationChart :is-vertical-chart="true" :graph-data="graphData" /> <!-- <div class="relation-content">
<RelationChart :is-vertical-chart="true" :graph-data="graphData" />
</div> -->
<div class="relation-charts-container">
<div v-for="(graphData, index) in graphDataList" :key="index" class="single-relation-chart-wrapper">
<!-- 可选:显示当前小图的标题,例如制裁名称 -->
<!-- <div class="chart-title" v-if="graphData.originalItem?.vertex?.name">
{{ graphData.originalItem.vertex.name }}
</div> -->
<div class="relation-content-item">
<RelationChart :is-vertical-chart="true" :graph-data="graphData" />
</div>
</div>
<!-- 空状态提示 -->
<div v-if="graphDataList.length === 0" class="empty-chart-tip">暂无关联详情数据</div>
</div>
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
...@@ -383,13 +400,16 @@ const fetchRecordRelation = async () => { ...@@ -383,13 +400,16 @@ const fetchRecordRelation = async () => {
recordRelation.value = { noRelationVertices: [], relationVoList: [] }; recordRelation.value = { noRelationVertices: [], relationVoList: [] };
} }
}; };
const emptyOfRelation = computed(
() => recordRelation.value.noRelationVertices.length == 0 || recordRelation.value.relationVoList.length == 0
);
const vertexInfo = ref({}); const vertexInfo = ref({});
const curNode = ref({}); const curNode = ref({});
const curLink = ref({}); const curLink = ref({});
const nodeVisible = ref(false); const nodeVisible = ref(false);
const relationVisible = ref(false); const relationVisible = ref(false);
const tipsInfo = ref(""); const tipsInfo = ref("");
const graphData = ref({}); const graphDataList = ref([]);
const getTipsInfo = (relationType, reason) => { const getTipsInfo = (relationType, reason) => {
switch (relationType) { switch (relationType) {
case "继承": case "继承":
...@@ -400,6 +420,8 @@ const getTipsInfo = (relationType, reason) => { ...@@ -400,6 +420,8 @@ const getTipsInfo = (relationType, reason) => {
return `${dayjs(curLink.value.data.originInfo.fromVertex.date).format("YYYY年MM月DD日")}-SDN清单更新与${dayjs(curLink.value.data.originInfo.toVertex.date).format("YYYY年MM月DD日")}-SDN清单更新存在相同${reason},属于相似关系。`; return `${dayjs(curLink.value.data.originInfo.fromVertex.date).format("YYYY年MM月DD日")}-SDN清单更新与${dayjs(curLink.value.data.originInfo.toVertex.date).format("YYYY年MM月DD日")}-SDN清单更新存在相同${reason},属于相似关系。`;
} }
}; };
// 在 constrainedAssociation.vue 的 script setup 中
const handleClickNode = node => { const handleClickNode = node => {
console.log("节点点击", node); console.log("节点点击", node);
if (node.dataType == "node") { if (node.dataType == "node") {
...@@ -418,25 +440,126 @@ const handleClickNode = node => { ...@@ -418,25 +440,126 @@ const handleClickNode = node => {
relationVisible.value = true; relationVisible.value = true;
curLink.value = node; curLink.value = node;
const relationType = node.data.relationType; const relationType = node.data.relationType;
// 继承 - 2025年10月1日-SDN清单更新依托于2024年2月08日-SDN清单更新,两次制裁存在继承关系。
// 冲突 - 2025年2月19日-SDN清单更新中制裁的实体在2024年2月08日-SDN清单更新中被移除,属于冲突关系。
// 相似 - 2025年2月19日-SDN清单更新与2024年2月08日-SDN清单更新存在相同制裁原因,属于相似关系。
// 相似 - 2025年2月19日-SDN清单更新与2024年2月08日-SDN清单更新存在同领域制裁实体,属于相似关系。
// 相似 - 2025年2月19日-SDN清单更新与2024年2月08日-SDN清单更新存在相同依托文件,属于相似关系。
getEdgeInfo(node.data.edgeInfo.key).then(res => { // 获取边详情数据
if (!!res) { getEdgeInfo(node.data.edgeInfo.key)
// recordRelation.value = res; .then(res => {
console.log("制裁之间的关系 =>", res); if (!!res && Array.isArray(res)) {
let reason = ""; console.log("制裁之间的关系 =>", res);
if (relationType == "相似") {
reason = res[0].edgeReasonList[0].reason; // 【核心修改】遍历 res,为每一项生成独立的图表数据
const list = [];
res.forEach((item, index) => {
const vertex = item.vertex;
if (!vertex || !vertex.id) return;
const nodes = [];
const lines = [];
const nodeMap = new Map();
// 辅助函数:添加节点
const addNode = (id, text, type = "vertex") => {
if (nodeMap.has(id)) return;
const newNode = {
id: id,
text: text,
// 样式:顶点用主题色,细节用白色
color: type === "vertex" ? "var(--color-primary-50)" : "#ffffff",
fontColor: type === "vertex" ? "var(--text-primary-90-color)" : "#333333",
customFontSize: type === "vertex" ? "14px" : "12px"
};
nodes.push(newNode);
nodeMap.set(id, newNode);
};
// 辅助函数:添加连线
const addLine = (fromId, toId, relationText) => {
lines.push({
from: fromId,
to: toId,
text: relationText,
color: "var(--color-primary-50)",
fontColor: "#666"
});
};
// 1. 添加出发点 (Vertex)
// addNode(vertex.id, vertex.name, "vertex");
// 1. 添加出发点 (Vertex)
// 【修改点】:根据 originInfo 中的 fromVertex 或 toVertex 动态生成名称
let vertexName = vertex.name; // 默认 fallback
// 尝试从 curLink (即 node) 中获取 originInfo
const originInfo = node.data?.originInfo;
if (originInfo) {
// 判断当前 vertex.id 是 from 还是 to,从而决定取哪个日期
// 通常 item.vertex 对应的是边的起点或终点之一,这里假设 item.vertex 就是我们要展示的核心节点
// 如果业务逻辑中 item.vertex 始终对应 fromVertex 或 toVertex 中的一个,我们可以这样判断:
let sourceDate = null;
if (originInfo.fromVertex && originInfo.fromVertex.id === vertex.id) {
sourceDate = originInfo.fromVertex.date;
} else if (originInfo.toVertex && originInfo.toVertex.id === vertex.id) {
sourceDate = originInfo.toVertex.date;
}
// 如果找到了对应的日期,则格式化;否则保持原名或使用默认逻辑
if (sourceDate) {
vertexName = dayjs(sourceDate).format("YYYY年MM月DD日") + " SDN清单更新";
}
}
addNode(vertex.id, vertexName, "vertex");
// 2. 处理 edgeReasonList -> reasonDetail
if (item.edgeReasonList && item.edgeReasonList.length > 0) {
item.edgeReasonList.forEach(reasonItem => {
const relationName = reasonItem.reason; // 例如: "依托文件"
if (reasonItem.reasonDetail && reasonItem.reasonDetail.length > 0) {
reasonItem.reasonDetail.forEach(detailItem => {
// 使用 detailItem.name 作为唯一 ID
// 注意:在这个独立的小图中,ID 只要不重复即可。
// 如果不同项之间有相同的 detailItem.name,它们在不同图中是隔离的,所以没问题。
const detailId = detailItem.name;
addNode(detailId, detailItem.name, "detail");
addLine(vertex.id, detailId, relationName);
});
}
});
}
// 只有当有连线时才加入列表,或者即使只有顶点也加入(视需求而定)
if (nodes.length > 0) {
list.push({
rootId: vertex.id,
nodes: nodes,
lines: lines,
// 可以保留原始数据用于调试或额外展示
originalItem: item
});
}
});
graphDataList.value = list;
// 处理提示文案 (取第一个或根据业务逻辑组合)
let reason = "";
if (relationType == "相似" && res[0]?.edgeReasonList?.[0]?.reason) {
reason = res[0].edgeReasonList[0].reason;
}
tipsInfo.value = getTipsInfo(relationType, reason);
} else {
graphDataList.value = [];
} }
tipsInfo.value = getTipsInfo(relationType, reason); })
} else { .catch(err => {
// recordRelation.value = { noRelationVertices: [], relationVoList: [] }; console.error("获取边信息失败", err);
} graphDataList.value = [];
}); });
} }
}; };
...@@ -481,10 +604,6 @@ const formatChangeSummary = (addList, delList) => { ...@@ -481,10 +604,6 @@ const formatChangeSummary = (addList, delList) => {
return `${item.value}${unit}${noun}`; return `${item.value}${unit}${noun}`;
}); });
// 拼接:移除 + item1 + , + item2 ...
// 注意:题目要求“删除”,但之前代码用的是“移除”,这里统一使用“移除”或“删除”。
// 根据题目描述“展示样本为:新增12个实体,3名个人,移除1个实体”,这里使用“移除”更贴切上下文,
// 如果必须用“删除”,请将下面的 '移除' 改为 '删除'。
parts.push(`移除${delItems.join(",")}`); parts.push(`移除${delItems.join(",")}`);
} }
...@@ -708,6 +827,57 @@ onMounted(() => { ...@@ -708,6 +827,57 @@ onMounted(() => {
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
border-top: 1px solid rgb(238, 238, 238); border-top: 1px solid rgb(238, 238, 238);
// 【新增】关系图容器样式
.relation-charts-container {
display: flex;
flex-direction: column;
gap: 20px;
height: 400px;
overflow-y: auto;
padding-right: 10px; // 给滚动条留空间
// 自定义滚动条样式
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
}
.single-relation-chart-wrapper {
border: 1px solid #eee;
border-radius: 8px;
padding: 10px;
background-color: #fafafa;
height: 400px;
.chart-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
padding-left: 5px;
border-left: 3px solid var(--color-primary-50);
}
.relation-content-item {
height: 400px; // 每个小图的高度,可根据需要调整
// width: 100%;
// 确保 RelationChart 内部能正确填充
:deep(.relation-graph) {
width: 100%;
height: 100%;
}
}
}
.empty-chart-tip {
text-align: center;
color: #999;
padding: 20px;
}
.hintWrap { .hintWrap {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -753,6 +923,10 @@ onMounted(() => { ...@@ -753,6 +923,10 @@ onMounted(() => {
} }
} }
} }
.relation-content {
height: 400px;
width: 100%;
}
.info-btn { .info-btn {
position: absolute; position: absolute;
top: 20px; top: 20px;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论