代码整合
This commit is contained in:
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
+5
-4
@@ -65,12 +65,13 @@ next/
|
||||
|
||||
### 状态管理
|
||||
|
||||
`stores/dashboard.ts` 是唯一业务状态入口,主要状态分为:
|
||||
`stores/dashboardStore.ts` 是唯一业务状态入口,主要状态分为:
|
||||
|
||||
- 筛选状态:`system`、`dimension`、`allowedSystems`。
|
||||
- 监控状态:集团或当前公司的 `monitoring`、`loading`、`error`。
|
||||
- 项目状态:`projectData`、`projectLoading`、`projectError`。
|
||||
- 穿透状态:`dialog`、`selectedMetric`、`metricAnalysis`、`metricLoading`。
|
||||
- 指标列表弹窗:集团和企业复用 `allMetrics`,使用率低于 70% 的指标显示红色告警卡片。
|
||||
- 动不动报告状态:`moveReport`、`moveReportLoading`、`moveReportError`。
|
||||
|
||||
集团和公司监控共用 `loadMonitoring`。新请求开始时会取消旧请求,并通过递增请求序号阻止旧响应覆盖最新筛选结果。项目详情和指标穿透分别使用独立 Action,避免页面组件自行维护请求状态。
|
||||
@@ -89,7 +90,7 @@ next/
|
||||
|
||||
集团监控接口要求查询字符串始终包含 `unitId=`,即使值为空也不能省略。`services/http.ts` 因此只过滤 `undefined`,显式空字符串会被保留。
|
||||
|
||||
`adapters/dashboard.ts` 负责兼容 PascalCase/camelCase 字段、非法数字、布尔字符串和空数组,并集中维护上线率口径:
|
||||
`adapters/dashboardAdapter.ts` 负责兼容 PascalCase/camelCase 字段、非法数字、布尔字符串和空数组,并集中维护上线率口径:
|
||||
|
||||
```text
|
||||
(已关联项目数 + 预立项项目数) / (主数据项目数 + 预立项项目数 - 申请不用项目数)
|
||||
@@ -108,7 +109,7 @@ next/
|
||||
### 安全与部署边界
|
||||
|
||||
- 浏览器只访问同源 `/qhse_webapi/`,由开发服务器或生产网关转发到正式接口。
|
||||
- 接口 token 固定在 `config/dashboard.ts`,由 `services/http.ts` 写入每个请求的 `token` 请求头。
|
||||
- 接口 token 固定在 `config/dashboardConfig.ts`,由 `services/http.ts` 写入每个请求的 `token` 请求头。
|
||||
- token 会进入浏览器构建产物,只适用于当前受控内网部署环境。
|
||||
- 生产环境仍需将 `/qhse_webapi/` 反向代理到 `https://qhse.cncecoa.com/qhse_webapi`,但无需在网关重复注入 token。
|
||||
- 网关应配置上游超时、请求 ID 透传、访问日志和 5xx 告警。
|
||||
@@ -143,7 +144,7 @@ npm run build
|
||||
|
||||
## 开发约定
|
||||
|
||||
- 新接口先在 `config/dashboard.ts` 注册,再通过 `services/http.ts` 请求。
|
||||
- 新接口先在 `config/dashboardConfig.ts` 注册,再通过 `services/http.ts` 请求。
|
||||
- 后端响应必须先经过 `adapters/`,页面不得直接依赖原始字段命名。
|
||||
- 跨页面状态进入 Pinia;仅组件内部使用的展示状态保留在组件中。
|
||||
- 通用展示提取到 `components/`,集团、公司、项目编排保留在 `views/`。
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { EXTERNAL_LINKS, SYSTEMS, type DimensionKey, type SystemKey } from "./config/dashboard";
|
||||
import { useDashboardStore } from "./stores/dashboard";
|
||||
import { EXTERNAL_LINKS, SYSTEMS, type DimensionKey, type SystemKey } from "./config/dashboardConfig";
|
||||
import { useDashboardStore } from "./stores/dashboardStore";
|
||||
|
||||
const DrillModal = defineAsyncComponent(() => import("./components/DrillModal.vue"));
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** 数据适配层回归测试:锁定接口兼容规则和核心统计口径。 */
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeMonitoring, normalizeMoveReport } from "./dashboard";
|
||||
import { normalizeMonitoring, normalizeMoveReport } from "./dashboardAdapter";
|
||||
|
||||
describe("normalizeMonitoring", () => {
|
||||
it("按业务口径归一化集团汇总、单位和指标数据", () => {
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
SummaryVM,
|
||||
UnitAnalysisVM,
|
||||
UnitMetricUsageVM
|
||||
} from "../types/dashboard";
|
||||
} from "../types/dashboardTypes";
|
||||
|
||||
type Raw = Record<string, unknown>;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 全局穿透弹窗。
|
||||
* 根据 store.dialog 渲染上线统计、未达标清单、单位使用率或指标穿透,避免每个页面维护独立弹窗状态。
|
||||
* 根据 store.dialog 渲染上线统计、指标卡片、报告或指标穿透,避免每个页面维护独立弹窗状态。
|
||||
*/
|
||||
import { computed } from "vue";
|
||||
import type { EChartsOption } from "echarts";
|
||||
import { useDashboardStore } from "../stores/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboardStore";
|
||||
import EChart from "./EChart.vue";
|
||||
import { normalizeMetric, numberValue } from "../adapters/dashboard";
|
||||
import { EXTERNAL_LINKS, UNIT_REPORT_FILES } from "../config/dashboard";
|
||||
import type { MasterRecordVM } from "../types/dashboard";
|
||||
import { normalizeMetric, numberValue } from "../adapters/dashboardAdapter";
|
||||
import { EXTERNAL_LINKS, UNIT_REPORT_FILES } from "../config/dashboardConfig";
|
||||
import type { MasterRecordVM } from "../types/dashboardTypes";
|
||||
|
||||
const store = useDashboardStore();
|
||||
const raw = computed(() => store.metricAnalysis || {});
|
||||
@@ -64,6 +64,11 @@ const unitChart = computed<EChartsOption>(() => ({
|
||||
],
|
||||
}));
|
||||
const title = computed(() => {
|
||||
if (store.dialog === "indicatorAnalysis") {
|
||||
return store.currentUnitId
|
||||
? `${store.currentUnitName || "企业"} · 指标分析`
|
||||
: "集团指标分析";
|
||||
}
|
||||
const labels = {
|
||||
online: "集团项目上线统计",
|
||||
moveReport: "数据治理“动不动”分析报告",
|
||||
@@ -91,6 +96,9 @@ const onlineRateTotal = computed(() => {
|
||||
const moveTableMinWidth = computed(() =>
|
||||
Math.max(980, 420 + store.moveReport.metrics.length * 138),
|
||||
);
|
||||
const lowUsageMetricCount = computed(() =>
|
||||
store.allMetrics.filter((item) => item.usageRate < 70).length,
|
||||
);
|
||||
|
||||
const sum = (field: keyof MasterRecordVM) =>
|
||||
store.monitoring.records.reduce(
|
||||
@@ -308,7 +316,7 @@ function previewUnitReport(unit: string) {
|
||||
class="panel drill-panel"
|
||||
>
|
||||
<div v-if="!store.monitoring.failProjects.length" class="empty-state">
|
||||
当前暂无指标使用率低于 80% 的项目。
|
||||
当前暂无指标使用率低于 70% 的项目。
|
||||
</div>
|
||||
<div v-else class="table-wrap">
|
||||
<table>
|
||||
@@ -372,6 +380,50 @@ function previewUnitReport(unit: string) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-else-if="store.dialog === 'indicatorAnalysis'"
|
||||
class="indicator-card-panel"
|
||||
>
|
||||
<div class="indicator-card-summary">
|
||||
<span>指标总数:{{ store.allMetrics.length }}</span>
|
||||
<span>使用率低于70%:{{ lowUsageMetricCount }}</span>
|
||||
</div>
|
||||
<div v-if="!store.allMetrics.length" class="empty-state">
|
||||
当前暂无指标分析数据。
|
||||
</div>
|
||||
<div v-else class="indicator-card-grid">
|
||||
<button
|
||||
v-for="metricItem in store.allMetrics"
|
||||
:key="metricItem.field || metricItem.name"
|
||||
type="button"
|
||||
class="indicator-summary-card"
|
||||
:class="{ 'low-usage': metricItem.usageRate < 70 }"
|
||||
@click="store.openMetric(metricItem.name)"
|
||||
>
|
||||
<div class="indicator-summary-head">
|
||||
<h3 :title="metricItem.name">{{ metricItem.name }}</h3>
|
||||
<span class="indicator-usage-state">
|
||||
{{ metricItem.usageRate < 70 ? "低于70%" : "正常" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="indicator-summary-stats">
|
||||
<div>
|
||||
<span>项目数</span>
|
||||
<strong>{{ metricItem.projects }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>使用率</span>
|
||||
<strong>{{ metricItem.usageRate.toFixed(1) }}%</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>准确率</span>
|
||||
<strong>{{ metricItem.accuracyRate.toFixed(1) }}%</strong>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<template v-else-if="metric">
|
||||
<section class="drill-hero">
|
||||
<article class="drill-title-card">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 每行固定展示项目覆盖量、使用率和准确率三条进度,用于集团页与公司页复用。
|
||||
*/
|
||||
import { computed } from "vue";
|
||||
import type { MetricVM } from "../types/dashboard";
|
||||
import type { MetricVM } from "../types/dashboardTypes";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{ metrics: MetricVM[]; limit?: number }>(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
/** 顶部重点指标条,点击指标后由父页面发起集团或公司级穿透。 */
|
||||
import type { MetricVM } from "../types/dashboard";
|
||||
import type { MetricVM } from "../types/dashboardTypes";
|
||||
defineProps<{ metrics: MetricVM[] }>();
|
||||
defineEmits<{ select: [name: string] }>();
|
||||
</script>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* 同源 GET 请求封装。
|
||||
* 负责查询参数编码、请求 ID、超时/取消和业务错误归一化,不承载具体业务字段转换。
|
||||
*/
|
||||
import type { ApiEnvelope } from "../types/dashboard";
|
||||
import { API_TOKEN } from "../config/dashboard";
|
||||
import type { ApiEnvelope } from "../types/dashboardTypes";
|
||||
import { API_TOKEN } from "../config/dashboardConfig";
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { API_ENDPOINTS, SYSTEMS, type DimensionKey, type SystemKey } from "../config/dashboard";
|
||||
import { normalizeMonitoring, normalizeMoveReport } from "../adapters/dashboard";
|
||||
import { API_ENDPOINTS, SYSTEMS, type DimensionKey, type SystemKey } from "../config/dashboardConfig";
|
||||
import { normalizeMonitoring, normalizeMoveReport } from "../adapters/dashboardAdapter";
|
||||
import { getJson } from "../services/http";
|
||||
import type { DialogKind, MasterRecordVM, MonitoringVM, MoveReportVM } from "../types/dashboard";
|
||||
import type { DialogKind, MasterRecordVM, MonitoringVM, MoveReportVM } from "../types/dashboardTypes";
|
||||
|
||||
const emptyMonitoring = (): MonitoringVM => normalizeMonitoring({});
|
||||
const emptyMoveReport = (): MoveReportVM => ({ metrics: [], rows: [] });
|
||||
@@ -109,6 +109,13 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||
}
|
||||
}
|
||||
|
||||
function openIndicatorAnalysis() {
|
||||
// 集团和企业共用当前监控数据,弹窗不重复请求;点击卡片后再按层级调用指标穿透接口。
|
||||
metricError.value = "";
|
||||
metricAnalysis.value = null;
|
||||
dialog.value = "indicatorAnalysis";
|
||||
}
|
||||
|
||||
async function openMoveReport() {
|
||||
// 动态表头和企业数据必须使用同一组 type/range 快照,防止切换筛选时新旧结果混用。
|
||||
const request = ++moveReportRequest;
|
||||
@@ -160,6 +167,6 @@ export const useDashboardStore = defineStore("dashboard", () => {
|
||||
allowedSystems, system, dimension, loading, error, monitoring, currentUnitId, currentUnitName,
|
||||
projectData, projectLoading, projectError, dialog, selectedMetric, selectedMetricVM, metricAnalysis,
|
||||
metricLoading, metricError, moveReport, moveReportLoading, moveReportError, allMetrics,
|
||||
loadMonitoring, loadProject, openMetric, openMoveReport, closeDialog, setFilters, findRecord
|
||||
loadMonitoring, loadProject, openMetric, openIndicatorAnalysis, openMoveReport, closeDialog, setFilters, findRecord
|
||||
};
|
||||
});
|
||||
@@ -55,6 +55,96 @@
|
||||
.move-report-table td:first-child { width: 112px; min-width: 112px; }
|
||||
.move-report-table tbody tr:first-child td { color: #ffffff; font-weight: 900; background: rgba(73, 214, 255, 0.14); }
|
||||
|
||||
/* 指标分析弹窗使用响应式卡片网格,低使用率指标采用明确的红色告警背景。 */
|
||||
.indicator-card-panel { min-height: 100%; }
|
||||
.indicator-card-summary {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 18px;
|
||||
margin-bottom: 12px;
|
||||
color: #bfeaff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.indicator-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
}
|
||||
.indicator-summary-card {
|
||||
min-width: 0;
|
||||
min-height: 138px;
|
||||
padding: 15px;
|
||||
border: 1px solid rgba(91, 180, 255, 0.38);
|
||||
border-radius: 6px;
|
||||
color: #effaff;
|
||||
background: #08375d;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.indicator-summary-card:hover { border-color: #72d9ff; background: #0b4974; }
|
||||
.indicator-summary-card.low-usage {
|
||||
/* border-color: #ff7b87; */
|
||||
background: #0B4974;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 16px rgba(255, 75, 91, 0.16);
|
||||
}
|
||||
.indicator-summary-card.low-usage:hover { background: #86293a; }
|
||||
.indicator-summary-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
min-height: 48px;
|
||||
}
|
||||
.indicator-summary-head h3 {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
color: #ffffff;
|
||||
font-size: 16px;
|
||||
line-height: 1.45;
|
||||
letter-spacing: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.indicator-usage-state {
|
||||
flex: 0 0 auto;
|
||||
padding: 3px 7px;
|
||||
border-radius: 4px;
|
||||
color: #bdf9e4;
|
||||
background: rgba(34, 139, 103, 0.46);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
.low-usage .indicator-usage-state { color: #ffffff; background: #b93246; }
|
||||
.indicator-summary-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid rgba(207, 239, 255, 0.16);
|
||||
}
|
||||
.indicator-summary-stats div { min-width: 0; }
|
||||
.indicator-summary-stats span {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: #b8d6e8;
|
||||
font-size: 11px;
|
||||
}
|
||||
.indicator-summary-stats strong {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #ffffff;
|
||||
font-family: var(--num);
|
||||
font-size: 17px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.low-usage .indicator-summary-stats span { color: #ffd6db; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DimensionKey, SystemKey } from "../config/dashboard";
|
||||
import type { DimensionKey, SystemKey } from "../config/dashboardConfig";
|
||||
|
||||
/** 接口统一响应外壳。 */
|
||||
export interface ApiEnvelope<T> {
|
||||
@@ -159,4 +159,4 @@ export interface AppFilters {
|
||||
dimension: DimensionKey;
|
||||
}
|
||||
|
||||
export type DialogKind = "online" | "moveReport" | "belowStandard" | "unitUsage" | "groupMetric" | "unitMetric" | null;
|
||||
export type DialogKind = "online" | "moveReport" | "belowStandard" | "unitUsage" | "indicatorAnalysis" | "groupMetric" | "unitMetric" | null;
|
||||
@@ -3,8 +3,7 @@
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import type { EChartsOption } from "echarts";
|
||||
import { EXTERNAL_LINKS } from "../config/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboardStore";
|
||||
import EChart from "../components/EChart.vue";
|
||||
import IndicatorAnalysis from "../components/IndicatorAnalysis.vue";
|
||||
import KpiCard from "../components/KpiCard.vue";
|
||||
@@ -120,13 +119,13 @@ onMounted(() => {
|
||||
label="项目上线率"
|
||||
:value="summary.proInfoText"
|
||||
:trend="`${summary.onlineRate.toFixed(2)}%`"
|
||||
description="上线项目/申请不用/应使用项目/项目总数"
|
||||
description="上线项目/申请关闭/应使用项目/项目总数"
|
||||
:progress="summary.onlineRate"
|
||||
clickable
|
||||
@select="store.dialog = 'online'"
|
||||
/>
|
||||
<KpiCard
|
||||
label="预立项项目数"
|
||||
label="待关联项目数"
|
||||
:value="summary.preProjectTotal"
|
||||
unit="个"
|
||||
:progress="summary.preProjectTotal / 2"
|
||||
@@ -134,7 +133,7 @@ onMounted(() => {
|
||||
@select="store.dialog = 'online'"
|
||||
/>
|
||||
<KpiCard
|
||||
label="待建项目数"
|
||||
label="待上线项目数"
|
||||
:value="summary.pendingTotal"
|
||||
unit="个"
|
||||
:progress="summary.pendingTotal"
|
||||
@@ -145,7 +144,7 @@ onMounted(() => {
|
||||
label="未达标项目数"
|
||||
:value="summary.failProjectNum"
|
||||
unit="个"
|
||||
description="指标使用率低于 80% 的项目"
|
||||
description="指标使用率低于 70% 的项目"
|
||||
:progress="100 - summary.failProjectNum"
|
||||
clickable
|
||||
@select="store.dialog = 'belowStandard'"
|
||||
@@ -233,13 +232,13 @@ onMounted(() => {
|
||||
按{{ unitName }}指标预警项目数、使用率和数据状态展示责任短板。
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
<button
|
||||
type="button"
|
||||
class="more-btn"
|
||||
:href="EXTERNAL_LINKS.enterpriseIndicatorMore"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>更多</a
|
||||
@click="store.openIndicatorAnalysis()"
|
||||
>
|
||||
更多
|
||||
</button>
|
||||
</div>
|
||||
<IndicatorAnalysis
|
||||
:metrics="store.allMetrics"
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import type { EChartsOption } from "echarts";
|
||||
import { EXTERNAL_LINKS } from "../config/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboardStore";
|
||||
import EChart from "../components/EChart.vue";
|
||||
import IndicatorAnalysis from "../components/IndicatorAnalysis.vue";
|
||||
import KpiCard from "../components/KpiCard.vue";
|
||||
@@ -114,13 +113,13 @@ onMounted(() => store.loadMonitoring());
|
||||
label="集团项目上线率"
|
||||
:value="summary.proInfoText"
|
||||
:trend="`${summary.onlineRate.toFixed(2)}%`"
|
||||
description="上线项目/申请不用/应使用项目/项目总数"
|
||||
description="上线项目/申请关闭/应使用项目/项目总数"
|
||||
:progress="summary.onlineRate"
|
||||
clickable
|
||||
@select="store.dialog = 'online'"
|
||||
/>
|
||||
<KpiCard
|
||||
label="预立项项目数"
|
||||
label="待关联项目数"
|
||||
:value="summary.preProjectTotal"
|
||||
unit="个"
|
||||
description="点击查看集团项目上线统计"
|
||||
@@ -129,7 +128,7 @@ onMounted(() => store.loadMonitoring());
|
||||
@select="store.dialog = 'online'"
|
||||
/>
|
||||
<KpiCard
|
||||
label="待建项目数"
|
||||
label="待上线项目数"
|
||||
:value="summary.pendingTotal"
|
||||
unit="个"
|
||||
description="点击查看集团项目上线统计"
|
||||
@@ -141,7 +140,7 @@ onMounted(() => store.loadMonitoring());
|
||||
label="未达标项目数"
|
||||
:value="summary.failProjectNum"
|
||||
unit="个"
|
||||
description="指标使用率低于 80% 的项目"
|
||||
description="指标使用率低于 70% 的项目"
|
||||
:progress="100 - summary.failProjectNum"
|
||||
clickable
|
||||
@select="store.dialog = 'belowStandard'"
|
||||
@@ -245,13 +244,13 @@ onMounted(() => store.loadMonitoring());
|
||||
<article class="panel risk-panel">
|
||||
<div class="panel-header">
|
||||
<h2 class="panel-title">指标分析</h2>
|
||||
<a
|
||||
<button
|
||||
type="button"
|
||||
class="more-btn"
|
||||
:href="EXTERNAL_LINKS.indicatorMore"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>更多</a
|
||||
@click="store.openIndicatorAnalysis()"
|
||||
>
|
||||
更多
|
||||
</button>
|
||||
</div>
|
||||
<IndicatorAnalysis
|
||||
:metrics="store.allMetrics"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/** 项目级页面:展示项目基本信息、治理指标历史值和异常状态。 */
|
||||
import { computed, onMounted } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useDashboardStore } from "../stores/dashboard";
|
||||
import { useDashboardStore } from "../stores/dashboardStore";
|
||||
|
||||
const store = useDashboardStore();
|
||||
const route = useRoute();
|
||||
|
||||
Reference in New Issue
Block a user