提交 a42df559 authored 作者: coderBryanFu's avatar coderBryanFu

Merge branch 'pre' of http://8.140.26.4:10003/caijian/risk-monitor into fk-dev

流水线 #435 已通过 于阶段
in 1 分 42 秒
......@@ -159,4 +159,12 @@ export function getRecordRelation(sanRecordIds) {
*/
export function getVertexInfo(sanRecordId) {
return http.get(`/api/sanctionList/invFin/getVertexInfo?sanRecordId=${sanRecordId}`);
}
\ No newline at end of file
}
/**
* 查询投融资限制关联-图谱-关系详情
* url:/sanctionList/invFin/getEdgeInfo
*/
export function getEdgeInfo(edgeId) {
return http.get(`/api/sanctionList/invFin/getEdgeInfo?edgeId=${edgeId}`);
}
......@@ -2,42 +2,42 @@ import request from "@/api/request.js";
// 规则限制-首页统计接口
export function getStatCount() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/statCount`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/statCount`
});
}
// 规则限制-查询最新动态接口
export function getLatestUpdates() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLatestUpdates`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getLatestUpdates`
});
}
// 规则限制-风险信号
export function getRiskSignal(params) {
return request({
method: 'GET',
url: `/api/commonFeature/riskSignal/${params}`
})
return request({
method: "GET",
url: `/api/commonFeature/riskSignal/${params}`
});
}
// 规则限制-查询新闻资讯
export function getNews(params) {
return request({
method: 'GET',
url: `/api/commonFeature/news/${params}`
})
return request({
method: "GET",
url: `/api/commonFeature/news/${params}`
});
}
// 规则限制-查询社交媒体
export function getRemarks(params) {
return request({
method: 'GET',
url: `/api/commonFeature/remarks/${params}`
})
return request({
method: "GET",
url: `/api/commonFeature/remarks/${params}`
});
}
// 规则限制-限制领域分布情况
......@@ -47,11 +47,11 @@ export function getRemarks(params) {
* @header token
*/
export function getAreaDistribution(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getAreaDistribution`,
params
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getAreaDistribution`,
params
});
}
// 规则限制-受限实体数量变化趋势
......@@ -62,11 +62,11 @@ export function getAreaDistribution(params) {
* @header token
*/
export function getEntityChangeTrend(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getEntityChangeTrend`,
params
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getEntityChangeTrend`,
params
});
}
// 规则限制-规则限制政令列表查询接口
......@@ -82,11 +82,11 @@ export function getEntityChangeTrend(params) {
* @header token
*/
export function getRuleLimitList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRuleLimitList`,
params
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getRuleLimitList`,
params
});
}
// 规则限制-排华科技联盟列表接口
......@@ -103,55 +103,52 @@ export function getRuleLimitList(params) {
* @header token
*/
export function getACTAList(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAList`,
params
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getACTAList`,
params
});
}
export function getAcTAAllcountry() {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getACTAAllCountry/`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getACTAAllCountry/`
});
}
// 规则限制-规则限制基本详情
export function getSanctionOverview(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getSanctionOverview/${params}`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getSanctionOverview/${params}`
});
}
// 规则限制-背景分析
export function getBackGround(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getBackGround/${params}`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getBackGround/${params}`
});
}
// 规则限制-限制条款
export function getLimitClause(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getLimitClause/${params}`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getLimitClause/${params}`
});
}
// 规则限制-相关举措
export function getRelevantMeasures(params) {
return request({
method: 'GET',
url: `/api/ruleLimitInfo/getRelevantMeasures/${params}`
})
return request({
method: "GET",
url: `/api/ruleLimitInfo/getRelevantMeasures/${params}`
});
}
// // 实体清单-制裁概况-获取发布机构机构动态
// /**
// * @param {Object} data
......@@ -167,8 +164,26 @@ export function getRelevantMeasures(params) {
// }
export function getRuleOrg(params) {
return request({
method: 'POST',
url: `/api/organization/relate/ruleOrg`, data: params
})
}
\ No newline at end of file
return request({
method: "POST",
url: `/api/organization/relate/ruleOrg`,
data: params
});
}
// 排华联盟-联盟简介
export function getUnionIntroduction(unionId) {
return request({
method: "GET",
url: `/api/ruleLimitInfo/getUnionIntroduction/${unionId}`
});
}
// 排华联盟-联盟动态
export function getUnionDynamicList(params) {
return request({
method: "GET",
url: `/api/ruleLimitInfo/getUnionDynamicList/${params.unionId}`,
params: { currentPage: params.currentPage, pageSize: params.pageSize }
});
}
<template>
<el-dialog
v-model="visible"
class="risk-signal-detail-dialog"
modal-class="risk-signal-detail-modal"
width="1280px"
align-center
:z-index="zIndex"
:show-close="true"
destroy-on-close
@closed="handleClosed"
>
<template #header>
<img class="header-icon" src="@/views/viewRiskSignal/assets/images/risk-icon.png" alt="" />
<span class="risk-signal-detail-dialog__level risk-signal-detail-dialog__level--lv1">特别重大风险</span>
</template>
<div class="risk-signal-detail-dialog__body">
<span class="risk-signal-detail-dialog__title">扩大实体清单制裁范围,对中企子公司实施同等管制</span>
<div class="risk-signal-detail-dialog__origin">政策法规打压类风险</div>
<div class="risk-signal-detail-dialog__meta">
<span>2025年11月10日 16:14·美国商务部</span>
<div class="risk-signal-detail-dialog__tags">
<AreaTag :key="'overview-risk-dialog-tag-bio'" tagName="生物科技">生物科技</AreaTag>
<AreaTag :key="'overview-risk-dialog-tag-ai'" tagName="人工智能">人工智能</AreaTag>
</div>
</div>
</div>
<div class="risk-signal-detail-dialog_relation">
<div class="relation">
<div class="logo">
<img src="@/views/viewRiskSignal/assets/images/logo.png" alt="" />
</div>
<div class="name-text">总统行政令——</div>
<div class="content-text">关于调整进口木材、锯材及其衍生产品进入美国的相关修正案</div>
</div>
<div class="right-arrow">
<img src="@/views/viewRiskSignal/assets/images/right-arrow.png" alt="" />
</div>
</div>
<div class="risk-signal-detail-dialog__desc">
<p class="risk-signal-detail-dialog__desc-p">
任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制
</p>
<p class="risk-signal-detail-dialog__desc-p">
任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制
</p>
</div>
<template #footer>
<el-button type="primary" class="risk-signal-detail-dialog__action-btn" @click="visible = false">
确定风险
</el-button>
</template>
</el-dialog>
</template>
<script setup>
import AreaTag from "@/components/base/AreaTag/index.vue";
defineProps({
/** 与遮罩、弹窗层级一致,避免被大屏/轮播盖住 */
zIndex: {
type: Number,
default: 20000
}
});
const emit = defineEmits(["closed"]);
const visible = defineModel({ type: Boolean, default: false });
function handleClosed() {
emit("closed");
}
</script>
<style lang="scss" src="./risk-signal-overview-detail-dialog.scss"></style>
/* 概览页风险信号写死详情弹窗(teleport 到 body,需全局样式;与 viewRiskSignal 管理页 dialog 视觉对齐) */
.risk-signal-detail-modal.el-overlay {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden !important;
z-index: 20000 !important;
background-color: rgba(0, 0, 0, 0.25) !important;
backdrop-filter: blur(30px);
-webkit-backdrop-filter: blur(30px);
}
.risk-signal-detail-modal .el-overlay-dialog {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden !important;
}
.el-dialog.is-align-center.risk-signal-detail-dialog,
.risk-signal-detail-dialog.el-dialog {
position: relative;
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 !important;
box-sizing: border-box;
padding: 0 12px 12px 12px !important;
}
.risk-signal-detail-dialog.el-dialog::-webkit-scrollbar,
.risk-signal-detail-dialog .el-dialog__body::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
.risk-signal-detail-dialog.el-dialog,
.risk-signal-detail-dialog .el-dialog__body {
scrollbar-width: none;
-ms-overflow-style: none;
}
.el-dialog.is-align-center.risk-signal-detail-dialog::after,
.risk-signal-detail-dialog.el-dialog::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 88px;
width: calc(1278px + 24px);
max-width: none;
height: 1px;
background-color: rgb(234, 236, 238);
pointer-events: none;
z-index: 5;
}
.risk-signal-detail-dialog .el-dialog__header .risk-signal-detail-dialog__level,
.risk-signal-detail-dialog__level {
font-family: "YouSheBiaoTiHei", sans-serif;
font-weight: 400;
font-size: 30px;
line-height: 39px;
letter-spacing: 0;
text-align: left;
}
.risk-signal-detail-dialog__level--lv1 {
color: rgb(206, 79, 81) !important;
}
.risk-signal-detail-dialog__level--lv2 {
color: rgba(250, 140, 22, 1) !important;
}
.risk-signal-detail-dialog__level--lv3 {
color: rgba(212, 177, 6, 1) !important;
}
.risk-signal-detail-dialog__level--lv4 {
color: rgba(82, 196, 26, 1) !important;
}
.risk-signal-detail-dialog__level--lv5 {
color: rgba(22, 119, 255, 1) !important;
}
.risk-signal-detail-dialog .el-dialog__footer {
padding: 0 !important;
margin: 0 !important;
border-top: none !important;
height: 0;
min-height: 0;
overflow: visible;
position: static;
}
.risk-signal-detail-dialog__action-btn {
position: absolute;
right: 53px;
bottom: 26px;
width: 360px !important;
height: 36px !important;
margin: 0 !important;
padding: 0 !important;
border-radius: 6px !important;
background-color: rgb(5, 95, 194) !important;
border-color: rgb(5, 95, 194) !important;
color: rgb(255, 255, 255) !important;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400 !important;
font-size: 16px !important;
line-height: 24px !important;
letter-spacing: 0 !important;
text-align: center !important;
z-index: 6;
}
.risk-signal-detail-dialog .el-dialog__header {
flex-shrink: 0;
display: flex;
align-items: center;
margin: 0;
padding: 0;
border-bottom: none;
padding: 8px 0;
position: relative;
}
.risk-signal-detail-dialog .el-dialog__header::after {
content: "";
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
width: calc(1278px + 32px);
max-width: none;
height: 1px;
background-color: rgb(234, 236, 238);
pointer-events: none;
z-index: 1;
}
.risk-signal-detail-dialog .el-dialog__body {
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden !important;
box-sizing: border-box;
padding-left: 22px;
padding-right: 22px;
padding-top: 34px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__title,
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc,
.risk-signal-detail-dialog .risk-signal-detail-dialog__meta,
.risk-signal-detail-dialog .risk-signal-detail-dialog__body {
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation {
max-width: 100%;
width: 100% !important;
box-sizing: border-box;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__title {
font-family: "Source Han Sans CN", sans-serif;
font-weight: 700;
font-size: 24px;
line-height: 36px;
letter-spacing: 0;
text-align: justify;
color: rgba(59, 65, 75, 1);
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__origin {
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 30px;
letter-spacing: 0;
text-align: justify;
color: rgb(206, 79, 81);
background-color: rgb(250, 237, 237);
width: fit-content;
padding: 0 12px;
border-radius: 20px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__body {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 12px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
color: rgb(59, 65, 75);
justify-content: space-between;
}
.risk-signal-detail-dialog .header-icon {
width: 32px;
height: 32px;
margin-right: 6px;
}
.risk-signal-detail-dialog .header-icon img {
width: 100%;
height: 100%;
display: block;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation {
width: 100%;
max-width: 100%;
height: 48px;
box-sizing: border-box;
margin-top: 24px;
background-color: rgb(246, 250, 255);
border: 1px solid rgb(231, 243, 255);
border-radius: 50px;
display: flex;
justify-content: space-between;
cursor: pointer;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .relation {
height: 36px;
display: flex;
flex-direction: row;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .logo {
width: 36px;
height: 36px;
margin-top: 6px;
margin-left: 7px;
display: block;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .logo img {
width: 100%;
height: 100%;
display: block;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .name-text {
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN", sans-serif;
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
margin-top: 12px;
margin-left: 8px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .content-text {
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
margin-top: 12px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .right-arrow {
width: 12px;
height: 11px;
margin-top: 19px;
margin-right: 18px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation .right-arrow img {
width: 100%;
height: 100%;
display: block;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 30px;
letter-spacing: 0;
text-align: justify;
margin-top: 24px;
padding-left: 12px;
padding-right: 12px;
height: 310px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc-p {
margin: 0;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc-p + .risk-signal-detail-dialog__desc-p {
margin-top: 12px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
......@@ -85,6 +85,7 @@ import { useRouter } from "vue-router";
import { useRoute } from "vue-router";
import { getPersonType } from "@/api/common/index";
import request, { removeToken } from "@/api/request.js";
import { broadcastAuthLogout } from "@/utils/authCrossTabLogout.js";
import SearchBar from "@/components/layout/SearchBar.vue";
import Menu1 from "@/assets/icons/overview/menu1.png";
......@@ -478,6 +479,7 @@ const handleUserCommand = async (command) => {
} catch {
// ignore
}
broadcastAuthLogout();
try {
window.sessionStorage.removeItem("auth_token");
} catch {
......@@ -502,7 +504,8 @@ onMounted(() => {
<style lang="scss" scoped>
.module-header-wrapper {
width: 100%;
// height: 64px;
position: relative;
z-index: 101;
border-bottom: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: linear-gradient(180deg, rgba(246, 250, 255, 0.8) 0%, rgba(255, 255, 255, 0.8) 100%);
......
import { createRouter, createWebHistory } from "vue-router";
import { setToken, removeToken, getToken } from "@/api/request.js";
import { AUTH_LOGOUT_CHANNEL } from "@/utils/authCrossTabLogout.js";
/** localStorage:跨标签页记录当前前端的 bootId(与 vite define 的 __APP_BOOT_ID__ 对齐) */
const VITE_BOOT_STORAGE_KEY = "app_vite_boot_id";
......@@ -241,5 +242,59 @@ router.beforeEach((to, from, next) => {
next();
});
/**
* 在其它标签页/窗口退出登录时,本页立即跳转登录页(不依赖用户再点一次路由)。
* 1)storage:监听同源其它文档对 `force_login` 的写入
* 2)BroadcastChannel:与登出时的 `broadcastAuthLogout()` 配对,减少仅依赖 storage 的时序问题
*/
function installCrossTabLogoutRedirect() {
if (typeof window === "undefined") {
return;
}
const redirectToLoginAfterRemoteLogout = () => {
try {
window.localStorage.setItem(FORCE_LOGIN_KEY, "1");
} catch {
// ignore
}
try {
removeToken();
window.localStorage.removeItem("auth_token");
window.sessionStorage.removeItem("auth_token");
} catch {
// ignore
}
try {
if (router.currentRoute.value.path !== "/login") {
router.replace({ path: "/login", replace: true }).catch(() => {});
}
} catch {
// ignore
}
};
window.addEventListener("storage", (e) => {
if (e.key !== FORCE_LOGIN_KEY || e.newValue !== "1") {
return;
}
redirectToLoginAfterRemoteLogout();
});
try {
if (typeof BroadcastChannel !== "undefined") {
const authBroadcast = new BroadcastChannel(AUTH_LOGOUT_CHANNEL);
authBroadcast.addEventListener("message", (ev) => {
if (ev?.data?.type === "force_logout") {
redirectToLoginAfterRemoteLogout();
}
});
}
} catch {
// ignore
}
}
installCrossTabLogoutRedirect();
export default router;
// 规则限制
const RuleRestriction = () => import('@/views/ruleRestriction/index.vue')
const RuleRestrictionDetail = () => import('@/views/ruleRestriction/detail/index.vue')
const RuleRestrictionsAlliance = () => import('@/views/ruleRestriction/alliance/index.vue')
const ruleRestrictionsRoutes = [
// 规则限制
{
path: "/ruleRestrictions",
name: "RuleRestrictions",
component: RuleRestriction,
meta: {
title: "规则限制概览",
isShowHeader: true
}
},
// 规则限制详情
{
path: "/ruleRestrictions/detail",
name: "RuleRestrictionsDetail",
component: RuleRestrictionDetail,
meta: {
title: "规则限制详情",
dynamicTitle: true
}
}, {
path: "/ruleRestrictions/alliance",
name: "RuleRestrictionsAlliance",
component: RuleRestrictionsAlliance,
meta: {
title: "规则限制联盟详情",
dynamicTitle: true
}
},
const RuleRestriction = () => import("@/views/ruleRestriction/index.vue");
const RuleRestrictionDetail = () => import("@/views/ruleRestriction/detail/index.vue");
const RuleRestrictionsAlliance = () => import("@/views/ruleRestriction/alliance/index.vue");
]
const ruleRestrictionsRoutes = [
// 规则限制
{
path: "/ruleRestrictions",
name: "RuleRestrictions",
component: RuleRestriction,
meta: {
title: "规则限制概览",
isShowHeader: true
}
},
// 规则限制详情
{
path: "/ruleRestrictions/detail",
name: "RuleRestrictionsDetail",
component: RuleRestrictionDetail,
meta: {
title: "规则限制详情",
dynamicTitle: true
}
},
{
path: "/ruleRestrictions/alliance",
name: "RuleRestrictionsAlliance",
component: RuleRestrictionsAlliance,
meta: {
title: "规则限制联盟详情",
isShowHeader: true,
dynamicTitle: true
}
}
];
export default ruleRestrictionsRoutes
export default ruleRestrictionsRoutes;
......@@ -54,41 +54,59 @@
}
/***tabs-bar左边悬浮***/
.left-float-nav-tabs,
.left-float-nav-tabs .el-tabs {
overflow: visible !important;
width: auto !important;
}
.left-float-nav-tabs {
position: relative;
.el-tabs__header.is-left {
position: absolute;
left: -140px;
top: 0px;
position: absolute !important;
left: -160px !important;
top: 0 !important;
width: auto !important;
display: flex;
flex-direction: column;
align-items: center;
overflow: visible !important;
.el-tabs__nav {
display: flex;
flex-direction: column;
gap: 16px;
align-items: center;
overflow: visible !important;
float: none !important;
}
.el-tabs__active-bar {
background-color: transparent;
right: 12px;
top: 11px;
height: 8px !important;
border-top: 6px solid transparent;
/* 顶部边框透明 */
border-bottom: 6px solid transparent;
/* 底部边框透明 */
border-left: 8px solid var(--bg-white-100);
/* 左侧边框有颜色 */
display: none;
}
}
.el-tabs__content {
display: none;
}
.el-tabs__item.is-left {
@extend .text-tip-1;
color: var(--text-primary-65-color);
height: 32px;
padding: 4px 26px 4px 28px;
border-radius: 16px 16px 16px 16px;
padding: 4px 16px;
border-radius: 16px;
justify-content: center;
color: var(--text-primary-65-color);
background-color: var(--bg-white-100);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border: none !important;
width: auto;
}
.el-tabs__item.is-left.is-active {
color: var(--bg-white-100);
background-color: var(--color-primary-100);
color: var(--bg-white-100) !important;
background-color: var(--color-primary-100) !important;
box-shadow: none;
}
}
\ No newline at end of file
/** 与 `router/index.js` 中 BroadcastChannel 监听使用同一频道名 */
export const AUTH_LOGOUT_CHANNEL = "risk_monitor_auth";
/**
* 通知同源其它标签页/窗口:用户已退出,应立即进入登录页。
* 与 localStorage `force_login=1` 配合;发起退出的标签页在写入后调用一次即可。
*/
export function broadcastAuthLogout() {
try {
if (typeof BroadcastChannel === "undefined") {
return;
}
const ch = new BroadcastChannel(AUTH_LOGOUT_CHANNEL);
ch.postMessage({ type: "force_logout" });
ch.close();
} catch {
// ignore
}
}
/** 从各概览页跳转风险信号管理页,并自动打开列表第一条详情的 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
......@@ -115,6 +115,7 @@
</div>
</div>
</div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</template>
......@@ -123,6 +124,7 @@ import { color } from "echarts";
import { onMounted, ref, computed } from "vue";
import WaveBall from "./WaveBall.vue";
import { getBillRiskSignal } from "@/api/bill/billHome";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
const sectionTab = [
{
textColor: "rgba(9, 88, 217, 1)",
......@@ -289,12 +291,10 @@ 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 isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
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 === '重大风险',
......@@ -119,6 +125,7 @@
</div>
</div>
</div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</template>
......@@ -128,6 +135,8 @@ 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 } from "@/utils/riskSignalOverviewNavigate";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import icon1 from "./icon/title-1.svg";
import icon2 from "./icon/title-2.svg";
import icon3 from "./icon/title-3.svg";
......@@ -645,12 +654,14 @@ const filteredHotNewsList = computed(() => {
return hotNewsList.value.filter(newsItem => newsItem.signalId === currentHoveredSignalId.value);
});
const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalRowToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
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>
......@@ -238,15 +238,18 @@
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader>
<ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" :on-after-page-change="handlePageChange" />
</div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</div>
</template>
<script setup>
import RiskSignal from "@/components/base/riskSignal/index.vue";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/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 } from "@/utils/riskSignalOverviewNavigate";
import setChart from "@/utils/setChart";
import {
getBillIndustry,
......@@ -465,11 +468,15 @@ const handleClickToDetailO = item => {
// router.push("/billLayout?billId=" + item.billId)
};
const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯(新闻主页)
......
......@@ -270,6 +270,8 @@ $axis-width: 2px;
.axis-col {
width: $node-size;
flex-shrink: 0;
position: relative;
// 修复:轴线不穿刺圆点 - 改为 flex-start,圆点用负margin浮在边界上
display: flex;
flex-direction: column;
align-items: center;
......@@ -281,7 +283,10 @@ $axis-width: 2px;
min-height: 12px;
&.invisible {
background-color: transparent;
// 第一个节点上方无线:隐藏并占位,使圆点紧贴timeline起点
height: 0;
min-height: 0;
overflow: hidden;
}
}
......@@ -290,12 +295,28 @@ $axis-width: 2px;
height: $node-size;
border-radius: 50%;
flex-shrink: 0;
overflow: hidden;
overflow: visible; // 改为 visible,使负 margin 不被裁剪
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
// 第一个节点特殊处理:居顶,上方无线
&:first-child {
margin-top: 0;
}
// 后续节点使用负 margin 浮在轴线上
&:not(:first-child) {
margin-top: -$node-size / 2;
}
// 最后一个节点特殊处理:下方无线
&:last-child {
margin-bottom: 0;
}
// 非首尾节点使用负 margin
&:not(:first-child):not(:last-child) {
margin-bottom: -$node-size / 2;
}
&.highlight {
background-color: rgba(245, 34, 45, 1);
......
......@@ -23,7 +23,10 @@
</div>
<!-- ECharts word cloud -->
<div ref="chartRef" class="pna-cloud"></div>
<div v-if="keywords.length > 0" ref="chartRef" class="pna-cloud"></div>
<div v-else class="pna-cloud">
<el-empty description="暂无数据" :image-size="80" />
</div>
</div>
</template>
......@@ -172,5 +175,8 @@ onBeforeUnmount(() => {
.pna-cloud {
flex: 1;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
......@@ -32,11 +32,12 @@
</div>
</div>
<div class="pn-rows" :class="{ 'pn-rows-loading': loading }">
<div class="pn-rows" :class="{ 'pn-rows-loading': loading || newsList.length === 0 }">
<div v-if="loading" class="pn-rows-spinner">
<div class="pn-spinner-icon"></div>
<span class="pn-spinner-text">加载中...</span>
</div>
<el-empty v-else-if="newsList.length === 0" description="暂无数据" :image-size="80" />
<div
v-for="(item, index) in newsList"
v-show="!loading"
......@@ -52,7 +53,7 @@
</div>
</div>
<div class="pn-footer">
<div class="pn-footer" v-if="newsList.length > 0">
<span class="pn-footer-total">{{ total }}条关键新闻</span>
<div class="pn-pagination">
<button
......
......@@ -95,7 +95,11 @@
</div>
</template>
<div class="main">
<div v-for="item in CharacterLatestDynamic" :key="item" class="main-item">
<template v-if="CharacterLatestDynamic.length === 0">
<el-empty description="暂无数据" :image-size="80" />
</template>
<template v-else>
<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] + "月" + item.time.split("-")[2] + "日" }}
......@@ -127,9 +131,9 @@
</div>
</div>
</div>
<!-- <div class="line-test"></div> -->
</template>
</div>
<div class="pagination">
<div v-if="CharacterLatestDynamic.length > 0" class="pagination">
<div class="total">{{ `共 ${total} 项` }}</div>
<el-pagination @current-change="handleCurrentChange" :page-size="pageSize"
:current-page="currentPage" background layout="prev, pager, next" :total="total"
......@@ -233,29 +237,25 @@
</div>
</div>
<!-- 历史提案 -->
<!-- 在 member-of-congress 同级的左侧添加标签栏 -->
<!-- 历史提案 tab 对应的内容区 -->
<div v-if="infoActive === '历史提案'" class="proposal-wrapper">
<div v-if="infoActive === '历史提案'" class="proposal-wrapper">
<div class="proposal-tab-switcher">
<button :class="['proposal-tab', { active: newsTab === 'history' }]" @click="newsTab = 'history'">
<span>历史提案</span>
<svg v-if="newsTab === 'history'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
<svg v-if="newsTab === 'history'" class="proposal-tab-arrow" width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
<path d="M4 0L8 4L4 8V0Z" />
</svg>
</button>
<button :class="['proposal-tab', { active: newsTab === 'potential' }]" @click="newsTab = 'potential'">
<span>潜在提案</span>
<svg v-if="newsTab === 'potential'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
<svg v-if="newsTab === 'potential'" class="proposal-tab-arrow" width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
<path d="M4 0L8 4L4 8V0Z" />
</svg>
</button>
</div>
<HistoricalProposal v-if="newsTab === 'history'" />
<PotentialNews v-else />
</div>
</div>
<CharacterRelationships v-if="infoActive === '人物关系'" />
<RelevantSituation v-if="infoActive === '相关情况'" />
<!-- 弹框 -->
......@@ -1837,45 +1837,40 @@ const handleClickTag = async (tag) => {
/* 作为定位参考 */
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
}
.proposal-tab-switcher {
position: absolute;
right: calc(100% + 24px);
left: 24px;
top: 0;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 1;
gap: 16px;
align-items: center;
}
.proposal-tab {
width: 120px;
width: 112px;
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
justify-content: center;
gap: 4px;
padding: 0 16px;
border-radius: 16px;
border: none;
font-size: 16px;
font-family: 'Microsoft YaHei', sans-serif;
font-weight: 400;
color: #8c8c8c;
background: none;
border: none;
border-radius: 20px;
color: var(--text-primary-65-color);
background-color: var(--bg-white-100);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
cursor: pointer;
white-space: nowrap;
box-sizing: border-box;
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
&.active {
color: var(--bg-white-100);
background-color: var(--color-primary-100);
box-shadow: none;
}
}
.proposal-tab-arrow {
......
/**
* 人物主页 - 类型配置
* type 1: 科技领袖, type 2: 国会议员, type 3: 智库研究人员
*/
export const CHARACTER_CONFIG = {
1: {
// 科技领袖
rootClass: "tech-leader",
tabs: ["人物详情", "人物关系"],
tabWidth: "50%",
useImageProxy: false,
headerTagType: "areaTag",
wordCloudTitle: "科技观点",
yearDefault: "全部时间",
yearBuildMode: "dynamic",
yearStaticOptions: [],
showFundSource: false,
resumeMode: "inline",
resumeTitle: "职业履历",
resumeHeight: "1336px",
companySectionTitle: "实体信息",
basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" },
{ label: "国籍:", key: "country", type: "text" },
{ label: "教育背景:", key: "educationList", type: "education", format: "school(major)" },
{ label: "净资产:", key: "assets", type: "text" },
{ label: "职业:", key: "positionTitle", type: "text" },
{ label: "婚姻状况:", key: "marital", type: "text" },
{ label: "出生地:", key: "birthPlace", type: "text" }
],
boxHeightRules: [
{ condition: "undefined", value: "605px" },
{ condition: "empty", value: "405px" },
{ condition: "lte2", value: "505px" },
{ condition: "default", value: "625px" }
],
useAreaTypeApi: true,
fieldViewMode: "areaList",
dialogTagPrefix: "#",
dialogTagSuffix: " 相关领域标签",
showRelevantSituation: false,
historicalProposalType: null
},
2: {
// 国会议员
rootClass: "member-of-congress",
tabs: ["人物详情", "历史提案", "人物关系"],
tabWidth: "auto",
useImageProxy: true,
headerTagType: "inline",
wordCloudTitle: "科技观点",
yearDefault: "全部",
yearBuildMode: "static",
yearStaticOptions: ["全部", "2025", "2024", "2023", "2022", "2021", "2020"],
showFundSource: true,
resumeMode: "inline",
resumeTitle: "职业履历",
resumeHeight: "1556px",
companySectionTitle: "社交媒体",
basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" },
{ label: "党派归属:", key: "party", type: "text" },
{ label: "教育背景:", key: "educationList", type: "education", format: "school+major" },
{ label: "代表州/选区:", key: "state", type: "text", titleClass: "address" },
{ label: "政治立场:", key: "political", type: "text", contentClass: "long" },
{ label: "出生地:", key: "birthPlace", type: "text" }
],
boxHeightRules: [
{ condition: "undefined", value: "545px" },
{ condition: "empty", value: "495px" },
{ condition: "lte2", value: "445px" },
{ condition: "default", value: "545px" }
],
useAreaTypeApi: false,
fieldViewMode: "sessionStorage",
dialogTagPrefix: "#",
dialogTagSuffix: "相关领域标签",
showRelevantSituation: false,
historicalProposalType: "bill"
},
3: {
// 智库研究人员
rootClass: "think-tank-person",
tabs: ["人物详情", "成果报告", "人物关系"],
tabWidth: "auto",
useImageProxy: false,
headerTagType: "areaTag",
wordCloudTitle: "核心观点",
yearDefault: "全部",
yearBuildMode: "static",
yearStaticOptions: ["全部", "2026", "2025", "2024", "2023", "2022", "2021"],
showFundSource: false,
resumeMode: "card",
resumeTitle: "政治履历",
resumeHeight: null,
companySectionTitle: "社交媒体",
basicInfoFields: [
{ label: "出生日期:", key: "birthday", type: "text" },
{ label: "现任职位:", key: "positionTitle", type: "text" },
{ label: "兼任职位:", key: "sideJob", type: "text" },
{ label: "政策倾向:", key: "political", type: "text" },
{ label: "国籍:", key: "country", type: "text" },
{ label: "教育背景:", key: "educationList", type: "education", format: "school(major)" },
{ label: "研究领域:", key: "industryList", type: "industry" }
],
boxHeightRules: [
{ condition: "undefined", value: "625px" },
{ condition: "empty", value: "425px" },
{ condition: "lte2", value: "525px" },
{ condition: "default", value: "625px" }
],
useAreaTypeApi: false,
fieldViewMode: "sessionStorage",
dialogTagPrefix: "",
dialogTagSuffix: "",
showRelevantSituation: false,
historicalProposalType: "news"
}
};
<template>
<div class="character-page">
<img src="./assets/images/background.png" alt="" class="bg" />
<!-- 主要内容 -->
<ModuleHeader />
<!-- 主要内容 -->
<div class="main">
<!-- 科技领袖 -->
<tech-leader v-if="type == 1"/>
<!-- 国会议员 -->
<member-of-congress v-if="type == 2" />
<!-- 智库研究人员 -->
<think-tank-person v-if="type == 3" />
<unified-character :type="type" :person-id="personId" />
</div>
</div>
</template>
......@@ -16,9 +12,8 @@
<script setup>
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 UnifiedCharacter from './components/unified/index.vue';
import ModuleHeader from '@/components/base/moduleHeader/index.vue';
import { getCharacterGlobalInfo } from "@/api/characterPage/characterPage.js";
......@@ -57,7 +52,7 @@ const getCharacterGlobalInfoFn = async () => {
}catch(error){
}
};
onMounted(() => {
......
......@@ -112,14 +112,17 @@
</div> -->
<RiskSignal :list="riskSignals" @more-click="handleToMoreRiskSignal" postDate="time" name="content"
riskLevel="title" @item-click="handleClickToDetail" />
riskLevel="title" @item-click="handleRiskSignalItemToManage" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</template>
<script setup>
import RiskSignal from "@/components/base/riskSignal/index.vue";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { ref, onMounted, computed } from "vue";
import router from "@/router";
import { navigateToViewRiskSignal } 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 +213,15 @@ const handleToRiskDetail = (item) => {
window.open(curRoute.href, "_blank");
};
const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
// 查看更多风险信号
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>
......@@ -403,12 +403,15 @@
</div>
</div>
</div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</template>
<script setup>
import { onMounted, ref, watch, nextTick, reactive, computed } from "vue";
import router from "@/router";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SimplePagination from "@/components/SimplePagination.vue";
import SummaryCardsPanel from "@/components/base/SummaryCardsPanel/index.vue";
......@@ -490,10 +493,14 @@ const onNavigateTo = () => {
}
// 查看更多风险信号
const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
window.open(route.href, "_blank");
// router.push("/viewRiskSignal")
navigateToViewRiskSignal(router);
};
// 查看更多新闻资讯
......
......@@ -22,7 +22,7 @@ defineProps({
align-items: center;
width: 100%;
margin-bottom: 36px;
padding: 0 15px;
padding: 0px;
}
.color-block {
......
......@@ -544,12 +544,14 @@
</custom-container>
</el-col>
</el-row>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div>
</div>
</template>
<script setup>
import RiskSignal from "@/components/base/riskSignal/index.vue";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import { onMounted, ref, computed } from "vue";
import scrollToTop from "@/utils/scrollToTop";
import setChart from "@/utils/setChart";
......@@ -632,6 +634,12 @@ const messageList = ref([
content: "提出特朗普政府的AI政策强调技术开放与快速应用,但可能以牺牲安全防范为代价,开启了“潘多拉魔盒”。"
}
]);
const isRiskOverviewDetailOpen = ref(false);
const handleToRiskSignalDetail = () => {
isRiskOverviewDetailOpen.value = true;
};
// 查看更多风险信号
const handleToMoreRiskSignal = () => {
const route = router.resolve("/viewRiskSignal");
......
......@@ -22,7 +22,7 @@ defineProps({
align-items: center;
width: 100%;
margin-bottom: 36px;
padding: 0 15px;
padding: 0;
}
.color-block {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论