提交 6d438cac authored 作者: 徐先红's avatar 徐先红

合并分支 'xxh-dev' 到 'master'

Xxh dev 查看合并请求 !67
......@@ -16,6 +16,7 @@
"axios": "^1.12.2",
"d3": "^7.9.0",
"d3-cloud": "^1.2.7",
"default-passive-events": "^4.0.0",
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
"echarts-wordcloud": "^2.1.0",
......@@ -3045,6 +3046,12 @@
"node": ">=0.10.0"
}
},
"node_modules/default-passive-events": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/default-passive-events/-/default-passive-events-4.0.0.tgz",
"integrity": "sha512-0whk/GqfDOjc0AJIpacXUSqX6kV9TjL3GFSXIxFvuXQYcK+bEdJ6rpJnAEfP4YYMYWibM+jhlwmdlVrlifoepg==",
"license": "MIT"
},
"node_modules/define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/define-property/-/define-property-2.0.2.tgz",
......
......@@ -25,6 +25,7 @@
"axios": "^1.12.2",
"d3": "^7.9.0",
"d3-cloud": "^1.2.7",
"default-passive-events": "^4.0.0",
"echarts": "^5.4.3",
"echarts-liquidfill": "^3.1.0",
"echarts-wordcloud": "^2.1.0",
......
......@@ -21,34 +21,139 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from "echarts";
import Center from "./assets/约翰·伦道夫·图恩.png";
import P1 from "./assets/唐纳德·特朗普.png";
import P2 from "./assets/约翰·巴拉索.png";
import P3 from "./assets/布里顿·图恩.png";
import P4 from "./assets/金伯利·韦姆斯·图恩.png";
import P5 from "./assets/乔治·S·米克尔森.png";
import P6 from "./assets/哈罗德·理查德·图恩.png";
import P7 from "./assets/伊冯娜·帕特里夏·图恩.png";
import P8 from "./assets/蒂姆·约翰逊.png";
import P9 from "./assets/吉姆·阿布德诺.png";
import P10 from "./assets/汤姆·达施勒.png";
import P11 from "./assets/拉里莎·图恩.png";
import PS from "./assets/拉里·普雷斯勒.png";
import {getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js";
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
const nodes = [
{
import 'default-passive-events';
// const nodes = [
// {
// id: "c",
// name: "埃隆·马斯克",
// category: 0,
// symbolSize: 80,
// symbol: `image://${Center}`,
// label: {
// show: true,
// position: "bottom",
// formatter: "{n|{b}}",
// rich: {
// n: {
// color: "rgba(5,95,194,1)",
// fontSize: 24,
// fontWeight: 700,
// fontFamily: "Microsoft YaHei",
// lineHeight: 36
// }
// }
// }
// },
// // 从三点钟方向顺时针排序
// { id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
// { id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
// { id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
// { id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
// { id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
// { id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
// { id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
// { id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
// { id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
// { id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
// { id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
// { id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
// ];
// const links = [
// { source: "n11", target: "c", label: { show: true, formatter: "第一任妻子" } },
// { source: "n7", target: "c", label: { show: true, formatter: "风险投资家" } },
// { source: "n6", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n9", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n12", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n5", target: "c", label: { show: true, formatter: "早期重要投资人" } },
// { source: "n8", target: "c", label: { show: true, formatter: "Boring Company 总裁" } },
// { source: "n10", target: "c", label: { show: true, formatter: "特斯拉独立董事" } },
// { source: "n2", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n1", target: "c", label: { show: true, formatter: "美国总统" } },
// { source: "n4", target: "c", label: { show: true, formatter: "马斯克弟弟" } },
// { source: "n3", target: "c", label: { show: true, formatter: "SpaceX 总裁" } }
// ];
// 处理图片代理
const getProxyUrl = (url) => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
const nodes = ref([]);
const links = ref([]);
// 人物全局信息
const characterInfo = ref({});
// 人物关系
const CharacterRelation = ref([]);
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
}catch(error){
}
};
const getCharacterRelationFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterRelation(params);
if (res.code === 200) {
console.log("人物关系", res);
if (res.data) {
CharacterRelation.value = res.data;
}
}
if(CharacterRelation.value.length > 0){
const centerNode = {
id: "c",
name: "约翰·伦道夫·图恩",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${Center}`,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|约翰·伦道夫·图恩}",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
......@@ -59,56 +164,155 @@ const nodes = [
}
}
}
}
const newNodes = CharacterRelation.value.map((item,index) => {
return{
id: index,
name: item.name,
category: 1,
symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}`
}
});
newNodes.push(centerNode);
const newLinks = CharacterRelation.value.map((item,index) =>({
source:index,
target:"c",
label: {
show: true,
formatter: item.relation }
}));
nodes.value = newNodes;
links.value = newLinks;
}else{
nodes.value = [{
id: "c",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
fontSize: 24,
fontWeight: 700,
fontFamily: "Microsoft YaHei",
lineHeight: 36
}
}
}
}];
links.value = [];
}
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
return { ...n, x, y };
});
chart.setOption({
tooltip: {},
series: [
{
type: "graph",
layout: activeIndex.value === "圆形布局" ? "none" : "force",
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
edgeLabel: {
show: true,
position: "middle",
distance: -18,
formatter: ({ data }) => data?.label?.formatter || "",
color: "rgb(5, 95, 194)",
fontSize: 12,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
lineHeight: 24,
backgroundColor: "rgba(231, 243, 255, 1)",
borderRadius: 24,
padding: [0, 12]
},
// 从三点钟方向顺时针排序
{ id: "n11", name: "拉里莎·图恩", category: 1, symbolSize: 80, symbol: `image://${P11}` },
{ id: "n7", name: "伊冯娜·帕特里夏·图恩", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
{ id: "n6", name: "哈罗德·理查德·图恩", category: 1, symbolSize: 80, symbol: `image://${P6}` },
{ id: "n9", name: "吉姆·阿布德诺", category: 1, symbolSize: 80, symbol: `image://${P9}` },
{ id: "n12", name: "拉里·普雷斯勒", category: 1, symbolSize: 80, symbol: `image://${PS}` },
{ id: "n5", name: "乔治·S·米克尔森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
{ id: "n8", name: "蒂姆·约翰逊", category: 1, symbolSize: 80, symbol: `image://${P8}` },
{ id: "n10", name: "汤姆·达施勒", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
{ id: "n2", name: "约翰·巴拉索", category: 1, symbolSize: 80, symbol: `image://${P2}` },
{ id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
{ id: "n4", name: "金伯利·韦姆斯·图恩", category: 1, symbolSize: 80, symbol: `image://${P4}` },
{ id: "n3", name: "布里顿·图恩", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
];
const links = [
{ source: "n11", target: "c", label: { show: true, formatter: "次女" } },
{ source: "n7", target: "c", label: { show: true, formatter: "母亲" } },
{ source: "n6", target: "c", label: { show: true, formatter: "父亲" } },
{ source: "n9", target: "c", label: { show: true, formatter: "" } },
{ source: "n12", target: "c", label: { show: true, formatter: "盟友" } },
{ source: "n5", target: "c", label: { show: true, formatter: "前南达科他州州长" } },
{ source: "n8", target: "c", label: { show: true, formatter: "政治对手" } },
{ source: "n10", target: "c", label: { show: true, formatter: "政治对手" } },
{ source: "n2", target: "c", label: { show: true, formatter: "亲密盟友" } },
{ source: "n1", target: "c", label: { show: true, formatter: "政治盟友" } },
{ source: "n4", target: "c", label: { show: true, formatter: "妻子" } },
{ source: "n3", target: "c", label: { show: true, formatter: "长女" } }
];
label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 },
itemStyle: { color: "rgba(5,95,194,1)" },
emphasis: { focus: "adjacency" }
}
]
});
};
setOption();
}catch(error){
}
};
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
let chart;
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterRelationFn();
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.map((n, i) => {
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.length - 1)) * Math.PI * 2;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
......@@ -124,8 +328,8 @@ onMounted(() => {
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes,
links: links,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
......@@ -151,6 +355,7 @@ onMounted(() => {
});
};
setOption();
console.log("node1", nodes);
const onResize = () => chart && chart.resize();
window.addEventListener("resize", onResize);
watch(activeIndex, () => setOption());
......
......@@ -12,18 +12,18 @@
</div>
</div>
<div class="main">
<div v-for="item in list" :key="item.id" class="item">
<div v-for="item in CharacterProposal" :key="item.id" class="item">
<div class="img-box">
<img :src="item.img" alt="" class="img" />
<div class="info">
<div class="title">{{ item.title }}</div>
<div>
<span
v-for="(tag, index) in item.tie"
v-for="tag in item.tie"
:key="tag"
class="tag"
:class="{ 'tag-1': index == 0, 'tag-2': index == 1 }"
>{{ tag }}</span
:class="{ 'tag-1': tag.status == 1, 'tag-2': tag.status == 8, 'tag-3': tag.status == 4 }"
>{{ tag.industryName }}</span
>
</div>
</div>
......@@ -49,10 +49,12 @@
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
:total="100"
v-model:current-page="currentPage"
:total="total"
class="custom-pagination"
/>
</div>
......@@ -60,14 +62,82 @@
</template>
<script setup>
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import img from "./assets/img.png";
import { getCharacterProposal } from "@/api/characterPage/characterPage.js";
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterProposalFn();
};
// 获取历史提案
const CharacterProposal = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterProposalFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
industryId: 1,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterProposal(params, abortController.value.signal);
console.log("历史提案", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterProposal.value = res.data.content.map(item => ({
id: item.billId,
title: item.name,
tie: item.industryList,
disc: item.description,
state: item.status,
time: item.time,
img: item.imageUrl || img
}));
total.value = res.data.totalElements;
}else {
CharacterProposal.value = [];
total.value = 0;
}
}else {
CharacterProposal.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
onMounted(() => {
getCharacterProposalFn();
});
const searchText = ref("");
const value1 = ref("全部法案");
const value2 = ref("全部领域");
const currentPage = ref(2);
const list = ref([
{
......@@ -227,6 +297,11 @@ const list = ref([
color: rgba(250, 173, 20, 1);
border-color: rgba(255, 241, 184, 1);
}
.tag-3 {
background-color: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
}
}
}
.info-box {
......@@ -251,6 +326,11 @@ const list = ref([
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
......
......@@ -18,9 +18,8 @@
:teleported="false"
size="small"
>
<el-option label="近五年" value="近五年"></el-option>
<el-option label="近十年" value="近十年"></el-option>
<el-option label="近一年" value="近一年"></el-option>
<el-option @click="" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -59,8 +58,8 @@
<span class="title-text">科技法案</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList" :key="item.id" class="item">
<div class="main-box-content" style="height: 300px;">
<div v-for="item in relationBillsList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -76,8 +75,8 @@
<span class="title-text">科技政令</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in twoList" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationAdList" :key="item.id" class="item">
<div>
<div class="item-name">{{ item.name }}</div>
<div class="item-date">{{ item.date }}</div>
......@@ -92,8 +91,8 @@
<span class="title-text">科技智库</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList2" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationThinkTankList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -124,10 +123,90 @@ import book6 from "./assets/book6.png";
import type1 from "./assets/type1.png";
import type2 from "./assets/type2.png";
import { getCharacterRelatedEntity } from "@/api/characterPage/characterPage.js";
const selectedOption = ref(1);
const yearList = ref([
{
label: "近一年",
value: 1
},
{
label: "近两年",
value: 2
},
{
label: "近五年",
value: 5
}
]);
function getDateYearsAgo(years) {
// 获取当前日期
const currentDate = new Date();
// 计算指定年数之前的日期
const pastDate = new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
// 格式化日期为 "YYYY-MM-DD" 的形式
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
//获取相关实体
const relationBillsList = ref({});
const relationAdList = ref({});
const relationThinkTankList = ref({});
const getCharacterRelatedEntityFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
startTime: getDateYearsAgo(selectedOption.value) || "2025-01-01"
};
try{
const res = await getCharacterRelatedEntity(params);
if (res.code === 200) {
console.log("人物相关实体", res);
if (res.data) {
relationBillsList.value = res.data.relationBillsList.map(item=>{
return{
id: item.billId,
name: item.billName,
type: item.industryList,
date: item.billTime,
img: book1 //待加
}
});
relationAdList.value = res.data.relationAdList.map(item=>{
return{
id: item.id,
name: item.adName,
type: item.adType,
date: item.adTime
}
});
relationThinkTankList.value = res.data.relationThinkTankList.map(item=>{
return{
id: item.reportId,
name: item.reportName,
type: type1, //缺属性
date: item.reportTime,
img: item.thinkTankUrl
}
});
}
}
}catch(error){
}
};
const searchText = ref("");
const selectedOption = ref("近五年");
const billList = ref([
{
......@@ -375,6 +454,7 @@ const makeOption = el => {
};
onMounted(() => {
getCharacterRelatedEntityFn();
const el = document.querySelector(".echarts");
if (!el) return;
chart = echarts.init(el);
......@@ -628,8 +708,8 @@ const setLayout = type => {
}
.main-box-content {
width: 100%;
min-height: 142px;
padding: 12px 0;
overflow-y: scroll;
.item {
width: 100%;
height: 53px;
......@@ -666,15 +746,18 @@ const setLayout = type => {
font-family: "Microsoft YaHei";
line-height: 20px;
padding: 1px 8px;
border-radius: 4px;
border: 1px solid;
}
.type-ai {
border-radius: 4px;
border: 1px solid;
color: rgba(19, 168, 168, 1);
border-color: rgba(135, 232, 222, 1);
background-color: rgba(230, 255, 251, 1);
}
.type-energy {
border-radius: 4px;
border: 1px solid;
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
background-color: rgba(255, 241, 240, 1);
......
......@@ -3,29 +3,31 @@
<!-- 国会议员人物基础 -->
<div class="header">
<div class="avatar">
<img :src="musk.avatar" alt="" />
<el-avatar :size="160" shape="circle" :src="getProxyUrl(characterInfo.imageUrl)" />
</div>
<div class="info">
<!-- <div class="name"> -->
<p class="name-cn">{{ musk.nameCn }}</p>
<p class="name-en">{{ musk.nameEn }}</p>
<p class="name-cn">{{ characterInfo.name }}</p>
<p class="name-en">{{ characterInfo.ename }}</p>
<!-- </div> -->
<div class="introduction">
<p>{{ musk.introduction }}</p>
<p>{{ characterInfo.description }}</p>
</div>
<div class="domain">
<p
v-for="item in musk.domain"
v-for="item in characterInfo.industryList"
:key="item"
class="cl1"
:class="{
cl1: item === '共和党',
cl2: item === '电信改革',
cl3: item === '美国参议员',
cl4: item === '对华强硬派',
cl5: item === '参议院多数党领袖'
cl1: item.status === '1',
cl2: item.status === '2',
cl3: item.status === '3',
cl4: item.status === '4',
cl5: item.status === '5',
cl6: item.status === '6'
}"
>
{{ item }}
{{ item.industryName }}
</p>
</div>
</div>
......@@ -50,13 +52,13 @@
</div>
<!-- 主要内容 -->
<div class="num-list">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="numActive = item">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="handleChangeYear(item)">
{{ item }}
</div>
</div>
<!-- echarts 图表 -->
<div class="echarts">
<div class="row" v-for="(row, index) in wordCloudData" :key="index">
<div class="echarts" id="wordCloudChart">
<!-- <div class="row" v-for="(row, index) in wordCloudData" :key="index">
<span
v-for="(item, idx) in row"
:key="idx"
......@@ -68,7 +70,7 @@
>
{{ item.text }}
</span>
</div>
</div> -->
</div>
</div>
<div class="left-center">
......@@ -77,9 +79,9 @@
<div class="text">资金来源</div>
<div class="input">
<el-select v-model="selectedOption" placeholder="请选择" class="select">
<el-option label="2025" value="2025"></el-option>
<el-option label="2024" value="2024"></el-option>
<el-option label="2023" value="2023"></el-option>
<el-option @click="handleChangeYearList()" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
<div class="btn">
......@@ -90,7 +92,7 @@
<!-- 主要内容 -->
<div class="main">
<el-table
:data="contributorData"
:data="CharacterFundSource"
style="width: 100%"
:header-cell-style="{
background: 'transparent',
......@@ -124,7 +126,7 @@
<div class="title">
<div class="box"></div>
<div class="text">最新动态</div>
<div class="input"><input type="checkbox" checked />只看涉华动态</div>
<div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange"/>只看涉华动态</div>
<div class="btn">
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
......@@ -132,30 +134,34 @@
</div>
<!-- 主要内容 -->
<div class="main">
<div v-for="item in newList" :key="item" class="main-item">
<div v-for="item in CharacterLatestDynamic" :key="item" class="main-item">
<div class="time">
<div class="year">{{ item.time.split("")[0] }}</div>
<div class="date">{{ item.time.split("年")[1] }}</div>
<div class="year">{{ item.time.split("-")[0] }}</div>
<div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日"}}</div>
</div>
<div class="image">
<img src="./assets/type1.png" alt="" v-if="item.type === 1" /><img
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png"
alt=""
v-else
/>
</div>
<div class="content">
<div :class="{ 'content-type1': item.type === 1, 'content-type2': item.type === 2 }">
<p :class="{ 'content-title1': item.type === 1, 'content-title2': item.type === 2 }">
<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-else class="content-title2">
{{ item.title }}
</p>
<p class="content-title-en">{{ item.titleEn }}</p>
<p v-if="item.remarks === true" class="content-title-en">{{ item.econtent }}</p>
</div>
<p class="content-contentcontent">{{ item.content }}</p>
<p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p>
<div class="content-tag">
<div>
<span
v-for="tag in item.pie"
v-for="tag in item.industryList"
:key="tag"
class="tag"
:class="{
......@@ -168,20 +174,21 @@
>{{ tag }}</span
>
</div>
<div class="origin">来源:{{ item.origin }}</div>
<div class="origin">来源:{{ item.orgName }}</div>
</div>
</div>
</div>
<div class="line-test"></div>
</div>
<div class="pagination">
<div class="total">共153项动态</div>
<div class="total">{{`共${total}项动态`}}</div>
<el-pagination
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
:page-count="10"
:pager-count="5"
v-model:current-page="currentPage"
:total="total"
class="custom-pagination"
/>
</div>
......@@ -201,45 +208,43 @@
<div class="baseInfo">
<div class="baseInfo-item">
<div class="baseInfo-item-title">出生日期:</div>
<div class="baseInfo-item-content">1961年1月7日</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthday}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">现任职位:</div>
<div class="baseInfo-item-content">参议院多数党领袖</div>
<div class="baseInfo-item-content">{{characterBasicInfo.positionTitle}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">党派归属:</div>
<div class="baseInfo-item-content">共和党</div>
<div class="baseInfo-item-content">{{characterBasicInfo.party}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">教育背景:</div>
<div class="baseInfo-item-content">南达科他大学工商管理硕士</div>
<div class="baseInfo-item-content" v-for="item in characterBasicInfo.educationList">{{item.school+item.major}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title address">代表州/选区:</div>
<div class="baseInfo-item-content">南达科他州</div>
<div class="baseInfo-item-content">{{characterBasicInfo.state}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">政治立场:</div>
<div class="baseInfo-item-content long">
财政保守主义、支持农业利益、对华强硬态度以及维护传统价值观为核心
{{characterBasicInfo.political}}
</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">出生地:</div>
<div class="baseInfo-item-content">南达科他州皮埃尔市</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthPlace}}</div>
</div>
</div>
<div class="company">
<div class="company-title">社交媒体</div>
<div class="company-content">
<div v-for="item in companyList" :key="item" class="company-item">
<div class="img">
<img :src="item.logo" alt="" />
</div>
<div v-for="item in characterBasicInfo.organizationList" :key="item" class="company-item">
<img :src="item.imageUrl?item.imageUrl:DefaultIcon2" alt="" />
<div>
<div class="company-cn">{{ item.cn }}</div>
<div class="company-name">{{ item.name }}</div>
<div class="company-cn">{{ item.name }}</div>
<div class="company-name">{{ item.ename }}</div>
</div>
</div>
</div>
......@@ -256,10 +261,10 @@
</div>
</div>
<div class="content-main">
<div v-for="item in resumeList" :key="item.id" class="content-item">
<div v-for="item in CharacterResume" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.time }}</div>
<div class="content-item-title">{{ item.title }}</div>
<div class="content-item-time">{{ item.startTime +'-' + item.endTime}}</div>
<div class="content-item-title">{{ item.orgName +'|' + item.jobName}}</div>
<div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" />
......@@ -292,12 +297,13 @@
</template>
<div class="viewpoint-body">
<div class="viewpoint-body-title">#人工智能</div>
<div v-for="item in dialogData" :key="item.id" class="viewpoint-item">
<img :src="item.img" alt="" />
<div v-for="item in CharacterFieldView" class="viewpoint-item">
<!-- <img :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" alt="" /> -->
<el-avatar :size="42" shape="circle" :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" class="viewpoint-item-img" />
<div class="viewpoint-item-content">
<div class="viewpoint-item-name">{{ item.name }}</div>
<div class="viewpoint-item-desc">{{ item.content }}</div>
<div class="viewpoint-item-job">{{ item.job }}</div>
<div class="viewpoint-item-desc">{{ item.remarks }}</div>
<div class="viewpoint-item-job">{{ item.jobName }}</div>
</div>
</div>
</div>
......@@ -306,11 +312,21 @@
</template>
<script setup>
import { ref } from "vue";
import { ref, onMounted } from "vue";
import CharacterRelationships from "./components/characterRelationships/index.vue";
import RelevantSituation from "./components/relevantSituation/index.vue";
import HistoricalProposal from "./components/historicalProposal/index.vue";
import getWordCloudChart from "../../utils/worldCloudChart";
import setChart from "@/utils/setChart";
import { getCharacterGlobalInfo,
getCharacterBasicInfo,
getCharacterView,
getCharacterLatestDynamic,
getCharacterResume,
getCharacterFieldView,
getCharacterFundSource } from "@/api/characterPage/characterPage.js";
import Musk from "./assets/Musk.png";
import cp1 from "./assets/cp1.png";
import cp2 from "./assets/cp2.png";
......@@ -323,58 +339,336 @@ import img3 from "./assets/img3.png";
import img4 from "./assets/img4.png";
import img5 from "./assets/img5.png";
import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png'
// 处理图片代理
const getProxyUrl = (url) => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
// 获取人物全局信息
const characterInfo = ref({});
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
}catch(error){
}
};
// 获取人物基本信息
const characterBasicInfo = ref({});
const getCharacterBasicInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterBasicInfo(params);
if (res.code === 200) {
console.log("人物基本信息", res);
if (res.data) {
characterBasicInfo.value = res.data;
}
}
}catch (error) {
console.error(error);
}
};
// 获取人物观点
const characterView = ref({});
const getCharacterViewFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
year: numActive.value
};
try{
const res = await getCharacterView(params);
if (res.code === 200) {
console.log("人物观点", res);
if (res.data) {
characterView.value = res.data.map(item=>{
return{
name:item.option,
value:item.count
};
});
}
}
}catch(error){
}
const wordCloudData = ref([
[
{ text: "Boring Company", color: "rgba(19, 168, 168, 1)" },
{ text: "地球化改造", color: "rgb(206, 79, 81)" },
{ text: "减少燃料对外依赖", color: "rgba(250, 173, 20, 1)" },
{ text: "外交政策立场", color: "rgba(250, 173, 20, 1)" },
{ text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
{ text: "预算纪律", color: "rgba(19, 168, 168, 1)" }
],
[
{ text: "保守派财政立场", color: "rgb(206, 79, 81)" },
{ text: "能源政策", color: "rgba(22, 119, 255, 1)" },
{ text: "项目改革", color: "rgba(250, 173, 20, 1)" },
{ text: "农业委员会成员", color: "rgb(206, 79, 81)" },
{ text: "工作可选项", color: "rgba(250, 173, 20, 1)" },
{ text: "教育政策", color: "rgba(19, 168, 168, 1)" },
{ text: "可持续能源", color: "rgba(255, 122, 69, 1)" }
],
[
{ text: "减少政府支出", color: "rgba(255, 122, 69, 1)" },
{ text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
{ text: "第一性原理", color: "rgba(250, 173, 20, 1)" },
{ text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
{ text: "农业经济引擎", color: "rgb(206, 79, 81)" },
{ text: "有限政府", color: "rgba(255, 122, 69, 1)" }
],
[
{ text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
{ text: "财政责任", color: "rgb(206, 79, 81)", fontSize: "26px", fontWeight: "700" },
{ text: "农业优先", color: "rgba(255, 122, 69, 1)" },
{ text: "保守派价值观", color: "rgba(22, 119, 255, 1)" },
{ text: "可持续富足", color: "rgba(250, 173, 20, 1)" },
{ text: "为后代负责", color: "rgba(250, 173, 20, 1)" }
],
[
{ text: "双边合作", color: "rgba(19, 168, 168, 1)" },
{ text: "农场法案倡导者", color: "rgba(250, 173, 20, 1)" },
{ text: "超音速海啸", color: "rgba(250, 173, 20, 1)" },
{ text: "环境议题", color: "rgba(19, 168, 168, 1)" },
{ text: "医疗保健改革", color: "rgb(206, 79, 81)" },
{ text: "多智能体协作", color: "rgba(19, 168, 168, 1)" }
]
};
const handleCharacterView = async () => {
await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart");
};
const handleChangeYear = (item) => {
numActive.value = item;
characterView.value = [];
handleCharacterView();
};
// 获取资金来源
const yearList = ref([
{
label: "2025",
value: 2025
},
{
label: "2024",
value: 2024
},
{
label: "2023",
value: 2023
}
]);
const musk = ref({
nameCn: "约翰·伦道夫·图恩",
nameEn: "John Randolph Thune",
introduction: "美国南达科他州共和党籍联邦参议员,现任参议院多数党领袖,以财政保守主义和对华强硬立场著称。",
domain: ["共和党", "电信改革", "美国参议员", "对华强硬派", "参议院多数党领袖"],
avatar: Musk
const CharacterFundSource = ref([]);
const getCharacterFundSourceFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
year: selectedOption.value || "2025"
};
try{
const res = await getCharacterFundSource(params);
if (res.code === 200) {
console.log("资金来源", res);
if (res.data) {
CharacterFundSource.value = res.data.map((item,index)=>{
return{
rank: index + 1,
contributor: item.orgName,
totalAmount: item.totalDonation,
individualAmount: item.personalDonation,
pacsAmount: item.pacsDonation
}
});
}
}
}catch(error){
}
};
const handleChangeYearList = async () => {
getCharacterFundSourceFn();
};
// 获取领域观点
const CharacterFieldView = ref({});
const getCharacterFieldViewFn = async () => {
const params = {
areaId: window.sessionStorage.getItem("areaId") || "20",
};
try{
const res = await getCharacterFieldView(params);
if (res.code === 200) {
console.log("领域观点", res);
if (res.data) {
CharacterFieldView.value = res.data;
}
}
}catch(error){
}
};
//是否涉华
const isChecked = ref(false);
const related = ref('N');
const handleChange = event => {
if(isChecked.value){
related.value = 'Y';
}else{
related.value = 'N';
}
getCharacterLatestDynamicFn();
console.log(related.value);
};
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterLatestDynamicFn();
};
// 获取最新动态
const CharacterLatestDynamic = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterLatestDynamicFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
cRelated: related.value,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterLatestDynamic(params, abortController.value.signal);
console.log("最新动态", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterLatestDynamic.value = res.data.content.map(item => ({
title: item.title,
content: item.content,
econtent: item.econtent,
time: item.time,
industryList: item.industryList || ["人工智能"],
orgName: item.orgName,
remarks: item.remarks
}));
total.value = res.data.totalElements;
}else {
CharacterLatestDynamic.value = [];
total.value = 0;
}
}else {
CharacterLatestDynamic.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
//获取职业履历
const CharacterResume = ref({});
const getCharacterResumeFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterResume(params);
if (res.code === 200) {
console.log("人物职业履历", res);
if (res.data) {
CharacterResume.value = res.data;
}
}
}catch(error){
}
};
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterBasicInfoFn();
// getCharacterViewFn();
getCharacterLatestDynamicFn();
getCharacterResumeFn();
getCharacterFieldViewFn();
handleCharacterView();
getCharacterFundSourceFn();
});
// const wordCloudData = ref([
// [
// { text: "Boring Company", color: "rgba(19, 168, 168, 1)" },
// { text: "地球化改造", color: "rgb(206, 79, 81)" },
// { text: "减少燃料对外依赖", color: "rgba(250, 173, 20, 1)" },
// { text: "外交政策立场", color: "rgba(250, 173, 20, 1)" },
// { text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
// { text: "预算纪律", color: "rgba(19, 168, 168, 1)" }
// ],
// [
// { text: "保守派财政立场", color: "rgb(206, 79, 81)" },
// { text: "能源政策", color: "rgba(22, 119, 255, 1)" },
// { text: "项目改革", color: "rgba(250, 173, 20, 1)" },
// { text: "农业委员会成员", color: "rgb(206, 79, 81)" },
// { text: "工作可选项", color: "rgba(250, 173, 20, 1)" },
// { text: "教育政策", color: "rgba(19, 168, 168, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" }
// ],
// [
// { text: "减少政府支出", color: "rgba(255, 122, 69, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
// { text: "第一性原理", color: "rgba(250, 173, 20, 1)" },
// { text: "可持续能源", color: "rgba(255, 122, 69, 1)" },
// { text: "农业经济引擎", color: "rgb(206, 79, 81)" },
// { text: "有限政府", color: "rgba(255, 122, 69, 1)" }
// ],
// [
// { text: "Neuralink+Grok", color: "rgba(22, 119, 255, 1)" },
// { text: "财政责任", color: "rgb(206, 79, 81)", fontSize: "26px", fontWeight: "700" },
// { text: "农业优先", color: "rgba(255, 122, 69, 1)" },
// { text: "保守派价值观", color: "rgba(22, 119, 255, 1)" },
// { text: "可持续富足", color: "rgba(250, 173, 20, 1)" },
// { text: "为后代负责", color: "rgba(250, 173, 20, 1)" }
// ],
// [
// { text: "双边合作", color: "rgba(19, 168, 168, 1)" },
// { text: "农场法案倡导者", color: "rgba(250, 173, 20, 1)" },
// { text: "超音速海啸", color: "rgba(250, 173, 20, 1)" },
// { text: "环境议题", color: "rgba(19, 168, 168, 1)" },
// { text: "医疗保健改革", color: "rgb(206, 79, 81)" },
// { text: "多智能体协作", color: "rgba(19, 168, 168, 1)" }
// ]
// ]);
// const musk = ref({
// nameCn: "约翰·伦道夫·图恩",
// nameEn: "John Randolph Thune",
// introduction: "美国南达科他州共和党籍联邦参议员,现任参议院多数党领袖,以财政保守主义和对华强硬立场著称。",
// domain: ["共和党", "电信改革", "美国参议员", "对华强硬派", "参议院多数党领袖"],
// avatar: Musk
// });
const tableRowClassName = ({ row, rowIndex }) => {
if (rowIndex % 2 === 0) {
return "highlight-row";
......@@ -422,8 +716,8 @@ const info = ref(["人物详情", "历史提案", "人物关系", "相关情况"
const infoActive = ref("人物详情");
const num = ref(["2025", "2024", "2023", "2022", "2021", "2020"]);
const numActive = ref("2025");
const selectedOption = ref("2024");
const currentPage = ref(5);
const selectedOption = ref("2025");
const dialogVisible = ref(false);
const handleClickTag = tag => {
dialogVisible.value = true;
......@@ -1063,6 +1357,11 @@ const dialogData = ref([
}
}
.content-contentcontent {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
......@@ -1295,39 +1594,41 @@ const dialogData = ref([
height: 114px;
display: flex;
flex-wrap: wrap;
overflow-y: scroll;
.company-item {
width: 185px;
width: 180px;
height: 49px;
margin-bottom: 16px;
display: flex;
align-items: center;
cursor: pointer;
.img {
img {
width: 48px;
height: 48px;
margin-right: 8px;
padding: 12px;
background-color: rgb(230, 231, 232);
border-radius: 75px;
img {
width: 24px;
height: 24px;
}
}
.company-cn {
width: 130px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.company-name {
width: 130px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.company-item:nth-child(2n-1) {
......@@ -1511,10 +1812,12 @@ const dialogData = ref([
margin-bottom: 12px;
position: relative;
padding-left: 48px;
img {
.viewpoint-item-img {
position: absolute;
top: -20px;
left: -20px;
top: 0;
left: -10px;
width: 42px;
height: 42px;
}
.viewpoint-item-content {
width: 665px;
......
......@@ -35,23 +35,138 @@ import P9 from "./assets/img10.png";
import P10 from "./assets/img11.png";
import P11 from "./assets/img12.png";
import PS from "./assets/img13.png";
import { id } from "@kangc/v-md-editor";
import 'default-passive-events';
// const nodes = [
// {
// id: "c",
// name: "埃隆·马斯克",
// category: 0,
// symbolSize: 80,
// symbol: `image://${Center}`,
// label: {
// show: true,
// position: "bottom",
// formatter: "{n|{b}}",
// rich: {
// n: {
// color: "rgba(5,95,194,1)",
// fontSize: 24,
// fontWeight: 700,
// fontFamily: "Microsoft YaHei",
// lineHeight: 36
// }
// }
// }
// },
// // 从三点钟方向顺时针排序
// { id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
// { id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
// { id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
// { id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
// { id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
// { id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
// { id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
// { id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
// { id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
// { id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
// { id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
// { id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
// ];
// const links = [
// { source: "n11", target: "c", label: { show: true, formatter: "第一任妻子" } },
// { source: "n7", target: "c", label: { show: true, formatter: "风险投资家" } },
// { source: "n6", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n9", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n12", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n5", target: "c", label: { show: true, formatter: "早期重要投资人" } },
// { source: "n8", target: "c", label: { show: true, formatter: "Boring Company 总裁" } },
// { source: "n10", target: "c", label: { show: true, formatter: "特斯拉独立董事" } },
// { source: "n2", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n1", target: "c", label: { show: true, formatter: "美国总统" } },
// { source: "n4", target: "c", label: { show: true, formatter: "马斯克弟弟" } },
// { source: "n3", target: "c", label: { show: true, formatter: "SpaceX 总裁" } }
// ];
// 处理图片代理
const getProxyUrl = (url) => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
const nodes = ref([]);
const links = ref([]);
// 人物全局信息
const characterInfo = ref({});
// 人物关系
const CharacterRelation = ref({});
// 人物关系
const CharacterRelation = ref([]);
const nodes = [
{
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
}catch(error){
}
};
const getCharacterRelationFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterRelation(params);
if (res.code === 200) {
console.log("人物关系", res);
if (res.data) {
CharacterRelation.value = res.data;
}
}
if(CharacterRelation.value.length > 0){
const centerNode = {
id: "c",
name: "埃隆·马斯克",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${Center}`,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|埃隆·马斯克}",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
......@@ -62,43 +177,33 @@ const nodes = [
}
}
}
},
// 从三点钟方向顺时针排序
{ id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
{ id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
{ id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
{ id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
{ id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
{ id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
{ id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
{ id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
{ id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
{ id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
{ id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
{ id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
];
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
const res1 = await getCharacterRelation(params);
if (res1.code === 200) {
console.log("人物关系", res1);
if (res1.data) {
CharacterRelation.value = res1.data;
}
const newNodes = CharacterRelation.value.map((item,index) => {
return{
id: index,
name: item.name,
category: 1,
symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}`
}
nodes = [
{
});
newNodes.push(centerNode);
const newLinks = CharacterRelation.value.map((item,index) =>({
source:index,
target:"c",
label: {
show: true,
formatter: item.relation }
}));
nodes.value = newNodes;
links.value = newLinks;
}else{
nodes.value = [{
id: "c",
name: characterInfo.value.name,
category: 0,
......@@ -107,7 +212,7 @@ const getCharacterGlobalInfoFn = async () => {
label: {
show: true,
position: "bottom",
formatter: "{n|埃隆·马斯克}",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
......@@ -118,92 +223,109 @@ const getCharacterGlobalInfoFn = async () => {
}
}
}
},
// 从三点钟方向顺时针排序
{ id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
{ id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
{ id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
{ id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
{ id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
{ id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
{ id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
{ id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
{ id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
{ id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
{ id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
{ id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
];
}];
links.value = [];
}
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
}catch(error){
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
return { ...n, x, y };
});
};
chart.setOption({
tooltip: {},
series: [
{
type: "graph",
layout: activeIndex.value === "圆形布局" ? "none" : "force",
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
edgeLabel: {
show: true,
position: "middle",
distance: -18,
formatter: ({ data }) => data?.label?.formatter || "",
color: "rgb(5, 95, 194)",
fontSize: 12,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
lineHeight: 24,
backgroundColor: "rgba(231, 243, 255, 1)",
borderRadius: 24,
padding: [0, 12]
},
label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 },
itemStyle: { color: "rgba(5,95,194,1)" },
emphasis: { focus: "adjacency" }
}
]
});
};
setOption();
const getCharacterRelationFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterRelation(params);
if (res.code === 200) {
console.log("人物关系", res);
if (res.data) {
CharacterRelation.value = res.data;
}
}
}catch(error){
}
};
onMounted(() => {
getCharacterRelationFn();
getCharacterGlobalInfoFn();
});
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
const links = [
{ source: "n11", target: "c", label: { show: true, formatter: "第一任妻子" } },
{ source: "n7", target: "c", label: { show: true, formatter: "风险投资家" } },
{ source: "n6", target: "c", label: { show: true, formatter: "特斯拉董事" } },
{ source: "n9", target: "c", label: { show: true, formatter: "特斯拉董事" } },
{ source: "n12", target: "c", label: { show: true, formatter: "特斯拉董事" } },
{ source: "n5", target: "c", label: { show: true, formatter: "早期重要投资人" } },
{ source: "n8", target: "c", label: { show: true, formatter: "Boring Company 总裁" } },
{ source: "n10", target: "c", label: { show: true, formatter: "特斯拉独立董事" } },
{ source: "n2", target: "c", label: { show: true, formatter: "特斯拉董事" } },
{ source: "n1", target: "c", label: { show: true, formatter: "美国总统" } },
{ source: "n4", target: "c", label: { show: true, formatter: "马斯克弟弟" } },
{ source: "n3", target: "c", label: { show: true, formatter: "SpaceX 总裁" } }
];
let chart;
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterRelationFn();
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.map((n, i) => {
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.length - 1)) * Math.PI * 2;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
......@@ -219,8 +341,8 @@ onMounted(() => {
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes,
links: links,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
......@@ -246,6 +368,7 @@ onMounted(() => {
});
};
setOption();
console.log("node1", nodes);
const onResize = () => chart && chart.resize();
window.addEventListener("resize", onResize);
watch(activeIndex, () => setOption());
......
......@@ -18,9 +18,8 @@
:teleported="false"
size="small"
>
<el-option label="近五年" value="近五年"></el-option>
<el-option label="近十年" value="近十年"></el-option>
<el-option label="近一年" value="近一年"></el-option>
<el-option @click="" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -59,8 +58,8 @@
<span class="title-text">科技法案</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList" :key="item.id" class="item">
<div class="main-box-content" style="height: 300px;">
<div v-for="item in relationBillsList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -76,8 +75,8 @@
<span class="title-text">科技政令</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in twoList" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationAdList" :key="item.id" class="item">
<div>
<div class="item-name">{{ item.name }}</div>
<div class="item-date">{{ item.date }}</div>
......@@ -92,8 +91,8 @@
<span class="title-text">科技智库</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList2" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationThinkTankList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -124,10 +123,90 @@ import book6 from "./assets/book6.png";
import type1 from "./assets/type1.png";
import type2 from "./assets/type2.png";
import { getCharacterRelatedEntity } from "@/api/characterPage/characterPage.js";
const selectedOption = ref(1);
const yearList = ref([
{
label: "近一年",
value: 1
},
{
label: "近两年",
value: 2
},
{
label: "近五年",
value: 5
}
]);
function getDateYearsAgo(years) {
// 获取当前日期
const currentDate = new Date();
// 计算指定年数之前的日期
const pastDate = new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
// 格式化日期为 "YYYY-MM-DD" 的形式
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
//获取相关实体
const relationBillsList = ref({});
const relationAdList = ref({});
const relationThinkTankList = ref({});
const getCharacterRelatedEntityFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
startTime: getDateYearsAgo(selectedOption.value) || "2025-01-01"
};
try{
const res = await getCharacterRelatedEntity(params);
if (res.code === 200) {
console.log("人物相关实体", res);
if (res.data) {
relationBillsList.value = res.data.relationBillsList.map(item=>{
return{
id: item.billId,
name: item.billName,
type: item.industryList,
date: item.billTime,
img: book1 //待加
}
});
relationAdList.value = res.data.relationAdList.map(item=>{
return{
id: item.id,
name: item.adName,
type: item.adType,
date: item.adTime
}
});
relationThinkTankList.value = res.data.relationThinkTankList.map(item=>{
return{
id: item.reportId,
name: item.reportName,
type: type1, //缺属性
date: item.reportTime,
img: item.thinkTankUrl
}
});
}
}
}catch(error){
}
};
const searchText = ref("");
const selectedOption = ref("近五年");
const billList = ref([
{
......@@ -375,6 +454,7 @@ const makeOption = el => {
};
onMounted(() => {
getCharacterRelatedEntityFn();
const el = document.querySelector(".echarts");
if (!el) return;
chart = echarts.init(el);
......@@ -628,8 +708,8 @@ const setLayout = type => {
}
.main-box-content {
width: 100%;
min-height: 142px;
padding: 12px 0;
overflow-y: scroll;
.item {
width: 100%;
height: 53px;
......@@ -666,15 +746,18 @@ const setLayout = type => {
font-family: "Microsoft YaHei";
line-height: 20px;
padding: 1px 8px;
border-radius: 4px;
border: 1px solid;
}
.type-ai {
border-radius: 4px;
border: 1px solid;
color: rgba(19, 168, 168, 1);
border-color: rgba(135, 232, 222, 1);
background-color: rgba(230, 255, 251, 1);
}
.type-energy {
border-radius: 4px;
border: 1px solid;
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
background-color: rgba(255, 241, 240, 1);
......
......@@ -53,13 +53,13 @@
</div>
<!-- 主要内容 -->
<div class="num-list">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="numActive = item">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="handleChangeYear(item)">
{{ item }}
</div>
</div>
<!-- echarts 图表 -->
<div class="echarts">
<div class="row" v-for="(row, index) in wordCloudData" :key="index">
<div class="echarts" id="wordCloudChart">
<!-- <div class="row" v-for="(row, index) in wordCloudData" :key="index">
<span
v-for="(item, idx) in row"
:key="idx"
......@@ -71,7 +71,7 @@
>
{{ item.text }}
</span>
</div>
</div> -->
</div>
</div>
<div class="left-bottom">
......@@ -261,8 +261,10 @@
<script setup>
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import CharacterRelationships from "./components/characterRelationships/index.vue";
import RelevantSituation from "./components/relevantSituation/index.vue";
import getWordCloudChart from "../../utils/worldCloudChart";
import { getCharacterGlobalInfo,
getCharacterBasicInfo,
getCharacterView,
......@@ -394,14 +396,19 @@ const characterView = ref({});
const getCharacterViewFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
year: numActive.value || 2025
year: numActive.value
};
try{
const res = await getCharacterView(params);
if (res.code === 200) {
console.log("人物观点", res);
if (res.data) {
characterView.value = res.data;
characterView.value = res.data.map(item=>{
return{
name:item.option,
value:item.count
};
});
}
}
}catch(error){
......@@ -410,6 +417,18 @@ const getCharacterViewFn = async () => {
};
const handleCharacterView = async () => {
await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart");
};
const handleChangeYear = (item) => {
numActive.value = item;
characterView.value = [];
handleCharacterView();
};
// 获取领域观点
const CharacterFieldView = ref({});
const getCharacterFieldViewFn = async () => {
......@@ -528,6 +547,8 @@ const getCharacterResumeFn = async () => {
};
const info = ref(["人物详情", "人物关系", "相关情况"]);
const infoActive = ref("人物详情");
const num = ref(["2025", "2024", "2023", "2022", "2021", "2020"]);
......@@ -716,10 +737,11 @@ const dialogData = ref([
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterBasicInfoFn();
getCharacterViewFn();
// getCharacterViewFn();
getCharacterLatestDynamicFn();
getCharacterResumeFn();
getCharacterFieldViewFn();
handleCharacterView();
});
</script>
......
......@@ -21,34 +21,139 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import * as echarts from "echarts";
import Center from "./assets/巴里·帕维尔.png";
import P1 from "./assets/卡罗尔·M·波滕格.png";
import P2 from "./assets/理查德·丹齐格.png";
import P3 from "./assets/吉姆·米特雷.png";
import P4 from "./assets/拉斐尔·S·科恩.png";
import P5 from "./assets/卡娅·卡拉斯.png";
import P6 from "./assets/伊万娜·柯.png";
import P7 from "./assets/丹尼尔·埃格尔.png";
import P8 from "./assets/马克·埃斯珀.png";
import P9 from "./assets/格雷戈里·史密斯.png";
import P10 from "./assets/斯特凡诺·斯特凡尼尼.png";
import P11 from "./assets/谢莉·卡尔伯森.png";
import PS from "./assets/菲利普·布里德洛夫.png";
import {getCharacterGlobalInfo, getCharacterRelation } from "@/api/characterPage/characterPage.js";
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
const nodes = [
{
import 'default-passive-events';
// const nodes = [
// {
// id: "c",
// name: "埃隆·马斯克",
// category: 0,
// symbolSize: 80,
// symbol: `image://${Center}`,
// label: {
// show: true,
// position: "bottom",
// formatter: "{n|{b}}",
// rich: {
// n: {
// color: "rgba(5,95,194,1)",
// fontSize: 24,
// fontWeight: 700,
// fontFamily: "Microsoft YaHei",
// lineHeight: 36
// }
// }
// }
// },
// // 从三点钟方向顺时针排序
// { id: "n11", name: "贾斯汀・马斯克", category: 1, symbolSize: 80, symbol: `image://${P11}` },
// { id: "n7", name: "杰弗里·凯斯勒", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
// { id: "n6", name: "斯科特·贝森特", category: 1, symbolSize: 80, symbol: `image://${P6}` },
// { id: "n9", name: "道格·伯格姆", category: 1, symbolSize: 80, symbol: `image://${P9}` },
// { id: "n12", name: "史蒂夫・尤尔韦松", category: 1, symbolSize: 80, symbol: `image://${PS}` },
// { id: "n5", name: "拉里・埃里森", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
// { id: "n8", name: "马尔科·卢比奥", category: 1, symbolSize: 80, symbol: `image://${P8}` },
// { id: "n10", name: "艾拉・埃伦普里斯", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
// { id: "n2", name: "詹姆斯・默多克", category: 1, symbolSize: 80, symbol: `image://${P2}` },
// { id: "n1", name: "唐纳德・特朗普", category: 1, symbolSize: 80, symbol: `image://${P1}` },
// { id: "n4", name: "金博尔・马斯克", category: 1, symbolSize: 80, symbol: `image://${P4}` },
// { id: "n3", name: "格温・肖特韦尔", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
// ];
// const links = [
// { source: "n11", target: "c", label: { show: true, formatter: "第一任妻子" } },
// { source: "n7", target: "c", label: { show: true, formatter: "风险投资家" } },
// { source: "n6", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n9", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n12", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n5", target: "c", label: { show: true, formatter: "早期重要投资人" } },
// { source: "n8", target: "c", label: { show: true, formatter: "Boring Company 总裁" } },
// { source: "n10", target: "c", label: { show: true, formatter: "特斯拉独立董事" } },
// { source: "n2", target: "c", label: { show: true, formatter: "特斯拉董事" } },
// { source: "n1", target: "c", label: { show: true, formatter: "美国总统" } },
// { source: "n4", target: "c", label: { show: true, formatter: "马斯克弟弟" } },
// { source: "n3", target: "c", label: { show: true, formatter: "SpaceX 总裁" } }
// ];
// 处理图片代理
const getProxyUrl = (url) => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
const nodes = ref([]);
const links = ref([]);
// 人物全局信息
const characterInfo = ref({});
// 人物关系
const CharacterRelation = ref([]);
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
}catch(error){
}
};
const getCharacterRelationFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterRelation(params);
if (res.code === 200) {
console.log("人物关系", res);
if (res.data) {
CharacterRelation.value = res.data;
}
}
if(CharacterRelation.value.length > 0){
const centerNode = {
id: "c",
name: "巴里·帕维尔",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${Center}`,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|巴里·帕维尔}",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
......@@ -59,56 +164,155 @@ const nodes = [
}
}
}
}
const newNodes = CharacterRelation.value.map((item,index) => {
return{
id: index,
name: item.name,
category: 1,
symbolSize: 80,
symbol: `image://${getProxyUrl(item.imageUrl)}`
}
});
newNodes.push(centerNode);
const newLinks = CharacterRelation.value.map((item,index) =>({
source:index,
target:"c",
label: {
show: true,
formatter: item.relation }
}));
nodes.value = newNodes;
links.value = newLinks;
}else{
nodes.value = [{
id: "c",
name: characterInfo.value.name,
category: 0,
symbolSize: 80,
symbol: `image://${characterInfo.value.imageUrl}`,
label: {
show: true,
position: "bottom",
formatter: "{n|{b}}",
rich: {
n: {
color: "rgba(5,95,194,1)",
fontSize: 24,
fontWeight: 700,
fontFamily: "Microsoft YaHei",
lineHeight: 36
}
}
}
}];
links.value = [];
}
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
return { ...n, x, y };
});
chart.setOption({
tooltip: {},
series: [
{
type: "graph",
layout: activeIndex.value === "圆形布局" ? "none" : "force",
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
edgeLabel: {
show: true,
position: "middle",
distance: -18,
formatter: ({ data }) => data?.label?.formatter || "",
color: "rgb(5, 95, 194)",
fontSize: 12,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
lineHeight: 24,
backgroundColor: "rgba(231, 243, 255, 1)",
borderRadius: 24,
padding: [0, 12]
},
// 从三点钟方向顺时针排序
{ id: "n11", name: "谢莉·卡尔伯森", category: 1, symbolSize: 80, symbol: `image://${P11}` },
{ id: "n7", name: "丹尼尔·埃格尔", category: 1, symbolSize: 80, symbol: `image://${P7}`, r: 80 },
{ id: "n6", name: "伊万娜·柯", category: 1, symbolSize: 80, symbol: `image://${P6}` },
{ id: "n9", name: "格雷戈里·史密斯", category: 1, symbolSize: 80, symbol: `image://${P9}` },
{ id: "n12", name: "菲利普·布里德洛夫", category: 1, symbolSize: 80, symbol: `image://${PS}` },
{ id: "n5", name: "卡娅·卡拉斯", category: 1, symbolSize: 80, symbol: `image://${P5}`, r: 80 },
{ id: "n8", name: "马克·埃斯珀", category: 1, symbolSize: 80, symbol: `image://${P8}` },
{ id: "n10", name: "斯特凡诺·斯特凡尼尼", category: 1, symbolSize: 80, symbol: `image://${P10}`, r: 80 },
{ id: "n2", name: "理查德·丹齐格", category: 1, symbolSize: 80, symbol: `image://${P2}` },
{ id: "n1", name: "卡罗尔·M·波滕格", category: 1, symbolSize: 80, symbol: `image://${P1}` },
{ id: "n4", name: "拉斐尔·S·科恩", category: 1, symbolSize: 80, symbol: `image://${P4}` },
{ id: "n3", name: "吉姆·米特雷", category: 1, symbolSize: 80, symbol: `image://${P3}`, r: 80 }
];
const links = [
{ source: "n11", target: "c", label: { show: true, formatter: "兰德同事" } },
{ source: "n7", target: "c", label: { show: true, formatter: "兰德共同作者" } },
{ source: "n6", target: "c", label: { show: true, formatter: "兰德共同作者" } },
{ source: "n9", target: "c", label: { show: true, formatter: "兰德共同作者" } },
{ source: "n12", target: "c", label: { show: true, formatter: "共同作者" } },
{ source: "n5", target: "c", label: { show: true, formatter: "爱沙尼亚总理" } },
{ source: "n8", target: "c", label: { show: true, formatter: "国防部同事" } },
{ source: "n10", target: "c", label: { show: true, formatter: "外交部同事" } },
{ source: "n2", target: "c", label: { show: true, formatter: "讲座嘉宾" } },
{ source: "n1", target: "c", label: { show: true, formatter: "议题搭档" } },
{ source: "n4", target: "c", label: { show: true, formatter: "兰德同事" } },
{ source: "n3", target: "c", label: { show: true, formatter: "兰德同事" } }
];
label: { show: true, position: "bottom", color: "rgb(59,65,75)", fontSize: 16 },
itemStyle: { color: "rgba(5,95,194,1)" },
emphasis: { focus: "adjacency" }
}
]
});
};
setOption();
}catch(error){
}
};
const list = ref(["圆形布局", "力导向布局", "树形布局"]);
const activeIndex = ref("圆形布局");
let chart;
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterRelationFn();
const el = document.getElementById("relGraph");
if (!el) return;
chart = echarts.init(el);
const setOption = () => {
const rect = el.getBoundingClientRect();
const cx = rect.width / 2;
const cy = rect.height / 2;
const radius = Math.min(cx, cy) - 140;
const dataNodes = nodes.map((n, i) => {
const dataNodes = nodes.value.map((n, i) => {
if (n.id === "c") {
return { ...n, x: cx, y: cy, fixed: true };
}
// 均匀环形分布
const idx = i - 1;
const angle = (idx / (nodes.length - 1)) * Math.PI * 2;
const angle = (idx / (nodes.value.length - 1)) * Math.PI * 2;
const rLocal = radius + (n.r || 0);
const x = cx + rLocal * Math.cos(angle);
const y = cy + rLocal * Math.sin(angle);
......@@ -124,8 +328,8 @@ onMounted(() => {
circular: { rotateLabel: true },
force: { repulsion: 800, edgeLength: [80, 160] },
roam: true,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes,
links: links,
data: activeIndex.value === "圆形布局" ? dataNodes : nodes.value,
links: links.value,
edgeSymbol: ["none", "arrow"],
edgeSymbolSize: [4, 10],
lineStyle: { color: "rgba(174,214,255,1)", width: 2, opacity: 0.8 },
......@@ -151,6 +355,7 @@ onMounted(() => {
});
};
setOption();
console.log("node1", nodes);
const onResize = () => chart && chart.resize();
window.addEventListener("resize", onResize);
watch(activeIndex, () => setOption());
......
......@@ -73,14 +73,84 @@
</template>
<script setup>
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import img from "./assets/img.png";
import { getCharacterAchievementReport } from "@/api/characterPage/characterPage.js";
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterProposalFn();
};
// 获取成果报告
const CharacterAchievementReport = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterAchievementReportFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
industryId: 1,
year: 2025,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterAchievementReport(params, abortController.value.signal);
console.log("成功报告", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterAchievementReport.value = res.data.content.map(item => ({
id: item.billId,
title: item.name,
tie: item.industryList,
disc: item.description,
state: item.status,
time: item.time,
img: item.imageUrl || img
}));
total.value = res.data.totalElements;
}else {
CharacterAchievementReport.value = [];
total.value = 0;
}
}else {
CharacterAchievementReport.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
onMounted(() => {
getCharacterAchievementReportFn();
});
const searchText = ref("");
const value1 = ref("全部法案");
const value2 = ref("全部领域");
const currentPage = ref(2);
const list = ref([
{
......
......@@ -18,9 +18,8 @@
:teleported="false"
size="small"
>
<el-option label="近五年" value="近五年"></el-option>
<el-option label="近十年" value="近十年"></el-option>
<el-option label="近一年" value="近一年"></el-option>
<el-option @click="" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -59,8 +58,8 @@
<span class="title-text">科技法案</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList" :key="item.id" class="item">
<div class="main-box-content" style="height: 300px;">
<div v-for="item in relationBillsList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -76,8 +75,8 @@
<span class="title-text">科技政令</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in twoList" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationAdList" :key="item.id" class="item">
<div>
<div class="item-name">{{ item.name }}</div>
<div class="item-date">{{ item.date }}</div>
......@@ -92,8 +91,8 @@
<span class="title-text">科技智库</span>
<img src="./assets/bottom.png" alt="" class="title-bottom" />
</div>
<div class="main-box-content">
<div v-for="item in billList2" :key="item.id" class="item">
<div class="main-box-content" style="height: 142px;">
<div v-for="item in relationThinkTankList" :key="item.id" class="item">
<img :src="item.img" alt="" class="item-img" />
<div>
<div class="item-name">{{ item.name }}</div>
......@@ -124,10 +123,90 @@ import book6 from "./assets/book6.png";
import type1 from "./assets/type1.png";
import type2 from "./assets/type2.png";
import { getCharacterRelatedEntity } from "@/api/characterPage/characterPage.js";
const selectedOption = ref(1);
const yearList = ref([
{
label: "近一年",
value: 1
},
{
label: "近两年",
value: 2
},
{
label: "近五年",
value: 5
}
]);
function getDateYearsAgo(years) {
// 获取当前日期
const currentDate = new Date();
// 计算指定年数之前的日期
const pastDate = new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
// 格式化日期为 "YYYY-MM-DD" 的形式
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
//获取相关实体
const relationBillsList = ref({});
const relationAdList = ref({});
const relationThinkTankList = ref({});
const getCharacterRelatedEntityFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
startTime: getDateYearsAgo(selectedOption.value) || "2025-01-01"
};
try{
const res = await getCharacterRelatedEntity(params);
if (res.code === 200) {
console.log("人物相关实体", res);
if (res.data) {
relationBillsList.value = res.data.relationBillsList.map(item=>{
return{
id: item.billId,
name: item.billName,
type: item.industryList,
date: item.billTime,
img: book1 //待加
}
});
relationAdList.value = res.data.relationAdList.map(item=>{
return{
id: item.id,
name: item.adName,
type: item.adType,
date: item.adTime
}
});
relationThinkTankList.value = res.data.relationThinkTankList.map(item=>{
return{
id: item.reportId,
name: item.reportName,
type: type1, //缺属性
date: item.reportTime,
img: item.thinkTankUrl
}
});
}
}
}catch(error){
}
};
const searchText = ref("");
const selectedOption = ref("近五年");
const billList = ref([
{
......@@ -375,6 +454,7 @@ const makeOption = el => {
};
onMounted(() => {
getCharacterRelatedEntityFn();
const el = document.querySelector(".echarts");
if (!el) return;
chart = echarts.init(el);
......@@ -628,8 +708,8 @@ const setLayout = type => {
}
.main-box-content {
width: 100%;
min-height: 142px;
padding: 12px 0;
overflow-y: scroll;
.item {
width: 100%;
height: 53px;
......@@ -666,15 +746,18 @@ const setLayout = type => {
font-family: "Microsoft YaHei";
line-height: 20px;
padding: 1px 8px;
border-radius: 4px;
border: 1px solid;
}
.type-ai {
border-radius: 4px;
border: 1px solid;
color: rgba(19, 168, 168, 1);
border-color: rgba(135, 232, 222, 1);
background-color: rgba(230, 255, 251, 1);
}
.type-energy {
border-radius: 4px;
border: 1px solid;
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
background-color: rgba(255, 241, 240, 1);
......
......@@ -3,29 +3,32 @@
<!-- 智库研究人员人物基础 -->
<div class="header">
<div class="avatar">
<img :src="musk.avatar" alt="" />
<!-- <img :src="characterInfo.imageUrl" alt="" /> -->
<el-avatar :size="160" shape="circle" :src="characterInfo.imageUrl" />
</div>
<div class="info">
<!-- <div class="name"> -->
<p class="name-cn">{{ musk.nameCn }}</p>
<p class="name-en">{{ musk.nameEn }}</p>
<p class="name-cn">{{ characterInfo.name }}</p>
<p class="name-en">{{ characterInfo.ename }}</p>
<!-- </div> -->
<div class="introduction">
<p>{{ musk.introduction }}</p>
<p>{{ characterInfo.description }}</p>
</div>
<div class="domain">
<p
v-for="item in musk.domain"
v-for="item in characterInfo.industryList"
:key="item"
class="cl1"
:class="{
cl1: item === '跨党派鹰派',
cl2: item === '大国竞争',
cl3: item === 'AI/AGI地缘政治',
cl4: item === '综合威慑',
cl5: item === '战略预测'
cl1: item.status === '1',
cl2: item.status === '2',
cl3: item.status === '3',
cl4: item.status === '4',
cl5: item.status === '5',
cl6: item.status === '6'
}"
>
{{ item }}
{{ item.industryName }}
</p>
</div>
</div>
......@@ -50,13 +53,13 @@
</div>
<!-- 主要内容 -->
<div class="num-list">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="numActive = item">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="handleChangeYear(item)">
{{ item }}
</div>
</div>
<!-- echarts 图表 -->
<div class="echarts">
<div class="row" v-for="(row, index) in wordCloudData" :key="index">
<div class="echarts" id="wordCloudChart">
<!-- <div class="row" v-for="(row, index) in wordCloudData" :key="index">
<span
v-for="(item, idx) in row"
:key="idx"
......@@ -68,14 +71,14 @@
>
{{ item.text }}
</span>
</div>
</div> -->
</div>
</div>
<div class="left-bottom">
<div class="title">
<div class="box"></div>
<div class="text">最新动态</div>
<div class="input"><input type="checkbox" checked />只看涉华动态</div>
<div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange"/>只看涉华动态</div>
<div class="btn">
<img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" />
......@@ -83,30 +86,34 @@
</div>
<!-- 主要内容 -->
<div class="main">
<div v-for="item in newList" :key="item" class="main-item">
<div v-for="item in CharacterLatestDynamic" :key="item" class="main-item">
<div class="time">
<div class="year">{{ item.time.split("")[0] }}</div>
<div class="date">{{ item.time.split("年")[1] }}</div>
<div class="year">{{ item.time.split("-")[0] }}</div>
<div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日"}}</div>
</div>
<div class="image">
<img src="./assets/type1.png" alt="" v-if="item.type === 1" /><img
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png"
alt=""
v-else
/>
</div>
<div class="content">
<div :class="{ 'content-type1': item.type === 1, 'content-type2': item.type === 2 }">
<p :class="{ 'content-title1': item.type === 1, 'content-title2': item.type === 2 }">
<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-else class="content-title2">
{{ item.title }}
</p>
<p class="content-title-en">{{ item.titleEn }}</p>
<p v-if="item.remarks === true" class="content-title-en">{{ item.econtent }}</p>
</div>
<p class="content-contentcontent">{{ item.content }}</p>
<p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p>
<div class="content-tag">
<div>
<span
v-for="tag in item.pie"
v-for="tag in item.industryList"
:key="tag"
class="tag"
:class="{
......@@ -119,20 +126,21 @@
>{{ tag }}</span
>
</div>
<div class="origin">来源:{{ item.origin }}</div>
<div class="origin">来源:{{ item.orgName }}</div>
</div>
</div>
</div>
<div class="line-test"></div>
</div>
<div class="pagination">
<div class="total">共153项动态</div>
<div class="total">{{`共${total}项动态`}}</div>
<el-pagination
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
:page-count="10"
:pager-count="5"
v-model:current-page="currentPage"
:total="total"
class="custom-pagination"
/>
</div>
......@@ -152,52 +160,45 @@
<div class="baseInfo">
<div class="baseInfo-item">
<div class="baseInfo-item-title">出生日期:</div>
<div class="baseInfo-item-content">未知</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthday}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">现任职位:</div>
<div class="baseInfo-item-content">兰德公司国家安全研究部副总裁兼主任</div>
<div class="baseInfo-item-content">{{characterBasicInfo.positionTitle}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">兼任职位:</div>
<div class="baseInfo-item-content">国家国防研究所(NDRI)主任</div>
<div class="baseInfo-item-content">{{characterBasicInfo.sideJob}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">政策倾向:</div>
<div class="baseInfo-item-content">主流鹰派现实主义者</div>
<div class="baseInfo-item-content">{{characterBasicInfo.political}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">国籍:</div>
<div class="baseInfo-item-content">美国</div>
<div class="baseInfo-item-content">{{characterBasicInfo.country}}</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">教育背景:</div>
<div class="baseInfo-item-content long">
布朗大学--应用数学与经济学学士;<br>
普林斯顿大学--伍德罗·威尔逊公共与国际事务学院公共事务硕士;
<div class="baseInfo-item-content long" v-for="item in characterBasicInfo.educationList">
{{ item.school + '--' + item.major}}<br>
</div>
</div>
<div class="baseInfo-item">
<div class="baseInfo-item-title">研究领域:</div>
<div class="baseInfo-item-content longlong">
<span class="span">地缘政治与大国战略竞争</span>
<span class="span">战略预测与情景规划</span>
<span class="span">印太地区与跨大西洋安全</span>
<span class="span">经济安全与技术-军事融合</span>
<span class="span">联盟现代化与国防态势调整</span>
<span class="span" v-for="item in characterBasicInfo.industryList">{{item.industryName}}</span>
</div>
</div>
</div>
<div class="company">
<div class="company-title">社交媒体</div>
<div class="company-content">
<div v-for="item in companyList" :key="item" class="company-item">
<div class="img">
<img :src="item.logo" alt="" />
</div>
<div v-for="item in characterBasicInfo.organizationList" :key="item" class="company-item">
<img :src="item.imageUrl?item.imageUrl:DefaultIcon2" alt="" />
<div>
<div class="company-cn">{{ item.cn }}</div>
<div class="company-name">{{ item.name }}</div>
<div class="company-cn">{{ item.name }}</div>
<div class="company-name">{{ item.ename }}</div>
</div>
</div>
</div>
......@@ -214,10 +215,10 @@
</div>
</div>
<div class="content-main">
<div v-for="item in resumeList" :key="item.id" class="content-item">
<div v-for="item in CharacterResume" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.time }}</div>
<div class="content-item-title">{{ item.title }}</div>
<div class="content-item-time">{{ item.startTime +'-' + item.endTime}}</div>
<div class="content-item-title">{{ item.orgName +'|' + item.jobName}}</div>
<div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" />
......@@ -264,10 +265,18 @@
</template>
<script setup>
import { ref } from "vue";
import { ref, onMounted } from "vue";
import setChart from "@/utils/setChart";
import CharacterRelationships from "./components/characterRelationships/index.vue";
import RelevantSituation from "./components/relevantSituation/index.vue";
import HistoricalProposal from "./components/historicalProposal/index.vue";
import getWordCloudChart from "../../utils/worldCloudChart";
import { getCharacterGlobalInfo,
getCharacterBasicInfo,
getCharacterView,
getCharacterLatestDynamic,
getCharacterResume,
getCharacterFieldView } from "@/api/characterPage/characterPage.js";
import Musk from "./assets/Musk.png";
import cp1 from "./assets/cp1.png";
......@@ -280,6 +289,231 @@ import img3 from "./assets/img3.png";
import img4 from "./assets/img4.png";
import img5 from "./assets/img5.png";
import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import DefaultIcon2 from '@/assets/icons/default-icon2.png'
// 处理图片代理
const getProxyUrl = (url) => {
if (!url) return "";
const urlStr = String(url);
// 排除非 http 开头(相对路径)、已经是代理链接、或者是本地链接
if (!urlStr.startsWith('http') || urlStr.includes('images.weserv.nl') || urlStr.includes('localhost') || urlStr.includes('127.0.0.1')) {
return url;
}
// 移除协议头 http:// 或 https://
const cleanUrl = urlStr.replace(/^https?:\/\//i, '');
return `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}`;
};
// 获取人物全局信息
const characterInfo = ref({});
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息", res);
if (res.data) {
characterInfo.value = res.data;
}
}
}catch(error){
}
};
// 获取人物基本信息
const characterBasicInfo = ref({});
const getCharacterBasicInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterBasicInfo(params);
if (res.code === 200) {
console.log("人物基本信息", res);
if (res.data) {
characterBasicInfo.value = res.data;
}
}
}catch (error) {
console.error(error);
}
};
// 获取人物观点
const characterView = ref({});
const getCharacterViewFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
year: numActive.value
};
try{
const res = await getCharacterView(params);
if (res.code === 200) {
console.log("人物观点", res);
if (res.data) {
characterView.value = res.data.map(item=>{
return{
name:item.option,
value:item.count
};
});
}
}
}catch(error){
}
};
const handleCharacterView = async () => {
await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart");
};
const handleChangeYear = (item) => {
numActive.value = item;
characterView.value = [];
handleCharacterView();
};
// 获取领域观点
const CharacterFieldView = ref({});
const getCharacterFieldViewFn = async () => {
const params = {
areaId: window.sessionStorage.getItem("areaId") || "20",
};
try{
const res = await getCharacterFieldView(params);
if (res.code === 200) {
console.log("领域观点", res);
if (res.data) {
CharacterFieldView.value = res.data;
}
}
}catch(error){
}
};
//是否涉华
const isChecked = ref(false);
const related = ref('N');
const handleChange = event => {
if(isChecked.value){
related.value = 'Y';
}else{
related.value = 'N';
}
getCharacterLatestDynamicFn();
console.log(related.value);
};
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterLatestDynamicFn();
};
// 获取最新动态
const CharacterLatestDynamic = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterLatestDynamicFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064",
cRelated: related.value,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterLatestDynamic(params, abortController.value.signal);
console.log("最新动态", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterLatestDynamic.value = res.data.content.map(item => ({
title: item.title,
content: item.content,
econtent: item.econtent,
time: item.time,
industryList: item.industryList || ["人工智能"],
orgName: item.orgName,
remarks: item.remarks
}));
total.value = res.data.totalElements;
}else {
CharacterLatestDynamic.value = [];
total.value = 0;
}
}else {
CharacterLatestDynamic.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
//获取职业履历
const CharacterResume = ref({});
const getCharacterResumeFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterResume(params);
if (res.code === 200) {
console.log("人物职业履历", res);
if (res.data) {
CharacterResume.value = res.data;
}
}
}catch(error){
}
};
onMounted(() => {
getCharacterGlobalInfoFn();
getCharacterBasicInfoFn();
// getCharacterViewFn();
getCharacterLatestDynamicFn();
getCharacterResumeFn();
getCharacterFieldViewFn();
handleCharacterView();
});
const wordCloudData = ref([
[
{ text: "AI安全与模型保护", color: "rgba(19, 168, 168, 1)" },
......@@ -336,7 +570,7 @@ const info = ref(["人物详情", "成果报告", "人物关系", "相关情况"
const infoActive = ref("人物详情");
const num = ref(["2025", "2024", "2023", "2022", "2021", "2020"]);
const numActive = ref("2025");
const currentPage = ref(5);
const dialogVisible = ref(false);
const handleClickTag = tag => {
dialogVisible.value = true;
......@@ -888,6 +1122,11 @@ const dialogData = ref([
}
}
.content-contentcontent {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
......@@ -1138,42 +1377,44 @@ const dialogData = ref([
}
.company-content {
width: 409px;
// height: 114px;
height: 114px;
display: flex;
flex-wrap: wrap;
overflow-y: scroll;
.company-item {
width: 185px;
width: 180px;
height: 49px;
margin-bottom: 16px;
display: flex;
align-items: center;
cursor: pointer;
.img {
img {
width: 48px;
height: 48px;
margin-right: 8px;
padding: 12px;
background-color: rgb(230, 231, 232);
border-radius: 75px;
img {
width: 24px;
height: 24px;
}
}
.company-cn {
width: 130px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.company-name {
width: 130px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.company-item:nth-child(2n-1) {
......
......@@ -14,16 +14,53 @@
</template>
<script setup>
import { ref } from 'vue';
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import TechLeader from './components/techLeader/index.vue';
import MemberOfCongress from './components/memberOfCongress/index.vue';
import ThinkTankPerson from './components/thinkTankPerson/index.vue';
import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js";
// 获取路由参数,1为科技领袖,2为国会议员,3为智库研究人员
const route = useRoute();
const type = ref(route.query.type || 1);
// 获取人物全局信息
const characterInfo = ref({});
const getCharacterGlobalInfoFn = async () => {
const params = {
personId: window.sessionStorage.getItem("personId") || "Y000064"
};
try{
const res = await getCharacterGlobalInfo(params);
if (res.code === 200) {
console.log("人物全局信息111", res);
if (res.data) {
characterInfo.value = res.data;
const personType = characterInfo.value.personType;
if(personType === "002"){
type.value = 1;
}
else if(personType === "004"){
type.value = 2;
}
else if(personType === "005"){
type.value = 3;
}
}
}
}catch(error){
}
};
onMounted(() => {
getCharacterGlobalInfoFn();
});
</script>
......
const getWordCloudChart = (data) => {
const option = {
grid: {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
series: [
{
type: "wordCloud",
width: '80%',
height: '80%',
shape: "rect", //
// 其他形状你可以使用形状路径
// 或者自定义路径
// shape: 'circle' // 圆形(默认)
// shape: 'rect' // 矩形
// shape: 'roundRect' // 圆角矩形
// shape: 'triangle' // 三角形
// shape: 'diamond' // 菱形
// shape: 'pentagon' // 五边形
// shape: 'star' // 星形
// shape: 'cardioid' // 心形
gridSize: 15, // 网格大小,影响词间距。
sizeRange: [10, 30], // 定义词云中文字大小的范围
rotationRange: [0, 0],
rotationStep: 15,
drawOutOfBound: false, // 是否超出画布
// 字体
textStyle: {
// normal: {
// color: function () {
// return 'rgb(' + [
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160)
// ].join(',') + ')';
// }
// },
color: function () {
let colors = [
"rgba(189, 33, 33, 1)",
"rgba(232, 151, 21, 1)",
"rgba(220, 190, 68, 1)",
"rgba(96, 58, 186, 1)",
"rgba(32, 121, 69, 1)",
"rgba(22, 119, 255, 1)",
];
return colors[parseInt(Math.random() * colors.length)];
},
emphasis: {
shadowBlur: 5,
shadowColor: "#333",
},
},
// 设置词云数据
data: data,
},
],
}
return option
}
export default getWordCloudChart
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论