提交 4e9a1efd authored 作者: 张伊明's avatar 张伊明

合并分支 'zz-dev' 到 'pre'

feat:风险信号页面dialog开发,智库点击风险信号列表dialog开发,智库报告页面搜索功能修改搜索bug 查看合并请求 !330
流水线 #413 已通过 于阶段
in 1 分 34 秒
/** 从各概览页跳转风险信号管理页,并自动打开列表第一条详情的 el-dialog */
export const OPEN_FIRST_RISK_DETAIL_QUERY_KEY = "openFirstDetail";
/**
* 概览页 -> 风险信号管理页(新页面打开,不弹窗)
* @param {import('vue-router').Router} router
*/
export const navigateToViewRiskSignal = (router) => {
const route = router.resolve({
path: "/viewRiskSignal"
});
window.open(route.href, "_blank");
};
/**
* 概览页 -> 风险信号管理页(新页面打开,并自动打开第一条详情弹窗)
* @param {import('vue-router').Router} router
*/
export const navigateToViewRiskSignalOpenFirstDetail = (router) => {
const route = router.resolve({
path: "/viewRiskSignal",
query: { [OPEN_FIRST_RISK_DETAIL_QUERY_KEY]: "1" }
});
window.open(route.href, "_blank");
};
......@@ -47,8 +47,8 @@
<div
class="risk-signals-item"
v-for="(item, index) in warningList"
:key="index"
@click="handleClickToDetailO(item)"
:key="item.signalId || item.billId || index"
@click="handleRiskSignalItemToManage"
:class="{ highlighted: item.eventType === highlightedEventType }"
>
<div
......@@ -123,6 +123,8 @@ import { color } from "echarts";
import { onMounted, ref, computed } from "vue";
import WaveBall from "./WaveBall.vue";
import { getBillRiskSignal } from "@/api/bill/billHome";
import router from "@/router";
import { navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const sectionTab = [
{
textColor: "rgba(9, 88, 217, 1)",
......@@ -289,12 +291,8 @@ const handleSwithCurNews = name => {
}
};
// 查看详情 传递参数
const handleClickToDetailO = item => {
window.sessionStorage.setItem("billId", item.billId);
window.sessionStorage.setItem("curTabName", item.name || item.signalTitle);
const route = router.resolve("/billLayout?billId=" + item.billId);
window.open(route.href, "_blank");
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
const highlightedEventType = ref("");
......
......@@ -43,9 +43,15 @@
<div style="display: flex">
<!-- 风险信号列表 -->
<div class="risk-signals" ref="riskSignalsRef">
<div class="risk-signals-item" v-for="(item, index) in warningList" :key="index"
@mouseenter="onMouseEnter(item, index)" @mouseleave="onMouseLeave"
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]">
<div
class="risk-signals-item"
v-for="(item, index) in warningList"
:key="item.signalId != null ? String(item.signalId) : 'risk-' + index"
@mouseenter="onMouseEnter(item, index)"
@mouseleave="onMouseLeave"
@click.stop="handleRiskSignalRowToManage"
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]"
>
<div class="item-left" :class="{
'item-status-1': item.signalLevel === '特别重大',
'item-status-2': item.signalLevel === '重大风险',
......@@ -128,6 +134,7 @@ import { onMounted, ref, onUnmounted, computed } from "vue";
import WaveBall from "./WaveBall.vue";
import { getLatestRiskUpdates, getLatestRisks } from "@/api/zmOverview/risk/index.js";
import router from "@/router/index";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import icon1 from "./icon/title-1.svg";
import icon2 from "./icon/title-2.svg";
import icon3 from "./icon/title-3.svg";
......@@ -645,12 +652,12 @@ const filteredHotNewsList = computed(() => {
return hotNewsList.value.filter(newsItem => newsItem.signalId === currentHoveredSignalId.value);
});
const handleRiskSignalRowToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
const handleToRiskManage = () => {
// 这里的路由路径请根据实际情况修改
// router.push('/riskSignalManage');
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
console.log("跳转到风险信号管理");
navigateToViewRiskSignal(router);
};
const highlightedEventType = ref("");
......
......@@ -106,7 +106,7 @@
</el-carousel>
</div>
</OverviewMainBox>
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleClickToDetailO"
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle" />
</div>
......@@ -247,6 +247,7 @@ import RiskSignal from "@/components/base/riskSignal/index.vue";
import SummaryCardsPanel from "@/components/base/SummaryCardsPanel/index.vue";
import { onMounted, ref, onUnmounted, nextTick, watch, computed } from "vue";
import router from "@/router/index";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import setChart from "@/utils/setChart";
import {
getBillIndustry,
......@@ -465,11 +466,13 @@ const handleClickToDetailO = item => {
// router.push("/billLayout?billId=" + item.billId)
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯(新闻主页)
......
......@@ -112,7 +112,7 @@
</div> -->
<RiskSignal :list="riskSignals" @more-click="handleToMoreRiskSignal" postDate="time" name="content"
riskLevel="title" @item-click="handleClickToDetail" />
riskLevel="title" @item-click="handleRiskSignalItemToManage" />
</div>
</template>
......@@ -120,6 +120,7 @@
import RiskSignal from "@/components/base/riskSignal/index.vue";
import { ref, onMounted, computed } from "vue";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import { getCoopRestrictionTrends, getCoopRestrictionSignals } from "@/api/coopRestriction/coopRestriction.js";
import defaultImg from "./assets/usImg.png";
import CommonPrompt from "../../commonPrompt/index.vue";
......@@ -210,10 +211,13 @@ const handleToRiskDetail = (item) => {
window.open(curRoute.href, "_blank");
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
onMounted(() => {
......
......@@ -102,7 +102,7 @@
</el-carousel-item>
</el-carousel>
</OverviewMainBox>
<RiskSignal :list="warningList" @item-click="onNavigateToDetail" @more-click="handleToMoreRiskSignal"
<RiskSignal :list="warningList" @item-click="handleRiskSignalItemToManage" @more-click="handleToMoreRiskSignal"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle">
</RiskSignal>
</div>
......@@ -409,6 +409,7 @@
<script setup>
import { onMounted, ref, watch, nextTick, reactive, computed } from "vue";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SimplePagination from "@/components/SimplePagination.vue";
import SummaryCardsPanel from "@/components/base/SummaryCardsPanel/index.vue";
......@@ -490,10 +491,12 @@ const onNavigateTo = () => {
}
// 查看更多风险信号
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -602,6 +602,7 @@ const entityListReleaseFreqChart = useChartInterpretation();
const commerceControlListReleaseFreqChart = useChartInterpretation();
import { useRouter } from "vue-router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const router = useRouter();
......@@ -673,17 +674,9 @@ const handleToPosi = id => {
}
};
// 跳转到单项制裁页面
const handleToRiskSignalDetail = item => {
window.sessionStorage.setItem("curTabName", item.title);
const routeData = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.sanId
}
});
// 打开新页面
window.open(routeData.href, "_blank");
// 风险信号:进入管理页并自动打开列表第一条详情
const handleToRiskSignalDetail = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
const sanctionList = ref([]);
......@@ -1649,9 +1642,7 @@ const handleSanc = item => {
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -693,6 +693,7 @@ const entityListReleaseFreqChart = useChartInterpretation();
const commerceControlListReleaseFreqChart = useChartInterpretation();
import { useRouter } from "vue-router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const router = useRouter();
......@@ -775,17 +776,9 @@ const handleToPosi = id => {
}
};
// 跳转到单项制裁页面
const handleToRiskSignalDetail = item => {
window.sessionStorage.setItem("curTabName", item.title);
const routeData = router.resolve({
path: "/finance/singleSanction",
query: {
id: item.sanId
}
});
// 打开新页面
window.open(routeData.href, "_blank");
// 风险信号:进入管理页并自动打开列表第一条详情
const handleToRiskSignalDetail = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
const sanctionList = ref([]);
......@@ -1649,9 +1642,7 @@ const handleSanc = item => {
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -75,7 +75,7 @@
</div>
<div class="item-header-divider" />
<div class="warning-wrap">
<div v-for="(item, index) in warningList" :key="index" class="waring-item">
<div v-for="(item, index) in warningList" :key="index" class="waring-item" @click="handleRiskSignalItemClick">
<div class="waring-row">
<div class="waring-status" :style="{
color: item.status === 0 ? '#CE4F51' : item.status === 1 ? '#FA8C16' : '#52C41A',
......@@ -120,6 +120,7 @@
<script setup>
import { ref } from 'vue';
import router from '@/router';
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from '@/utils/riskSignalOverviewNavigate';
import MeansAnalysis from './component/MeansAnalysis.vue';
import ResourceAnalysis from './component/ResourceAnalysis.vue'
import AdvantagesAnalysis from './component/AdvantagesAnalysis.vue'
......@@ -220,10 +221,13 @@ const warningList = ref([
{ title: '首次提出“限制外国敏感实体获取补偿', time: '一天前', status: 0 },
]);
const handleRiskSignalItemClick = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
</script>
......
......@@ -145,8 +145,8 @@
<div class="text">{{ "查看更多" }}</div>
</div>
</div> -->
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" riskLevel="signalLevel"
postDate="signalTime" name="signalTitle" />
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle" />
</div>
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center">
......@@ -349,6 +349,7 @@ import NewsList from "@/components/base/newsList/index.vue";
import { onMounted, ref, computed } from "vue";
import * as echarts from "echarts";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import DivideHeader from "@/components/DivideHeader.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import getPieChart from "./utils/piechart";
......@@ -514,10 +515,13 @@ const handleClickToDetail = university => {
// window.open(route.href, "_blank");
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -58,7 +58,7 @@
<div class="box1-right" @click="handleSwithCurSurvey('right')"> <RightBtn /> </div>
</overviewMainBox>
</div>
<RiskSignal :list="box2Data" @more-click="handleToMoreRiskSignal" @item-click="onNavigateToDetail" postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
<RiskSignal :list="box2Data" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
</div>
<DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader>
......@@ -278,6 +278,7 @@ import CarouselItem232 from '@/views/marketAccessRestrictions/marketAccessHome/c
import setChart from "@/utils/setChart";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import getMultiLineChart from "./utils/multiLineChart";
import getPieChart from "./utils/piechart";
......@@ -928,10 +929,13 @@ const handleFetchSurveyList = async () => {
} catch (error) { }
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -112,7 +112,7 @@
</div>
<div class="item-header-divider"></div>
<div style="padding: 30px 23px; height: 400px">
<div class="waring-item" v-for="(item, index) in warningList" :key="index">
<div class="waring-item" v-for="(item, index) in warningList" :key="index" @click="handleRiskSignalItemClick">
<div style="display: flex; height: 47px">
<div
class="waring-status"
......@@ -204,6 +204,7 @@ import Thematicanalysis from "./component/Thematicanalysis.vue";
import ResourceSupport from "./component/ResourceSupport.vue";
import strengthComparison from "./component/strengthComparison.vue";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const searchText = ref("");
const handleSearch = () => {
......@@ -223,10 +224,13 @@ const handleToSearch = () => {
window.open(route.href, "_blank");
};
const handleRiskSignalItemClick = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -69,8 +69,8 @@
查看更多
</div>
</div> -->
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" riskLevel="signalLevel" postDate="signalTime"
name="signalTitle" />
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle" />
</div>
</template>
......@@ -78,6 +78,7 @@
import RiskSignal from "@/components/base/riskSignal/index.vue";
import { ref, onBeforeMount, computed } from "vue";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
import { getLatestUpdates, getRiskSignal } from '@/api/ruleRestriction/index.js'
const list = ref([
......@@ -194,10 +195,13 @@ const handleToRiskDetail = (item) => {
// window.open(curRoute.href, "_blank");
// };
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多动态
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
onBeforeMount(async () => {
......
......@@ -90,8 +90,8 @@
查看更多
</div>
</div> -->
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" postDate="signalTime" name="signalTitle"
riskLevel="signalLevel" />
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
</div>
</template>
......@@ -102,6 +102,7 @@ import {
getNewProject, getRiskSignal
} from "@/api/scientificFunding/overview";
import router from "@/router";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const list = ref([
......@@ -124,10 +125,13 @@ const formatDate = (dateStr) => {
const [y, m, d] = dateStr.split('-');
return `${y}${m}${d}日`;
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
const box1Data = ref([])
const carouselRef = ref(null);
......
......@@ -152,8 +152,8 @@
</div>
</OverviewMainBox>
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" postDate="signalTime"
name="signalTitle" riskLevel="signalLevel" />
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
</div>
<DivideHeader id="position2" class="divide-header" :titleText="'言论动态'"></DivideHeader>
<div class="center-center">
......@@ -346,10 +346,10 @@ import SpeechStance from "./component/speechStance.vue";
import PersonTable from "./component/PersonTable.vue";
import SourceLibrary from "./component/SourceLibrary.vue";
import { useContainerScroll } from "@/hooks/useScrollShow";
import { navigateToViewRiskSignal, navigateToViewRiskSignalOpenFirstDetail } from "@/utils/riskSignalOverviewNavigate";
const router = useRouter();
const containerRef = ref(null);
const { isShow } = useContainerScroll(containerRef);
......@@ -802,10 +802,13 @@ const handleClickCate = cate => {
typeId.value = cate.typeId;
};
const handleRiskSignalItemToManage = () => {
navigateToViewRiskSignalOpenFirstDetail(router);
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
navigateToViewRiskSignal(router);
};
// === 图表数据 ===
......
<template>
<div class="home-wrapper">
<div class="home-main" ref="containerRef">
<div class="home-main" ref="containerRef" :class="{ 'is-risk-detail-open': isRiskDetailVisible }">
<div class="home-top-bg"></div>
<div class="home-main-header">
<!-- <div class="home-main-header-top">
......@@ -209,7 +209,29 @@
</el-carousel>
</OverviewMainBox>
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" postDate="time" name="title"
@item-click="handleClickToDetail" />
@item-click="handleRiskSignalItemToManage" />
<el-dialog v-model="isRiskDetailVisible" class="risk-signal-detail-dialog"
modal-class="risk-signal-detail-modal" width="1280px" align-center :z-index="20000" :show-close="true"
destroy-on-close
@closed="handleCloseRiskDetail">
<template #header>
<span class="risk-signal-detail-dialog__title">{{ riskDetailItem.title }}</span>
</template>
<div v-if="riskDetailItem.title" class="risk-signal-detail-dialog__body">
<div class="risk-signal-detail-dialog__meta">
<span>{{ riskDetailItem.origin }}</span>
<span>{{ riskDetailItem.time }}</span>
<span class="risk-signal-detail-dialog__level">{{ riskDetailItem.risktype }}</span>
</div>
<div class="risk-signal-detail-dialog__desc">{{ riskDetailItem.dsc }}</div>
<div v-if="riskDetailItem.tag.length" class="risk-signal-detail-dialog__tags">
<AreaTag v-for="(tag, index) in riskDetailItem.tag" :key="'risk-detail-tag-' + index + '-' + tag"
:tagName="tag">
{{ tag }}</AreaTag>
</div>
</div>
</el-dialog>
</div>
<DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center">
......@@ -442,6 +464,7 @@ import MessageBubble from "@/components/base/messageBubble/index.vue"
import { onBeforeUnmount, onMounted, ref, computed, reactive, nextTick } from "vue";
import scrollToTop from "@/utils/scrollToTop";
import router from "@/router";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import DivideHeader from "@/components/DivideHeader.vue";
import setChart from "@/utils/setChart";
import HomeMainFooterMain from "./components/HomeMainFooterMain.vue";
......@@ -511,6 +534,52 @@ import { useRouter } from 'vue-router';
import { useGotoNewsDetail } from '@/router/modules/news';
const gotoNewsDetail = useGotoNewsDetail()
const containerRef = ref(null);
const isRiskDetailVisible = ref(false);
const riskDetailItem = reactive({
title: "",
origin: "",
time: "",
risktype: "",
dsc: "",
tag: []
});
const HARD_CODED_RISK_DETAIL_MAP = {
"关于对中华人民共和国合成阿片类药物供应链...": {
origin: "智库概览 · 风险信号",
time: "一天前",
risktype: "特别重大",
dsc:
"这里先写死详情内容:该风险信号涉及合成阿片类药物供应链相关议题,可能对相关实体合规、供应链与对外合作产生影响。后续接入后端后将替换为真实详情。",
tag: ["生物科技", "供应链", "合规风险"]
},
"关于调整钢铁进口的公告": {
origin: "智库概览 · 风险信号",
time: "一天前",
risktype: "重大风险",
dsc:
"这里先写死详情内容:钢铁进口政策调整可能影响相关行业成本结构与贸易路径,需关注后续执行细则与范围变化。",
tag: ["材料", "贸易政策", "成本影响"]
},
"关于修订对中华人民共和国低价值进口商品适...": {
origin: "智库概览 · 风险信号",
time: "一天前",
risktype: "一般风险",
dsc:
"这里先写死详情内容:低价值进口商品规则修订可能带来申报、税费与物流环节的流程变化,建议提前梳理影响链路。",
tag: ["跨境电商", "关务", "流程变化"]
}
};
const handleCloseRiskDetail = () => {
isRiskDetailVisible.value = false;
riskDetailItem.title = "";
riskDetailItem.origin = "";
riskDetailItem.time = "";
riskDetailItem.risktype = "";
riskDetailItem.dsc = "";
riskDetailItem.tag = [];
};
const statCountInfo = ref([]);
const pageSize = ref(15)
const totalAllItem = ref(0)
......@@ -2119,11 +2188,28 @@ const handleClick = tank => {
// router.push({ name: "ThinkTankDetail", params: { id: tank.id, name: tank.name } })
};
// 查看更多风险信号
// 风险信号 item:当前页弹窗(先写死内容,后续接后端)
const handleRiskSignalItemToManage = (item) => {
const title = item?.title || "";
const time = item?.time || "";
const risktype = item?.status || "";
const hardCoded = HARD_CODED_RISK_DETAIL_MAP[title] || {};
riskDetailItem.title = title;
riskDetailItem.origin = hardCoded.origin || "智库概览 · 风险信号";
riskDetailItem.time = hardCoded.time || time || "—";
riskDetailItem.risktype = hardCoded.risktype || risktype || "一般风险";
riskDetailItem.dsc =
hardCoded.dsc ||
"这里先写死详情内容:后续接入后端后将替换为真实详情。";
riskDetailItem.tag = hardCoded.tag || ["暂无标签"];
isRiskDetailVisible.value = true;
};
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......@@ -4590,3 +4676,95 @@ onBeforeUnmount(() => {
}
}
</style>
<style lang="scss">
/* el-dialog 默认 teleport 到 body,壳子样式需非 scoped */
/* 遮罩层内水平垂直居中(勿对 .el-dialog 写 margin:0 !important,会顶到左上角) */
.risk-signal-detail-modal.el-overlay {
display: flex;
align-items: center;
justify-content: center;
z-index: 20000 !important;
}
.risk-signal-detail-modal .el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-dialog.el-dialog {
width: 1280px !important;
max-width: calc(100vw - 32px);
height: 750px;
max-height: calc(100vh - 32px);
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.risk-signal-detail-dialog .el-dialog__header {
flex-shrink: 0;
padding: 16px 20px 8px;
margin: 0;
}
.risk-signal-detail-dialog .el-dialog__body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 12px 20px 20px;
box-sizing: border-box;
}
.risk-signal-detail-dialog__title {
font-size: 18px;
font-weight: 700;
line-height: 24px;
color: rgba(59, 65, 75, 1);
}
.risk-signal-detail-dialog__body {
display: flex;
flex-direction: column;
gap: 12px;
}
.risk-signal-detail-dialog__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-size: 14px;
line-height: 22px;
color: rgba(95, 101, 108, 1);
}
.risk-signal-detail-dialog__level {
color: rgba(5, 95, 194, 1);
}
.risk-signal-detail-dialog__desc {
font-size: 16px;
line-height: 24px;
color: rgba(59, 65, 75, 1);
text-align: justify;
}
.risk-signal-detail-dialog__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
/* 弹窗打开时,禁用轮播左右切换按钮,避免穿透点击 */
.home-main.is-risk-detail-open {
.box1-left,
.box1-right {
pointer-events: none;
}
}
</style>
......@@ -190,7 +190,8 @@
</div>
<div class="right-main">
<div class="itemlist" v-for="(val, idx) in riskList" :key="idx">
<div class="itemlist itemlist--clickable" v-for="(val, idx) in riskList" :key="val.rowKey"
@click="handleOpenRiskDetail(val)">
<div class="box-title">
<div class="risktitle">{{ val.title }}</div>
<div class="risktype" :class="{
......@@ -233,11 +234,33 @@
</div>
</div>
</div>
<el-dialog v-model="isRiskDetailVisible" class="risk-signal-detail-dialog" modal-class="risk-signal-detail-modal"
width="1280px" align-center :show-close="true" destroy-on-close @closed="handleCloseRiskDetail">
<template #header>
<span class="risk-signal-detail-dialog__title">{{ riskDetailItem.title }}</span>
</template>
<div v-if="riskDetailItem.title" class="risk-signal-detail-dialog__body">
<div class="risk-signal-detail-dialog__meta">
<span>{{ riskDetailItem.origin }}</span>
<span>{{ riskDetailItem.time }}</span>
<span class="risk-signal-detail-dialog__level">{{ riskDetailItem.risktype }}</span>
</div>
<div class="risk-signal-detail-dialog__desc">{{ riskDetailItem.dsc }}</div>
<div v-if="riskDetailItem.tag.length" class="risk-signal-detail-dialog__tags">
<AreaTag v-for="(tag, index) in riskDetailItem.tag" :key="'risk-detail-tag-' + index + '-' + tag"
:tagName="tag">
{{ tag }}</AreaTag>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import { nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { OPEN_FIRST_RISK_DETAIL_QUERY_KEY } from "@/utils/riskSignalOverviewNavigate";
import { getCountInfo, getDailyCount, getPageQuery } from "@/api/riskSignal/index";
import { getHylyList } from "@/api/thinkTank/overview";
import setChart from "@/utils/setChart";
......@@ -390,6 +413,7 @@ const handleClickBtn = item => {
const riskList = ref([
{
rowKey: "risk-demo-1",
title: "扩大实体清单制裁范围,对中企子公司实施同等管制",
origin: "美国商务部",
fileType: "实体清单",
......@@ -400,6 +424,7 @@ const riskList = ref([
pic: "src/views/riskSignal/assets/images/origin1.png"
},
{
rowKey: "risk-demo-2",
title: "大而美法案通过国会众议院投票,将提交至总统签署",
origin: "美国国会 · 科技法案",
time: "2025年11月10日 16:14",
......@@ -411,6 +436,7 @@ const riskList = ref([
bgcolor: "rgba(255, 149, 77, 0.1)"
},
{
rowKey: "risk-demo-3",
title: "兰德公司发布智库报告《中美经济竞争:复杂经济和地缘政治关系中的收益和风险》",
origin: "兰德公司 · 科技智库",
time: "2025年11月10日 16:14",
......@@ -422,6 +448,7 @@ const riskList = ref([
bgcolor: "rgba(5, 95, 194, 0.1)"
},
{
rowKey: "risk-demo-4",
title: "美国白宫发布总统政令《关于进一步延长TikTok执法宽限期的行政令》",
origin: "美国白宫 · 总统政令",
time: "2025年11月10日 16:14",
......@@ -433,6 +460,7 @@ const riskList = ref([
bgcolor: "rgba(5, 95, 194, 0.1)"
},
{
rowKey: "risk-demo-5",
title: "美国财政部更新《特别指定国民清单》",
origin: "美国财政部 · 特别指定国民清单",
time: "2025年11月10日 16:14",
......@@ -444,6 +472,7 @@ const riskList = ref([
bgcolor: "rgba(206, 79, 81, 0.1)"
},
{
rowKey: "risk-demo-6",
title: "美国FDA针对两家中国第三方检测机构的数据完整性问题采取行动",
origin: "美国食品药品监督管理局 · 规则限制",
time: "2025年11月10日 16:14",
......@@ -456,6 +485,50 @@ const riskList = ref([
}
]);
const isRiskDetailVisible = ref(false);
const riskDetailItem = reactive({
title: "",
origin: "",
time: "",
dsc: "",
tag: [],
risktype: ""
});
const assignRiskDetail = (val) => {
riskDetailItem.title = val.title ?? "";
riskDetailItem.origin = val.origin ?? "";
riskDetailItem.time = val.time ?? "";
riskDetailItem.dsc = val.dsc ?? "";
riskDetailItem.tag = Array.isArray(val.tag) ? [...val.tag] : [];
riskDetailItem.risktype = val.risktype ?? "";
};
const handleOpenRiskDetail = (val) => {
assignRiskDetail(val);
isRiskDetailVisible.value = true;
};
const handleCloseRiskDetail = () => {
assignRiskDetail({ tag: [] });
};
const route = useRoute();
const router = useRouter();
const consumeOpenFirstDetailFromQuery = async () => {
if (route.query[OPEN_FIRST_RISK_DETAIL_QUERY_KEY] !== "1") {
return;
}
if (!riskList.value.length) {
return;
}
handleOpenRiskDetail(riskList.value[0]);
const nextQuery = { ...route.query };
delete nextQuery[OPEN_FIRST_RISK_DETAIL_QUERY_KEY];
await router.replace({ path: route.path, query: nextQuery });
};
const calendarData = ref([
]);
......@@ -596,8 +669,13 @@ const handleGetPageQuery = async () => {
console.log("按条件查询", res);
if (res.code === 200 && res.data) {
totalNum.value = res.data.totalElements;
riskList.value = res.data.content.map(item => {
riskList.value = res.data.content.map((item, i) => {
const stableId = item.id ?? item.riskId ?? item.riskSignalId;
return {
rowKey:
stableId != null
? String(stableId)
: `p${currentPage.value}-i${i}-${String(item.time ?? "")}-${String(item.titleZh ?? "")}`,
title: item.titleZh,
origin: item.srcOrgId,
fileType: "暂无数据",
......@@ -614,11 +692,24 @@ const handleGetPageQuery = async () => {
}
};
watch(
() => route.query[OPEN_FIRST_RISK_DETAIL_QUERY_KEY],
async (v) => {
if (v !== "1") {
return;
}
await nextTick();
await consumeOpenFirstDetailFromQuery();
}
);
onMounted(async () => {
handleGetCountInfo();
handleCleandarChart();
await handleGetHylyList();
handleGetPageQuery();
await handleGetPageQuery();
await nextTick();
await consumeOpenFirstDetailFromQuery();
});
</script>
......@@ -1063,6 +1154,10 @@ onMounted(async () => {
}
&.itemlist--clickable {
cursor: pointer;
}
}
}
......@@ -1093,4 +1188,86 @@ onMounted(async () => {
}
/* 复选框尺寸由 .checkbox-group 内统一控制,避免重复覆盖 */
.risk-signal-detail-dialog__title {
font-size: 18px;
font-weight: 700;
line-height: 24px;
color: rgba(59, 65, 75, 1);
}
.risk-signal-detail-dialog__body {
display: flex;
flex-direction: column;
gap: 12px;
}
.risk-signal-detail-dialog__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-size: 14px;
line-height: 22px;
color: rgba(95, 101, 108, 1);
}
.risk-signal-detail-dialog__level {
color: rgba(5, 95, 194, 1);
}
.risk-signal-detail-dialog__desc {
font-size: 16px;
line-height: 24px;
color: rgba(59, 65, 75, 1);
text-align: justify;
}
.risk-signal-detail-dialog__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
</style>
<style lang="scss">
/* el-dialog 默认 teleport 到 body,壳子样式需非 scoped */
/* 遮罩层内水平垂直居中(勿对 .el-dialog 写 margin:0 !important,会顶到左上角) */
.risk-signal-detail-modal.el-overlay {
display: flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-modal .el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-dialog.el-dialog {
width: 1280px !important;
max-width: calc(100vw - 32px);
height: 750px;
max-height: calc(100vh - 32px);
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.risk-signal-detail-dialog .el-dialog__header {
flex-shrink: 0;
padding: 16px 20px 8px;
margin: 0;
}
.risk-signal-detail-dialog .el-dialog__body {
flex: 1;
min-height: 0;
overflow: auto;
padding: 12px 20px 20px;
box-sizing: border-box;
}
</style>
\ No newline at end of file
import dayjs from "dayjs";
const RISK_CALENDAR_MONTH_TOTAL = 12;
const RISK_HEAT_CELL_INNER = 16;
const RISK_HEAT_CELL_GAP = 2;
const RISK_HEAT_CELL_STRIDE = RISK_HEAT_CELL_INNER + RISK_HEAT_CELL_GAP;
const RISK_HEAT_CELL_BORDER = RISK_HEAT_CELL_GAP / 2;
const getCalendarHeatChart = (data) => {
const option = {
title: {
top: 30,
left: 'center',
text: `日历热力图`,
show: false
},
tooltip: {
position: 'top',
formatter: function (params) {
const date = new Date(params.data[0]);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}<br/>数值: ${params.data[1]}`;
const RISK_HEAT_CELL_SEPARATOR_COLOR = "rgb(255, 255, 255)";
const RISK_HEAT_EMPTY_FILL = "rgb(247, 248, 249)";
/** 每行 5 列;日期按「左→右、上→下」排布(不再用 6 列×6 行) */
const COLS_PER_MONTH = 5;
/**
* 行数 = ceil(天数/5);月初补 (行数*5-天数) 个空位,使最后一行尽量排满。
* 常见:28–30 天为 5×6 内;31 天需 7 行(5×6=30 格装不下 31 天)。
* @param {number} daysInMonth
*/
const getMonthGridLayout = (daysInMonth) => {
const rows = Math.ceil(daysInMonth / COLS_PER_MONTH);
const paddingSlots = rows * COLS_PER_MONTH - daysInMonth;
return { rows, paddingSlots };
};
const HEAT_RGB_STOPS = [
[231, 243, 255],
[137, 193, 255],
[5, 95, 194]
];
const lerpChannel = (a, b, t) => Math.round(a + (b - a) * t);
/** v<=0 用空色;v>0 按三段渐变 */
const getHeatColor = (v, maxV) => {
const n = Number(v);
if (!Number.isFinite(n) || n <= 0) {
return RISK_HEAT_EMPTY_FILL;
}
},
visualMap: {
show: false,
const ratio = Math.min(n / maxV, 1);
const [c0, c1, c2] = HEAT_RGB_STOPS;
if (ratio <= 0.5) {
const t = ratio / 0.5;
return `rgb(${lerpChannel(c0[0], c1[0], t)},${lerpChannel(c0[1], c1[1], t)},${lerpChannel(c0[2], c1[2], t)})`;
}
const t = (ratio - 0.5) / 0.5;
return `rgb(${lerpChannel(c1[0], c2[0], t)},${lerpChannel(c1[1], c2[1], t)},${lerpChannel(c1[2], c2[2], t)})`;
};
const getCalendarHeatChart = (rawData) => {
const now = dayjs();
const monthLabels = [];
for (let offset = RISK_CALENDAR_MONTH_TOTAL - 1; offset >= 0; offset -= 1) {
monthLabels.push(now.subtract(offset, "month").format("YYYY-MM"));
}
const startBound = dayjs(`${monthLabels[0]}-01`).startOf("month");
const endBound = now.endOf("month");
const countMap = new Map();
let maxVal = 0;
for (const row of rawData || []) {
if (!row || row.length < 2) continue;
const d = dayjs(row[0]);
if (!d.isValid()) continue;
if (d.isBefore(startBound, "day") || d.isAfter(endBound, "day")) continue;
const key = d.format("YYYY-MM-DD");
const c = Number(row[1]) || 0;
countMap.set(key, c);
if (c > maxVal) maxVal = c;
}
const visualMax = Math.max(1, maxVal);
const monthWidthPx = COLS_PER_MONTH * RISK_HEAT_CELL_STRIDE;
const monthLayouts = monthLabels.map((ym) => {
const daysInMonth = dayjs(`${ym}-01`).daysInMonth();
return {
ym,
daysInMonth,
...getMonthGridLayout(daysInMonth)
};
});
/** 12 个月紧挨排列;月与月之间不额外加空隙,仅靠两侧 1px 描边形成固定 2px */
const CONTENT_MARGIN_LEFT_PX = 22;
const CONTENT_MARGIN_TOP_PX = 15;
/** 月份文字在上,格子在其下(12px 字号约一行高) */
const MONTH_LABEL_TOP_PX = CONTENT_MARGIN_TOP_PX;
const GRID_TOP_PX = CONTENT_MARGIN_TOP_PX + 18;
const monthTitleGraphics = monthLayouts.map((layout, idx) => {
const monthNum = dayjs(`${layout.ym}-01`).month() + 1;
return {
type: "text",
id: `risk-heat-month-${idx}`,
left: CONTENT_MARGIN_LEFT_PX + idx * monthWidthPx,
top: MONTH_LABEL_TOP_PX,
z: 10,
silent: true,
style: {
text: `${monthNum}月`,
fill: "rgb(59, 65, 75)",
fontSize: 12,
fontWeight: 500,
fontFamily: "Source Han Sans CN, PingFang SC, Microsoft YaHei, sans-serif"
}
};
});
const grids = monthLayouts.map((layout, idx) => ({
left: CONTENT_MARGIN_LEFT_PX + idx * monthWidthPx,
top: GRID_TOP_PX,
width: monthWidthPx,
height: layout.rows * RISK_HEAT_CELL_STRIDE,
containLabel: false
}));
const xAxes = monthLabels.map((_, idx) => ({
type: "value",
gridIndex: idx,
min: 0,
max: 20,
calculable: true,
orient: 'horizontal',
left: 'center',
top: 65,
inRange: {
color: ['rgb(231, 243, 255)', 'rgb(137, 193, 255)', 'rgb(5, 95, 194)']
},
textStyle: {
color: 'rgba(95, 101, 108, 1)'
max: COLS_PER_MONTH,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
splitLine: { show: false }
}));
const yAxes = monthLayouts.map((layout, idx) => ({
type: "value",
gridIndex: idx,
min: 0,
max: layout.rows,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: false },
splitLine: { show: false }
}));
const series = monthLayouts.map((layout, idx) => {
const { ym, daysInMonth, rows, paddingSlots } = layout;
const m = dayjs(`${ym}-01`);
const data = [];
const totalSlots = rows * COLS_PER_MONTH;
for (let slot = 0; slot < totalSlots; slot += 1) {
const col = slot % COLS_PER_MONTH;
const row = Math.floor(slot / COLS_PER_MONTH);
if (slot < paddingSlots) {
data.push([col, row, "", null, 1]);
continue;
}
const day = slot - paddingSlots + 1;
const dateStr = m.date(day).format("YYYY-MM-DD");
const v = countMap.get(dateStr) ?? 0;
data.push([col, row, dateStr, v, 0]);
}
return {
type: "custom",
name: ym,
xAxisIndex: idx,
yAxisIndex: idx,
data,
renderItem(params, api) {
const col = api.value(0);
const row = api.value(1);
const dateStr = api.value(2);
const v = api.value(3);
const isPadding = api.value(4) === 1;
/**
* 不能用 api.coord([0,0]) 当「顶部」:默认 value 轴 y=0 在网格下方,
* 会导致整块热力画在偏下位置并从 canvas 底部被裁切。
* 用当前 grid 的 coordSys 像素框,按列/行直接铺格,保证第 0 行贴 grid 顶。
*/
const coordSys = params.coordSys;
if (!coordSys || typeof coordSys.x !== "number" || typeof coordSys.y !== "number") {
return null;
}
const xPx = coordSys.x + col * RISK_HEAT_CELL_STRIDE;
const yPx = coordSys.y + row * RISK_HEAT_CELL_STRIDE;
return {
type: "rect",
shape: {
x: xPx,
y: yPx,
width: RISK_HEAT_CELL_STRIDE,
height: RISK_HEAT_CELL_STRIDE
},
calendar: {
top: 24,
left: 30,
right: 30,
cellSize: ['auto', 20],
range: '2026',
splitLine: {
show:false
},
itemStyle: {
borderWidth: 0.5,
borderColor: '#ccc'
style: api.style({
fill: isPadding ? RISK_HEAT_EMPTY_FILL : getHeatColor(v, visualMax),
stroke: RISK_HEAT_CELL_SEPARATOR_COLOR,
lineWidth: RISK_HEAT_CELL_BORDER
}),
silent: isPadding
};
},
yearLabel: { show: true },
monthLabel: {
nameMap: 'cn'
encode: {
tooltip: [2, 3]
},
dayLabel: {
nameMap: ['日', '一', '二', '三', '四', '五', '六'],
show: false,
tooltip: {
formatter(p) {
const dateStr = p.data?.[2] || "";
if (!dateStr) {
return "";
}
},
series: {
type: 'heatmap',
coordinateSystem: 'calendar',
data: data
const v = p.data?.[3] ?? 0;
return `${dateStr}<br/>数值: ${v}`;
}
}
};
});
return {
tooltip: {
trigger: "item",
position: "top"
},
graphic: monthTitleGraphics,
grid: grids,
xAxis: xAxes,
yAxis: yAxes,
series
};
return option
}
};
export default getCalendarHeatChart
\ No newline at end of file
export default getCalendarHeatChart;
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论