Files
ccdi/ruoyi-ui/src/views/ccdiProject/detail.vue

767 lines
19 KiB
Vue
Raw Normal View History

2026-03-02 19:18:45 +08:00
<template>
<div class="app-container dpc-detail-container">
<!-- 原页面头部 (已隐藏使用UploadData组件的头部) -->
<div class="detail-header">
<div class="header-left">
<el-button size="small" icon="el-icon-back" @click="handleBack"
>返回</el-button
>
<div class="title-section">
<div class="page-title">
<h2>
{{ projectInfo.projectName }}
</h2>
<el-tag
:type="getStatusType(projectInfo.projectStatus)"
size="small"
>
{{ getStatusLabel(projectInfo.projectStatus) }}
</el-tag>
<!-- 配置类型标签 -->
<el-tag
:type="getConfigTypeStyle(projectInfo.configType)"
size="small"
style="margin-left: 8px"
>
{{ getConfigTypeLabel(projectInfo.configType) }}
</el-tag>
2026-03-02 19:18:45 +08:00
</div>
<p class="update-time">
最后更新时间{{ formatUpdateTime(projectInfo.updateTime) }}
</p>
</div>
</div>
<div class="header-right">
2026-04-21 16:46:47 +08:00
<el-button
class="evidence-entry-btn"
size="mini"
plain
icon="el-icon-collection-tag"
@click="evidenceDrawerVisible = true"
>
证据线索
</el-button>
<el-menu
:default-active="activeTab"
mode="horizontal"
@select="handleMenuSelect"
class="nav-menu"
>
2026-03-24 21:45:55 +08:00
<el-menu-item index="upload" :disabled="isArchiveLockedTab('upload')">上传数据</el-menu-item>
<el-menu-item index="config" :disabled="isArchiveLockedTab('config')">参数配置</el-menu-item>
<el-menu-item index="overview">结果总览</el-menu-item>
<el-menu-item index="special">专项排查</el-menu-item>
<el-menu-item index="detail">流水明细查询</el-menu-item>
</el-menu>
2026-03-02 19:18:45 +08:00
</div>
</div>
<!-- 动态组件渲染区域 -->
<component
:is="currentComponent"
2026-03-02 19:18:45 +08:00
:project-id="projectId"
:project-info="projectInfo"
@menu-change="handleMenuChange"
@refresh-project="handleRefreshProject"
2026-03-02 19:18:45 +08:00
@data-uploaded="handleDataUploaded"
@name-selected="handleNameSelected"
@generate-report="handleGenerateReport"
@fetch-bank-info="handleFetchBankInfo"
2026-04-21 16:46:47 +08:00
@evidence-confirm="handleEvidenceConfirm"
/>
<evidence-confirm-dialog
:visible.sync="evidenceConfirmVisible"
:payload="evidencePayload"
@saved="handleEvidenceSaved"
/>
<evidence-drawer
ref="evidenceDrawer"
:visible.sync="evidenceDrawerVisible"
:project-id="projectId"
2026-03-02 19:18:45 +08:00
/>
</div>
</template>
<script>
import UploadData from "./components/detail/UploadData";
import ParamConfig from "./components/detail/ParamConfig";
import PreliminaryCheck from "./components/detail/PreliminaryCheck";
import SpecialCheck from "./components/detail/SpecialCheck";
import DetailQuery from "./components/detail/DetailQuery";
2026-04-21 16:46:47 +08:00
import EvidenceConfirmDialog from "./components/detail/EvidenceConfirmDialog";
import EvidenceDrawer from "./components/detail/EvidenceDrawer";
import { getProject } from "@/api/ccdiProject";
2026-03-02 19:18:45 +08:00
export default {
name: "ProjectDetail",
components: {
UploadData,
ParamConfig,
PreliminaryCheck,
SpecialCheck,
DetailQuery,
2026-04-21 16:46:47 +08:00
EvidenceConfirmDialog,
EvidenceDrawer,
2026-03-02 19:18:45 +08:00
},
data() {
return {
// 当前激活的菜单项索引
activeTab: "upload",
// 当前显示的组件名称
currentComponent: "UploadData",
2026-03-02 19:18:45 +08:00
// 项目ID
projectId: this.$route.params.projectId,
// 项目信息
projectInfo: {
projectId: this.$route.params.projectId,
projectName: "",
projectDesc: "",
createTime: "",
updateTime: "",
startDate: "",
endDate: "",
targetCount: 0,
warningCount: 0,
warningThreshold: 60,
projectStatus: "0",
},
2026-04-21 16:46:47 +08:00
evidenceConfirmVisible: false,
evidenceDrawerVisible: false,
evidencePayload: {},
projectStatusPollingTimer: null,
projectStatusPollingInterval: 1000,
projectStatusPollingLoading: false,
2026-03-02 19:18:45 +08:00
};
},
2026-03-24 21:45:55 +08:00
computed: {
isProjectArchived() {
return String(this.projectInfo.projectStatus) === "2";
},
},
2026-03-02 19:18:45 +08:00
watch: {
"$route.params.projectId"(newId) {
this.stopProjectStatusPolling();
this.projectStatusPollingLoading = false;
2026-03-02 19:18:45 +08:00
if (newId) {
this.projectId = newId;
this.projectInfo.projectId = newId;
this.initActiveTabFromRoute();
2026-03-02 19:18:45 +08:00
this.initPageData();
}
},
"$route.query.tab"() {
this.initActiveTabFromRoute();
},
"projectInfo.projectStatus"() {
this.syncProjectStatusPolling();
2026-03-24 21:45:55 +08:00
const accessibleTab = this.resolveAccessibleTab(this.activeTab);
if (accessibleTab !== this.activeTab) {
this.setActiveTab(accessibleTab);
this.syncRouteTab(accessibleTab);
2026-03-24 21:45:55 +08:00
}
},
2026-03-02 19:18:45 +08:00
},
created() {
// 初始化页面数据
this.initActiveTabFromRoute();
2026-03-02 19:18:45 +08:00
this.initPageData();
2026-04-21 16:46:47 +08:00
this.$root.$on("ccdi-evidence-confirm", this.handleEvidenceConfirm);
2026-03-02 19:18:45 +08:00
},
beforeDestroy() {
2026-04-21 16:46:47 +08:00
this.$root.$off("ccdi-evidence-confirm", this.handleEvidenceConfirm);
this.stopProjectStatusPolling();
},
2026-03-02 19:18:45 +08:00
methods: {
initActiveTabFromRoute() {
const tab = (this.$route.query && this.$route.query.tab) || "";
const validTabs = ["upload", "config", "overview", "special", "detail"];
const targetTab = validTabs.includes(tab) ? tab : "upload";
const accessibleTab = this.resolveAccessibleTab(targetTab);
this.setActiveTab(accessibleTab);
if (accessibleTab !== targetTab) {
this.syncRouteTab(accessibleTab);
}
2026-03-24 21:45:55 +08:00
},
isArchiveLockedTab(tab) {
return this.isProjectArchived && ["upload", "config"].includes(tab);
},
resolveAccessibleTab(tab) {
if (this.isArchiveLockedTab(tab)) {
return "overview";
}
return tab;
},
setActiveTab(index) {
this.activeTab = index;
const componentMap = {
upload: "UploadData",
config: "ParamConfig",
overview: "PreliminaryCheck",
special: "SpecialCheck",
detail: "DetailQuery",
};
this.currentComponent = componentMap[index] || "UploadData";
},
syncRouteTab(tab) {
const currentTab = (this.$route.query && this.$route.query.tab) || "";
if (currentTab === tab) {
return;
}
this.$router.replace({
path: this.$route.path,
query: {
...this.$route.query,
tab,
},
});
},
2026-03-02 19:18:45 +08:00
/** 初始化页面数据 */
initPageData() {
return this.fetchProjectDetail();
},
async fetchProjectDetail(options = {}) {
const { silent = false } = options;
2026-03-05 15:53:56 +08:00
if (!this.projectId) {
return null;
}
if (!silent) {
this.projectInfo.projectName = "";
this.updatePageTitle();
}
try {
const res = await getProject(this.projectId);
const data = res.data || {};
this.projectInfo = {
...this.projectInfo,
...data,
projectId: data.projectId || this.projectId,
projectName: data.projectName || "",
projectDesc: data.projectDesc || data.description || "",
projectStatus: String(
data.projectStatus !== undefined && data.projectStatus !== null
? data.projectStatus
: data.status !== undefined && data.status !== null
? data.status
: this.projectInfo.projectStatus
),
};
this.updatePageTitle();
this.syncProjectStatusPolling();
return this.projectInfo;
} catch (error) {
if (!silent) {
this.$message.error("加载项目详情失败");
} else {
console.error("轮询项目状态失败:", error);
throw error;
}
this.updatePageTitle();
return null;
}
},
syncProjectStatusPolling() {
if (String(this.projectInfo.projectStatus) === "3") {
this.startProjectStatusPolling();
2026-03-05 15:53:56 +08:00
return;
}
this.stopProjectStatusPolling();
},
startProjectStatusPolling() {
if (this.projectStatusPollingTimer) {
return;
}
this.projectStatusPollingTimer = setInterval(() => {
this.pollProjectStatus();
}, this.projectStatusPollingInterval);
},
stopProjectStatusPolling() {
if (!this.projectStatusPollingTimer) {
return;
}
clearInterval(this.projectStatusPollingTimer);
this.projectStatusPollingTimer = null;
},
async pollProjectStatus() {
if (this.projectStatusPollingLoading || !this.projectId) {
return;
}
this.projectStatusPollingLoading = true;
try {
await this.fetchProjectDetail({ silent: true });
if (String(this.projectInfo.projectStatus) !== "3") {
this.stopProjectStatusPolling();
}
} catch (error) {
console.error("项目状态轮询请求失败:", error);
} finally {
this.projectStatusPollingLoading = false;
}
2026-03-05 15:53:56 +08:00
},
updatePageTitle() {
const title = this.projectInfo.projectName || `ProjectDetail-${this.projectId}`;
this.$route.meta.title = title;
this.$store.dispatch("settings/setTitle", title);
this.$store.dispatch("tagsView/updateVisitedView", {
path: this.$route.path,
title,
});
2026-03-02 19:18:45 +08:00
},
/** 格式化更新时间 */
formatUpdateTime(time) {
if (!time) return "-";
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
/** 模拟项目信息 */
mockProjectInfo() {
// 模拟数据实际应该调用API
this.projectInfo = {
projectId: this.projectId,
projectName: "2024年Q1初核项目",
projectDesc: "第一季度员工异常行为排查",
createTime: "2024-01-01 10:00:00",
updateTime: new Date().toISOString(),
startDate: "2024-01-01",
endDate: "2024-03-31",
targetCount: 500,
warningCount: 15,
warningThreshold: 60,
projectStatus: "0",
};
},
/** 获取状态类型 */
getStatusType(status) {
const statusMap = {
0: "primary", // 进行中
1: "success", // 已完成
2: "info", // 已归档
3: "warning", // 打标中
2026-03-02 19:18:45 +08:00
};
return statusMap[status] || "info";
},
/** 获取状态标签 */
getStatusLabel(status) {
const statusMap = {
0: "进行中",
1: "已完成",
2: "已归档",
3: "打标中",
2026-03-02 19:18:45 +08:00
};
return statusMap[status] || "未知";
},
/** 获取配置类型标签文字 */
getConfigTypeLabel(configType) {
const configTypeMap = {
"default": "默认配置",
"custom": "自定义配置"
}
return configTypeMap[configType] || "默认配置"
},
/** 获取配置类型标签样式 */
getConfigTypeStyle(configType) {
const styleMap = {
"default": "info", // 蓝色
"custom": "warning" // 橙色
}
return styleMap[configType] || "info"
},
2026-03-02 19:18:45 +08:00
/** 标签页切换 */
handleTabChange(tab) {
console.log("切换到标签页:", tab.name);
},
/** 返回列表页 */
handleBack() {
this.$router.push("/ccdiProject");
},
/** 菜单选择事件 */
handleMenuSelect(index) {
2026-03-24 21:45:55 +08:00
if (this.isArchiveLockedTab(index)) {
return;
}
console.log("菜单选择:", index);
this.setActiveTab(index);
},
2026-03-02 19:18:45 +08:00
/** UploadData 组件:菜单切换 */
handleMenuChange({ key, route }) {
console.log("切换到菜单:", key, route);
// 直接触发菜单选择
this.handleMenuSelect(route);
2026-03-02 19:18:45 +08:00
},
/** UploadData 组件:数据上传完成 */
handleDataUploaded({ type }) {
console.log("数据上传完成:", type);
this.$message.success(`${type} 数据上传成功`);
},
/** UploadData 组件:名单选择完成 */
handleNameSelected(nameList) {
console.log("名单选择完成:", nameList);
this.$message.success("名单选择成功");
},
/** UploadData 组件:生成报告 */
handleGenerateReport() {
console.log("生成报告");
// this.$message.info("生成报告功能开发中");
},
/** UploadData 组件:拉取本行信息 */
handleFetchBankInfo() {
console.log("拉取本行信息");
this.$message.info("拉取本行信息功能开发中");
},
/** 数据上传完成 */
handleDataUploaded() {
console.log("数据上传完成");
this.$message.success("数据上传成功");
},
/** 刷新页面 */
handleRefresh() {
2026-03-05 15:53:56 +08:00
this.initPageData();
2026-03-02 19:18:45 +08:00
this.$message.success("刷新成功");
},
handleRefreshProject() {
this.initPageData();
},
2026-04-21 16:46:47 +08:00
handleEvidenceConfirm(payload) {
this.evidencePayload = {
projectId: this.projectId,
...(payload || {}),
};
this.evidenceConfirmVisible = true;
},
handleEvidenceSaved() {
this.evidenceDrawerVisible = true;
this.$nextTick(() => {
if (this.$refs.evidenceDrawer) {
this.$refs.evidenceDrawer.loadEvidence();
}
});
},
2026-03-02 19:18:45 +08:00
/** 导出报告 */
handleExport() {
console.log("导出报告");
this.$message.info("报告导出功能开发中");
},
/** 完成项目 */
handleComplete() {
this.$confirm("确定要完成当前项目吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.$message.success("项目已完成");
this.projectInfo.projectStatus = "1";
})
.catch(() => {
this.$message.info("已取消");
});
},
/** 归档项目 */
handleArchive() {
this.$confirm(
"确定要归档当前项目吗?归档后将不能进行修改操作。",
"警告",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
this.$message.success("项目已归档");
this.projectInfo.projectStatus = "2";
})
.catch(() => {
this.$message.info("已取消");
});
},
},
};
</script>
<style lang="scss" scoped>
.dpc-detail-container {
padding: 16px;
2026-04-03 11:00:33 +08:00
background: var(--ccdi-page-bg);
2026-03-02 19:18:45 +08:00
min-height: calc(100vh - 84px);
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 16px 20px;
background: #ffffff;
2026-04-03 11:00:33 +08:00
border: 1px solid var(--ccdi-border);
border-radius: 14px;
box-shadow: var(--ccdi-shadow);
2026-03-02 19:18:45 +08:00
.header-left {
display: flex;
align-items: flex-start;
gap: 12px;
.el-button {
padding: 8px 12px;
font-size: 13px;
margin-top: 4px;
}
.title-section {
.page-title {
display: flex;
align-items: center;
h2 {
margin: 0 0 4px 0;
font-size: 20px;
2026-04-03 11:00:33 +08:00
font-weight: 700;
color: var(--ccdi-text-primary);
2026-03-02 19:18:45 +08:00
margin-right: 5px;
}
}
.update-time {
margin: 0;
font-size: 13px;
2026-04-03 11:00:33 +08:00
color: var(--ccdi-text-muted);
2026-03-02 19:18:45 +08:00
}
}
}
.header-right {
display: flex;
align-items: center;
2026-04-21 16:46:47 +08:00
gap: 10px;
.evidence-entry-btn {
padding: 6px 10px;
font-size: 12px;
color: #5b7fb8;
border-color: #d6e4f7;
background: #f8fbff;
&:hover {
color: var(--ccdi-primary);
border-color: #9fc3ff;
background: #edf5ff;
}
}
.nav-menu {
// 移除默认背景色和边框
background-color: transparent;
border-bottom: none;
// 菜单项基础样式
.el-menu-item,
.el-submenu__title {
2026-03-02 19:18:45 +08:00
font-size: 14px;
2026-04-03 11:00:33 +08:00
color: var(--ccdi-text-secondary);
padding: 0 16px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
2026-04-03 11:00:33 +08:00
border-radius: 10px 10px 0 0;
&:hover {
2026-04-03 11:00:33 +08:00
background-color: #f7fafd;
color: var(--ccdi-text-primary);
2026-03-02 19:18:45 +08:00
}
}
// 子菜单容器高度统一
.el-submenu {
height: 40px;
line-height: 40px;
}
// 激活状态:底部下划线 + 蓝色文字
.el-menu-item.is-active {
2026-04-03 11:00:33 +08:00
color: var(--ccdi-primary);
border-bottom: 2px solid var(--ccdi-primary);
background-color: transparent;
}
// 下拉菜单激活状态
.el-submenu.is-active > .el-submenu__title {
2026-04-03 11:00:33 +08:00
color: var(--ccdi-primary);
border-bottom: 2px solid var(--ccdi-primary);
}
// 下拉菜单图标
.el-submenu__icon-arrow {
2026-03-02 19:18:45 +08:00
margin-left: 4px;
}
}
}
}
.info-card {
margin-bottom: 16px;
:deep(.el-card__body) {
padding: 20px;
}
}
.info-header {
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
.info-content {
.info-row {
display: flex;
gap: 32px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.info-item {
display: flex;
align-items: center;
font-size: 14px;
.label {
color: #606266;
min-width: 100px;
font-weight: 500;
}
.value {
color: #303133;
font-weight: 400;
&.warning-count {
color: #e6a23c;
font-weight: 600;
}
}
}
}
.content-card {
margin-bottom: 16px;
:deep(.el-card__body) {
padding: 0;
.el-tabs {
height: 100%;
.el-tabs__content {
padding: 20px;
}
}
}
}
.action-bar {
display: flex;
justify-content: center;
gap: 12px;
padding: 16px;
background: #ffffff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
.el-button {
padding: 10px 20px;
i {
margin-right: 4px;
}
}
}
// 响应式设计
@media (max-width: 768px) {
.dpc-detail-container {
padding: 8px;
}
.detail-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.header-right {
width: 100%;
margin-top: 12px;
.nav-menu {
width: 100%;
display: flex;
justify-content: flex-start;
.el-menu-item,
.el-submenu {
flex: 1;
text-align: center;
padding: 0 8px;
font-size: 13px;
}
}
}
2026-03-02 19:18:45 +08:00
}
.info-content {
.info-row {
flex-direction: column;
gap: 8px;
.info-item {
.label {
min-width: auto;
}
}
}
}
.action-bar {
flex-wrap: wrap;
.el-button {
width: 100%;
}
}
}
// 下拉菜单弹窗样式
::v-deep .el-menu--popup {
min-width: 140px;
.el-menu-item {
font-size: 14px;
&:hover {
background-color: #f5f7fa;
}
&.is-active {
color: #1890ff;
background-color: #e6f7ff;
}
}
}
2026-03-02 19:18:45 +08:00
</style>