提交 c395a230 authored 作者: 朱政's avatar 朱政

feat:所有概览页风险信号点击item逻辑开发,以及风险信号管理功能样式开发

上级 eaea9d5b
流水线 #498 已通过 于阶段
in 2 分 3 秒
...@@ -24,3 +24,27 @@ export function getRiskSignalInfoById(id) { ...@@ -24,3 +24,27 @@ export function getRiskSignalInfoById(id) {
url: `/api/riskSignal/getRiskSignalInfoById/${id}` 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}`,
});
})
} }
/** /**
......
...@@ -17,21 +17,29 @@ ...@@ -17,21 +17,29 @@
class="risk-signal-detail-dialog__level" class="risk-signal-detail-dialog__level"
:class="listLevelModifierClass" :class="listLevelModifierClass"
>{{ listLevelText }}</span> >{{ 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__main" v-loading="detailLoading"> <div class="risk-signal-detail-dialog__main" v-loading="detailLoading">
<div v-if="bodyFromApi"> <div v-if="bodyFromApi">
<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 <div v-if="riskDetailItem.directionLabels.length" class="risk-signal-detail-dialog__directions">
v-if="riskDetailItem.directionLabels.length || riskDetailItem.category"
class="risk-signal-detail-dialog__directions"
>
<div <div
v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels" v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels"
:key="'overview-risk-detail-direction-' + dirIndex + '-' + dirLabel" :key="'overview-risk-detail-direction-' + dirIndex + '-' + dirLabel"
class="risk-signal-detail-dialog__origin" class="risk-signal-detail-dialog__origin"
>{{ dirLabel }}</div> >{{ dirLabel }}</div>
<div v-if="riskDetailItem.category" class="risk-signal-detail-dialog__origin">{{ riskDetailItem.category }}</div>
</div> </div>
<div class="risk-signal-detail-dialog__meta"> <div class="risk-signal-detail-dialog__meta">
<span>{{ metaLine }}</span> <span>{{ metaLine }}</span>
...@@ -64,8 +72,13 @@ ...@@ -64,8 +72,13 @@
</div> </div>
</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>
...@@ -75,7 +88,10 @@ ...@@ -75,7 +88,10 @@
import { computed, nextTick, reactive, ref, watch } from "vue"; import { computed, nextTick, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import AreaTag from "@/components/base/AreaTag/index.vue"; import AreaTag from "@/components/base/AreaTag/index.vue";
import { getRiskSignalInfoById } from "@/api/riskSignal/index.js"; 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 { import {
buildListRowFallbackFromRawRow, buildListRowFallbackFromRawRow,
buildRiskDetailPayloadFromApi, buildRiskDetailPayloadFromApi,
...@@ -114,7 +130,7 @@ const props = defineProps({ ...@@ -114,7 +130,7 @@ const props = defineProps({
} }
}); });
const emit = defineEmits(["closed"]); const emit = defineEmits(["closed", "confirmed"]);
const visible = defineModel({ type: Boolean, default: false }); const visible = defineModel({ type: Boolean, default: false });
...@@ -123,6 +139,9 @@ const router = useRouter(); ...@@ -123,6 +139,9 @@ const router = useRouter();
const listLevelText = ref(""); const listLevelText = ref("");
const bodyFromApi = ref(false); const bodyFromApi = ref(false);
const detailLoading = ref(false); const detailLoading = ref(false);
const confirmLoading = ref(false);
const riskDetailCurrentId = ref(null);
const riskDetailStatus = ref(null);
const riskDetailItem = reactive(getEmptyRiskDetailPayload()); const riskDetailItem = reactive(getEmptyRiskDetailPayload());
...@@ -171,6 +190,9 @@ const resetState = () => { ...@@ -171,6 +190,9 @@ const resetState = () => {
listLevelText.value = ""; listLevelText.value = "";
bodyFromApi.value = false; bodyFromApi.value = false;
detailLoading.value = false; detailLoading.value = false;
confirmLoading.value = false;
riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
assignRiskDetail(getEmptyRiskDetailPayload()); assignRiskDetail(getEmptyRiskDetailPayload());
}; };
...@@ -181,6 +203,8 @@ const loadDetail = async () => { ...@@ -181,6 +203,8 @@ const loadDetail = async () => {
} }
const listFallback = buildListRowFallbackFromRawRow(props.row, fieldMap.value); const listFallback = buildListRowFallbackFromRawRow(props.row, fieldMap.value);
listLevelText.value = normalizeRiskSignalListLevelText(listFallback.risktype); listLevelText.value = normalizeRiskSignalListLevelText(listFallback.risktype);
riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
assignRiskDetail(getEmptyRiskDetailPayload()); assignRiskDetail(getEmptyRiskDetailPayload());
bodyFromApi.value = false; bodyFromApi.value = false;
detailLoading.value = false; detailLoading.value = false;
...@@ -192,6 +216,8 @@ const loadDetail = async () => { ...@@ -192,6 +216,8 @@ const loadDetail = async () => {
try { try {
const res = await getRiskSignalInfoById(id); const res = await getRiskSignalInfoById(id);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
riskDetailCurrentId.value = id;
riskDetailStatus.value = Boolean(res.data.status);
assignRiskDetail(buildRiskDetailPayloadFromApi(res.data, listFallback)); assignRiskDetail(buildRiskDetailPayloadFromApi(res.data, listFallback));
bodyFromApi.value = true; bodyFromApi.value = true;
} }
...@@ -226,6 +252,30 @@ const handleRelationClick = () => { ...@@ -226,6 +252,30 @@ const handleRelationClick = () => {
window.open(href, "_blank"); 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(); resetState();
emit("closed"); emit("closed");
......
...@@ -232,6 +232,52 @@ ...@@ -232,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%;
...@@ -319,7 +365,13 @@ ...@@ -319,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 {
......
...@@ -5,7 +5,10 @@ export function resolveRiskSignalRowId(row) { ...@@ -5,7 +5,10 @@ export function resolveRiskSignalRowId(row) {
if (!row || typeof row !== "object") { if (!row || typeof row !== "object") {
return ""; return "";
} }
const keys = ["id", "riskId", "riskSignalId", "orderId", "signalId"]; // 概览页通用接口 `/api/commonFeature/riskSignal/{moduleId}` 返回结构里,
// 详情接口 getRiskSignalInfoById 需要的是 signalId(而非 orderId/reportId 等业务 id)。
// 因此优先取 signalId;其次再取已映射的 id/riskId 等。
const keys = ["signalId", "id", "riskId", "riskSignalId", "orderId"];
for (const k of keys) { for (const k of keys) {
const v = row[k]; const v = row[k];
if (v != null && String(v).trim() !== "") { if (v != null && String(v).trim() !== "") {
...@@ -296,6 +299,7 @@ export function buildRiskDetailPayloadFromApi(data, listRow) { ...@@ -296,6 +299,7 @@ export function buildRiskDetailPayloadFromApi(data, listRow) {
const category = const category =
data.typeName ?? data.module ?? data.modle ?? data.riskCategory ?? ""; data.typeName ?? data.module ?? data.modle ?? data.riskCategory ?? "";
const org = data.orgName != null && String(data.orgName).trim() !== "" ? String(data.orgName).trim() : ""; 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 modelCode = data.model != null ? String(data.model).trim() : "";
const referedRaw = data.referedId ?? data.referredId; const referedRaw = data.referedId ?? data.referredId;
const relationReferedId = referedRaw != null ? String(referedRaw).trim() : ""; const relationReferedId = referedRaw != null ? String(referedRaw).trim() : "";
...@@ -308,6 +312,7 @@ export function buildRiskDetailPayloadFromApi(data, listRow) { ...@@ -308,6 +312,7 @@ export function buildRiskDetailPayloadFromApi(data, listRow) {
const detailTags = parseRiskSignalTagList(data); const detailTags = parseRiskSignalTagList(data);
const apiCategory = String(category).trim(); const apiCategory = String(category).trim();
const apiTime = formatRiskPublishDisplay(postRaw) || String(postRaw || "").trim(); const apiTime = formatRiskPublishDisplay(postRaw) || String(postRaw || "").trim();
// 风险动向类型:严格使用 direction(0100~0103),支持多值
const directionCodes = [ const directionCodes = [
...parseRiskSignalDirectionCodes(data.direction), ...parseRiskSignalDirectionCodes(data.direction),
...parseRiskSignalDirectionCodes(list.direction) ...parseRiskSignalDirectionCodes(list.direction)
......
...@@ -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(item)" @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,13 +125,6 @@ ...@@ -125,13 +125,6 @@
</div> </div>
</div> </div>
</div> </div>
<RiskSignalOverviewDetailDialog
v-model="isRiskOverviewDetailOpen"
:row="riskOverviewDetailRow"
name-field="signalTitle"
post-date-field="signalTime"
risk-level-field="signalLevel"
/>
</div> </div>
</template> </template>
...@@ -142,7 +135,6 @@ import WaveBall from "./WaveBall.vue"; ...@@ -142,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";
...@@ -660,13 +652,7 @@ const filteredHotNewsList = computed(() => { ...@@ -660,13 +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 riskOverviewDetailRow = ref(null);
const handleRiskSignalRowToManage = (item) => {
riskOverviewDetailRow.value = item ?? null;
isRiskOverviewDetailOpen.value = true;
};
const handleToRiskManage = () => { const handleToRiskManage = () => {
navigateToViewRiskSignal(router); navigateToViewRiskSignal(router);
......
...@@ -1388,7 +1388,7 @@ const strengthLabels = { ...@@ -1388,7 +1388,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 => ({
...@@ -1408,7 +1408,7 @@ const fetchRiskSignals = async () => { ...@@ -1408,7 +1408,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 => ({
...@@ -1429,7 +1429,7 @@ const fetchSocialMediaInfo = async () => { ...@@ -1429,7 +1429,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,
......
...@@ -807,15 +807,15 @@ const warningList = ref([ ...@@ -807,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 || "暂无数据"
}; };
}); });
} }
......
...@@ -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>
...@@ -222,19 +227,23 @@ ...@@ -222,19 +227,23 @@
<img class="header-icon" src="./assets/images/risk-icon.png" alt="" /> <img class="header-icon" src="./assets/images/risk-icon.png" alt="" />
<span v-if="riskDetailListLevelText" class="risk-signal-detail-dialog__level" <span v-if="riskDetailListLevelText" class="risk-signal-detail-dialog__level"
:class="riskDetailListLevelModifierClass">{{ riskDetailListLevelText }}</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 class="risk-signal-detail-dialog__main" v-loading="riskDetailDetailLoading">
<div v-if="riskDetailBodyFromApi"> <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 v-if="riskDetailItem.directionLabels.length || riskDetailItem.category" <div v-if="riskDetailItem.directionLabels.length" class="risk-signal-detail-dialog__directions">
class="risk-signal-detail-dialog__directions">
<div v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels" <div v-for="(dirLabel, dirIndex) in riskDetailItem.directionLabels"
:key="'risk-detail-direction-' + dirIndex + '-' + dirLabel" class="risk-signal-detail-dialog__origin">{{ :key="'risk-detail-direction-' + dirIndex + '-' + dirLabel" class="risk-signal-detail-dialog__origin">{{
dirLabel }}</div> dirLabel }}</div>
<div v-if="riskDetailItem.category" class="risk-signal-detail-dialog__origin">{{ riskDetailItem.category
}}
</div>
</div> </div>
<div class="risk-signal-detail-dialog__meta"> <div class="risk-signal-detail-dialog__meta">
...@@ -260,8 +269,9 @@ ...@@ -260,8 +269,9 @@
</div> </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>
...@@ -280,16 +290,20 @@ import { ...@@ -280,16 +290,20 @@ import {
parseRiskSignalTagList, parseRiskSignalTagList,
resolveRiskSignalRelationRoute resolveRiskSignalRelationRoute
} from "@/utils/riskSignalOverviewDetailHelpers.js"; } from "@/utils/riskSignalOverviewDetailHelpers.js";
import { getRiskSignalBaseInfo, getPageQuery, getRiskSignalInfoById } from "@/api/riskSignal/index"; 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) => {
...@@ -316,36 +330,14 @@ const handleTimeGroupChange = (val) => { ...@@ -316,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) => {
...@@ -353,6 +345,10 @@ const handleRiskSourceGroupChange = (val) => { ...@@ -353,6 +345,10 @@ const handleRiskSourceGroupChange = (val) => {
handleGetPageQuery(); handleGetPageQuery();
}; };
const handleToggleRiskSourceExpanded = () => {
riskSourceExpanded.value = !riskSourceExpanded.value;
};
const riskDegree = ref([ const riskDegree = ref([
{ {
name: "特别重大风险", name: "特别重大风险",
...@@ -439,6 +435,12 @@ const riskDetailListLevelText = ref(""); ...@@ -439,6 +435,12 @@ const riskDetailListLevelText = ref("");
const riskDetailBodyFromApi = ref(false); const riskDetailBodyFromApi = ref(false);
/** 详情接口请求中(与列表 v-loading 一致,遮罩内垂直水平居中) */ /** 详情接口请求中(与列表 v-loading 一致,遮罩内垂直水平居中) */
const riskDetailDetailLoading = ref(false); const riskDetailDetailLoading = ref(false);
/** 当前详情弹窗对应的列表项 id(确定风险时提交 UpdateStatu) */
const riskDetailCurrentId = ref(null);
/** 详情状态:false=未读,true=已读;仅接口返回后展示 */
const riskDetailStatus = ref(null);
/** 确定风险按钮提交中 */
const riskDetailConfirmLoading = ref(false);
const getEmptyRiskDetailPayload = () => ({ const getEmptyRiskDetailPayload = () => ({
title: "", title: "",
...@@ -521,12 +523,15 @@ const assignRiskDetailFromApi = (data, listRow) => { ...@@ -521,12 +523,15 @@ const assignRiskDetailFromApi = (data, listRow) => {
}; };
const handleOpenRiskDetail = async (val) => { const handleOpenRiskDetail = async (val) => {
const rowId = val?.id;
riskDetailCurrentId.value = rowId != null && rowId !== "" ? rowId : null;
riskDetailStatus.value = null;
riskDetailListLevelText.value = normalizeRiskSignalListLevelText(val?.risktype); riskDetailListLevelText.value = normalizeRiskSignalListLevelText(val?.risktype);
assignRiskDetail(getEmptyRiskDetailPayload()); assignRiskDetail(getEmptyRiskDetailPayload());
riskDetailBodyFromApi.value = false; riskDetailBodyFromApi.value = false;
riskDetailDetailLoading.value = false; riskDetailDetailLoading.value = false;
isRiskDetailVisible.value = true; isRiskDetailVisible.value = true;
const id = val?.id; const id = rowId;
if (id == null || id === "") { if (id == null || id === "") {
return; return;
} }
...@@ -535,6 +540,7 @@ const handleOpenRiskDetail = async (val) => { ...@@ -535,6 +540,7 @@ const handleOpenRiskDetail = async (val) => {
const res = await getRiskSignalInfoById(String(id)); const res = await getRiskSignalInfoById(String(id));
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
assignRiskDetailFromApi(res.data, val); assignRiskDetailFromApi(res.data, val);
riskDetailStatus.value = Boolean(res.data.status);
riskDetailBodyFromApi.value = true; riskDetailBodyFromApi.value = true;
} }
} catch (error) { } catch (error) {
...@@ -545,12 +551,39 @@ const handleOpenRiskDetail = async (val) => { ...@@ -545,12 +551,39 @@ const handleOpenRiskDetail = async (val) => {
}; };
const handleCloseRiskDetail = () => { const handleCloseRiskDetail = () => {
riskDetailCurrentId.value = null;
riskDetailStatus.value = null;
riskDetailListLevelText.value = ""; riskDetailListLevelText.value = "";
riskDetailBodyFromApi.value = false; riskDetailBodyFromApi.value = false;
riskDetailDetailLoading.value = false; riskDetailDetailLoading.value = false;
assignRiskDetail(getEmptyRiskDetailPayload()); assignRiskDetail(getEmptyRiskDetailPayload());
}; };
/** 确定风险:GET /api/riskSignal/UpdateStatu/{id} */
const handleConfirmRiskDetail = async () => {
const id = riskDetailCurrentId.value;
if (id == null || id === "") {
isRiskDetailVisible.value = false;
return;
}
riskDetailConfirmLoading.value = true;
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 riskDetailListLevelModifierClass = computed( const riskDetailListLevelModifierClass = computed(
() => `risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(riskDetailListLevelText.value)}` () => `risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(riskDetailListLevelText.value)}`
); );
...@@ -724,10 +757,10 @@ const handleCurrentChange = page => { ...@@ -724,10 +757,10 @@ const handleCurrentChange = page => {
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 riskLevel = 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,其它同理)
...@@ -752,8 +785,9 @@ const handleGetPageQuery = async () => { ...@@ -752,8 +785,9 @@ 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: [],
riskLevel, riskLevel,
day, day,
...@@ -765,8 +799,9 @@ const handleGetPageQuery = async () => { ...@@ -765,8 +799,9 @@ const handleGetPageQuery = async () => {
}; };
} else { } else {
params = { params = {
riskTypes, riskType,
countryId: srcCountryList, countryId: srcCountryList,
domainIds,
directionId: [], directionId: [],
riskLevel, riskLevel,
dealStatus: activeProcessStatusId.value, dealStatus: activeProcessStatusId.value,
...@@ -823,6 +858,38 @@ const handleGetPageQuery = async () => { ...@@ -823,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) => {
...@@ -837,6 +904,8 @@ watch( ...@@ -837,6 +904,8 @@ watch(
onMounted(async () => { onMounted(async () => {
handleCleandarChart(); handleCleandarChart();
handleGetHylyList(); handleGetHylyList();
handleGetRiskCountryList();
handleGetRiskTypes();
handleGetPageQuery(); handleGetPageQuery();
nextTick(); nextTick();
consumeOpenFirstDetailFromQuery(); consumeOpenFirstDetailFromQuery();
...@@ -848,6 +917,15 @@ onMounted(async () => { ...@@ -848,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%;
...@@ -1007,7 +1085,7 @@ onMounted(async () => { ...@@ -1007,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;
...@@ -1023,6 +1101,7 @@ onMounted(async () => { ...@@ -1023,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;
...@@ -1042,6 +1121,22 @@ onMounted(async () => { ...@@ -1042,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 {
...@@ -1082,7 +1177,7 @@ onMounted(async () => { ...@@ -1082,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;
...@@ -1097,7 +1192,7 @@ onMounted(async () => { ...@@ -1097,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 {
...@@ -1127,7 +1222,7 @@ onMounted(async () => { ...@@ -1127,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;
...@@ -1172,17 +1267,17 @@ onMounted(async () => { ...@@ -1172,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;
...@@ -1732,4 +1827,60 @@ onMounted(async () => { ...@@ -1732,4 +1827,60 @@ onMounted(async () => {
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论