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

778 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>
</div>
<p class="update-time">
最后更新时间{{ formatUpdateTime(projectInfo.updateTime) }}
</p>
</div>
</div>
<div class="header-right">
<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"
>
<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>
</div>
</div>
<!-- 动态组件渲染区域 -->
<component
:is="currentComponent"
:project-id="projectId"
:project-info="projectInfo"
@menu-change="handleMenuChange"
@refresh-project="handleRefreshProject"
@data-uploaded="handleDataUploaded"
@name-selected="handleNameSelected"
@generate-report="handleGenerateReport"
@fetch-bank-info="handleFetchBankInfo"
@evidence-confirm="handleEvidenceConfirm"
@open-detail-query="handleOpenDetailQuery"
:detail-query-prefill="detailQueryPrefill"
/>
<evidence-confirm-dialog
:visible.sync="evidenceConfirmVisible"
:payload="evidencePayload"
@saved="handleEvidenceSaved"
/>
<evidence-drawer
ref="evidenceDrawer"
:visible.sync="evidenceDrawerVisible"
:project-id="projectId"
/>
</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";
import EvidenceConfirmDialog from "./components/detail/EvidenceConfirmDialog";
import EvidenceDrawer from "./components/detail/EvidenceDrawer";
import { getProject } from "@/api/ccdiProject";
export default {
name: "ProjectDetail",
components: {
UploadData,
ParamConfig,
PreliminaryCheck,
SpecialCheck,
DetailQuery,
EvidenceConfirmDialog,
EvidenceDrawer,
},
data() {
return {
// 当前激活的菜单项索引
activeTab: "upload",
// 当前显示的组件名称
currentComponent: "UploadData",
// 项目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",
},
evidenceConfirmVisible: false,
evidenceDrawerVisible: false,
evidencePayload: {},
detailQueryPrefill: null,
projectStatusPollingTimer: null,
projectStatusPollingInterval: 1000,
projectStatusPollingLoading: false,
};
},
computed: {
isProjectArchived() {
return String(this.projectInfo.projectStatus) === "2";
},
},
watch: {
"$route.params.projectId"(newId) {
this.stopProjectStatusPolling();
this.projectStatusPollingLoading = false;
if (newId) {
this.projectId = newId;
this.projectInfo.projectId = newId;
this.initActiveTabFromRoute();
this.initPageData();
}
},
"$route.query.tab"() {
this.initActiveTabFromRoute();
},
"projectInfo.projectStatus"() {
this.syncProjectStatusPolling();
const accessibleTab = this.resolveAccessibleTab(this.activeTab);
if (accessibleTab !== this.activeTab) {
this.setActiveTab(accessibleTab);
this.syncRouteTab(accessibleTab);
}
},
},
created() {
// 初始化页面数据
this.initActiveTabFromRoute();
this.initPageData();
this.$root.$on("ccdi-evidence-confirm", this.handleEvidenceConfirm);
},
beforeDestroy() {
this.$root.$off("ccdi-evidence-confirm", this.handleEvidenceConfirm);
this.stopProjectStatusPolling();
},
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);
}
},
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,
},
});
},
/** 初始化页面数据 */
initPageData() {
return this.fetchProjectDetail();
},
async fetchProjectDetail(options = {}) {
const { silent = false } = options;
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();
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;
}
},
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,
});
},
/** 格式化更新时间 */
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", // 打标中
};
return statusMap[status] || "info";
},
/** 获取状态标签 */
getStatusLabel(status) {
const statusMap = {
0: "进行中",
1: "已完成",
2: "已归档",
3: "打标中",
};
return statusMap[status] || "未知";
},
/** 获取配置类型标签文字 */
getConfigTypeLabel(configType) {
const configTypeMap = {
"default": "默认配置",
"custom": "自定义配置"
}
return configTypeMap[configType] || "默认配置"
},
/** 获取配置类型标签样式 */
getConfigTypeStyle(configType) {
const styleMap = {
"default": "info", // 蓝色
"custom": "warning" // 橙色
}
return styleMap[configType] || "info"
},
/** 标签页切换 */
handleTabChange(tab) {
console.log("切换到标签页:", tab.name);
},
/** 返回列表页 */
handleBack() {
this.$router.push("/ccdiProject");
},
/** 菜单选择事件 */
handleMenuSelect(index) {
if (this.isArchiveLockedTab(index)) {
return;
}
console.log("菜单选择:", index);
this.setActiveTab(index);
},
/** UploadData 组件:菜单切换 */
handleMenuChange({ key, route }) {
console.log("切换到菜单:", key, route);
// 直接触发菜单选择
this.handleMenuSelect(route);
},
handleOpenDetailQuery(payload = {}) {
this.detailQueryPrefill = {
...payload,
nonce: Date.now(),
};
this.setActiveTab("detail");
this.syncRouteTab("detail");
},
/** 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() {
this.initPageData();
this.$message.success("刷新成功");
},
handleRefreshProject() {
this.initPageData();
},
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();
}
});
},
/** 导出报告 */
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;
background: var(--ccdi-page-bg);
min-height: calc(100vh - 84px);
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 16px 20px;
background: #ffffff;
border: 1px solid var(--ccdi-border);
border-radius: 14px;
box-shadow: var(--ccdi-shadow);
.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;
font-weight: 700;
color: var(--ccdi-text-primary);
margin-right: 5px;
}
}
.update-time {
margin: 0;
font-size: 13px;
color: var(--ccdi-text-muted);
}
}
}
.header-right {
display: flex;
align-items: center;
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 {
font-size: 14px;
color: var(--ccdi-text-secondary);
padding: 0 16px;
height: 40px;
line-height: 40px;
border-bottom: 2px solid transparent;
border-radius: 10px 10px 0 0;
&:hover {
background-color: #f7fafd;
color: var(--ccdi-text-primary);
}
}
// 子菜单容器高度统一
.el-submenu {
height: 40px;
line-height: 40px;
}
// 激活状态:底部下划线 + 蓝色文字
.el-menu-item.is-active {
color: var(--ccdi-primary);
border-bottom: 2px solid var(--ccdi-primary);
background-color: transparent;
}
// 下拉菜单激活状态
.el-submenu.is-active > .el-submenu__title {
color: var(--ccdi-primary);
border-bottom: 2px solid var(--ccdi-primary);
}
// 下拉菜单图标
.el-submenu__icon-arrow {
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;
}
}
}
}
.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;
}
}
}
</style>