提交 ad82f0ce authored 作者: 刘宇琪's avatar 刘宇琪

刘宇琪 科技人物观点 概览页

上级 e370ef79
......@@ -20,7 +20,7 @@
</div>
</div>
</div>
<div class="person-info">
<div class="person-info" @click="handleClickToCharacter(item.personId)">
<div class="person-name">{{ item.name }}</div>
<div class="person-position">{{ item.position }}</div>
</div>
......@@ -49,7 +49,8 @@
import { ref,onMounted,defineProps,watch } from "vue";
import personData from "../json/personData.json"; // 引入JSON数据
import {getMainCharactersView } from "@/api/technologyFigures/technologyFigures";
import { useCharacterNav } from "../utils/useCharacterNav";
const { handleClickToCharacter } = useCharacterNav();
const props = defineProps({
persontypeid: {
type: String,
......@@ -86,7 +87,8 @@ const handlegetMainCharactersViewFn = async () => {
position: item.positionTitle,
tags: ["1", "2"],
chinaRelatedCount: item.remarksCount,
mediaQuoteCount: item.remarksCount
mediaQuoteCount: item.remarksCount,
personId:item.personId
}
});
......
......@@ -77,7 +77,7 @@ watch(
const router = useRouter();
const total = ref(0);
const pageSize = ref(16);
const pageSize = ref(20);
const loading = ref(false);
const abortController = ref(null);
const currentPage = ref(1);
......@@ -138,16 +138,17 @@ const handleCurrentChange = page => {
// 跳转人物主页
const handleClcikToCharacter = async id => {
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList"));
let type = 0;
let personTypeName = "";
const params = {
personId: id
};
try {
const res = await getPersonSummaryInfo(params);
console.log("人物全局信息", res);
console.log("人物全局信息2", res);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => {
return item.typeId === res.data.personType;
......@@ -158,13 +159,13 @@ const handleClcikToCharacter = async id => {
personTypeName = arr[0].typeName;
console.log("personTypeName", personTypeName);
if (personTypeName === "科技企业领袖") {
if (personTypeName === "科技企业领袖"||personTypeName ==='政府官员') {
type = 1;
} else if (personTypeName === "国会议员") {
type = 2;
} else if (personTypeName === "智库研究人员") {
type = 3;
} else {
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
......@@ -205,10 +206,11 @@ const handlePageChange = p => {
}
.source-library-grid {
width: 1600px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px 16px;
width: 1600px;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 16px 16px;
}
.source-library-card {
......
<template>
<div ref="map" style="width: 1600px; height: 551px"></div>
<div
style="width: 1231px; height: 72px; margin-top: -55px; background: linear-gradient(to top, #DAEBFD, #ffffff); margin-left: 182px">
<div style="width: 1231px; height: 40px; display: flex; justify-content: space-between;">
<div style="display: flex; align-self: center;">
<div class="time-btn">
<div class="map-wrapper">
<div ref="map" class="map-container"></div>
<div class="map-overlay" ref="mapOverlay">
<div
v-for="(card, idx) in mapCards"
:key="idx"
class="map-card"
:style="{ left: card.x + 'px', top: card.y + 'px' }"
>
<img class="map-card__bg" src="/icon/map-text-bg.png" alt="" />
<img class="map-card__avatar" :src="card.avatar" alt="" />
<div class="map-card__info">
<div class="map-card__time">{{ card.time }}</div>
<div class="map-card__text">{{ card.text }}</div>
</div>
<div class="time-btn">
</div>
</div>
<div class="timeline-panel">
<div class="control-bar">
<div class="control-left">
<button class="ctrl-btn" @click="handlePause" :title="isPlaying ? '暂停' : '播放'">
<svg v-if="isPlaying" width="10" height="14" viewBox="0 0 10 14" fill="none">
<rect x="0" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
<rect x="7" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
</svg>
<svg v-else width="10" height="14" viewBox="0 0 10 14" fill="none">
<polygon points="0,0 10,7 0,14" fill="#04295A"/>
</svg>
</button>
<button class="ctrl-btn" @click="handleStop" title="停止">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<rect x="0" y="0" width="12" height="12" rx="1" fill="#04295A"/>
</svg>
</button>
<button class="ctrl-btn ctrl-btn--text" @click="handleReset">重置</button>
</div>
<div class="time-btn">
重置
<div class="control-right">
<span class="control-label">当前时间</span>
<el-date-picker
v-model="currentDate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="选择日期"
class="timeline-date-picker"
@change="handleDateChange"
/>
<span class="control-label control-label--gap">刻度</span>
<el-select v-model="scaleMode" class="timeline-select" @change="handleScaleChange">
<el-option label="周制" value="week" />
<el-option label="月制" value="month" />
<el-option label="日制" value="day" />
</el-select>
<span class="control-label control-label--gap">速率</span>
<el-select v-model="speedRate" class="timeline-select" @change="handleSpeedChange">
<el-option label="1X" value="1" />
<el-option label="2X" value="2" />
<el-option label="4X" value="4" />
</el-select>
</div>
</div>
<div style="display: flex; align-self: center;">
<div>
当前时间:
<div class="timeline-track" ref="timelineTrack"
@mousedown="onTimelineMouseDown"
>
<div class="timeline-inner" ref="timelineInner">
<template v-for="(tick, index) in visibleTicks" :key="tick.date">
<div class="tick-cell" :style="{ width: tickWidth + 'px' }" @click="onTickClick(tick.globalIndex)">
<div class="tick-line" :class="{ 'tick-line--major': tick.showLabel }"></div>
<span class="tick-label" v-if="tick.showLabel">{{ tick.label }}</span>
</div>
</template>
<div class="timeline-cursor" v-if="cursorLeft >= 0" :style="{ left: cursorLeft + 'px' }"></div>
</div>
<input></input>
<div style="margin: 0 10px 0 40px; color: #04295A;">刻度</div>
<select name="firstSelect" id="firstSelect">
<option value="option1">周制</option>
</select>
<div style="margin: 0 10px 0 40px; color: #04295A;">速率</div>
<select name="secondSelect" id="secondSelect" style="margin-right: 20px;">
<option value="optionA">1X</option>
<option value="optionB">2X</option>
</select>
</div>
</div>
<div ref="timeLineChart" style="width: 1231px; height: 30px; background-color: #6091D6;"></div>
</div>
</template>
<script>
import * as echarts from "echarts";
import worldJson from "@/assets/json/world.json";
import {getCharacterTrends} from "@/api/technologyFigures/technologyFigures"
import { ref,onMounted } from "vue";
// const props = defineProps({
// peoDate: {
// type: String,
// default:"jinri"
// }
// })
import { getCharacterTrends } from "@/api/technologyFigures/technologyFigures";
import { ref } from "vue";
// 获取人物动向
const CharacterTrends = ref([]);
const time = ref("");
const date = ref("");
const handlegetCharacterTrendsFn = async () => {
const params = {
startTime: "2025-01-01" || date.value
};
try {
const res = await getCharacterTrends(params);
console.log("人物动向", res);
if (res.code === 200) {
CharacterTrends.value = res.data.map(item=>{
return{
const params = {
startTime: "2025-01-01" || date.value,
};
try {
const res = await getCharacterTrends(params);
if (res.code === 200) {
CharacterTrends.value = res.data.map((item) => {
return {
time: item.newsDate,
text: item.newsContent.substring(0, 17),
lon: item.lon || (Math.random() * 360 - 180).toFixed(6), //没数据
lat: item.lat || (Math.random() * 180 - 90).toFixed(6), //没数据
avatar: item.imageUrl
lon: item.lon || (Math.random() * 360 - 180).toFixed(6),
lat: item.lat || (Math.random() * 180 - 90).toFixed(6),
avatar: item.imageUrl,
};
});
}
} catch (error) {}
}
} catch (error) {}
};
function getDateDaysAgo(days) {
// 获取当前日期
const currentDate = new Date();
// 计算指定月数之前的日期
const pastDate = new Date(currentDate);
pastDate.setDate(currentDate.getDate() - days); // 减去指定的天数
// 格式化日期为 "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 currentDate = new Date();
const pastDate = new Date(currentDate);
pastDate.setDate(currentDate.getDate() - days);
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0");
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
// onMounted(async () => {
// handlegetCharacterTrendsFn();
// })
function generateTicks(startDate, endDate, mode) {
const ticks = [];
const start = new Date(startDate);
const end = new Date(endDate);
const current = new Date(start);
while (current <= end) {
const m = String(current.getMonth() + 1).padStart(2, "0");
const d = String(current.getDate()).padStart(2, "0");
ticks.push({
date: `${current.getFullYear()}-${m}-${d}`,
label: `${m}.${d}`,
showLabel: ticks.length % 7 === 0,
});
if (mode === "week") {
current.setDate(current.getDate() + 7);
} else if (mode === "month") {
current.setMonth(current.getMonth() + 1);
} else {
current.setDate(current.getDate() + 1);
}
}
return ticks;
}
export default {
name: "MapAnimation",
props: {
peoDate: {
type: String,
default:"本周"
}
default: "本周",
},
},
data() {
const today = new Date();
const todayStr = today.getFullYear() + "-" +
String(today.getMonth() + 1).padStart(2, "0") + "-" +
String(today.getDate()).padStart(2, "0");
return {
isPlaying: false,
playTimer: null,
currentDate: todayStr,
scaleMode: "day",
speedRate: "2",
currentTickIndex: 0,
timelineTicks: [],
startDate: "",
endDate: "",
chartInstance: null,
mapCards: [],
tickWidth: 10,
viewStartIndex: 0,
viewDays: 120,
};
},
computed: {
visibleTicks() {
const start = Math.max(0, this.viewStartIndex);
const end = Math.min(this.timelineTicks.length, start + this.viewDays);
return this.timelineTicks.slice(start, end).map((tick, i) => ({
...tick,
globalIndex: start + i,
}));
},
cursorLeft() {
const offset = this.currentTickIndex - this.viewStartIndex;
if (offset < 0 || offset >= this.viewDays) return -9999;
return offset * this.tickWidth + this.tickWidth / 2;
},
},
watch: {
async peoDate(Val) {
time.value = Val;
const days = ref();
if(time.value === "本周"){
days.value = 7;
}else if(time.value === "今天"){
days.value = 0;
}else if(time.value === "昨天"){
days.value = 1;
}else if(time.value === "近三天"){
days.value = 3;
}else if(time.value === "本月"){
days.value = new Date().getDate()-1;
}
if (time.value === "本周") days.value = 7;
else if (time.value === "今天") days.value = 0;
else if (time.value === "昨天") days.value = 1;
else if (time.value === "近三天") days.value = 3;
else if (time.value === "本月") days.value = new Date().getDate() - 1;
date.value = getDateDaysAgo(days.value);
await handlegetCharacterTrendsFn();
this.initTimelineFromData();
this.initMap();
}
},
setup(props) {
onMounted(async () => {
time.value = props.peoDate;
const days = ref();
if(time.value === "本周"){
days.value = 7;
}else if(time.value === "今天"){
days.value = 0;
}else if(time.value === "昨天"){
days.value = 1;
}else if(time.value === "近三天"){
days.value = 3;
}else if(time.value === "本月"){
days.value = new Date().getDate()-1;
}
date.value = getDateDaysAgo(days.value);
},
});
},
async mounted() {
time.value = this.peoDate;
let days = 7;
if (time.value === "本周") days = 7;
else if (time.value === "今天") days = 0;
else if (time.value === "昨天") days = 1;
else if (time.value === "近三天") days = 3;
else if (time.value === "本月") days = new Date().getDate() - 1;
date.value = getDateDaysAgo(days);
await handlegetCharacterTrendsFn();
this.initTimelineFromData();
this.initMap();
this._tlWheelHandler = (e) => {
e.preventDefault();
this.onTimelineWheel(e);
};
const tlEl = this.$refs.timelineTrack;
if (tlEl) {
tlEl.addEventListener("wheel", this._tlWheelHandler, { passive: false });
}
},
beforeUnmount() {
this.clearPlayTimer();
const tlEl = this.$refs.timelineTrack;
if (tlEl && this._tlWheelHandler) {
tlEl.removeEventListener("wheel", this._tlWheelHandler);
}
if (this.chartInstance) {
this.chartInstance.dispose();
this.chartInstance = null;
}
},
methods: {
/**
* 鼠标滚轮左右滚动时间轴
*/
onTimelineWheel(e) {
const delta = e.deltaY || e.deltaX;
const step = delta > 0 ? 7 : -7;
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
this.viewStartIndex + step
));
},
/**
* 鼠标拖拽滚动时间轴
*/
onTimelineMouseDown(e) {
const startX = e.clientX;
const startViewIndex = this.viewStartIndex;
const onMove = (ev) => {
const dx = ev.clientX - startX;
const daysMoved = Math.round(dx / this.tickWidth);
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
startViewIndex - daysMoved
));
};
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
},
/**
* 点击某个刻度跳转
*/
onTickClick(globalIndex) {
if (this.isPlaying) this.pausePlay();
this.currentTickIndex = globalIndex;
this.syncDateFromTick();
this.updateMapByDate(this.currentDate);
this.$emit("timeline-change", this.currentDate);
},
/**
* 将可视窗口居中到指定 tick
*/
scrollTimelineTo(tickIdx) {
const half = Math.floor(this.viewDays / 2);
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
tickIdx - half
));
},
rebuildTimeline() {
if (!this.startDate || !this.endDate) return;
this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
this.initTimelinePosition();
this.scrollTimelineTo(this.currentTickIndex);
},
handleDateChange(val) {
this.currentDate = val;
if (val < this.startDate || val > this.endDate) {
const y = parseInt(val.substring(0, 4));
if (val < this.startDate) {
this.startDate = y + "-01-01";
}
if (val > this.endDate) {
this.endDate = y + "-12-31";
}
this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
}
const idx = this.timelineTicks.findIndex((t) => t.date >= val);
this.currentTickIndex = idx >= 0 ? idx : this.timelineTicks.length - 1;
this.currentDate = this.timelineTicks[this.currentTickIndex].date;
this.updateMapByDate(this.currentDate);
this.scrollTimelineTo(this.currentTickIndex);
this.$emit("timeline-change", this.currentDate);
},
handleScaleChange(val) {
this.$emit("scale-change", val);
},
handleSpeedChange(val) {
this.$emit("speed-change", val);
if (this.isPlaying) {
this.clearPlayTimer();
this.scheduleNextTick();
}
},
handlePause() {
if (this.isPlaying) {
this.pausePlay();
} else {
if (this.currentTickIndex >= this.timelineTicks.length - 1) {
return;
}
this.startPlay();
}
this.$emit("play-toggle", this.isPlaying);
},
handleStop() {
this.clearPlayTimer();
this.isPlaying = false;
this.$emit("stop", this.currentDate);
},
handleReset() {
this.clearPlayTimer();
this.isPlaying = false;
const now = new Date();
this.currentDate = now.getFullYear() + "-" +
String(now.getMonth() + 1).padStart(2, "0") + "-" +
String(now.getDate()).padStart(2, "0");
this.initTimelinePosition();
this.scrollTimelineTo(this.currentTickIndex);
this.updateMapByDate(this.currentDate);
this.$emit("reset");
this.$emit("timeline-change", this.currentDate);
},
startPlay() {
this.clearPlayTimer();
this.isPlaying = true;
this.scheduleNextTick();
},
pausePlay() {
this.clearPlayTimer();
this.isPlaying = false;
},
scheduleNextTick() {
const interval = 1000 / Number(this.speedRate);
this.playTimer = setTimeout(() => {
if (this.currentTickIndex < this.timelineTicks.length - 1) {
this.currentTickIndex++;
this.syncDateFromTick();
this.updateMapByDate(this.currentDate);
this.scrollTimelineTo(this.currentTickIndex);
this.$emit("timeline-change", this.currentDate);
this.scheduleNextTick();
} else {
this.isPlaying = false;
this.playTimer = null;
this.$emit("play-end");
}
}, interval);
},
clearPlayTimer() {
if (this.playTimer) {
clearTimeout(this.playTimer);
this.playTimer = null;
}
},
syncDateFromTick() {
const tick = this.timelineTicks[this.currentTickIndex];
if (tick) {
this.currentDate = tick.date;
}
},
initMap() {
// 注册自定义地图数据
echarts.registerMap("China", worldJson);
// const eventsData = [
// {
// time: "9月23日",
// text: "随美国总统特朗普进行国事访问",
// lon: 116.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "出席中国发展高层论坛2025年年会",
// lon: 116.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "与民主党领导人查克·舒默及哈基姆...",
// lon: 1.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "与阿拉伯国家领导人会晤,商讨加...",
// lon: 116.46,
// lat: -44.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "对印度进行为期四天的访问,与总理...",
// lon: 78.1,
// lat: 20.7,
// avatar: "/testData/united_states 1 copy.png"
// }
// ];
const eventsData = CharacterTrends;
if (this.chartInstance) {
this.chartInstance.dispose();
}
const chart = echarts.init(this.$refs.map);
this.chartInstance = chart;
const option = {
grid: {
......@@ -206,211 +407,454 @@ export default {
right: "10%",
bottom: "10%",
top: "10%",
containLabel: true
containLabel: true,
},
geo: {
map: "China",
roam: true,
roam: 'move',
label: {
emphasis: {
show: false,
color: "#fff"
}
emphasis: { show: false, color: "#fff" },
},
silent: true,
itemStyle: {
areaColor: "#F6FAFF",
borderColor: "#B9DCFF"
}
borderColor: "#B9DCFF",
},
},
series: [
{
name: "行程",
type: "effectScatter",
coordinateSystem: "geo",
data: eventsData.value.map(item => ({
value: [item.lon, item.lat],
time: item.time,
text: item.text,
avatar: item.avatar,
itemStyle: { color: "#ffcc00" }
})),
data: [],
symbolSize: 10,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke"
},
// label: {
// normal: {
// show: true,
// formatter: params => {
// const { time, text } = eventsData[params.dataIndex];
// return `{time|${time}} \n {text|${text}}`;
// },
// rich: {
// time: { fontSize: 16, color: 'rgba(5, 95, 194, 1)' },
// text: { fontSize: 14, color: 'rgb(95, 101, 108)' }
// }
// }
// },
rippleEffect: { brushType: "stroke" },
itemStyle: {
color: "#ddb926",
shadowBlur: 10,
shadowColor: "#333"
}
}
]
shadowColor: "#333",
},
},
],
};
chart.setOption(option);
// 添加 graphic 元素并设置其位置
this.updateGraphics(chart, eventsData);
// 监听地图缩放和移动事件,以更新 graphic 的位置
chart.on('georoam', () => {
this.updateGraphics(chart, eventsData);
// 地图拖拽/缩放时重新计算 DOM 卡片位置(加防抖避免频繁���发)
let georoamTimer = null;
chart.on("georoam", () => {
if (georoamTimer) clearTimeout(georoamTimer);
georoamTimer = setTimeout(() => {
const events = this._currentDisplayData || [];
this.updateMapCards(events);
}, 100);
});
const chart2 = echarts.init(this.$refs.timeLineChart);
const option2 = {
xAxis: {
type: "time",
position: "top",
axisLine: {
show: true,
lineStyle: {
color: "#000"
}
},
axisTick: {
show: false
},
axisLabel: {
formatter: "{value}日",
color: "#000"
},
splitLine: {
show: true
},
data: [
"2024-03-25",
"2024-04-08",
"2024-04-15",
"2024-04-22",
"2024-04-29",
"2024-05-06",
"2024-05-13",
"2024-05-20",
"2024-05-27",
"2024-06-03",
"2024-06-10",
"2024-06-17",
"2024-06-24",
"2024-07-01",
"2024-07-08",
"2024-07-15",
"2024-07-22",
"2024-07-29"
]
},
yAxis: {
type: "value",
show: false
},
// 初始化:默认定位到当前日期
this.initTimelinePosition();
this.scrollTimelineTo(this.currentTickIndex);
this.updateMapByDate(this.currentDate);
},
/**
* 根据数据范围 + 当前日期,构建覆盖完整年份的每日时间轴
* 取数据最小年份1月1日 ~ 当前年份12月31日
*/
initTimelineFromData() {
const allData = CharacterTrends.value || [];
const now = new Date();
const currentYear = now.getFullYear();
let minYear = currentYear;
let maxYear = currentYear;
// 从数据中找最早和最晚的年份
allData.forEach((item) => {
if (item.time) {
const y = parseInt(item.time.substring(0, 4));
if (y < minYear) minYear = y;
if (y > maxYear) maxYear = y;
}
});
this.startDate = minYear + "-01-01";
this.endDate = maxYear + "-12-31";
this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
this.initTimelinePosition();
},
/**
* 将时间轴指针定位到当前日期(today),如果 today 超出范围则取最近端
*/
initTimelinePosition() {
if (!this.timelineTicks.length) return;
const idx = this.timelineTicks.findIndex((t) => t.date >= this.currentDate);
if (idx >= 0) {
this.currentTickIndex = idx;
this.currentDate = this.timelineTicks[idx].date;
} else {
// currentDate 超出时间轴末尾,定位到最后
this.currentTickIndex = this.timelineTicks.length - 1;
this.currentDate = this.timelineTicks[this.currentTickIndex].date;
}
},
/**
* 按日期筛选数据并更新地图
* 窗口模式:仅显示 [currentDate - 7天, currentDate + 7天] 内的数据
*/
updateMapByDate(dateStr) {
const chart = this.chartInstance;
if (!chart) return;
const allData = CharacterTrends.value || [];
// 计算前后一周范围
const cur = new Date(dateStr);
const weekBefore = new Date(cur);
weekBefore.setDate(cur.getDate() - 7);
const weekAfter = new Date(cur);
weekAfter.setDate(cur.getDate() + 7);
const fmt = (d) => d.getFullYear() + "-" +
String(d.getMonth() + 1).padStart(2, "0") + "-" +
String(d.getDate()).padStart(2, "0");
const rangeStart = fmt(weekBefore);
const rangeEnd = fmt(weekAfter);
const filteredData = allData.filter((item) => item.time >= rangeStart && item.time <= rangeEnd);
// 保存当前显示的数据,供 georoam 时刷新位置用
this._currentDisplayData = filteredData;
// 更新散点
chart.setOption({
series: [
{
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
type: "line",
areaStyle: {},
emphasis: {
focus: "series"
}
}
data: filteredData.map((item) => ({
value: [item.lon, item.lat],
time: item.time,
text: item.text,
avatar: item.avatar,
itemStyle: { color: "#ffcc00" },
})),
},
],
visualMap: {
show: false,
dimension: 1,
pieces: [{ gt: 0, lte: 1, color: "#ffcc00" }]
}
};
});
chart2.setOption(option2);
// 更新 DOM 浮窗卡片位置
this.updateMapCards(filteredData);
},
updateGraphics(chart, eventsData) {
const graphics = eventsData.value.map((event, index) => {
const position = chart.convertToPixel({ geoIndex: 0 }, [event.lon, event.lat]);
/**
* 根据事件数据更新 DOM 浮窗卡片(绝对定位在地图上方)
*/
updateMapCards(events) {
const chart = this.chartInstance;
if (!chart) {
this.mapCards = [];
return;
}
this.mapCards = events.map((event) => {
const pos = chart.convertToPixel({ geoIndex: 0 }, [event.lon, event.lat]);
if (!pos) return null;
return {
type: 'group',
position: position,
children: [
{
type: 'image',
x: -170, y: -72,
style: {
image: "/icon/map-text-bg.png",
borderWidth: 2,
borderColor: '#1890ff', width: 339, height: 72
}
},
{
type: 'image',
x: -160, y: -63,
style: {
image: event.avatar,
borderWidth: 2,
borderColor: '#1890ff', width: 42, height: 42
}
}, {
type: 'text',
x: -106, y: -63,
style: {
text: event.time,
fontSize: 16,
fontWeight: '800',
fill: 'rgba(5, 95, 194, 1)', // 文字颜色(ECharts 中用 fill,不是 color)
textAlign: 'left'
}
},
{
type: 'text',
x: -106, y: -40,
style: {
text: event.text,
fontSize: 16,
fontWeight: '400',
fill: 'rgba(59, 65, 75, 1)', // 文字颜色(ECharts 中用 fill,不是 color)
textAlign: 'left',
}
}
]
x: pos[0] - 170,
y: pos[1] - 72,
avatar: event.avatar,
time: event.time,
text: event.text,
};
});
}).filter(Boolean);
},
chart.setOption({
graphic: graphics
});
chart.resize(); // 强制刷新图表
}
}
/**
* 地图拖拽/缩放后,重新计算 DOM 卡片位置
*/
refreshGraphicPositions() {
const events = this._currentDisplayData || [];
this.updateMapCards(events);
},
},
};
</script>
<style scoped>
#map {
<style lang="scss" scoped>
.map-wrapper {
width: 1600px;
height: 581px;
font-family: Microsoft YaHei;
position: relative;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
.map-container {
width: 100%;
height: 551px;
position: relative;
z-index: 1;
}
/* ========== 地图浮窗卡片 ========== */
.map-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 551px;
pointer-events: none;
z-index: 2;
overflow: hidden;
}
.map-card {
position: absolute;
width: 339px;
height: 72px;
pointer-events: auto;
}
.map-card__bg {
position: absolute;
top: 0;
left: 0;
width: 339px;
height: 72px;
object-fit: fill;
}
.map-card__avatar {
position: absolute;
top: 9px;
left: 10px;
width: 42px;
height: 42px;
border: 2px solid #1890ff;
border-radius: 2px;
object-fit: cover;
z-index: 1;
}
.map-card__info {
position: absolute;
top: 9px;
left: 64px;
z-index: 1;
}
.map-card__time {
font-size: 16px;
font-weight: 800;
color: rgba(5, 95, 194, 1);
line-height: 1.2;
}
.map-card__text {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 260px;
}
/* ========== 时间线面板 ========== */
.timeline-panel {
width: 1231px;
margin: -50px auto 0 182px;
background: #fff;
border: 1px solid #c8d8e8;
border-radius: 2px;
overflow: hidden;
position: relative;
z-index: 10;
}
/* ========== 控制栏 ========== */
.control-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 42px;
padding: 0 12px;
}
/* -- 左侧按钮组 -- */
.control-left {
display: flex;
align-items: center;
gap: 8px;
}
.ctrl-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: 34px;
height: 28px;
padding: 0 8px;
border: 1px solid #04295a;
border-radius: 2px;
background-color: #fff;
color: #04295a;
cursor: pointer;
transition: all 0.15s;
position: relative;
z-index: 11;
pointer-events: auto;
&:hover {
background-color: #eaf2fd;
}
&:active {
background-color: #d4e3f5;
}
}
.ctrl-btn--text {
font-size: 14px;
font-weight: 400;
padding: 0 14px;
color: #04295a;
letter-spacing: 2px;
}
/* -- 右侧控制区 -- */
.control-right {
display: flex;
align-items: center;
gap: 8px;
}
.control-label {
font-size: 14px;
color: #04295a;
white-space: nowrap;
user-select: none;
}
.control-label--gap {
margin-left: 24px;
}
/* Element Plus 日期选择器 */
.timeline-date-picker {
width: 160px !important;
:deep(.el-input__wrapper) {
height: 28px;
border-radius: 2px;
box-shadow: 0 0 0 1px #b8cde5 inset !important;
background: #fff;
padding: 0 8px;
}
:deep(.el-input__inner) {
font-size: 14px;
color: #04295a;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
:deep(.el-input__prefix) {
display: none;
}
}
/* Element Plus 下拉选择器 */
.timeline-select {
width: 80px !important;
:deep(.el-input__wrapper) {
height: 28px;
border-radius: 2px;
box-shadow: 0 0 0 1px #b8cde5 inset !important;
background: #fff;
padding: 0 8px;
}
:deep(.el-input__inner) {
font-size: 14px;
color: #04295a;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
}
/* ========== 可滚动时间轴 ========== */
.timeline-track {
width: 100%;
height: 32px;
background-color: #6593d7;
position: relative;
overflow: hidden;
user-select: none;
cursor: grab;
&:active {
cursor: grabbing;
}
/* 顶部 1px 白色横线 */
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: rgba(255, 255, 255, 0.85);
z-index: 5;
pointer-events: none;
}
}
.timeline-inner {
height: 100%;
position: relative;
display: flex;
align-items: flex-start;
}
.tick-cell {
flex-shrink: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
position: relative;
&:hover {
background-color: rgba(255, 255, 255, 0.08);
}
}
.tick-line {
width: 1px;
height: 5px;
background: rgba(255, 255, 255, 0.35);
margin-top: 2px;
}
.tick-line--major {
height: 8px;
background: rgba(255, 255, 255, 0.6);
}
.tick-label {
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
font-size: 11px;
color: #fff;
white-space: nowrap;
pointer-events: none;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
line-height: 1;
}
.time-btn {
color: #04295A;
background-color: #F9FDFE;
border: 1px solid #04295A;
padding: 0 10px;
margin: 0 10px;
.timeline-cursor {
position: absolute;
top: 0;
bottom: 0;
width: 2px;
background: #fff;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.8);
z-index: 6;
transform: translateX(-50%);
pointer-events: none;
}
</style>
\ No newline at end of file
</style>
......@@ -3,16 +3,16 @@
<div class="speech-stance-container">
<div class="speech-stance-grid">
<div v-for="(item, index) in PersonRelation" :key="index" class="speech-stance-card">
<div class="speech-stance-avatar-wrapper">
<div class="speech-stance-avatar-wrapper" @click="handleClcikToCharacter(item.personId)">
<img :src="item.personImage" alt="" class="speech-stance-avatar" />
</div>
<div class="speech-stance-text-content">
<div class="speech-stance-text-content" @click="handleClcikToCharacter(item.personId)">
<div style="display: flex; width: 683px;">
<h3 class="speech-stance-name">{{ item.personName }}</h3>
<p class="speech-stance-title">{{ item.positionTitle }}</p>
</div>
<p class="speech-stance-content">{{ item.remarks }}</p>
<p class="speech-stance-content" :title="item.remarks">{{ item.remarks }}</p>
</div>
</div>
......@@ -26,6 +26,9 @@ import { onMounted, ref,defineProps,watch } from "vue";
import speechStance from '../json/speechStance.json';
import {getPersonRelation} from "@/api/technologyFigures/technologyFigures"
import { getPersonSummaryInfo } from "@/api/technologyFigures/technologyFigures";
import { useRouter } from "vue-router";
const router = useRouter();
const props = defineProps({
fieldSelected: {
type: String,
......@@ -36,7 +39,59 @@ const props = defineProps({
default: 0
}
});
// 跳转人物主页
const handleClcikToCharacter = async id => {
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList"));
let type = 0;
let personTypeName = "";
const params = {
personId: id
};
try {
const res = await getPersonSummaryInfo(params);
console.log("人物全局信息", res);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => {
return item.typeId === res.data.personType;
});
console.log("arr", arr);
if (arr && arr.length > 0) {
personTypeName = arr[0].typeName;
console.log("personTypeName", personTypeName);
if (personTypeName === "科技企业领袖") {
type = 1;
} else if (personTypeName === "国会议员") {
type = 2;
} else if (personTypeName === "智库研究人员") {
type = 3;
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const route = router.resolve({
path: "/characterPage",
query: {
type: type, // type=1为科技企业领袖,2为国会议员,3为智库研究人员
personId: id
}
});
window.open(route.href, "_blank");
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
} else {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
} catch (error) {}
};
const aId = ref();
const params = ref({});
......@@ -149,11 +204,8 @@ onMounted(async () => {
line-height: 30px;
letter-spacing: 0px;
text-align: left;
white-space: nowrap;
/* 禁止换行 */
overflow: hidden;
/* 隐藏溢出内容 */
text-overflow: ellipsis;
/* 溢出部分显示省略号 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
\ No newline at end of file
......@@ -20,8 +20,8 @@
<div class="header-item">科技人物观点</div>
</div> -->
<div class="home-main-header-center">
<SearchContainer style="margin-bottom: 0;margin-top: 48px; " v-if="containerRef" placeholder="搜索科技人物及观点"
:containerRef="containerRef" areaName="人物" />
<SearchContainer style="margin-bottom: 0;margin-top: 48px; " v-if="containerRef"
placeholder="搜索科技人物及观点" :containerRef="containerRef" areaName="人物" />
<!-- <el-input v-model="input" style="width: 838px; height: 100%" placeholder="搜索科技人物及观点" />
<div class="search">
<div class="search-icon">
......@@ -78,131 +78,80 @@
<div class="home-main-center">
<DivideHeader id="position1" class="divide-header" :titleText="'最新动态'"></DivideHeader>
<div class="center-top">
<div class="box1">
<div class="box1-left" @click="handleSwithCurBill('left')">
<img src="./assets/images/box1-left.png" alt="" />
</div>
<div class="box1-right" @click="handleSwithCurBill('right')">
<img src="./assets/images/box1-right.png" alt="" />
</div>
<div class="box1-header">
<div class="box1-header-left">
<div class="icon">
<img src="./assets/images/TechnologyFigures-icon4.png" alt="" />
</div>
<div class="title">{{ "人物新闻动态" }}</div>
<OverviewMainBox title="人物新闻动态" width="1064px" height="460px"
@to-detail="handleClcikToCharacter(curnews.personId, curnews.name)">
<!-- 自定义左上角图标插槽 -->
<template #headerIcon>
<img src="./assets/images/personNewIcon.svg " alt="" />
</template>
<!-- 主内容区域 -->
<div class="box1-wrapper">
<div class="box1-left" @click="handleSwithCurBill('left')">
<img src="./assets/images/box1-left.png" alt="" />
</div>
<div class="box1-header-right" @click="handleClcikToCharacter(curnews.personId, curnews.name)">
{{ "查看详情 >" }}
<div class="box1-right" @click="handleSwithCurBill('right')">
<img src="./assets/images/box1-right.png" alt="" />
</div>
</div>
<div class="box1-main" style="display: block">
<el-carousel ref="carouselRef" height="354px" :autoplay="true" :interval="3000" arrow="never"
indicator-position="none" @change="handleCarouselChange">
<el-carousel-item v-for="person in newsDynamics">
<div class="carousel-content" style="display: flex; height: 100%">
<!-- 左侧:头像 -->
<div class="avatar">
<img :src="person.imageUrl" alt="人物头像" />
</div>
<!-- 事件列表 -->
<div class="events">
<!-- 头部区域 -->
<div class="header">
<!-- 右侧:信息 -->
<div class="info" style="display: flex; width: 100%; justify-content: space-between">
<div>
<h2>{{ person.name }}</h2>
<p class="newtitle">{{ person.positionTitle }}</p>
</div>
<div style="display: flex; gap: 10px;">
<div class="source-tag" v-for="item in person.personTypeList"
:class="{ tag2: item.typeId === '002' }">{{ item.typeName }}</div>
<div class="box1-main">
<el-carousel ref="carouselRef" height="354px" :autoplay="true" :interval="3000"
arrow="never" indicator-position="none" @change="handleCarouselChange">
<el-carousel-item v-for="person in newsDynamics" :key="person.personId">
<div class="carousel-content">
<!-- 左侧:头像 -->
<div class="avatar">
<img :src="person.imageUrl" alt="人物头像" />
</div>
<!-- 事件列表 -->
<div class="events">
<!-- 头部区域 -->
<div class="header">
<div class="info">
<div>
<h2>{{ person.name }}</h2>
<p class="newtitle">{{ person.positionTitle }}</p>
</div>
<!-- 使用 AreaTag 组件替换 -->
<div class="tag-group">
<AreaTag v-for="item in person.personTypeList"
:key="item.typeId" :tag-name="item.typeName" />
</div>
</div>
</div>
</div>
<div class="line"></div>
<div style="height: 289px; width: 688px;">
<el-timeline style="margin-top: 14px">
<el-timeline-item placement="top" v-for="(activity, index) in person.newsList" :key="index"
type="primary" :hollow="true">
<template #dot>
<div style="
display: flex;
justify-content: space-between;
width: 688px;
text-align: center;
align-items: center;
margin-bottom: 12px;
">
<div style="display: flex">
<img src="./assets/images/dot1.png" alt="" style="width: 10px; height: 10px" />
<div style="
margin-left: 16px;
margin-top: -6px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
letter-spacing: 1px;
text-align: left;
">
{{ activity.newsDate }}
<div class="line"></div>
<div class="timeline-wrapper">
<el-timeline>
<el-timeline-item
v-for="(activity, index) in person.newsList"
:key="index" placement="top" type="primary"
:hollow="true">
<template #dot>
<div class="timeline-dot">
<div class="dot-left">
<img src="./assets/images/dot1.png" alt=""
class="dot-icon" />
<div class="dot-date">{{ activity.newsDate
}}</div>
</div>
<img src="./assets/images/dot2.png" alt=""
class="dot-arrow" />
</div>
<img src="./assets/images/dot2.png" alt="" style="width: 20px; height: 18px" />
</div>
</template>
{{ activity.newsContent }}
</el-timeline-item>
</el-timeline>
</template>
{{ activity.newsContent }}
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
<!-- <div class="box2">
<div class="box2-header">
<div class="icon">
<img src="./assets/images/box2-header-icon.png" alt="" />
</div>
<div class="title">
<div class="text">{{ "风险信号" }}</div>
<div class="num">{{ warningList.length }}</div>
</el-carousel-item>
</el-carousel>
</div>
</div>
<div class="box2-main">
<div class="box2-main-item" v-for="(item, index) in warningList" :key="index">
<div class="item-left" :class="{
itemLeftStatus1: item.signalLevel === '一般风险',
itemLeftStatus2: item.signalLevel === '重大风险'
}">
{{ item.signalLevel ? item.signalLevel : "一般风险" }}
</div>
<div class="item-right">
<div class="text">
{{ item.signalTitle }}
</div>
<div class="time">{{ item.signalTime }}</div>
</div>
</div>
</div>
<div class="box2-footer" @click="handleToMoreRiskSignal">
<div class="icon">
<img src="./assets/images/box2-footer-icon.png" alt="" />
</div>
<div class="text">{{ "查看更多" }}</div>
</div>
</div> -->
</OverviewMainBox>
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" postDate="signalTime"
name="signalTitle" riskLevel="signalLevel" />
</div>
......@@ -270,13 +219,16 @@
</div>
<div>
<div style="height: 45px; display: flex; align-items: center">
<el-select v-model="wordCloudvalue" style="width: 120px; height: 28px" @change="handleBox5Change">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
<el-select v-model="wordCloudvalue" style="width: 120px; height: 28px"
@change="handleBox5Change">
<el-option v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
<el-select v-model="wordCloudfield" style="width: 120px; height: 28px; margin: 10px 24px 10px 5px"
<el-select v-model="wordCloudfield"
style="width: 120px; height: 28px; margin: 10px 24px 10px 5px"
@change="handleBox5areaChange">
<el-option v-for="item in fieldSelectList" :key="item.value" :label="item.label"
:value="item.value" />
<el-option v-for="item in fieldSelectList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -296,7 +248,8 @@
</div>
<el-select v-model="areaSelect" style="width: 120px; height: 28px">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in options" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
......@@ -321,16 +274,17 @@
<div class="box8-header-icon">
<img src="./assets/images/TechnologyFigures-icon2.png" alt="" />
</div>
<div style="display: flex; width: 730px; justify-content: space-between; align-items: center">
<div class="box8-header-title">{{ "主要人物涉华观点统计" }}</div>
<div style="gap: 2px; display: flex;width:510px;">
<div v-for="value in PersonType"
:class="viewSelect !== value.typeName ? 'btn-box-samll' : 'btn-box-select-samll'"
@click="selectpersontype(value)">
{{ value.typeName }}
</div>
<el-select v-model="yearSelect" style="width: 100px; height: 28px; margin-top: -5px">
<el-option v-for="item in yearList" :key="item.value" :label="item.label" :value="item.value" />
<div class="box8-header-content">
<div class="box8-header-title">观点统计</div>
<div class="box8-header-filters">
<el-select v-model="viewSelect" placeholder="请选择类型"
@change="handlePersonTypeChange">
<el-option v-for="item in PersonType" :key="item.typeId"
:label="item.typeName" :value="item.typeName" />
</el-select>
<el-select v-model="yearSelect" placeholder="请选择年份">
<el-option v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -368,6 +322,7 @@ import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import scrollToTop from "@/utils/scrollToTop";
import DivideHeader from "@/components/DivideHeader.vue";
import OverviewMainBox from '@/components/base/boxBackground/overviewMainBox.vue'
import setChart from "@/utils/setChart";
import {
getnewsDynamics,
......@@ -461,7 +416,12 @@ const handleGetBillRiskSignalFn = async () => {
// 获取行业领域
const areaTypeList = ref([]);
const handlePersonTypeChange = (typeName) => {
const selectedType = PersonType.value.find(item => item.typeName === typeName)
if (selectedType) {
selectpersontype(selectedType)
}
}
const handlegetareaTypeFn = async () => {
try {
const res = await getareaType();
......@@ -677,7 +637,7 @@ const handleClcikToCharacter = async (id, name) => {
};
try {
const res = await getPersonSummaryInfo(params);
console.log("人物全局信息", res);
console.log("人物全局信息5", res);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => {
return item.typeId === res.data.personType;
......@@ -839,7 +799,7 @@ const handleClickCate = cate => {
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
const route = router.resolve("/riskSignal");
window.open(route.href, "_blank");
};
......@@ -1752,55 +1712,77 @@ onMounted(async () => {
}
.box8 {
width: 792px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box8-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box8-header-left {
display: flex;
.box8-header-icon {
margin-top: 18px;
margin-left: 2px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box8-header-title {
margin-left: 20px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
}
.box8-main {
width: 469px;
height: 360px;
}
}
width: 792px;
height: 460px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.box8-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin: 0 auto;
display: flex;
justify-content: space-between;
padding: 0 20px;
position: relative;
.box8-header-left {
display: flex;
width: 100%;
.box8-header-icon {
margin-top: 18px;
margin-left: 2px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.box8-header-content {
display: flex;
width: 730px;
justify-content: space-between;
align-items: center;
}
.box8-header-title {
margin-left: 20px;
height: 26px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.box8-header-filters {
display: flex;
gap: 10px;
align-items: center;
:deep(.el-select) {
width: 120px;
.el-input__wrapper {
height: 28px;
}
}
}
}
}
.box8-main {
width: 469px;
height: 360px;
}
}
}
}
......@@ -2057,7 +2039,7 @@ onMounted(async () => {
}
.events {
margin-left: 24px;
margin-left: 4px;
width: 710px;
}
......@@ -2127,4 +2109,492 @@ onMounted(async () => {
text-align: justify;
}
.box1-content {
position: relative;
height: 100%;
width: 100%;
display: block;
}
.box1-left,
.box1-right {
position: absolute;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
z-index: 10;
}
.box1-left {
left: 0;
}
.box1-right {
right: 0;
}
.avatar {
width: 280px;
height: 354px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.events {
flex: 1;
padding: 0 20px;
.header {
padding-top: 16px;
.info {
display: flex;
width: 100%;
justify-content: space-between;
h2 {
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 24px;
font-weight: 700;
margin: 0;
}
.newtitle {
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
margin-top: 8px;
}
}
}
.tag-group {
display: flex;
gap: 10px;
}
.source-tag {
padding: 4px 12px;
background: rgba(20, 89, 187, 0.1);
color: rgba(20, 89, 187, 1);
border-radius: 4px;
font-size: 14px;
height: fit-content;
}
.tag2 {
background: rgba(255, 152, 0, 0.1);
color: rgba(255, 152, 0, 1);
}
.line {
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 16px;
}
}
.timeline-wrapper {
height: 289px;
width: 688px;
overflow-y: auto;
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
:deep(.el-timeline) {
margin-top: 14px;
padding-left: 0;
}
:deep(.el-timeline-item__wrapper) {
padding-left: 40px;
}
:deep(.el-timeline-item__tail) {
left: 4px;
}
:deep(.el-timeline-item__content) {
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
line-height: 24px;
}
}
.timeline-wrapper::-webkit-scrollbar {
display: none;
}
.timeline-dot {
display: flex;
justify-content: space-between;
width: 688px;
text-align: center;
align-items: center;
margin-bottom: 12px;
.dot-left {
display: flex;
align-items: center;
}
.dot-icon {
width: 10px;
height: 10px;
}
.dot-date {
margin-left: 16px;
margin-top: -6px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
letter-spacing: 1px;
text-align: left;
}
.dot-arrow {
width: 20px;
height: 18px;
}
}
.box1-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.box1-left {
position: absolute;
left: 0;
top: 180px;
width: 24px;
height: 48px;
cursor: pointer;
z-index: 10;
img {
width: 100%;
height: 100%;
}
}
.box1-right {
position: absolute;
right: 0;
top: 180px;
width: 24px;
height: 48px;
cursor: pointer;
z-index: 10;
img {
width: 100%;
height: 100%;
}
}
.box1-main {
width: 1009px;
height: 354px;
margin-top: 28px;
margin-left: 40px;
}
.carousel-content {
display: flex;
height: 100%;
}
.avatar {
width: 280px;
height: 354px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.events {
flex: 1;
padding-left: 20px;
.header {
.info {
display: flex;
width: 100%;
justify-content: space-between;
padding-right: 20px;
h2 {
margin: 0;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 24px;
font-weight: 700;
}
.newtitle {
margin-top: 8px;
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
}
}
}
.tag-group {
display: flex;
gap: 10px;
}
.source-tag {
padding: 4px 12px;
background: rgba(20, 89, 187, 0.1);
color: rgba(20, 89, 187, 1);
border-radius: 4px;
font-size: 14px;
height: fit-content;
}
.tag2 {
background: rgba(255, 152, 0, 0.1);
color: rgba(255, 152, 0, 1);
}
.line {
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 16px;
}
}
.timeline-wrapper {
height: 289px;
width: 688px;
overflow-y: auto;
:deep(.el-timeline) {
margin-top: 14px;
padding-left: 0;
}
:deep(.el-timeline-item__wrapper) {
padding-left: 40px;
}
:deep(.el-timeline-item__tail) {
left: 4px;
}
:deep(.el-timeline-item__content) {
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
line-height: 24px;
}
}
.timeline-dot {
display: flex;
justify-content: space-between;
width: 688px;
align-items: center;
margin-bottom: 12px;
.dot-left {
display: flex;
align-items: center;
}
.dot-icon {
width: 10px;
height: 10px;
}
.dot-date {
margin-left: 16px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
letter-spacing: 1px;
}
.dot-arrow {
width: 20px;
height: 18px;
}
}
.box1-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.box1-left {
position: absolute;
left: 0;
top: 180px;
width: 24px;
height: 48px;
cursor: pointer;
z-index: 10;
img {
width: 100%;
height: 100%;
}
}
.box1-right {
position: absolute;
right: 0;
top: 180px;
width: 24px;
height: 48px;
cursor: pointer;
z-index: 10;
img {
width: 100%;
height: 100%;
}
}
.box1-main {
width: 1009px;
height: 354px;
margin-top: 28px;
margin-left: 40px;
}
.carousel-content {
display: flex;
height: 100%;
}
.avatar {
width: 280px;
height: 354px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.events {
flex: 1;
padding-left: 20px;
.header {
.info {
display: flex;
width: 100%;
justify-content: space-between;
padding-right: 20px;
h2 {
margin: 0;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-size: 24px;
font-weight: 700;
}
.newtitle {
margin-top: 8px;
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
}
}
}
.tag-group {
display: flex;
gap: 10px;
}
.line {
border-bottom: 1px solid rgba(240, 242, 244, 1);
margin-top: 16px;
}
}
.timeline-wrapper {
height: 289px;
width: 688px;
overflow-y: auto;
:deep(.el-timeline) {
margin-top: 14px;
padding-left: 0;
}
:deep(.el-timeline-item__wrapper) {
padding-left: 40px;
}
:deep(.el-timeline-item__tail) {
left: 4px;
}
:deep(.el-timeline-item__content) {
color: rgba(102, 102, 102, 1);
font-family: Microsoft YaHei;
font-size: 14px;
line-height: 24px;
}
}
.timeline-dot {
display: flex;
justify-content: space-between;
width: 688px;
align-items: center;
margin-bottom: 12px;
.dot-left {
display: flex;
align-items: center;
}
.dot-icon {
width: 10px;
height: 10px;
}
.dot-date {
margin-left: 16px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
letter-spacing: 1px;
}
.dot-arrow {
width: 20px;
height: 18px;
}
}
</style>
/**
* useCharacterNav - 人物跳转复用方法(Vue 3 Composable)
*
* 使用方式:
* import { useCharacterNav } from "@/hooks/useCharacterNav";
* const { handleClickToCharacter } = useCharacterNav();
* handleClickToCharacter(personId);
*/
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { getPersonSummaryInfo } from "@/api/technologyFigures/technologyFigures";
// 人物类型名称 -> type 映射
const PERSON_TYPE_MAP = {
"科技企业领袖": 1,
"国会议员": 2,
"智库研究人员": 3,
};
export function useCharacterNav() {
const router = useRouter();
/**
* 根据人物 ID 查询类型并跳转到人物详情页
* @param {string|number} id - 人物 ID
*/
const handleClickToCharacter = async (id) => {
const personTypeList = JSON.parse(
window.sessionStorage.getItem("personTypeList") || "[]"
);
const params = { personId: id };
console.log('dsdsdwdwf',id)
try {
const res = await getPersonSummaryInfo(params);
if (res.code !== 200 || !res.data) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const matched = personTypeList.find(
(item) => item.typeId === res.data.personType
);
if (!matched) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const personTypeName = matched.typeName;
const type = PERSON_TYPE_MAP[personTypeName];
if (!type) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const route = router.resolve({
path: "/characterPage",
query: { type, personId: id },
});
window.open(route.href, "_blank");
} catch (error) {
console.error("查询人物信息失败:", error);
}
};
return { handleClickToCharacter };
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论