| {{ row.unitName }} |
@@ -299,7 +265,7 @@ function previewUnitReport(unit: string) {
{{ row.notPassProjectNum }} |
{{ row.usageRate }} |
@@ -312,11 +278,11 @@ function previewUnitReport(unit: string) {
-
- 当前暂无指标使用率低于 70% 的项目。
+
+ 当前暂无指标使用率低于 60% 的项目。
@@ -330,7 +296,7 @@ function previewUnitReport(unit: string) {
| {{ row.abbreviation || row.unitName }} |
@@ -344,11 +310,11 @@ function previewUnitReport(unit: string) {
当前暂无单位项目指标使用数据。
@@ -366,7 +332,7 @@ function previewUnitReport(unit: string) {
| {{ row.abbreviation || row.unitName }} |
@@ -381,29 +347,29 @@ function previewUnitReport(unit: string) {
- 指标总数:{{ store.allMetrics.length }}
- 使用率低于70%:{{ lowUsageMetricCount }}
+ 指标总数:{{ monitoring.allMetrics.length }}
+ 使用率低于60%:{{ lowUsageMetricCount }}
-
+
当前暂无指标分析数据。
-
- | {{ row.ProjectName || row.projectName }} |
+
+ | {{ row.projectName }} |
{{
- row.IsAbnormal || row.isAbnormal ? "异常" : "正常"
+ row.isAbnormal ? "异常" : "正常"
}}
|
- {{ row.NowData ?? "-" }} |
- {{ row.YesterdayData ?? "-" }} |
- {{ row.ThreeDaysAgoData ?? "-" }} |
- {{ row.WeekAgoData ?? "-" }} |
- {{ row.MonthAgoData ?? "-" }} |
- {{ row.ThreeMonthsAgoData ?? "-" }} |
+ {{ row.now || "-" }} |
+ {{ row.yesterday || "-" }} |
+ {{ row.threeDaysAgo || "-" }} |
+ {{ row.weekAgo || "-" }} |
+ {{ row.monthAgo || "-" }} |
+ {{ row.threeMonthsAgo || "-" }} |
diff --git a/next/src/components/IndicatorAnalysis.vue b/next/src/components/features/monitoring/IndicatorAnalysis.vue
similarity index 97%
rename from next/src/components/IndicatorAnalysis.vue
rename to next/src/components/features/monitoring/IndicatorAnalysis.vue
index 7de7d9b..9dba55a 100644
--- a/next/src/components/IndicatorAnalysis.vue
+++ b/next/src/components/features/monitoring/IndicatorAnalysis.vue
@@ -4,7 +4,7 @@
* 每行固定展示项目覆盖量、使用率和准确率三条进度,用于集团页与公司页复用。
*/
import { computed } from "vue";
-import type { MetricVM } from "../types/dashboardTypes";
+import type { MetricVM } from "@/types/dashboard";
const props = withDefaults(
defineProps<{ metrics: MetricVM[]; limit?: number }>(),
diff --git a/next/src/components/features/monitoring/KpiCard.test.ts b/next/src/components/features/monitoring/KpiCard.test.ts
new file mode 100644
index 0000000..f0eb517
--- /dev/null
+++ b/next/src/components/features/monitoring/KpiCard.test.ts
@@ -0,0 +1,12 @@
+// @vitest-environment jsdom
+import { describe, expect, it } from "vitest";
+import { mount } from "@vue/test-utils";
+import KpiCard from "./KpiCard.vue";
+
+describe("KpiCard", () => {
+ it("仅在可点击时发出选择事件", async () => {
+ const wrapper = mount(KpiCard, { props: { label: "项目数", value: 12, clickable: true } });
+ await wrapper.trigger("click");
+ expect(wrapper.emitted("select")).toHaveLength(1);
+ });
+});
diff --git a/next/src/components/KpiCard.vue b/next/src/components/features/monitoring/KpiCard.vue
similarity index 100%
rename from next/src/components/KpiCard.vue
rename to next/src/components/features/monitoring/KpiCard.vue
diff --git a/next/src/components/MetricStrip.vue b/next/src/components/features/monitoring/MetricStrip.vue
similarity index 93%
rename from next/src/components/MetricStrip.vue
rename to next/src/components/features/monitoring/MetricStrip.vue
index c91f70c..9456b2c 100644
--- a/next/src/components/MetricStrip.vue
+++ b/next/src/components/features/monitoring/MetricStrip.vue
@@ -1,6 +1,6 @@
diff --git a/next/src/components/features/project/ProjectMetricSnapshotModal.vue b/next/src/components/features/project/ProjectMetricSnapshotModal.vue
new file mode 100644
index 0000000..4bd2ede
--- /dev/null
+++ b/next/src/components/features/project/ProjectMetricSnapshotModal.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 当前日期暂无指标数据。
+
+
+
+
+ {{ metric.name }}
+ 异常
+
+ {{ metric.value || "-" }}
+
+
+
+
+
+
+
diff --git a/next/src/composables/useDrilldownActions.test.ts b/next/src/composables/useDrilldownActions.test.ts
new file mode 100644
index 0000000..4d85ce8
--- /dev/null
+++ b/next/src/composables/useDrilldownActions.test.ts
@@ -0,0 +1,63 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { useDrilldownActions } from "./useDrilldownActions";
+
+const mocks = vi.hoisted(() => ({
+ filters: {
+ apiType: 1,
+ apiRange: 0,
+ setFilters: vi.fn(),
+ },
+ monitoring: {
+ currentUnitId: "unit-1",
+ allMetrics: [{
+ name: "隐患排查",
+ field: "Danger",
+ projects: 10,
+ projectUsageNum: 8,
+ usageRate: 80,
+ accuracyRate: 90,
+ frequency: 0,
+ sortIndex: 1,
+ }],
+ },
+ drilldown: {
+ selectedMetric: "",
+ selectedMetricVM: null,
+ openMetric: vi.fn(),
+ openMoveReport: vi.fn(),
+ openIndicatorAnalysis: vi.fn(),
+ openDialog: vi.fn(),
+ closeDialog: vi.fn(),
+ },
+}));
+
+vi.mock("@/stores/filterStore", () => ({ useFilterStore: () => mocks.filters }));
+vi.mock("@/stores/monitoringStore", () => ({ useMonitoringStore: () => mocks.monitoring }));
+vi.mock("@/stores/drilldownStore", () => ({ useDrilldownStore: () => mocks.drilldown }));
+
+describe("useDrilldownActions", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mocks.drilldown.selectedMetric = "";
+ });
+
+ it("使用当前筛选和单位打开指标穿透", async () => {
+ const actions = useDrilldownActions();
+ await actions.openMetric("隐患排查");
+
+ expect(mocks.drilldown.openMetric).toHaveBeenCalledWith(
+ mocks.monitoring.allMetrics[0],
+ "unit-1",
+ 1,
+ 0,
+ );
+ });
+
+ it("筛选变化时先关闭弹窗再更新筛选", () => {
+ const actions = useDrilldownActions();
+ actions.changeFilters("quality", "new");
+
+ expect(mocks.drilldown.closeDialog).toHaveBeenCalledBefore(mocks.filters.setFilters);
+ expect(mocks.filters.setFilters).toHaveBeenCalledWith("quality", "new");
+ });
+});
diff --git a/next/src/composables/useDrilldownActions.ts b/next/src/composables/useDrilldownActions.ts
new file mode 100644
index 0000000..e262f6d
--- /dev/null
+++ b/next/src/composables/useDrilldownActions.ts
@@ -0,0 +1,47 @@
+import { computed } from "vue";
+import { useDrilldownStore, type DialogKind } from "@/stores/drilldownStore";
+import { useFilterStore } from "@/stores/filterStore";
+import { useMonitoringStore } from "@/stores/monitoringStore";
+import type { DimensionKey, SystemKey } from "@/types/dashboard";
+
+/** 协调筛选、监控与穿透 Store,不额外保存业务状态。 */
+export function useDrilldownActions() {
+ const filters = useFilterStore();
+ const monitoring = useMonitoringStore();
+ const drilldown = useDrilldownStore();
+
+ const selectedMetricVM = computed(() =>
+ drilldown.selectedMetricVM
+ || monitoring.allMetrics.find((item) => item.name === drilldown.selectedMetric)
+ || monitoring.allMetrics[0],
+ );
+
+ async function openMetric(name: string) {
+ const metric = monitoring.allMetrics.find((item) => item.name === name);
+ if (!metric?.field) return;
+ await drilldown.openMetric(metric, monitoring.currentUnitId, filters.apiType, filters.apiRange);
+ }
+
+ async function openMoveReport() {
+ await drilldown.openMoveReport(filters.apiType, filters.apiRange, monitoring.allMetrics);
+ }
+
+ function openDialog(kind: Exclude ) {
+ drilldown.openDialog(kind);
+ }
+
+ function changeFilters(system: SystemKey, dimension: DimensionKey) {
+ drilldown.closeDialog();
+ filters.setFilters(system, dimension);
+ }
+
+ return {
+ selectedMetricVM,
+ openMetric,
+ openMoveReport,
+ openIndicatorAnalysis: drilldown.openIndicatorAnalysis,
+ openDialog,
+ closeDialog: drilldown.closeDialog,
+ changeFilters,
+ };
+}
diff --git a/next/src/composables/useMonitoringPage.ts b/next/src/composables/useMonitoringPage.ts
new file mode 100644
index 0000000..62a6c9f
--- /dev/null
+++ b/next/src/composables/useMonitoringPage.ts
@@ -0,0 +1,25 @@
+import { onScopeDispose, watch, type MaybeRefOrGetter, toValue } from "vue";
+import { useFilterStore } from "@/stores/filterStore";
+import { useMonitoringStore } from "@/stores/monitoringStore";
+
+/** 将路由范围与筛选条件连接到监控请求,并在页面卸载时取消未完成请求。 */
+export function useMonitoringPage(unitId: MaybeRefOrGetter, unitName: MaybeRefOrGetter = "") {
+ const filters = useFilterStore();
+ const monitoring = useMonitoringStore();
+
+ function reload() {
+ return monitoring.load(filters.apiType, filters.apiRange, toValue(unitId), toValue(unitName));
+ }
+
+ watch(
+ [() => filters.apiType, () => filters.apiRange, () => toValue(unitId), () => toValue(unitName)],
+ ([type, range, nextUnitId, nextUnitName], _, onCleanup) => {
+ monitoring.load(type, range, nextUnitId, nextUnitName);
+ onCleanup(() => monitoring.cancel());
+ },
+ { immediate: true },
+ );
+
+ onScopeDispose(() => monitoring.cancel());
+ return { monitoring, reload };
+}
diff --git a/next/src/composables/useProjectPage.ts b/next/src/composables/useProjectPage.ts
new file mode 100644
index 0000000..5a30d6c
--- /dev/null
+++ b/next/src/composables/useProjectPage.ts
@@ -0,0 +1,25 @@
+import { onScopeDispose, watch, type MaybeRefOrGetter, toValue } from "vue";
+import { useFilterStore } from "@/stores/filterStore";
+import { useProjectStore } from "@/stores/projectStore";
+
+/** 项目路由参数或系统筛选改变时刷新详情,并取消失效请求。 */
+export function useProjectPage(unitId: MaybeRefOrGetter, projectId: MaybeRefOrGetter) {
+ const filters = useFilterStore();
+ const project = useProjectStore();
+
+ function reload() {
+ return project.load(filters.apiType, toValue(unitId), toValue(projectId));
+ }
+
+ watch(
+ [() => filters.apiType, () => toValue(unitId), () => toValue(projectId)],
+ ([type, nextUnitId, nextProjectId], _, onCleanup) => {
+ project.load(type, nextUnitId, nextProjectId);
+ onCleanup(() => project.cancel());
+ },
+ { immediate: true },
+ );
+
+ onScopeDispose(() => project.cancel());
+ return { project, reload };
+}
diff --git a/next/src/layouts/DashboardLayout.vue b/next/src/layouts/DashboardLayout.vue
new file mode 100644
index 0000000..6fe32cd
--- /dev/null
+++ b/next/src/layouts/DashboardLayout.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+ DATA GOVERNANCE COMMAND CENTER 集团数据治理项目监控大屏
+
+
+ 集团总览›{{ pageName }}当前维度:{{ filters.dimension === "all" ? "所有项目" : "新开工项目" }}当前系统:{{ SYSTEMS.find((item) => item.key === filters.system)?.label }}
+
+
+
+
diff --git a/next/src/main.ts b/next/src/main.ts
index acf0c19..0c36ae4 100644
--- a/next/src/main.ts
+++ b/next/src/main.ts
@@ -4,14 +4,14 @@
*/
import { createApp } from "vue";
import { createPinia } from "pinia";
-import App from "./App.vue";
-import router from "./router";
+import App from "@/App.vue";
+import router from "@/router";
import "../../res/css/indexV2.base.css";
import "../../res/css/indexV2.cards.css";
import "../../res/css/indexV2.panels.css";
import "../../res/css/indexV2.modal-table.css";
import "../../res/css/indexV2.detail.css";
import "../../res/css/indexV2.responsive.css";
-import "./styles/next.css";
+import "@/assets/styles/next.css";
createApp(App).use(createPinia()).use(router).mount("#app");
diff --git a/next/src/views/EnterpriseView.vue b/next/src/pages/EnterprisePage.vue
similarity index 58%
rename from next/src/views/EnterpriseView.vue
rename to next/src/pages/EnterprisePage.vue
index dad3b7f..aee4035 100644
--- a/next/src/views/EnterpriseView.vue
+++ b/next/src/pages/EnterprisePage.vue
@@ -1,93 +1,32 @@
- 正在获取企业监控数据...
-
- {{ store.error }}
- |