| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- <template>
- <div class="cockpit-health-panel">
- <CockpitTechTabs v-model="activeTab" :tabs="healthTabs" variant="panel" />
- <div class="cockpit-health-panel__tab-body">
- <div
- v-show="activeTab === 'healthy'"
- v-hasPermi="['home:health:btn']"
- class="cockpit-health-panel__pane cockpit-health-panel__pane--healthy"
- >
- <div class="cockpit-health-panel__subtitle">综合健康评分</div>
- <div class="cockpit-health-panel__body">
- <ul class="cockpit-health-panel__legend">
- <li
- v-for="item in legendItems"
- :key="item.name"
- class="cockpit-health-panel__legend-item"
- >
- <i :style="{ background: item.color }"></i>
- <span>{{ item.name }}</span>
- </li>
- </ul>
- <div
- ref="chartRef"
- v-loading="loading"
- class="cockpit-health-panel__chart"
- element-loading-background="rgba(5, 14, 35, 0.6)"
- ></div>
- </div>
- <CockpitHealthTable
- class="cockpit-health-panel__table"
- :table-data="tableData"
- :loading="loading"
- :datatime="datatime"
- :sort-config="sortConfig"
- @sort-change="handleSortChange"
- />
- </div>
- <div
- v-show="activeTab === 'abnormal'"
- v-hasPermi="['home:anomalyDetection:btn']"
- class="cockpit-health-panel__pane cockpit-health-panel__pane--abnormal"
- >
- <CockpitAbnormalPanel
- :maplistArr="maplistArr"
- :defaultdata="defaultdata"
- :datatime="datatime"
- :parent-opt="parentOpt"
- />
- </div>
- </div>
- </div>
- </template>
- <script>
- import * as echarts from "echarts";
- import { store } from "@/store";
- import { getHealthscoresWindList } from "@/api/healthAnalyse";
- import CockpitTechTabs from "./CockpitTechTabs.vue";
- import CockpitHealthTable from "./CockpitHealthTable.vue";
- import CockpitAbnormalPanel from "./CockpitAbnormalPanel.vue";
- import {
- aggregateHealthScoreDistribution,
- buildFieldCodeMap,
- buildFieldNameMap,
- mapHealthscoresWindList,
- filterEvaluatedRows,
- filterRowsByNodeFields,
- sortHealthTableData,
- } from "./cockpitHealthMapper";
- const HEALTH_TAB_PERMI = ["home:health:btn"];
- const ABNORMAL_TAB_PERMI = ["home:anomalyDetection:btn"];
- function hasTabPermi(permi) {
- const flags = Array.isArray(permi) ? permi : [];
- if (!flags.length) return true;
- const allPermission = "*:*:*";
- const permissions = store?.getters?.permissions || [];
- return permissions.some(
- (permission) => permission === allPermission || flags.includes(permission),
- );
- }
- export default {
- name: "CockpitHealthPanel",
- components: { CockpitTechTabs, CockpitHealthTable, CockpitAbnormalPanel },
- props: {
- maplistArr: { type: Object, default: () => ({}) },
- defaultdata: { type: Object, default: () => ({}) },
- parentOpt: { type: Array, default: () => [] },
- datatime: { type: String, default: "" },
- },
- data() {
- return {
- activeTab: "healthy",
- healthTabs: [
- { label: "健康", value: "healthy", permi: HEALTH_TAB_PERMI },
- { label: "异常", value: "abnormal", permi: ABNORMAL_TAB_PERMI },
- ],
- chart: null,
- loading: false,
- tableData: [],
- scoreDistribution: [],
- lastHealthList: [],
- sortConfig: { prop: "", order: "descending" },
- legendItems: [
- { name: "优", color: "#7ECF51" },
- { name: "良", color: "#2979ff" },
- { name: "中", color: "#EECB5F" },
- { name: "差", color: "#E16757" },
- ],
- };
- },
- watch: {
- maplistArr: {
- handler() {
- this.fetchHealthData();
- },
- deep: true,
- immediate: true,
- },
- datatime() {
- this.fetchHealthData();
- },
- parentOpt: {
- handler() {
- if (this.tableData.length) {
- this.applyHealthList(this.lastHealthList);
- }
- },
- deep: true,
- },
- activeTab(val) {
- if (val === "healthy") {
- this.$nextTick(() => {
- if (this.chart) {
- this.chart.resize();
- } else {
- this.initChart();
- }
- });
- }
- },
- healthTabs: {
- deep: true,
- handler() {
- this.syncActiveTabByPermi();
- },
- },
- scoreDistribution: {
- deep: true,
- handler() {
- this.renderChart();
- },
- },
- },
- mounted() {
- this.syncActiveTabByPermi();
- this.$nextTick(() => {
- this.initChart();
- });
- window.addEventListener("resize", this.handleResize);
- },
- beforeDestroy() {
- window.removeEventListener("resize", this.handleResize);
- if (this.chart) {
- this.chart.dispose();
- this.chart = null;
- }
- },
- methods: {
- syncActiveTabByPermi() {
- const visibleTabs = this.healthTabs.filter((tab) =>
- hasTabPermi(tab.permi),
- );
- if (!visibleTabs.length) return;
- if (!visibleTabs.some((tab) => tab.value === this.activeTab)) {
- this.activeTab = visibleTabs[0].value;
- }
- },
- getChartTheme() {
- const host =
- this.$refs.chartRef?.closest(".cockpit-dashboard-page") ||
- document.documentElement;
- const read = (name, fallback) =>
- getComputedStyle(host).getPropertyValue(name).trim() || fallback;
- return {
- labelColor: read("--cockpit-chart-label-text", "#ffffff"),
- labelLineColor: read(
- "--cockpit-chart-label-line",
- "rgba(255, 255, 255, 0.55)",
- ),
- };
- },
- initChart() {
- if (!this.$refs.chartRef) return;
- if (this.chart) {
- this.chart.dispose();
- }
- this.chart = echarts.init(this.$refs.chartRef);
- this.renderChart();
- },
- renderChart() {
- if (!this.chart) return;
- const { labelColor, labelLineColor } = this.getChartTheme();
- const chartData = this.scoreDistribution.length
- ? this.scoreDistribution
- : aggregateHealthScoreDistribution([]);
- this.chart.setOption(
- {
- tooltip: {
- trigger: "item",
- formatter: "{b}: {c}台 ({d}%)",
- },
- series: [
- {
- type: "pie",
- radius: ["42%", "72%"],
- center: ["55%", "50%"],
- avoidLabelOverlap: true,
- label: {
- show: true,
- overflow: "none",
- formatter: (params) => {
- if (!params.value) return "";
- return `{name|${params.name}}\n{value|${params.value}台}`;
- },
- rich: {
- name: {
- fontSize: 11,
- color: labelColor,
- lineHeight: 16,
- },
- value: {
- fontSize: 11,
- color: labelColor,
- lineHeight: 16,
- width: 72,
- overflow: "none",
- },
- },
- },
- emphasis: {
- label: {
- color: labelColor,
- overflow: "none",
- },
- },
- labelLine: {
- show: true,
- length: 6,
- length2: 12,
- smooth: true,
- lineStyle: { color: labelLineColor },
- },
- data: chartData,
- },
- ],
- },
- true,
- );
- },
- handleSortChange(sort) {
- this.sortConfig = sort;
- this.applyHealthList(this.lastHealthList);
- },
- applyHealthList(list) {
- const fieldNameMap = buildFieldNameMap(this.parentOpt);
- const fieldCodeMap = buildFieldCodeMap(this.parentOpt);
- const rows = mapHealthscoresWindList(list, fieldNameMap).map(
- (row, index) => {
- const raw = list[index] || {};
- const fieldCode =
- raw.fieldCode ||
- raw.codeNumber ||
- row.fieldCode ||
- fieldCodeMap[row.fieldName] ||
- "";
- return {
- ...row,
- fieldCode: fieldCode ? String(fieldCode) : "",
- };
- },
- );
- const scoped = filterRowsByNodeFields(rows, this.maplistArr);
- // 只展示已评估(有任意一台风机评分数据)的场站
- const evaluated = filterEvaluatedRows(scoped);
- // 按排序配置排序
- const sorted = sortHealthTableData(evaluated, this.sortConfig);
- this.tableData = sorted;
- this.scoreDistribution = aggregateHealthScoreDistribution(sorted);
- },
- buildHealthRequestParams() {
- const fieldCode =
- this.maplistArr?.codeNumber || this.maplistArr?.fieldCode;
- if (!fieldCode) {
- return null;
- }
- const params = { fieldCode };
- if (this.datatime) {
- params.datatime = this.datatime;
- }
- return params;
- },
- async fetchHealthData() {
- const selectedNode = this.maplistArr;
- if (!selectedNode || !Object.keys(selectedNode).length) return;
- this.loading = true;
- try {
- let list = [];
- // fieldCode 可传公司/区域 codeNumber,接口一次返回下属全部风场
- const params = this.buildHealthRequestParams();
- if (params) {
- const res = await getHealthscoresWindList(params);
- list = Array.isArray(res?.data) ? res.data : [];
- }
- this.lastHealthList = list;
- this.applyHealthList(list);
- } catch (error) {
- console.error("加载健康风场统计失败:", error);
- this.lastHealthList = [];
- this.tableData = [];
- this.scoreDistribution = aggregateHealthScoreDistribution([]);
- } finally {
- this.loading = false;
- this.$nextTick(() => {
- if (!this.chart) {
- this.initChart();
- } else {
- this.renderChart();
- this.chart.resize();
- }
- });
- }
- },
- handleResize() {
- if (this.chart) this.chart.resize();
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .cockpit-health-panel__subtitle {
- margin: 4px 0 10px;
- font-size: 13px;
- font-weight: 600;
- color: var(--cockpit-panel-title, #303133);
- }
- .cockpit-health-panel__tab-body {
- position: relative;
- height: var(--cockpit-health-pane-h);
- min-height: var(--cockpit-health-pane-h);
- overflow: hidden;
- }
- .cockpit-health-panel__pane {
- position: absolute;
- inset: 0;
- overflow: hidden;
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- }
- .cockpit-health-panel__body {
- display: flex;
- align-items: center;
- gap: 8px;
- flex-shrink: 0;
- height: var(--cockpit-health-chart-h, 150px);
- min-height: var(--cockpit-health-chart-h, 150px);
- }
- .cockpit-health-panel__legend {
- flex: 0 0 52px;
- margin: 0;
- padding: 0;
- list-style: none;
- }
- .cockpit-health-panel__legend-item {
- display: flex;
- align-items: center;
- gap: 6px;
- margin-bottom: 10px;
- font-size: 12px;
- color: var(--cockpit-panel-muted, #909399);
- i {
- flex-shrink: 0;
- width: 10px;
- height: 10px;
- border-radius: 1px;
- }
- }
- .cockpit-health-panel__chart {
- flex: 1;
- min-width: 0;
- height: var(--cockpit-health-chart-h, 150px);
- }
- .cockpit-health-panel__table {
- flex: 0 0 auto;
- margin-top: 8px;
- }
- </style>
|