提交 75af5d0f authored 作者: yanpeng's avatar yanpeng

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

流水线 #503 已通过 于阶段
in 1 分 59 秒
...@@ -16,3 +16,35 @@ export function getPageQuery(data) { ...@@ -16,3 +16,35 @@ export function getPageQuery(data) {
data: data data: data
}) })
} }
/** 风险信号详情(GET /api/riskSignal/getRiskSignalInfoById/{id}) */
export function getRiskSignalInfoById(id) {
return request({
method: "GET",
url: `/api/riskSignal/getRiskSignalInfoById/${id}`
});
}
/** 确定风险:更新处理状态(GET /api/riskSignal/UpdateStatu/{id}) */
export function updateRiskSignalStatus(id) {
return request({
method: "GET",
url: `/api/riskSignal/UpdateStatu/${id}`
});
}
/** 风险类型字典(GET /api/riskSignal/getRiskTypes) */
export function getRiskTypes() {
return request({
method: "GET",
url: `/api/riskSignal/getRiskTypes`
});
}
/** 风险来源国家字典(GET /api/riskSignal/listRiskCountry) */
export function listRiskCountry() {
return request({
method: "GET",
url: `/api/riskSignal/listRiskCountry`
});
}
...@@ -26,13 +26,12 @@ export function getNewReport() { ...@@ -26,13 +26,12 @@ export function getNewReport() {
}) })
} }
// 风险信号 // 风险信号(统一走通用接口 /api/commonFeature/riskSignal/{moduleId})
export function getThinkTankRiskSignal() { export function getThinkTankRiskSignal(moduleId = "0102") {
return request({ return request({
method: 'GET', method: "GET",
url: `/api/thinkTankOverview/riskSignal`, url: `/api/commonFeature/riskSignal/${moduleId}`,
});
})
} }
/** /**
......
...@@ -12,51 +12,117 @@ ...@@ -12,51 +12,117 @@
> >
<template #header> <template #header>
<img class="header-icon" src="@/views/viewRiskSignal/assets/images/risk-icon.png" alt="" /> <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> <span
v-if="listLevelText"
class="risk-signal-detail-dialog__level"
:class="listLevelModifierClass"
>{{ listLevelText }}</span>
<div v-if="bodyFromApi" class="risk-signal-detail-dialog__read-indicator">
<el-icon v-if="riskDetailStatus === false" class="risk-signal-detail-dialog__header-badge-close">
<Close />
</el-icon>
<img
v-else-if="riskDetailStatus === true"
class="risk-signal-detail-dialog__header-badge-read"
:src="greenRightImg"
alt=""
/>
<span v-if="riskDetailStatus != null" class="read">{{ riskDetailStatus ? "已读" : "未读" }}</span>
</div>
</template> </template>
<div class="risk-signal-detail-dialog__body"> <div class="risk-signal-detail-dialog__main" v-loading="detailLoading">
<span class="risk-signal-detail-dialog__title">扩大实体清单制裁范围,对中企子公司实施同等管制</span> <div v-if="bodyFromApi">
<div class="risk-signal-detail-dialog__origin">政策法规打压类风险</div> <div v-if="riskDetailItem.title" class="risk-signal-detail-dialog__body">
<span class="risk-signal-detail-dialog__title">{{ riskDetailItem.title }}</span>
<div v-if="riskDetailItem.directionLabels.length" class="risk-signal-detail-dialog__directions">
<div
v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels"
:key="'overview-risk-detail-direction-' + dirIndex + '-' + dirLabel"
class="risk-signal-detail-dialog__origin"
>{{ dirLabel }}</div>
</div>
<div class="risk-signal-detail-dialog__meta"> <div class="risk-signal-detail-dialog__meta">
<span>2025年11月10日 16:14·美国商务部</span> <span>{{ metaLine }}</span>
<div class="risk-signal-detail-dialog__tags"> <div v-if="riskDetailItem.tag.length" class="risk-signal-detail-dialog__tags">
<AreaTag :key="'overview-risk-dialog-tag-bio'" tagName="生物科技">生物科技</AreaTag> <AreaTag
<AreaTag :key="'overview-risk-dialog-tag-ai'" tagName="人工智能">人工智能</AreaTag> v-for="(tag, index) in riskDetailItem.tag"
:key="'overview-risk-detail-tag-' + index + '-' + tag"
:tag-name="tag"
>{{ tag }}</AreaTag>
</div> </div>
</div> </div>
</div> </div>
<div class="risk-signal-detail-dialog_relation"> <div
v-if="showRelationBar"
class="risk-signal-detail-dialog_relation"
@click="handleRelationClick"
>
<div class="relation"> <div class="relation">
<div class="logo"> <div class="logo">
<img src="@/views/viewRiskSignal/assets/images/logo.png" alt="" /> <img src="@/views/viewRiskSignal/assets/images/logo.png" alt="" />
</div> </div>
<div class="name-text">总统行政令——</div> <div class="name-text">{{ riskDetailItem.relationType }}</div>
<div class="content-text">关于调整进口木材、锯材及其衍生产品进入美国的相关修正案</div> <div class="content-text">{{ riskDetailItem.relationTitle }}</div>
</div> </div>
<div class="right-arrow"> <div class="right-arrow">
<img src="@/views/viewRiskSignal/assets/images/right-arrow.png" alt="" /> <img src="@/views/viewRiskSignal/assets/images/right-arrow.png" alt="" />
</div> </div>
</div> </div>
<div class="risk-signal-detail-dialog__desc"> <div class="risk-signal-detail-dialog__desc">{{ riskDetailItem.dsc }}</div>
<p class="risk-signal-detail-dialog__desc-p"> </div>
任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制
</p>
<p class="risk-signal-detail-dialog__desc-p">
任何被列入美国出口管制“实体清单”或“军事最终用户清单”的企业,如果其直接或间接持有另一家公司50%或以上的股权,那么这家被控股的公司也将自动受到与清单上母公司同等的出口管制限制
</p>
</div> </div>
<template #footer> <template #footer>
<el-button type="primary" class="risk-signal-detail-dialog__action-btn" @click="visible = false"> <el-button
确定风险 type="primary"
class="risk-signal-detail-dialog__action-btn"
:loading="confirmLoading"
@click="handleConfirm"
>
确定
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script setup> <script setup>
import { computed, nextTick, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import AreaTag from "@/components/base/AreaTag/index.vue"; import AreaTag from "@/components/base/AreaTag/index.vue";
import { ElMessage } from "element-plus";
import { Close } from "@element-plus/icons-vue";
import greenRightImg from "@/views/viewRiskSignal/assets/images/green-right.png";
import { getRiskSignalInfoById, updateRiskSignalStatus } from "@/api/riskSignal/index.js";
import {
buildListRowFallbackFromRawRow,
buildRiskDetailPayloadFromApi,
getEmptyRiskDetailPayload,
getRiskDetailLevelModifier,
normalizeRiskSignalListLevelText,
resolveRiskSignalRelationRoute,
resolveRiskSignalRowId
} from "@/utils/riskSignalOverviewDetailHelpers.js";
defineProps({ const props = defineProps({
/** 当前点击的列表行(建议先赋值再打开 v-model) */
row: {
type: Object,
default: null
},
/** 与 RiskSignal 组件 name 一致 */
nameField: {
type: String,
default: "name"
},
/** 与 RiskSignal 组件 postDate 一致 */
postDateField: {
type: String,
default: "postDate"
},
/** 与 RiskSignal 组件 riskLevel 一致 */
riskLevelField: {
type: String,
default: "riskLevel"
},
/** 与遮罩、弹窗层级一致,避免被大屏/轮播盖住 */ /** 与遮罩、弹窗层级一致,避免被大屏/轮播盖住 */
zIndex: { zIndex: {
type: Number, type: Number,
...@@ -64,11 +130,154 @@ defineProps({ ...@@ -64,11 +130,154 @@ defineProps({
} }
}); });
const emit = defineEmits(["closed"]); const emit = defineEmits(["closed", "confirmed"]);
const visible = defineModel({ type: Boolean, default: false }); const visible = defineModel({ type: Boolean, default: false });
const router = useRouter();
const listLevelText = ref("");
const bodyFromApi = ref(false);
const detailLoading = ref(false);
const confirmLoading = ref(false);
const riskDetailCurrentId = ref(null);
const riskDetailStatus = ref(null);
const riskDetailItem = reactive(getEmptyRiskDetailPayload());
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 ?? "";
riskDetailItem.category = val.category ?? "";
riskDetailItem.directionLabels = Array.isArray(val.directionLabels) ? [...val.directionLabels] : [];
riskDetailItem.relationType = val.relationType ?? "";
riskDetailItem.relationTitle = val.relationTitle ?? "";
riskDetailItem.relationModelCode = val.relationModelCode ?? "";
riskDetailItem.relationReferedId = val.relationReferedId ?? "";
};
const metaLine = computed(() => {
const t = String(riskDetailItem.time ?? "").trim();
const o = String(riskDetailItem.origin ?? "").trim();
if (t && o) {
return `${t}·${o}`;
}
return t || o;
});
const showRelationBar = computed(() => {
const ti = String(riskDetailItem.relationTitle ?? "").trim();
const ty = String(riskDetailItem.relationType ?? "").trim();
const id = String(riskDetailItem.relationReferedId ?? "").trim();
return Boolean(ti || ty || id);
});
const listLevelModifierClass = computed(
() => `risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(listLevelText.value)}`
);
const fieldMap = computed(() => ({
nameField: props.nameField,
postDateField: props.postDateField,
riskLevelField: props.riskLevelField
}));
const resetState = () => {
listLevelText.value = "";
bodyFromApi.value = false;
detailLoading.value = false;
confirmLoading.value = false;
riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
assignRiskDetail(getEmptyRiskDetailPayload());
};
/** 拉取详情:标题区等级来自列表行;正文区仅接口成功后展示 */
const loadDetail = async () => {
if (!visible.value || !props.row) {
return;
}
const listFallback = buildListRowFallbackFromRawRow(props.row, fieldMap.value);
listLevelText.value = normalizeRiskSignalListLevelText(listFallback.risktype);
riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
assignRiskDetail(getEmptyRiskDetailPayload());
bodyFromApi.value = false;
detailLoading.value = false;
const id = resolveRiskSignalRowId(props.row);
if (!id) {
return;
}
detailLoading.value = true;
try {
const res = await getRiskSignalInfoById(id);
if (res.code === 200 && res.data) {
riskDetailCurrentId.value = id;
riskDetailStatus.value = Boolean(res.data.status);
assignRiskDetail(buildRiskDetailPayloadFromApi(res.data, listFallback));
bodyFromApi.value = true;
}
} catch (error) {
console.error("获取风险信号详情 error", error);
} finally {
detailLoading.value = false;
}
};
watch(
() => [visible.value, props.row],
async () => {
if (!visible.value) {
resetState();
return;
}
await nextTick();
await loadDetail();
}
);
const handleRelationClick = () => {
const loc = resolveRiskSignalRelationRoute(
riskDetailItem.relationModelCode,
riskDetailItem.relationReferedId
);
if (!loc) {
return;
}
const href = router.resolve(loc).href;
window.open(href, "_blank");
};
const handleConfirm = async () => {
const id = riskDetailCurrentId.value;
if (id == null || id === "") {
visible.value = false;
return;
}
confirmLoading.value = true;
try {
const res = await updateRiskSignalStatus(String(id));
if (res.code === 200) {
ElMessage.success(typeof res.msg === "string" && res.msg.trim() ? res.msg : "操作成功");
visible.value = false;
emit("confirmed", { id: String(id) });
} else {
ElMessage.warning(typeof res.msg === "string" && res.msg.trim() ? res.msg : "操作失败");
}
} catch (error) {
console.error("确定风险 UpdateStatu error", error);
ElMessage.error("操作失败");
} finally {
confirmLoading.value = false;
}
};
function handleClosed() { function handleClosed() {
resetState();
emit("closed"); emit("closed");
} }
</script> </script>
......
...@@ -196,6 +196,14 @@ ...@@ -196,6 +196,14 @@
border-radius: 20px; border-radius: 20px;
} }
.risk-signal-detail-dialog .risk-signal-detail-dialog__directions {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__body { .risk-signal-detail-dialog .risk-signal-detail-dialog__body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -224,6 +232,52 @@ ...@@ -224,6 +232,52 @@
margin-right: 6px; margin-right: 6px;
} }
.risk-signal-detail-dialog .risk-signal-detail-dialog__read-indicator {
position: absolute;
right: 115px;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
gap: 8px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close {
width: 16px;
height: 16px;
border-radius: 8px;
background-color: rgba(206, 79, 81, 1);
color: rgba(255, 255, 255, 1);
display: inline-flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close svg {
width: 12px;
height: 12px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close svg path {
fill: rgba(255, 255, 255, 1) !important;
stroke: rgba(255, 255, 255, 1) !important;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-read {
width: 16px;
height: 16px;
display: block;
}
.risk-signal-detail-dialog .read {
font-family: "Source Han Sans CN", sans-serif;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
color: rgb(95, 101, 108);
}
.risk-signal-detail-dialog .header-icon img { .risk-signal-detail-dialog .header-icon img {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -311,7 +365,13 @@ ...@@ -311,7 +365,13 @@
margin-top: 24px; margin-top: 24px;
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
height: 310px; height: 360px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 14;
line-clamp: 14;
overflow: hidden;
text-overflow: ellipsis;
} }
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc-p { .risk-signal-detail-dialog .risk-signal-detail-dialog__desc-p {
...@@ -327,3 +387,11 @@ ...@@ -327,3 +387,11 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
} }
/* 详情请求中 v-loading 遮罩区域(与列表一致有足够高度以便居中) */
.risk-signal-detail-dialog .risk-signal-detail-dialog__main {
position: relative;
width: 100%;
min-height: 480px;
box-sizing: border-box;
}
/** 风险信号概览详情弹窗:与 viewRiskSignal 详情逻辑对齐的纯函数 */
/** 从概览/列表行解析详情接口 id */
export function resolveRiskSignalRowId(row) {
if (!row || typeof row !== "object") {
return "";
}
// 概览页通用接口 `/api/commonFeature/riskSignal/{moduleId}` 返回结构里,
// 详情接口 getRiskSignalInfoById 需要的是 signalId(而非 orderId/reportId 等业务 id)。
// 因此优先取 signalId;其次再取已映射的 id/riskId 等。
const keys = ["signalId", "id", "riskId", "riskSignalId", "orderId"];
for (const k of keys) {
const v = row[k];
if (v != null && String(v).trim() !== "") {
return String(v).trim();
}
}
return "";
}
/** PageLimit 列表项:领域标签(优先 domainNames,兼容 domainName / domains) */
export function parseRiskSignalTagList(item) {
const names = item?.domainNames;
if (names != null && String(names).trim() !== "") {
return String(names)
.split(/[,,、;;|]+/)
.map((s) => s.trim())
.filter(Boolean);
}
const dn = item?.domainName;
if (dn != null && String(dn).trim() !== "") {
return [String(dn).trim()];
}
const raw = item?.domains;
if (Array.isArray(raw)) {
return raw.map((d) => (typeof d === "string" ? d : d?.name)).filter(Boolean);
}
if (typeof raw === "string" && raw.trim()) {
return raw.split(/[,,]/).map((s) => s.trim()).filter(Boolean);
}
return [];
}
/** 发布时间:YYYY-MM-DD HH:mm:ss →「2026年1月26日 00:00」;仅日期则「2026年1月26日」 */
export function formatRiskPublishDisplay(raw) {
if (raw == null || raw === "") {
return "";
}
const s = String(raw).trim();
const dt = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?/);
if (dt) {
const h = String(Number(dt[4])).padStart(2, "0");
const mi = String(Number(dt[5])).padStart(2, "0");
return `${Number(dt[1])}${Number(dt[2])}${Number(dt[3])}${h}:${mi}`;
}
const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:$|[T\s])/);
if (m) {
return `${Number(m[1])}${Number(m[2])}${Number(m[3])}日`;
}
return s;
}
export function getEmptyRiskDetailPayload() {
return {
title: "",
origin: "",
time: "",
dsc: "",
tag: [],
risktype: "",
category: "",
/** 风险方向(接口 direction,可多值)对应中文文案列表 */
directionLabels: [],
relationType: "",
relationTitle: "",
relationModelCode: "",
relationReferedId: ""
};
}
/** 风险信号详情接口 `direction` 字段 → 中文(与 `model` 的 0100/0101 含义不同,单独映射) */
export const RISK_SIGNAL_DIRECTION_LABEL_MAP = {
"0100": "政策法规打压类风险",
"0101": "核心自主可控性风险",
"0102": "科技要素风险",
"0103": "其他风险动向"
};
export function getRiskSignalDirectionLabel(code) {
const c = String(code ?? "").trim();
return RISK_SIGNAL_DIRECTION_LABEL_MAP[c] ?? "";
}
/** 解析接口 `direction`:支持数组、逗号/分号分隔字符串 */
export function parseRiskSignalDirectionCodes(raw) {
if (raw == null) {
return [];
}
if (Array.isArray(raw)) {
return raw.map((x) => String(x ?? "").trim()).filter(Boolean);
}
const s = String(raw).trim();
if (!s) {
return [];
}
return s.split(/[,,、;;|]+/).map((x) => x.trim()).filter(Boolean);
}
/** 方向编码列表 → 去重后的中文标签(顺序:先 data 后 list 合并去重) */
export function buildRiskSignalDirectionLabels(codes) {
if (!Array.isArray(codes) || !codes.length) {
return [];
}
const seen = new Set();
const labels = [];
for (const code of codes) {
const c = String(code ?? "").trim();
if (!c || seen.has(c)) {
continue;
}
seen.add(c);
const lbl = getRiskSignalDirectionLabel(c);
if (lbl) {
labels.push(lbl);
}
}
return labels;
}
/** 风险信号详情:model 字典编码 → 中文类型 */
export const RISK_SIGNAL_MODEL_LABEL_MAP = {
"0100": "法案",
"0101": "政令",
"0102": "智库报告",
"0103": "出口管制",
"0104": "市场准入",
"0105": "创新主体",
"0106": "合作限制",
"0107": "科研资助体系",
"0108": "规则限制",
"0109": "投融资限制",
"01031": "实体清单",
"01032": "商业管制清单",
"01091": "特别指定国民清单",
"01092": "涉军企业清单",
"01041": "337调查",
"01042": "232调查",
"01043": "301调查"
};
export function getRiskSignalModelLabel(code) {
const c = String(code ?? "").trim();
return RISK_SIGNAL_MODEL_LABEL_MAP[c] ?? "";
}
/** 根据 model + referedId 解析新开页路由 */
export function resolveRiskSignalRelationRoute(modelCode, referedId) {
const code = String(modelCode ?? "").trim();
const id = String(referedId ?? "").trim();
if (!id) {
return null;
}
switch (code) {
case "0100":
return { path: "/billLayout/bill/introduction", query: { billId: id } };
case "0101":
return { path: "/decreeLayout/overview/introduction", query: { id } };
case "0102":
return { name: "ReportDetail", params: { id } };
case "0103":
return { path: "/exportControl", query: { id } };
case "0104":
case "01041":
case "01042":
case "01043":
return { path: "/marketSingleCaseLayout/overview", query: { id } };
case "0105": {
const segs = id
.split("/")
.map((s) => s.trim())
.filter(Boolean);
if (segs.length >= 2) {
return { name: "InnovativeInstitutions", params: { id: segs[0], type: segs[1] } };
}
return { name: "InnovativeInstitutions", params: { id, type: "0" } };
}
case "0106":
return { path: "/cooperationRestrictions/detail", query: { id } };
case "0107":
return { path: "/exportControl/researchfunding", query: { id } };
case "0108":
return { path: "/exportControl/ruledetail", query: { id } };
case "0109":
return { path: "/finance", query: { id } };
case "01031":
return { path: "/exportControl/entityList", query: { sanTypeId: id } };
case "01032":
return { path: "/exportControl/commercialControlList", query: { sanTypeId: id } };
case "01091":
return { path: "/finance/singleSanction", query: { id } };
case "01092":
return { path: "/finance/cmccontrolList", query: { sanTypeId: id } };
default:
return null;
}
}
/** 与详情弹窗标题区等级色块一致 */
export function getRiskDetailLevelModifier(level) {
const t = String(level ?? "").trim();
if (t === "status" || t.length <= 1) {
return "lv4";
}
if (t.includes("特别重大")) {
return "lv1";
}
if (t === "重大风险" || (t.includes("重大") && !t.includes("较大") && !t.includes("特别"))) {
return "lv2";
}
if (t === "较大风险" || t.includes("较大")) {
return "lv3";
}
if (t === "低风险") {
return "lv5";
}
if (t === "一般风险" || !t || t === "暂无数据") {
return "lv4";
}
return "lv4";
}
/** 与 `@/components/base/riskSignal` 左侧等级一致:空 / null 等展示「暂无数据」 */
export const RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT = "暂无数据";
/**
* 列表行风险等级 → 弹窗标题区文案(null、空串与「暂无数值」等与列表「暂无数据」对齐)
* @param {unknown} raw
*/
export function normalizeRiskSignalListLevelText(raw) {
if (raw == null) {
return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT;
}
const s = String(raw).trim();
if (s === "" || s === "null" || s === "undefined" || s === "暂无数值") {
return RISK_SIGNAL_LIST_LEVEL_EMPTY_TEXT;
}
return s;
}
/**
* 将概览页原始行转为详情接口合并用的兜底对象(优先已映射字段 title/time/risktype,再用字段别名)
* @param {Record<string, unknown>} row
* @param {{ nameField?: string; postDateField?: string; riskLevelField?: string }} fields
*/
export function buildListRowFallbackFromRawRow(row, fields) {
if (!row || typeof row !== "object") {
return {};
}
const nameField = fields?.nameField ?? "name";
const postDateField = fields?.postDateField ?? "postDate";
const riskLevelField = fields?.riskLevelField ?? "riskLevel";
let title = "";
if (row.title != null && String(row.title).trim() !== "") {
title = String(row.title);
} else if (row.titleZh != null && String(row.titleZh).trim() !== "") {
title = String(row.titleZh);
} else if (row[nameField] != null) {
title = String(row[nameField]);
}
const time = String(row.time ?? row[postDateField] ?? row.postDate ?? "").trim();
const risktype = String(row.risktype ?? row[riskLevelField] ?? row.riskLevel ?? "").trim();
const origin = String(row.orgName ?? row.origin ?? "").trim();
const dsc = String(row.summary ?? row.contentZh ?? row.description ?? row.dsc ?? row.content ?? "").trim();
const category = String(row.typeName ?? row.module ?? row.category ?? "").trim();
const direction = row.direction != null && row.direction !== "" ? row.direction : "";
return {
title,
time,
risktype,
origin,
dsc,
tag: parseRiskSignalTagList(row),
category,
direction
};
}
/**
* 将 getRiskSignalInfoById 返回 data 转为详情 payload(listRow 为列表行兜底,形状同 buildListRowFallbackFromRawRow)
* @param {Record<string, unknown>} data
* @param {Record<string, unknown>} listRow
*/
export function buildRiskDetailPayloadFromApi(data, listRow) {
if (!data || typeof data !== "object") {
return getEmptyRiskDetailPayload();
}
const list = listRow && typeof listRow === "object" ? listRow : {};
const postRaw = data.postDate ?? data.publishDate ?? data.time ?? "";
const category =
data.typeName ?? data.module ?? data.modle ?? data.riskCategory ?? "";
const org = data.orgName != null && String(data.orgName).trim() !== "" ? String(data.orgName).trim() : "";
// 相关模块类型:严格使用 data.model(0100/0101/0103...),与风险动向类型 data.direction(0100~0103) 语义不同
const modelCode = data.model != null ? String(data.model).trim() : "";
const referedRaw = data.referedId ?? data.referredId;
const relationReferedId = referedRaw != null ? String(referedRaw).trim() : "";
const modelLabel = getRiskSignalModelLabel(modelCode);
const relationType = modelLabel ? `${modelLabel}-` : modelCode ? `${modelCode}-` : "";
const relationTitle =
data.modelTitle != null && String(data.modelTitle).trim() !== ""
? String(data.modelTitle).trim()
: "";
const detailTags = parseRiskSignalTagList(data);
const apiCategory = String(category).trim();
const apiTime = formatRiskPublishDisplay(postRaw) || String(postRaw || "").trim();
// 风险动向类型:严格使用 direction(0100~0103),支持多值
const directionCodes = [
...parseRiskSignalDirectionCodes(data.direction),
...parseRiskSignalDirectionCodes(list.direction)
];
const directionLabels = buildRiskSignalDirectionLabels(directionCodes);
return {
title: data.title ?? list.title ?? "",
category: apiCategory || String(list.category ?? "").trim(),
directionLabels,
time: apiTime || String(list.time ?? "").trim(),
origin: org || list.origin || "",
dsc: data.description ?? data.contentZh ?? data.summary ?? list.dsc ?? "",
tag: detailTags.length ? detailTags : Array.isArray(list.tag) ? [...list.tag] : [],
risktype: String(list.risktype ?? "").trim(),
relationType,
relationTitle,
relationModelCode: modelCode,
relationReferedId: relationReferedId
};
}
...@@ -48,7 +48,7 @@ ...@@ -48,7 +48,7 @@
class="risk-signals-item" class="risk-signals-item"
v-for="(item, index) in warningList" v-for="(item, index) in warningList"
:key="item.signalId || item.billId || index" :key="item.signalId || item.billId || index"
@click="handleRiskSignalItemToManage" @click="handleRiskSignalItemToManage(item)"
:class="{ highlighted: item.eventType === highlightedEventType }" :class="{ highlighted: item.eventType === highlightedEventType }"
> >
<div <div
...@@ -115,7 +115,13 @@ ...@@ -115,7 +115,13 @@
</div> </div>
</div> </div>
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</template> </template>
...@@ -292,8 +298,10 @@ const handleSwithCurNews = name => { ...@@ -292,8 +298,10 @@ const handleSwithCurNews = name => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
:key="item.signalId != null ? String(item.signalId) : 'risk-' + index" :key="item.signalId != null ? String(item.signalId) : 'risk-' + index"
@mouseenter="onMouseEnter(item, index)" @mouseenter="onMouseEnter(item, index)"
@mouseleave="onMouseLeave" @mouseleave="onMouseLeave"
@click.stop="handleRiskSignalRowToManage" @click.stop
:class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]" :class="['risk-signals-item', { 'risk-signals-item-hightLight': riskSignalActiveIndex === index }]"
> >
<div class="item-left" :class="{ <div class="item-left" :class="{
...@@ -125,7 +125,6 @@ ...@@ -125,7 +125,6 @@
</div> </div>
</div> </div>
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" />
</div> </div>
</template> </template>
...@@ -136,7 +135,6 @@ import WaveBall from "./WaveBall.vue"; ...@@ -136,7 +135,6 @@ import WaveBall from "./WaveBall.vue";
import { getLatestRiskUpdates, getLatestRisks } from "@/api/zmOverview/risk/index.js"; import { getLatestRiskUpdates, getLatestRisks } from "@/api/zmOverview/risk/index.js";
import router from "@/router/index"; import router from "@/router/index";
import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate"; import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import RiskSignalOverviewDetailDialog from "@/components/base/RiskSignalOverviewDetailDialog/index.vue";
import icon1 from "./icon/title-1.svg"; import icon1 from "./icon/title-1.svg";
import icon2 from "./icon/title-2.svg"; import icon2 from "./icon/title-2.svg";
import icon3 from "./icon/title-3.svg"; import icon3 from "./icon/title-3.svg";
...@@ -654,11 +652,7 @@ const filteredHotNewsList = computed(() => { ...@@ -654,11 +652,7 @@ const filteredHotNewsList = computed(() => {
return hotNewsList.value.filter(newsItem => newsItem.signalId === currentHoveredSignalId.value); return hotNewsList.value.filter(newsItem => newsItem.signalId === currentHoveredSignalId.value);
}); });
const isRiskOverviewDetailOpen = ref(false); // 首页风险信号不弹详情弹窗
const handleRiskSignalRowToManage = () => {
isRiskOverviewDetailOpen.value = true;
};
const handleToRiskManage = () => { const handleToRiskManage = () => {
navigateToViewRiskSignal(router); navigateToViewRiskSignal(router);
......
...@@ -238,7 +238,13 @@ ...@@ -238,7 +238,13 @@
<DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader> <DivideHeader id="position4" class="divide4" :titleText="'资源库'"></DivideHeader>
<ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" :on-after-page-change="handlePageChange" /> <ResourceLibrarySection :on-click-to-detail="handleClickToDetailO" :on-after-page-change="handlePageChange" />
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</div> </div>
</template> </template>
...@@ -469,8 +475,10 @@ const handleClickToDetailO = item => { ...@@ -469,8 +475,10 @@ const handleClickToDetailO = item => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -113,7 +113,13 @@ ...@@ -113,7 +113,13 @@
<RiskSignal :list="riskSignals" @more-click="handleToMoreRiskSignal" postDate="time" name="content" <RiskSignal :list="riskSignals" @more-click="handleToMoreRiskSignal" postDate="time" name="content"
riskLevel="title" @item-click="handleRiskSignalItemToManage" /> riskLevel="title" @item-click="handleRiskSignalItemToManage" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="content"
post-date-field="time"
risk-level-field="title"
/>
</div> </div>
</template> </template>
...@@ -214,8 +220,10 @@ const handleToRiskDetail = (item) => { ...@@ -214,8 +220,10 @@ const handleToRiskDetail = (item) => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -403,7 +403,13 @@ ...@@ -403,7 +403,13 @@
</div> </div>
</div> </div>
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</template> </template>
...@@ -496,7 +502,10 @@ const onNavigateTo = () => { ...@@ -496,7 +502,10 @@ const onNavigateTo = () => {
// 查看更多风险信号 // 查看更多风险信号
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => { const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -721,7 +721,13 @@ ...@@ -721,7 +721,13 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</template> </template>
<script setup> <script setup>
...@@ -830,9 +836,10 @@ const handleToPosi = id => { ...@@ -830,9 +836,10 @@ const handleToPosi = id => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
// 风险信号:概览写死详情弹窗 const handleToRiskSignalDetail = (item) => {
const handleToRiskSignalDetail = () => { riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -544,7 +544,13 @@ ...@@ -544,7 +544,13 @@
</custom-container> </custom-container>
</el-col> </el-col>
</el-row> </el-row>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="title"
post-date-field="time"
risk-level-field="status"
/>
</div> </div>
</div> </div>
</template> </template>
...@@ -635,8 +641,10 @@ const messageList = ref([ ...@@ -635,8 +641,10 @@ const messageList = ref([
} }
]); ]);
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleToRiskSignalDetail = () => { const handleToRiskSignalDetail = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -688,7 +688,13 @@ ...@@ -688,7 +688,13 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</template> </template>
<script setup> <script setup>
...@@ -807,9 +813,10 @@ const handleToPosi = id => { ...@@ -807,9 +813,10 @@ const handleToPosi = id => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
// 风险信号:概览写死详情弹窗 const handleToRiskSignalDetail = (item) => {
const handleToRiskSignalDetail = () => { riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
...@@ -1529,7 +1536,7 @@ const strengthLabels = { ...@@ -1529,7 +1536,7 @@ const strengthLabels = {
// 获取风险信号数据 // 获取风险信号数据
const fetchRiskSignals = async () => { const fetchRiskSignals = async () => {
try { try {
const data = await getRiskSignal(); const data = await getRiskSignal("0109");
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
console.log(data); console.log(data);
warningList.value = data.map(item => ({ warningList.value = data.map(item => ({
...@@ -1549,7 +1556,7 @@ const fetchRiskSignals = async () => { ...@@ -1549,7 +1556,7 @@ const fetchRiskSignals = async () => {
// 添加获取社交媒体信息的方法 // 添加获取社交媒体信息的方法
const fetchSocialMediaInfo = async () => { const fetchSocialMediaInfo = async () => {
try { try {
const data = await getSocialMediaInfo(); const data = await getSocialMediaInfo("0109");
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
// console.log(data); // console.log(data);
socialMediaList.value = data.map(item => ({ socialMediaList.value = data.map(item => ({
...@@ -1570,7 +1577,7 @@ const fetchSocialMediaInfo = async () => { ...@@ -1570,7 +1577,7 @@ const fetchSocialMediaInfo = async () => {
// 添加获取新闻资讯的方法 // 添加获取新闻资讯的方法
const fetchNewsInfo = async () => { const fetchNewsInfo = async () => {
try { try {
const data = await getNewsInfo(); const data = await getNewsInfo("0109");
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
newsList.value = data.map(item => ({ newsList.value = data.map(item => ({
...item, ...item,
......
...@@ -75,7 +75,12 @@ ...@@ -75,7 +75,12 @@
</div> </div>
<div class="item-header-divider" /> <div class="item-header-divider" />
<div class="warning-wrap"> <div class="warning-wrap">
<div v-for="(item, index) in warningList" :key="index" class="waring-item" @click="handleRiskSignalItemClick"> <div
v-for="(item, index) in warningList"
:key="item.title + '-' + item.time + '-' + index"
class="waring-item"
@click="handleRiskSignalItemClick(item)"
>
<div class="waring-row"> <div class="waring-row">
<div class="waring-status" :style="{ <div class="waring-status" :style="{
color: item.status === 0 ? '#CE4F51' : item.status === 1 ? '#FA8C16' : '#52C41A', color: item.status === 0 ? '#CE4F51' : item.status === 1 ? '#FA8C16' : '#52C41A',
...@@ -114,7 +119,13 @@ ...@@ -114,7 +119,13 @@
<AdvantagesAnalysis /> <AdvantagesAnalysis />
<div style="width: 100%;height: 100px;"></div> <div style="width: 100%;height: 100px;"></div>
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="title"
post-date-field="time"
risk-level-field="riskLevel"
/>
</div> </div>
</template> </template>
...@@ -224,8 +235,15 @@ const warningList = ref([ ...@@ -224,8 +235,15 @@ const warningList = ref([
]); ]);
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemClick = () => { const mapRiskOverviewRowFromStatus = (item) => ({
...item,
riskLevel: item.status === 0 ? "特别重大" : item.status === 1 ? "重大风险" : "一般风险"
});
const handleRiskSignalItemClick = (item) => {
riskOverviewDetailRow.value = mapRiskOverviewRowFromStatus(item);
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -147,7 +147,13 @@ ...@@ -147,7 +147,13 @@
</div> --> </div> -->
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" <RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle" /> riskLevel="signalLevel" postDate="signalTime" name="signalTitle" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
<DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader> <DivideHeader id="position2" class="divide2" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center"> <div class="center-center">
...@@ -518,8 +524,10 @@ const handleClickToDetail = university => { ...@@ -518,8 +524,10 @@ const handleClickToDetail = university => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -59,7 +59,13 @@ ...@@ -59,7 +59,13 @@
</overviewMainBox> </overviewMainBox>
</div> </div>
<RiskSignal :list="box2Data" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" postDate="signalTime" name="signalTitle" riskLevel="signalLevel" /> <RiskSignal :list="box2Data" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
<DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader> <DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader>
...@@ -1007,7 +1013,10 @@ const handleFetchSurveyList = async () => { ...@@ -1007,7 +1013,10 @@ const handleFetchSurveyList = async () => {
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const handleRiskSignalItemToManage = () => { const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -112,7 +112,12 @@ ...@@ -112,7 +112,12 @@
</div> </div>
<div class="item-header-divider"></div> <div class="item-header-divider"></div>
<div style="padding: 30px 23px; height: 400px"> <div style="padding: 30px 23px; height: 400px">
<div class="waring-item" v-for="(item, index) in warningList" :key="index" @click="handleRiskSignalItemClick"> <div
class="waring-item"
v-for="(item, index) in warningList"
:key="item.title + '-' + item.time + '-' + index"
@click="handleRiskSignalItemClick(item)"
>
<div style="display: flex; height: 47px"> <div style="display: flex; height: 47px">
<div <div
class="waring-status" class="waring-status"
...@@ -195,7 +200,13 @@ ...@@ -195,7 +200,13 @@
<strengthComparison /> <strengthComparison />
</div> </div>
</div> </div>
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="title"
post-date-field="time"
risk-level-field="riskLevel"
/>
</div> </div>
</template> </template>
...@@ -227,8 +238,15 @@ const handleToSearch = () => { ...@@ -227,8 +238,15 @@ const handleToSearch = () => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const mapRiskOverviewRowFromStatus = (item) => ({
...item,
riskLevel: item.status === 0 ? "特别重大" : item.status === 1 ? "重大风险" : "一般风险"
});
const handleRiskSignalItemClick = () => { const handleRiskSignalItemClick = (item) => {
riskOverviewDetailRow.value = mapRiskOverviewRowFromStatus(item);
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -71,7 +71,13 @@ ...@@ -71,7 +71,13 @@
</div> --> </div> -->
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" <RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
riskLevel="signalLevel" postDate="signalTime" name="signalTitle" /> riskLevel="signalLevel" postDate="signalTime" name="signalTitle" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</template> </template>
...@@ -198,8 +204,10 @@ const handleToRiskDetail = (item) => { ...@@ -198,8 +204,10 @@ const handleToRiskDetail = (item) => {
// }; // };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -92,7 +92,13 @@ ...@@ -92,7 +92,13 @@
</div> --> </div> -->
<RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" <RiskSignal :list="list" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
postDate="signalTime" name="signalTitle" riskLevel="signalLevel" /> postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</template> </template>
...@@ -128,8 +134,10 @@ const formatDate = (dateStr) => { ...@@ -128,8 +134,10 @@ const formatDate = (dateStr) => {
return `${y}${m}${d}日`; return `${y}${m}${d}日`;
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -157,7 +157,13 @@ ...@@ -157,7 +157,13 @@
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage" <RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" @item-click="handleRiskSignalItemToManage"
postDate="signalTime" name="signalTitle" riskLevel="signalLevel" /> postDate="signalTime" name="signalTitle" riskLevel="signalLevel" />
<RiskSignalOverviewDetailDialog v-model="isRiskOverviewDetailOpen" /> <RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
<DivideHeader id="position2" class="divide-header" :titleText="'言论动态'" v-if="false"></DivideHeader> <DivideHeader id="position2" class="divide-header" :titleText="'言论动态'" v-if="false"></DivideHeader>
<div class="center-center"> <div class="center-center">
...@@ -824,8 +830,10 @@ const handleClickCate = cate => { ...@@ -824,8 +830,10 @@ const handleClickCate = cate => {
}; };
const isRiskOverviewDetailOpen = ref(false); const isRiskOverviewDetailOpen = ref(false);
const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => { const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true; isRiskOverviewDetailOpen.value = true;
}; };
......
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
</div> </div>
</div> </div>
<div class="time-tab-pane"> <div class="time-tab-pane">
<TimeTabPane @time-click="handleTimeClick" /> <TimeTabPane :activeTime="'近一年'" @time-click="handleTimeClick" />
</div> </div>
</div> </div>
<div class="all-item"> <div class="all-item">
...@@ -129,7 +129,7 @@ const sortedCardList = computed(() => { ...@@ -129,7 +129,7 @@ const sortedCardList = computed(() => {
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const total = ref(0) const total = ref(0)
const timePeriod = ref("WEEK") const timePeriod = ref("YEAR")
const handleTimeClick = item => { const handleTimeClick = item => {
const time = item?.time const time = item?.time
......
...@@ -52,7 +52,7 @@ ...@@ -52,7 +52,7 @@
</div> </div>
</div> </div>
<div class="time-tab-pane"> <div class="time-tab-pane">
<TimeTabPane @time-click="handleTimeClick" /> <TimeTabPane :activeTime="'近一年'" @time-click="handleTimeClick" />
</div> </div>
</div> </div>
<div class="home-main-header-card-box"> <div class="home-main-header-card-box">
...@@ -211,7 +211,12 @@ ...@@ -211,7 +211,12 @@
<RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" postDate="time" name="title" <RiskSignal :list="warningList" @more-click="handleToMoreRiskSignal" postDate="time" name="title"
@item-click="handleRiskSignalItemToManage" /> @item-click="handleRiskSignalItemToManage" />
<RiskSignalOverviewDetailDialog v-model="isRiskDetailVisible" /> <RiskSignalOverviewDetailDialog
v-model="isRiskDetailVisible"
:row="riskOverviewDetailRow"
name-field="title"
post-date-field="time"
/>
</div> </div>
<DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader> <DivideHeader id="position2" class="divide-header" :titleText="'资讯要闻'"></DivideHeader>
<div class="center-center"> <div class="center-center">
...@@ -574,7 +579,7 @@ const goToAllThinkTank = () => { ...@@ -574,7 +579,7 @@ const goToAllThinkTank = () => {
// 替换为你的实际路由路径 // 替换为你的实际路由路径
routerTo.push('/thinkTank/allThinkTank'); routerTo.push('/thinkTank/allThinkTank');
}; };
const timePeriod = ref("WEEK") const timePeriod = ref("YEAR")
const handleTimeClick = item => { const handleTimeClick = item => {
const time = item?.time const time = item?.time
...@@ -802,15 +807,15 @@ const warningList = ref([ ...@@ -802,15 +807,15 @@ const warningList = ref([
// 获取智库风险信号 // 获取智库风险信号
const handleGetThinkTankRiskSignal = async () => { const handleGetThinkTankRiskSignal = async () => {
try { try {
const res = await getThinkTankRiskSignal(); const res = await getThinkTankRiskSignal("0102");
console.log("智库风险信号", res); console.log("智库风险信号", res);
if (res.code === 200) { if (res.code === 200) {
warningList.value = res.data.map(item => { warningList.value = res.data.map(item => {
return { return {
title: item.name, title: item.signalTitle,
time: item.times, time: item.signalTime,
id: item.reportId, id: item.signalId,
status: item.riskLevel || "暂无数据" status: item.signalLevel || "暂无数据"
}; };
}); });
} }
...@@ -2139,8 +2144,10 @@ const handleClick = tank => { ...@@ -2139,8 +2144,10 @@ const handleClick = tank => {
// router.push({ name: "ThinkTankDetail", params: { id: tank.id, name: tank.name } }) // router.push({ name: "ThinkTankDetail", params: { id: tank.id, name: tank.name } })
}; };
// 风险信号 item:当前页弹窗(内容与样式与风险信号管理页 dialog 一致,写死展示) const riskOverviewDetailRow = ref(null);
const handleRiskSignalItemToManage = () => {
const handleRiskSignalItemToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskDetailVisible.value = true; isRiskDetailVisible.value = true;
}; };
......
...@@ -99,17 +99,21 @@ ...@@ -99,17 +99,21 @@
</div> </div>
</div> </div>
<div class="select-box"> <div class="select-box">
<div class="header"> <div class="header risk-source-header">
<div class="icon"></div> <div class="icon"></div>
<div class="title">{{ "风险来源" }}</div> <div class="title">{{ "涉及国家" }}</div>
<img class="risk-source-header__toggle"
:src="riskSourceExpanded ? riskSourceUpArrow : riskSourceDownArrow" alt=""
@click.stop="handleToggleRiskSourceExpanded" />
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group class="checkbox-group" :model-value="selectedRiskSourceModel" <el-checkbox-group class="checkbox-group risk-source-checkbox-group"
@change="handleRiskSourceGroupChange"> :model-value="selectedRiskSourceModel" @change="handleRiskSourceGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RISK_FILTER_ALL_SOURCE"> <el-checkbox class="filter-checkbox all-checkbox" :label="RISK_FILTER_ALL_SOURCE">
{{ RISK_FILTER_ALL_SOURCE }} {{ RISK_FILTER_ALL_SOURCE }}
</el-checkbox> </el-checkbox>
<el-checkbox v-for="item in riskSource" :key="item.id" class="filter-checkbox" :label="item.id"> <el-checkbox v-for="item in riskSourceDisplayedOptions" :key="item.code" class="filter-checkbox"
:label="item.code">
{{ item.name }} {{ item.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -126,8 +130,9 @@ ...@@ -126,8 +130,9 @@
<el-checkbox class="filter-checkbox all-checkbox" :label="RISK_FILTER_ALL_TYPE"> <el-checkbox class="filter-checkbox all-checkbox" :label="RISK_FILTER_ALL_TYPE">
{{ RISK_FILTER_ALL_TYPE }} {{ RISK_FILTER_ALL_TYPE }}
</el-checkbox> </el-checkbox>
<el-checkbox v-for="item in riskType" :key="item" class="filter-checkbox" :label="item"> <el-checkbox v-for="item in riskTypeOptions" :key="item.code" class="filter-checkbox"
{{ item }} :label="item.code">
{{ item.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</div> </div>
...@@ -171,14 +176,14 @@ ...@@ -171,14 +176,14 @@
style="width: 120px" :teleported="true" placement="bottom-start" style="width: 120px" :teleported="true" placement="bottom-start"
:popper-options="resourceLibrarySortPopperOptions" @change="handleSortChange"> :popper-options="resourceLibrarySortPopperOptions" @change="handleSortChange">
<template #prefix> <template #prefix>
<img v-if="sortModel !== true" <img v-if="sortValue === 1"
src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class="resource-library-sort-prefix-img" alt="" @click.stop="toggleSortPrefix" /> class="resource-library-sort-prefix-img" alt="" @click.stop="toggleSortPrefix" />
<img v-else src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png" <img v-else src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png"
class="resource-library-sort-prefix-img" alt="" @click.stop="toggleSortPrefix" /> class="resource-library-sort-prefix-img" alt="" @click.stop="toggleSortPrefix" />
</template> </template>
<el-option :key="'risk-sort-asc'" label="正序" :value="true" /> <el-option :key="'risk-sort-asc'" label="正序" :value="0" />
<el-option :key="'risk-sort-desc'" label="倒序" :value="false" /> <el-option :key="'risk-sort-desc'" label="倒序" :value="1" />
</el-select> </el-select>
</div> </div>
</div> </div>
...@@ -205,7 +210,7 @@ ...@@ -205,7 +210,7 @@
</div> </div>
<div class="right-footer"> <div class="right-footer">
<div class="footer-left"> <div class="footer-left">
{{ `共 ${formatRiskStatCount(totalNum)}调查` }} {{ `共 ${totalNum} 项调查` }}
</div> </div>
<div class="footer-right"> <div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage" <el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage"
...@@ -220,15 +225,29 @@ ...@@ -220,15 +225,29 @@
width="1280px" align-center :show-close="true" destroy-on-close @closed="handleCloseRiskDetail"> width="1280px" align-center :show-close="true" destroy-on-close @closed="handleCloseRiskDetail">
<template #header> <template #header>
<img class="header-icon" src="./assets/images/risk-icon.png" alt="" /> <img class="header-icon" src="./assets/images/risk-icon.png" alt="" />
<span class="risk-signal-detail-dialog__level" :class="riskDetailLevelModifierClass">{{ riskDetailItem.risktype <span v-if="riskDetailListLevelText" class="risk-signal-detail-dialog__level"
}}</span> :class="riskDetailListLevelModifierClass">{{ riskDetailListLevelText }}</span>
<div v-if="riskDetailBodyFromApi" class="risk-signal-detail-dialog__read-indicator">
<el-icon v-if="riskDetailStatus === false" class="risk-signal-detail-dialog__header-badge-close">
<Close />
</el-icon>
<img v-else-if="riskDetailStatus === true" class="risk-signal-detail-dialog__header-badge-read"
src="./assets/images/green-right.png" alt="" />
<span v-if="riskDetailStatus != null" class="read">{{ riskDetailStatus ? "已读" : "未读" }}</span>
</div>
</template> </template>
<div class="risk-signal-detail-dialog__main" v-loading="riskDetailDetailLoading">
<div v-if="riskDetailBodyFromApi">
<div v-if="riskDetailItem.title" class="risk-signal-detail-dialog__body"> <div v-if="riskDetailItem.title" class="risk-signal-detail-dialog__body">
<span class="risk-signal-detail-dialog__title">{{ riskDetailItem.title }}</span> <span class="risk-signal-detail-dialog__title">{{ riskDetailItem.title }}</span>
<div class="risk-signal-detail-dialog__origin">{{ "政策法规打压类风险" }}</div> <div v-if="riskDetailItem.directionLabels.length" class="risk-signal-detail-dialog__directions">
<div v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels"
:key="'risk-detail-direction-' + dirIndex + '-' + dirLabel" class="risk-signal-detail-dialog__origin">{{
dirLabel }}</div>
</div>
<div class="risk-signal-detail-dialog__meta"> <div class="risk-signal-detail-dialog__meta">
<span> {{ riskDetailItem.time }}·{{ riskDetailItem.origin }}</span> <span>{{ riskDetailMetaLine }}</span>
<div v-if="riskDetailItem.tag.length" class="risk-signal-detail-dialog__tags"> <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" <AreaTag v-for="(tag, index) in riskDetailItem.tag" :key="'risk-detail-tag-' + index + '-' + tag"
...@@ -237,18 +256,22 @@ ...@@ -237,18 +256,22 @@
</div> </div>
</div> </div>
</div> </div>
<div class="risk-signal-detail-dialog_relation"> <div v-if="riskDetailShowRelationBar" class="risk-signal-detail-dialog_relation"
@click="handleRiskDetailRelationClick">
<div class="relation"> <div class="relation">
<div class="logo"><img src="./assets/images/logo.png" alt="" /></div> <div class="logo"><img src="./assets/images/logo.png" alt="" /></div>
<div class="name-text">{{ "总统行政令" }}</div> <div class="name-text">{{ riskDetailItem.relationType }}</div>
<div class="content-text">{{ "关于调整进口木材、锯材及其衍生产品进入美国的相关修正案" }}</div> <div class="content-text">{{ riskDetailItem.relationTitle }}</div>
</div> </div>
<div class="right-arrow"><img src="./assets/images/right-arrow.png" alt="" /></div> <div class="right-arrow"><img src="./assets/images/right-arrow.png" alt="" /></div>
</div> </div>
<div class="risk-signal-detail-dialog__desc">{{ riskDetailItem.dsc }}</div> <div class="risk-signal-detail-dialog__desc">{{ riskDetailItem.dsc }}</div>
</div>
</div>
<template #footer> <template #footer>
<el-button type="primary" class="risk-signal-detail-dialog__action-btn" @click="isRiskDetailVisible = false"> <el-button type="primary" class="risk-signal-detail-dialog__action-btn" :loading="riskDetailConfirmLoading"
确定风险 @click="handleConfirmRiskDetail">
确定
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
...@@ -259,16 +282,28 @@ ...@@ -259,16 +282,28 @@
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue"; import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { OPEN_FIRST_RISK_DETAIL_QUERY_KEY } from "@/utils/riskSignalOverviewNavigate"; import { OPEN_FIRST_RISK_DETAIL_QUERY_KEY } from "@/utils/riskSignalOverviewNavigate";
import { getRiskSignalBaseInfo, getPageQuery } from "@/api/riskSignal/index"; import {
buildRiskDetailPayloadFromApi,
formatRiskPublishDisplay,
getRiskDetailLevelModifier,
normalizeRiskSignalListLevelText,
parseRiskSignalTagList,
resolveRiskSignalRelationRoute
} from "@/utils/riskSignalOverviewDetailHelpers.js";
import { getRiskSignalBaseInfo, getPageQuery, getRiskSignalInfoById, updateRiskSignalStatus, getRiskTypes, listRiskCountry } from "@/api/riskSignal/index";
import { ElMessage } from "element-plus";
import { Close } from "@element-plus/icons-vue";
import { getHylyList } from "@/api/thinkTank/overview"; import { getHylyList } from "@/api/thinkTank/overview";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import getCalendarHeatChart from "./utils/cleandarHeat"; import getCalendarHeatChart from "./utils/cleandarHeat";
import riskSourceDownArrow from "./assets/images/down-arrow.png";
import riskSourceUpArrow from "./assets/images/up-arrow.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png"; import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import { normalizeExclusiveAllOption } from "@/views/thinkTank/utils/resourceLibraryFilters"; import { normalizeExclusiveAllOption } from "@/views/thinkTank/utils/resourceLibraryFilters";
const riskType = ref(["科技法案", "行政令", "智库报告", "出口管制", "投融资限制", "市场准入限制", "规则限制"]); const riskTypeOptions = ref([]);
const RISK_FILTER_ALL_TYPE = "全部类型"; const RISK_FILTER_ALL_TYPE = "全部类型";
const selectedRiskTypeModel = ref([RISK_FILTER_ALL_TYPE]); const selectedRiskTypeModel = ref([RISK_FILTER_ALL_TYPE]);
const handleRiskTypeGroupChange = (val) => { const handleRiskTypeGroupChange = (val) => {
...@@ -295,36 +330,14 @@ const handleTimeGroupChange = (val) => { ...@@ -295,36 +330,14 @@ const handleTimeGroupChange = (val) => {
handleGetPageQuery(); handleGetPageQuery();
}; };
const riskSource = ref([ const riskSourceOptions = ref([]);
{ const riskSourceExpanded = ref(false);
name: "美国", const riskSourceDisplayedOptions = computed(() => {
id: "0502" if (riskSourceExpanded.value) {
}, return riskSourceOptions.value;
{
name: "韩国",
id: "0103"
},
{
name: "日本",
id: "0104"
},
{
name: "德国",
id: "0216"
},
{
name: "英国",
id: "0217"
},
{
name: "法国",
id: "0222"
},
{
name: "澳大利亚",
id: "0401"
} }
]); return riskSourceOptions.value.slice(0, 9);
});
const RISK_FILTER_ALL_SOURCE = "全部国家"; const RISK_FILTER_ALL_SOURCE = "全部国家";
const selectedRiskSourceModel = ref([RISK_FILTER_ALL_SOURCE]); const selectedRiskSourceModel = ref([RISK_FILTER_ALL_SOURCE]);
const handleRiskSourceGroupChange = (val) => { const handleRiskSourceGroupChange = (val) => {
...@@ -332,6 +345,10 @@ const handleRiskSourceGroupChange = (val) => { ...@@ -332,6 +345,10 @@ const handleRiskSourceGroupChange = (val) => {
handleGetPageQuery(); handleGetPageQuery();
}; };
const handleToggleRiskSourceExpanded = () => {
riskSourceExpanded.value = !riskSourceExpanded.value;
};
const riskDegree = ref([ const riskDegree = ref([
{ {
name: "特别重大风险", name: "特别重大风险",
...@@ -412,13 +429,47 @@ const handleClickBtn = item => { ...@@ -412,13 +429,47 @@ const handleClickBtn = item => {
const riskList = ref([]); const riskList = ref([]);
const isRiskDetailVisible = ref(false); const isRiskDetailVisible = ref(false);
/** 弹窗标题区:始终展示当前列表项的风险等级文案 */
const riskDetailListLevelText = ref("");
/** 标题/分类/时间/标签/正文/关联条等:仅接口返回后再展示 */
const riskDetailBodyFromApi = ref(false);
/** 详情接口请求中(与列表 v-loading 一致,遮罩内垂直水平居中) */
const riskDetailDetailLoading = ref(false);
/** 当前详情弹窗对应的列表项 id(确定风险时提交 UpdateStatu) */
const riskDetailCurrentId = ref(null);
/** 详情状态:false=未读,true=已读;仅接口返回后展示 */
const riskDetailStatus = ref(null);
/** 确定风险按钮提交中 */
const riskDetailConfirmLoading = ref(false);
const getEmptyRiskDetailPayload = () => ({
title: "",
origin: "",
time: "",
dsc: "",
tag: [],
risktype: "",
category: "",
directionLabels: [],
relationType: "",
relationTitle: "",
relationModelCode: "",
relationReferedId: ""
});
const riskDetailItem = reactive({ const riskDetailItem = reactive({
title: "", title: "",
origin: "", origin: "",
time: "", time: "",
dsc: "", dsc: "",
tag: [], tag: [],
risktype: "" risktype: "",
category: "",
directionLabels: [],
relationType: "",
relationTitle: "",
relationModelCode: "",
relationReferedId: ""
}); });
const assignRiskDetail = (val) => { const assignRiskDetail = (val) => {
...@@ -428,30 +479,113 @@ const assignRiskDetail = (val) => { ...@@ -428,30 +479,113 @@ const assignRiskDetail = (val) => {
riskDetailItem.dsc = val.dsc ?? ""; riskDetailItem.dsc = val.dsc ?? "";
riskDetailItem.tag = Array.isArray(val.tag) ? [...val.tag] : []; riskDetailItem.tag = Array.isArray(val.tag) ? [...val.tag] : [];
riskDetailItem.risktype = val.risktype ?? ""; riskDetailItem.risktype = val.risktype ?? "";
riskDetailItem.category = val.category ?? "";
riskDetailItem.directionLabels = Array.isArray(val.directionLabels) ? [...val.directionLabels] : [];
riskDetailItem.relationType = val.relationType ?? "";
riskDetailItem.relationTitle = val.relationTitle ?? "";
riskDetailItem.relationModelCode = val.relationModelCode ?? "";
riskDetailItem.relationReferedId = val.relationReferedId ?? "";
}; };
const handleOpenRiskDetail = (val) => { /** 详情弹窗:时间与来源一行(避免模板里拼接) */
assignRiskDetail(val); const riskDetailMetaLine = computed(() => {
const t = String(riskDetailItem.time ?? "").trim();
const o = String(riskDetailItem.origin ?? "").trim();
if (t && o) {
return `${t}·${o}`;
}
return t || o;
});
/** 详情关联条:有文案或有关联 id 时展示 */
const riskDetailShowRelationBar = computed(() => {
const t = String(riskDetailItem.relationTitle ?? "").trim();
const p = String(riskDetailItem.relationType ?? "").trim();
const id = String(riskDetailItem.relationReferedId ?? "").trim();
return Boolean(t || p || id);
});
const handleRiskDetailRelationClick = () => {
const loc = resolveRiskSignalRelationRoute(
riskDetailItem.relationModelCode,
riskDetailItem.relationReferedId
);
if (!loc) {
return;
}
const href = router.resolve(loc).href;
window.open(href, "_blank");
};
/** 将 getRiskSignalInfoById 返回 data 写入详情(listRow 为列表行兜底) */
const assignRiskDetailFromApi = (data, listRow) => {
assignRiskDetail(buildRiskDetailPayloadFromApi(data, listRow));
};
const handleOpenRiskDetail = async (val) => {
const rowId = val?.id;
riskDetailCurrentId.value = rowId != null && rowId !== "" ? rowId : null;
riskDetailStatus.value = null;
riskDetailListLevelText.value = normalizeRiskSignalListLevelText(val?.risktype);
assignRiskDetail(getEmptyRiskDetailPayload());
riskDetailBodyFromApi.value = false;
riskDetailDetailLoading.value = false;
isRiskDetailVisible.value = true; isRiskDetailVisible.value = true;
const id = rowId;
if (id == null || id === "") {
return;
}
riskDetailDetailLoading.value = true;
try {
const res = await getRiskSignalInfoById(String(id));
if (res.code === 200 && res.data) {
assignRiskDetailFromApi(res.data, val);
riskDetailStatus.value = Boolean(res.data.status);
riskDetailBodyFromApi.value = true;
}
} catch (error) {
console.error("获取风险信号详情 error", error);
} finally {
riskDetailDetailLoading.value = false;
}
}; };
const handleCloseRiskDetail = () => { const handleCloseRiskDetail = () => {
assignRiskDetail({ tag: [] }); riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
riskDetailListLevelText.value = "";
riskDetailBodyFromApi.value = false;
riskDetailDetailLoading.value = false;
assignRiskDetail(getEmptyRiskDetailPayload());
}; };
/** 与通用风险信号组件 `riskSignal` 中 itemLeftStatus1~5 色值一致 */ /** 确定风险:GET /api/riskSignal/UpdateStatu/{id} */
const getRiskDetailLevelModifier = (level) => { const handleConfirmRiskDetail = async () => {
const t = String(level ?? "").trim(); const id = riskDetailCurrentId.value;
if (t.includes("特别重大")) return "lv1"; if (id == null || id === "") {
if (t === "重大风险" || (t.includes("重大") && !t.includes("较大") && !t.includes("特别"))) return "lv2"; isRiskDetailVisible.value = false;
if (t === "较大风险" || t.includes("较大")) return "lv3"; return;
if (t === "低风险") return "lv5"; }
if (t === "一般风险" || !t || t === "暂无数据") return "lv4"; riskDetailConfirmLoading.value = true;
return "lv4"; try {
const res = await updateRiskSignalStatus(String(id));
if (res.code === 200) {
ElMessage.success(typeof res.msg === "string" && res.msg.trim() ? res.msg : "操作成功");
isRiskDetailVisible.value = false;
await handleGetPageQuery();
} else {
ElMessage.warning(typeof res.msg === "string" && res.msg.trim() ? res.msg : "操作失败");
}
} catch (error) {
console.error("确定风险 UpdateStatu error", error);
ElMessage.error("操作失败");
} finally {
riskDetailConfirmLoading.value = false;
}
}; };
const riskDetailLevelModifierClass = computed( const riskDetailListLevelModifierClass = computed(
() => `risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(riskDetailItem.risktype)}` () => `risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(riskDetailListLevelText.value)}`
); );
/** 列表项风险等级样式键:与 `@/components/base/riskSignal` itemLeftStatus1~5 一致 */ /** 列表项风险等级样式键:与 `@/components/base/riskSignal` itemLeftStatus1~5 一致 */
...@@ -545,7 +679,9 @@ const handleCleandarChart = async () => { ...@@ -545,7 +679,9 @@ const handleCleandarChart = async () => {
setChart(chartCalendar, "chartCalendar"); setChart(chartCalendar, "chartCalendar");
}; };
/** null:占位「发布时间」且默认倒序;true 正序;false 倒序(显式选中),与智库概览资源库一致 */ /** sort:0 正序;1 倒序;接口默认倒序=1 */
const sortValue = ref(1);
/** el-select 展示用:默认 null => 显示 placeholder「发布时间」 */
const sortModel = ref(null); const sortModel = ref(null);
const resourceLibrarySortPopperOptions = { const resourceLibrarySortPopperOptions = {
...@@ -556,17 +692,22 @@ const resourceLibrarySortPopperOptions = { ...@@ -556,17 +692,22 @@ const resourceLibrarySortPopperOptions = {
}; };
const toggleSortPrefix = () => { const toggleSortPrefix = () => {
sortModel.value = sortModel.value === true ? false : true; sortValue.value = sortValue.value === 1 ? 0 : 1;
sortModel.value = sortValue.value;
handleSortChange(); handleSortChange();
}; };
const handleSortChange = () => { const handleSortChange = () => {
if (sortModel.value === 0 || sortModel.value === 1) {
sortValue.value = sortModel.value;
}
handleGetPageQuery(); handleGetPageQuery();
}; };
// 搜索 // 搜索
const kewword = ref(""); const kewword = ref("");
const handleSearch = async () => { const handleSearch = async () => {
currentPage.value = 1;
handleGetPageQuery(); handleGetPageQuery();
}; };
...@@ -612,56 +753,14 @@ const handleCurrentChange = page => { ...@@ -612,56 +753,14 @@ const handleCurrentChange = page => {
handleGetPageQuery(); handleGetPageQuery();
}; };
/** PageLimit 列表项:领域标签(优先 domainNames,兼容 domainName / domains) */
const parseRiskSignalTagList = (item) => {
const names = item?.domainNames;
if (names != null && String(names).trim() !== "") {
return String(names)
.split(/[,,、;;|]+/)
.map((s) => s.trim())
.filter(Boolean);
}
const dn = item?.domainName;
if (dn != null && String(dn).trim() !== "") {
return [String(dn).trim()];
}
const raw = item?.domains;
if (Array.isArray(raw)) {
return raw.map((d) => (typeof d === "string" ? d : d?.name)).filter(Boolean);
}
if (typeof raw === "string" && raw.trim()) {
return raw.split(/[,,]/).map((s) => s.trim()).filter(Boolean);
}
return [];
};
/** 发布时间:YYYY-MM-DD HH:mm:ss →「2026年1月26日 00:00」;仅日期则「2026年1月26日」 */
const formatRiskPublishDisplay = (raw) => {
if (raw == null || raw === "") {
return "";
}
const s = String(raw).trim();
const dt = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2})(?::(\d{2}))?/);
if (dt) {
const h = String(Number(dt[4])).padStart(2, "0");
const mi = String(Number(dt[5])).padStart(2, "0");
return `${Number(dt[1])}${Number(dt[2])}${Number(dt[3])}${h}:${mi}`;
}
const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:$|[T\s])/);
if (m) {
return `${Number(m[1])}${Number(m[2])}${Number(m[3])}日`;
}
return s;
};
// 按条件分页查询风险信号信息(POST /api/riskSignal/PageLimit) // 按条件分页查询风险信号信息(POST /api/riskSignal/PageLimit)
const handleGetPageQuery = async () => { const handleGetPageQuery = async () => {
const stripAll = (list, allLabel) => (Array.isArray(list) ? list.filter((x) => x !== allLabel) : []); const stripAll = (list, allLabel) => (Array.isArray(list) ? list.filter((x) => x !== allLabel) : []);
// 选中「全部xxx」时,传空数组表示不按该条件过滤(与之前未勾选时语义一致) // 选中「全部xxx」时,传空数组表示不按该条件过滤(与之前未勾选时语义一致)
const riskTypes = stripAll(selectedRiskTypeModel.value, RISK_FILTER_ALL_TYPE); const riskType = stripAll(selectedRiskTypeModel.value, RISK_FILTER_ALL_TYPE);
const srcCountryList = stripAll(selectedRiskSourceModel.value, RISK_FILTER_ALL_SOURCE); const srcCountryList = stripAll(selectedRiskSourceModel.value, RISK_FILTER_ALL_SOURCE);
const riskLevels = stripAll(selectedRiskDegreeModel.value, RISK_FILTER_ALL_LEVEL); const riskLevel = stripAll(selectedRiskDegreeModel.value, RISK_FILTER_ALL_LEVEL);
stripAll(selectedAreaModel.value, RISK_FILTER_ALL_AREA); const domainIds = stripAll(selectedAreaModel.value, RISK_FILTER_ALL_AREA);
const timeFilters = stripAll(selectedTimeModel.value, Time_FILTER_ALL_SOURCE); const timeFilters = stripAll(selectedTimeModel.value, Time_FILTER_ALL_SOURCE);
// 发布时间筛选统一传 day(后端:全部时间=900000,近一年=365,其它同理) // 发布时间筛选统一传 day(后端:全部时间=900000,近一年=365,其它同理)
...@@ -686,11 +785,13 @@ const handleGetPageQuery = async () => { ...@@ -686,11 +785,13 @@ const handleGetPageQuery = async () => {
let params; let params;
if (activeProcessStatusId.value === -1) { if (activeProcessStatusId.value === -1) {
params = { params = {
riskTypes, riskType,
countryId: srcCountryList, countryId: srcCountryList,
domainIds,
directionId: [], directionId: [],
riskLevels, riskLevel,
day, day,
sort: sortValue.value,
keyWord: kewword.value, keyWord: kewword.value,
pageNum: currentPage.value - 1, pageNum: currentPage.value - 1,
...@@ -698,12 +799,14 @@ const handleGetPageQuery = async () => { ...@@ -698,12 +799,14 @@ const handleGetPageQuery = async () => {
}; };
} else { } else {
params = { params = {
riskTypes, riskType,
countryId: srcCountryList, countryId: srcCountryList,
domainIds,
directionId: [], directionId: [],
riskLevels, riskLevel,
dealStatus: activeProcessStatusId.value, dealStatus: activeProcessStatusId.value,
day, day,
sort: sortValue.value,
keyWord: kewword.value, keyWord: kewword.value,
pageNum: currentPage.value - 1, pageNum: currentPage.value - 1,
...@@ -723,18 +826,22 @@ const handleGetPageQuery = async () => { ...@@ -723,18 +826,22 @@ const handleGetPageQuery = async () => {
const stableId = item.id ?? item.riskId ?? item.riskSignalId; const stableId = item.id ?? item.riskId ?? item.riskSignalId;
const title = item.title ?? item.titleZh ?? ""; const title = item.title ?? item.titleZh ?? "";
const publishRaw = item.publishDate ?? item.time ?? ""; const publishRaw = item.publishDate ?? item.time ?? "";
const typeName = item.typeName ?? item.type ?? item.modle ?? item.module ?? "";
return { return {
id: stableId != null ? stableId : null,
rowKey: rowKey:
stableId != null stableId != null
? String(stableId) ? String(stableId)
: `p${currentPage.value}-i${i}-${String(publishRaw)}-${String(title)}`, : `p${currentPage.value}-i${i}-${String(publishRaw)}-${String(title)}`,
title, title,
origin: item.orgName != null && String(item.orgName).trim() !== "" ? String(item.orgName).trim() : "", origin: item.orgName != null && String(item.orgName).trim() !== "" ? String(item.orgName).trim() : "",
typeName: item.typeName ?? item.type ?? item.modle ?? item.module ?? "", typeName,
category: String(typeName).trim(),
time: formatRiskPublishDisplay(publishRaw) || String(publishRaw || ""), time: formatRiskPublishDisplay(publishRaw) || String(publishRaw || ""),
dsc: item.contentZh ?? item.summary ?? item.description ?? "", dsc: item.contentZh ?? item.summary ?? item.description ?? "",
tag: parseRiskSignalTagList(item), tag: parseRiskSignalTagList(item),
risktype: item.riskLevel ?? item.level ?? "", risktype: item.riskLevel ?? item.level ?? "",
direction: item.direction == null || item.direction === "" ? "" : item.direction,
pic: item.orgLogo || "" pic: item.orgLogo || ""
}; };
}); });
...@@ -751,6 +858,38 @@ const handleGetPageQuery = async () => { ...@@ -751,6 +858,38 @@ const handleGetPageQuery = async () => {
} }
}; };
const handleGetRiskTypes = async () => {
try {
const res = await getRiskTypes();
if (res && res.code === 200 && Array.isArray(res.data)) {
riskTypeOptions.value = res.data
.map((x) => ({ code: String(x?.code ?? "").trim(), name: String(x?.name ?? "").trim() }))
.filter((x) => x.code && x.name);
} else {
riskTypeOptions.value = [];
}
} catch (error) {
console.error("获取风险类型字典 error", error);
riskTypeOptions.value = [];
}
};
const handleGetRiskCountryList = async () => {
try {
const res = await listRiskCountry();
if (res && res.code === 200 && Array.isArray(res.data)) {
riskSourceOptions.value = res.data
.map((x) => ({ code: String(x?.code ?? "").trim(), name: String(x?.name ?? "").trim() }))
.filter((x) => x.code && x.name);
} else {
riskSourceOptions.value = [];
}
} catch (error) {
console.error("获取风险来源国家字典 error", error);
riskSourceOptions.value = [];
}
};
watch( watch(
() => route.query[OPEN_FIRST_RISK_DETAIL_QUERY_KEY], () => route.query[OPEN_FIRST_RISK_DETAIL_QUERY_KEY],
async (v) => { async (v) => {
...@@ -765,6 +904,8 @@ watch( ...@@ -765,6 +904,8 @@ watch(
onMounted(async () => { onMounted(async () => {
handleCleandarChart(); handleCleandarChart();
handleGetHylyList(); handleGetHylyList();
handleGetRiskCountryList();
handleGetRiskTypes();
handleGetPageQuery(); handleGetPageQuery();
nextTick(); nextTick();
consumeOpenFirstDetailFromQuery(); consumeOpenFirstDetailFromQuery();
...@@ -776,6 +917,15 @@ onMounted(async () => { ...@@ -776,6 +917,15 @@ onMounted(async () => {
box-shadow: none; box-shadow: none;
} }
.risk-source-checkbox-group :deep(.el-checkbox__label) {
display: inline-block;
max-width: 8em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.home-wrapper { .home-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
...@@ -935,7 +1085,7 @@ onMounted(async () => { ...@@ -935,7 +1085,7 @@ onMounted(async () => {
display: flex; display: flex;
.left { .left {
width: 388px; width: 360px;
margin-bottom: 24px; margin-bottom: 24px;
border-radius: 10px; border-radius: 10px;
padding-bottom: 24px; padding-bottom: 24px;
...@@ -951,6 +1101,7 @@ onMounted(async () => { ...@@ -951,6 +1101,7 @@ onMounted(async () => {
.header { .header {
display: flex; display: flex;
gap: 17px; gap: 17px;
position: relative;
.icon { .icon {
margin-top: 4px; margin-top: 4px;
...@@ -970,6 +1121,22 @@ onMounted(async () => { ...@@ -970,6 +1121,22 @@ onMounted(async () => {
letter-spacing: 1px; letter-spacing: 1px;
text-align: left; text-align: left;
} }
&.risk-source-header {
padding-right: 31px;
/* 16 + 15,给箭头留空间 */
}
.risk-source-header__toggle {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
cursor: pointer;
display: block;
}
} }
.select-main { .select-main {
...@@ -1010,7 +1177,7 @@ onMounted(async () => { ...@@ -1010,7 +1177,7 @@ onMounted(async () => {
.right { .right {
margin-left: 16px; margin-left: 16px;
margin-bottom: 24px; margin-bottom: 24px;
width: 1196px; width: 1224px;
position: relative; position: relative;
border-radius: 10px; border-radius: 10px;
...@@ -1025,7 +1192,7 @@ onMounted(async () => { ...@@ -1025,7 +1192,7 @@ onMounted(async () => {
.header-left { .header-left {
display: flex; display: flex;
margin-left: 44px; margin-left: 24px;
gap: 36px; gap: 36px;
.btn-left { .btn-left {
...@@ -1055,7 +1222,7 @@ onMounted(async () => { ...@@ -1055,7 +1222,7 @@ onMounted(async () => {
.header-right { .header-right {
display: flex; display: flex;
margin-right: 42px; margin-right: 25px;
gap: 12px; gap: 12px;
align-items: center; align-items: center;
...@@ -1100,17 +1267,17 @@ onMounted(async () => { ...@@ -1100,17 +1267,17 @@ onMounted(async () => {
} }
.right-main { .right-main {
width: 1196px; width: 1224px;
padding-left: 18px; padding-left: 24px;
padding-top: 6px; padding-top: 6px;
min-height: 520px; min-height: 520px;
.itemlist { .itemlist {
padding-left: 25px; padding-left: 0px;
padding-top: 16px; padding-top: 16px;
padding-bottom: 16px; padding-bottom: 16px;
width: 1138px; width: 1176px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -1306,6 +1473,14 @@ onMounted(async () => { ...@@ -1306,6 +1473,14 @@ onMounted(async () => {
border-radius: 20px; border-radius: 20px;
} }
.risk-signal-detail-dialog__directions {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.risk-signal-detail-dialog__body { .risk-signal-detail-dialog__body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -1411,7 +1586,7 @@ onMounted(async () => { ...@@ -1411,7 +1586,7 @@ onMounted(async () => {
} }
.risk-signal-detail-dialog__desc { .risk-signal-detail-dialog__desc {
height: 360px;
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
font-family: 'Source Han Sans CN'; font-family: 'Source Han Sans CN';
font-weight: 400; font-weight: 400;
...@@ -1422,7 +1597,12 @@ onMounted(async () => { ...@@ -1422,7 +1597,12 @@ onMounted(async () => {
margin-top: 24px; margin-top: 24px;
padding-left: 12px; padding-left: 12px;
padding-right: 12px; padding-right: 12px;
height: 310px; display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 12;
line-clamp: 12;
overflow: hidden;
text-overflow: ellipsis;
} }
.risk-signal-detail-dialog__tags { .risk-signal-detail-dialog__tags {
...@@ -1604,19 +1784,103 @@ onMounted(async () => { ...@@ -1604,19 +1784,103 @@ onMounted(async () => {
padding-top: 34px; padding-top: 34px;
} }
/* 详情请求中 v-loading 遮罩区域(与列表一致有足够高度以便居中) */
.risk-signal-detail-dialog .risk-signal-detail-dialog__main {
position: relative;
width: 100%;
min-height: 480px;
box-sizing: border-box;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__directions {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
/* 避免内容撑出横向滚动;长文换行(不出现滚动条) */ /* 避免内容撑出横向滚动;长文换行(不出现滚动条) */
.risk-signal-detail-dialog .risk-signal-detail-dialog__title, .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__meta,
.risk-signal-detail-dialog .risk-signal-detail-dialog__body { .risk-signal-detail-dialog .risk-signal-detail-dialog__body,
.risk-signal-detail-dialog .risk-signal-detail-dialog__directions {
max-width: 100%; max-width: 100%;
overflow-wrap: anywhere; overflow-wrap: anywhere;
word-break: break-word; word-break: break-word;
} }
.risk-signal-detail-dialog .risk-signal-detail-dialog__desc {
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 14;
line-clamp: 14;
overflow: hidden;
text-overflow: ellipsis;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog_relation { .risk-signal-detail-dialog .risk-signal-detail-dialog_relation {
max-width: 100%; max-width: 100%;
width: 100% !important; width: 100% !important;
box-sizing: border-box; box-sizing: border-box;
} }
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close {
position: absolute;
right: 115px;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
border-radius: 8px;
background-color: rgba(206, 79, 81, 1);
color: rgba(255, 255, 255, 1);
display: inline-flex;
align-items: center;
justify-content: center;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close :deep(svg) {
width: 12px;
height: 12px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close :deep(svg path) {
fill: rgba(255, 255, 255, 1) !important;
stroke: rgba(255, 255, 255, 1) !important;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__read-indicator {
position: absolute;
right: 115px;
top: 50%;
transform: translateY(-50%);
display: inline-flex;
align-items: center;
gap: 8px;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-close {
position: static;
transform: none;
}
.risk-signal-detail-dialog .risk-signal-detail-dialog__header-badge-read {
width: 16px;
height: 16px;
display: block;
}
.risk-signal-detail-dialog .read {
font-family: 'Source Han Sans CN';
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
color: rgb(95, 101, 108);
}
</style> </style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论