|
@@ -15,104 +15,141 @@
|
|
|
label: 'companyName',
|
|
|
value: 'codeNumber',
|
|
|
}"
|
|
|
- >
|
|
|
- </selecttree>
|
|
|
+ />
|
|
|
</p>
|
|
|
|
|
|
<p>
|
|
|
时间:
|
|
|
-
|
|
|
<el-date-picker
|
|
|
v-model="timevalue"
|
|
|
size="small"
|
|
|
type="month"
|
|
|
placeholder="选择月"
|
|
|
- >
|
|
|
- </el-date-picker>
|
|
|
- <!-- <el-date-picker
|
|
|
- size="small"
|
|
|
- v-model="timevalue"
|
|
|
- type="datetimerange"
|
|
|
- range-separator="至"
|
|
|
- start-placeholder="开始日期"
|
|
|
- end-placeholder="结束日期"
|
|
|
- >
|
|
|
- </el-date-picker> -->
|
|
|
+ />
|
|
|
</p>
|
|
|
- <el-button type="primary" size="small" @click="conditions"
|
|
|
- >查询</el-button
|
|
|
- >
|
|
|
+
|
|
|
+ <el-button type="primary" size="small" @click="conditions">
|
|
|
+ 查询
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
+
|
|
|
<div class="kuang">
|
|
|
- <div class="main-body">
|
|
|
- <div class="basics" v-for="(item, index) in ListData" :key="item + index">
|
|
|
- <div class="title">
|
|
|
- <span>
|
|
|
- <SvgIcons
|
|
|
- name="WindPower3"
|
|
|
- class="WindPower3"
|
|
|
- width="30"
|
|
|
- height="30"
|
|
|
- ></SvgIcons>
|
|
|
- </span>
|
|
|
- <h4>
|
|
|
- {{ item.fanName }}
|
|
|
- </h4>
|
|
|
- </div>
|
|
|
- <div class="content">
|
|
|
- <div class="summary" v-if="item.status == '1'" style="color: #28a745">
|
|
|
- <span>100</span>
|
|
|
- <span>健康</span>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="summary"
|
|
|
- v-else-if="item.status == '2'"
|
|
|
- style="color: #ffc107"
|
|
|
- >
|
|
|
- <span>84</span>
|
|
|
- <span>亚健康</span>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="summary"
|
|
|
- v-else-if="item.status == '3'"
|
|
|
- style="color: #fd7e14"
|
|
|
- >
|
|
|
- <span>65</span>
|
|
|
- <span>一般</span>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="summary"
|
|
|
- v-else-if="item.status == '4'"
|
|
|
- style="color: #dc3545"
|
|
|
- >
|
|
|
- <span>50</span>
|
|
|
- <span> 故障 </span>
|
|
|
+ <!-- 加载中动画 -->
|
|
|
+ <div v-if="loading" class="loader-wrapper">
|
|
|
+ <div class="loader"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <el-empty
|
|
|
+ class="Anempty"
|
|
|
+ v-if="!loading && ListData.length === 0"
|
|
|
+ :image-size="200"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 主体内容 -->
|
|
|
+ <div class="main-body" v-if="!loading && ListData.length > 0">
|
|
|
+ <div
|
|
|
+ class="basics"
|
|
|
+ v-for="(item, index) in ListData"
|
|
|
+ :key="item + index"
|
|
|
+ >
|
|
|
+ <div class="title">
|
|
|
+ <span>
|
|
|
+ <SvgIcons
|
|
|
+ name="WindPower3"
|
|
|
+ class="WindPower3"
|
|
|
+ width="26"
|
|
|
+ height="26"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ <h4>风机:{{ item.wind_turbine_name }}</h4>
|
|
|
+ <span class="mill">{{ millTypeMap[item.mill_type] || "-" }}</span>
|
|
|
</div>
|
|
|
- <div class="summary" v-else>未知状态</div>
|
|
|
|
|
|
- <ul class="health">
|
|
|
- <li v-for="item in item.systems" :key="item.name">
|
|
|
- <span>{{ item.name }}</span>
|
|
|
- <span :class="'health-' + item.val">
|
|
|
- {{ getStatusText(item.val) }}
|
|
|
+ <div class="content">
|
|
|
+ <div class="summary" style="color: #28a745">
|
|
|
+ <span :style="{ color: getHealthColor(item.total_health_score) }">
|
|
|
+ {{
|
|
|
+ item.total_health_score < 0
|
|
|
+ ? "-"
|
|
|
+ : Math.floor(item.total_health_score)
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ <span :style="{ color: getHealthColor(item.total_health_score) }">
|
|
|
+ {{ getHealthLabel(item.total_health_score) }}
|
|
|
</span>
|
|
|
- </li>
|
|
|
- </ul>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <ul class="health">
|
|
|
+ <li>
|
|
|
+ <span>发电机组</span>
|
|
|
+ <span :class="'health-' + item.val">
|
|
|
+ {{
|
|
|
+ item.subsystems.generator.health_score >= 0
|
|
|
+ ? String(
|
|
|
+ Math.floor(item.subsystems.generator.health_score)
|
|
|
+ ).slice(0, 3)
|
|
|
+ : "-"
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </li>
|
|
|
+
|
|
|
+ <li v-if="item.subsystems.drive_train">
|
|
|
+ <span>传动系统</span>
|
|
|
+ <span :class="'health-' + item.val">
|
|
|
+ {{
|
|
|
+ item.subsystems.drive_train.health_score >= 0
|
|
|
+ ? String(
|
|
|
+ Math.floor(item.subsystems.drive_train.health_score)
|
|
|
+ ).slice(0, 3)
|
|
|
+ : "-"
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </li>
|
|
|
+
|
|
|
+ <li>
|
|
|
+ <span>机舱系统</span>
|
|
|
+ <span :class="'health-' + item.val">
|
|
|
+ {{
|
|
|
+ item.subsystems.nacelle.health_score >= 0
|
|
|
+ ? String(
|
|
|
+ Math.floor(item.subsystems.nacelle.health_score)
|
|
|
+ ).slice(0, 3)
|
|
|
+ : "-"
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </li>
|
|
|
+
|
|
|
+ <li>
|
|
|
+ <span>电网环境</span>
|
|
|
+ <span :class="'health-' + item.val">
|
|
|
+ {{
|
|
|
+ item.subsystems.grid.health_score >= 0
|
|
|
+ ? String(
|
|
|
+ Math.floor(item.subsystems.grid.health_score)
|
|
|
+ ).slice(0, 3)
|
|
|
+ : "-"
|
|
|
+ }}
|
|
|
+ </span>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- </div>
|
|
|
- <p class="tishi"><span>健康度说明:</span>100-85代表健康,84-70代表亚健康,69-55一代表般, <55代表故障</p>
|
|
|
+ <p class="tishi">
|
|
|
+ <span>健康度说明:</span>100-85代表健康,84-70代表亚健康,69-55代表一般,
|
|
|
+ <55代表故障
|
|
|
+ </p>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import * as FileSaver from "file-saver";
|
|
|
-import * as XLSX from "xlsx";
|
|
|
import { getSysOrganizationAuthTreeByRoleId } from "@/api/ledger.js";
|
|
|
import selecttree from "../../components/selecttree.vue";
|
|
|
import axios from "axios";
|
|
|
+
|
|
|
export default {
|
|
|
components: {
|
|
|
selecttree,
|
|
@@ -121,101 +158,24 @@ export default {
|
|
|
return {
|
|
|
parentOpt: [],
|
|
|
companyCode: "",
|
|
|
- timevalue: [], // 绑定 el-date-picker 的值
|
|
|
- startTime: "", // 开始时间
|
|
|
- endTime: "", // 结束时间
|
|
|
- ListData: [
|
|
|
- {
|
|
|
- fanName: "FXX风机-1号", // 风机名称
|
|
|
- status: "1",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "100" },
|
|
|
- { name: "传动系统", val: "100" },
|
|
|
- { name: "机舱系统", val: "100" },
|
|
|
- { name: "电网环境", val: "100" },
|
|
|
- { name: "辅助系统", val: "100" },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- fanName: "FXX风机-2号", // 风机名称
|
|
|
- status: "2",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "84" },
|
|
|
- { name: "传动系统", val: "84" },
|
|
|
- { name: "机舱系统", val: "84" },
|
|
|
- { name: "电网环境", val: "84" },
|
|
|
- { name: "辅助系统", val: "84" },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- fanName: "FXX风机-1号", // 风机名称
|
|
|
- status: "3",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "65" },
|
|
|
- { name: "传动系统", val: "65" },
|
|
|
- { name: "机舱系统", val: "65" },
|
|
|
- { name: "电网环境", val: "65" },
|
|
|
- { name: "辅助系统", val: "65" },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- fanName: "FXX风机-1号", // 风机名称
|
|
|
- status: "4",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "50" },
|
|
|
- { name: "传动系统", val: "50" },
|
|
|
- { name: "机舱系统", val: "50" },
|
|
|
- { name: "电网环境", val: "50" },
|
|
|
- { name: "辅助系统", val: "50" },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- fanName: "FXX风机-1号", // 风机名称
|
|
|
- status: "1",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "100" },
|
|
|
- { name: "传动系统", val: "100" },
|
|
|
- { name: "机舱系统", val: "100" },
|
|
|
- { name: "电网环境", val: "100" },
|
|
|
- { name: "辅助系统", val: "100" },
|
|
|
- ],
|
|
|
- },
|
|
|
- {
|
|
|
- fanName: "FXX风机-1号", // 风机名称
|
|
|
- status: "1",
|
|
|
- systems: [
|
|
|
- // 子系统列表
|
|
|
- { name: "发电机组", val: "100" },
|
|
|
- { name: "传动系统", val: "100" },
|
|
|
- { name: "机舱系统", val: "100" },
|
|
|
- { name: "电网环境", val: "100" },
|
|
|
- { name: "辅助系统", val: "100" },
|
|
|
- ],
|
|
|
- },
|
|
|
- ],
|
|
|
+ timevalue: [],
|
|
|
+ ListData: [],
|
|
|
+ loading: false, // ✅ 加载状态
|
|
|
};
|
|
|
},
|
|
|
-
|
|
|
+ computed: {
|
|
|
+ millTypeMap() {
|
|
|
+ return {
|
|
|
+ dfig: "双馈",
|
|
|
+ direct: "直驱",
|
|
|
+ semi: "半直驱",
|
|
|
+ };
|
|
|
+ },
|
|
|
+ },
|
|
|
created() {
|
|
|
this.GETtree();
|
|
|
},
|
|
|
- watch: {},
|
|
|
methods: {
|
|
|
- getStatusText(val) {
|
|
|
- const statusMap = {
|
|
|
- 1: "健康",
|
|
|
- 2: "亚健康",
|
|
|
- 3: "一般",
|
|
|
- 4: "故障",
|
|
|
- };
|
|
|
- return statusMap[val] || val;
|
|
|
- },
|
|
|
- // 获取风场
|
|
|
async GETtree() {
|
|
|
const res = await getSysOrganizationAuthTreeByRoleId();
|
|
|
const treedata = res.data;
|
|
@@ -243,7 +203,52 @@ export default {
|
|
|
return processedData;
|
|
|
},
|
|
|
|
|
|
- conditions() {},
|
|
|
+ getHealthLabel(val) {
|
|
|
+ if (!Number.isFinite(val) || val < 0) return "无数据";
|
|
|
+ if (val >= 85) return "健康";
|
|
|
+ if (val >= 70) return "亚健康";
|
|
|
+ if (val >= 55) return "一般";
|
|
|
+ return "故障";
|
|
|
+ },
|
|
|
+
|
|
|
+ getHealthColor(val) {
|
|
|
+ if (!Number.isFinite(val) || val < 0) return "#6c757d"; // 灰色
|
|
|
+ if (val >= 85) return "#28a745"; // 绿色
|
|
|
+ if (val >= 70) return "#ffc107"; // 黄色
|
|
|
+ if (val >= 55) return "#fd7e14"; // 橙色
|
|
|
+ return "#dc3545"; // 红色
|
|
|
+ },
|
|
|
+
|
|
|
+ conditions() {
|
|
|
+ const fullStr = this.$formatDateTWO(this.timevalue);
|
|
|
+ const datePart = typeof fullStr === "string" ? fullStr.slice(0, 7) : "";
|
|
|
+ const parms = {
|
|
|
+ windcode: this.companyCode,
|
|
|
+ month: datePart,
|
|
|
+ };
|
|
|
+
|
|
|
+ this.loading = true; // ✅ 显示 loading
|
|
|
+ const start = performance.now(); // ⏱️ 请求开始时间
|
|
|
+
|
|
|
+ axios
|
|
|
+ .post(`/WJJapi/health_assess`, parms)
|
|
|
+ .then((res) => {
|
|
|
+ this.ListData = res.data;
|
|
|
+
|
|
|
+ const end = performance.now(); // ⏱️ 请求结束时间
|
|
|
+ const duration = ((end - start) / 1000).toFixed(2);
|
|
|
+ console.log(`接口请求耗时:${duration} 秒`);
|
|
|
+ })
|
|
|
+ .catch((error) => {
|
|
|
+ console.error("Error fetching data:", error);
|
|
|
+ const end = performance.now(); // ⏱️ 请求结束时间
|
|
|
+ const duration = ((end - start) / 1000).toFixed(2);
|
|
|
+ console.log(`接口请求耗时:${duration} 秒`);
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ this.loading = false; // ✅ 隐藏 loading
|
|
|
+ });
|
|
|
+ },
|
|
|
},
|
|
|
};
|
|
|
</script>
|
|
@@ -267,7 +272,7 @@ export default {
|
|
|
.main-body {
|
|
|
margin: 0 auto;
|
|
|
display: grid;
|
|
|
- grid-template-columns: repeat(auto-fill, 280px); /* 严格保持300px宽度 */
|
|
|
+ grid-template-columns: repeat(auto-fill, 260px); /* 严格保持300px宽度 */
|
|
|
justify-content: space-evenly; /* 均匀分布 */
|
|
|
gap: 10px;
|
|
|
|
|
@@ -276,7 +281,7 @@ export default {
|
|
|
overflow: hidden;
|
|
|
overflow-y: auto;
|
|
|
.basics {
|
|
|
- width: 280px; /* 固定宽度 */
|
|
|
+ width: 260px; /* 固定宽度 */
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
margin-bottom: 20px;
|
|
@@ -286,7 +291,7 @@ export default {
|
|
|
padding: 5px 0;
|
|
|
width: 100%;
|
|
|
border-bottom: 1px solid var(--header-bg);
|
|
|
- text-align: center;
|
|
|
+ text-align: left;
|
|
|
|
|
|
display: flex;
|
|
|
align-items: center; // 垂直居中
|
|
@@ -302,17 +307,22 @@ export default {
|
|
|
}
|
|
|
|
|
|
h4 {
|
|
|
+ width: 175px;
|
|
|
color: var(--header-bg);
|
|
|
font-size: 1.1em;
|
|
|
font-weight: 600;
|
|
|
margin: 0;
|
|
|
}
|
|
|
+ .mill {
|
|
|
+ font-size: 12px;
|
|
|
+ color: rgb(43, 42, 42);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.content {
|
|
|
display: flex;
|
|
|
width: 100%;
|
|
|
- gap: 8px;
|
|
|
+ gap: 28px;
|
|
|
.summary {
|
|
|
width: 70px;
|
|
|
display: flex;
|
|
@@ -320,7 +330,7 @@ export default {
|
|
|
align-items: center; // 居中对齐
|
|
|
justify-content: center;
|
|
|
font-weight: bold;
|
|
|
-
|
|
|
+ padding-left: 26px;
|
|
|
span {
|
|
|
display: block;
|
|
|
text-align: center;
|
|
@@ -368,19 +378,6 @@ export default {
|
|
|
li span {
|
|
|
&:last-child {
|
|
|
color: var(--header-bg);
|
|
|
- // Status color classes
|
|
|
- &.health-1 {
|
|
|
- color: #28a745;
|
|
|
- } // 健康 - 绿色
|
|
|
- &.health-2 {
|
|
|
- color: #ffc107;
|
|
|
- } // 亚健康 - 黄色
|
|
|
- &.health-3 {
|
|
|
- color: #fd7e14;
|
|
|
- } // 一般 - 橙色
|
|
|
- &.health-4 {
|
|
|
- color: #dc3545;
|
|
|
- } // 故障 - 红色
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -390,14 +387,64 @@ export default {
|
|
|
.jia {
|
|
|
color: var(--header-bg);
|
|
|
}
|
|
|
-.kuang{
|
|
|
- // height: min-content;
|
|
|
-height: 680px;
|
|
|
+.kuang {
|
|
|
+ height: 680px;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ overflow-y: auto;
|
|
|
+ .Anempty {
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ }
|
|
|
}
|
|
|
-.tishi{
|
|
|
+
|
|
|
+.tishi {
|
|
|
font-size: 12px;
|
|
|
- span{
|
|
|
+ span {
|
|
|
font-weight: 800;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// 动画
|
|
|
+/* ✅ loading 包裹容器 */
|
|
|
+.loader-wrapper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ height: 100%; /* 可根据页面需要调整高度 */
|
|
|
+}
|
|
|
+
|
|
|
+/* ✅ loading 样式动画 */
|
|
|
+
|
|
|
+.loader {
|
|
|
+ width: 50px;
|
|
|
+ --b: 8px;
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border-radius: 50%;
|
|
|
+ padding: 1px;
|
|
|
+ background: conic-gradient(#0000 10%, var(--header-bg)) content-box;
|
|
|
+
|
|
|
+ -webkit-mask: repeating-conic-gradient(
|
|
|
+ #0000 0deg,
|
|
|
+ #000 1deg 20deg,
|
|
|
+ #0000 21deg 36deg
|
|
|
+ ),
|
|
|
+ radial-gradient(
|
|
|
+ farthest-side,
|
|
|
+ #0000 calc(100% - var(--b) - 1px),
|
|
|
+ #000 calc(100% - var(--b))
|
|
|
+ );
|
|
|
+ -webkit-mask-composite: destination-in;
|
|
|
+ mask-composite: intersect;
|
|
|
+ animation: l4 1s infinite steps(10);
|
|
|
+}
|
|
|
+@keyframes l4 {
|
|
|
+ to {
|
|
|
+ transform: rotate(1turn);
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|
|
|
+
|
|
|
+
|