Files
emsfront/src/views/ems/dzjk/sbjk/bmsdcc/index.vue
xiaoyang 20df411925 1.一周充放曲线改为了时间聚合柱状图。
2.PCS最高温度修复bug展示多PCS设备的数据
3.PCS的状态根据状态枚举映射配置的内容显示
4.BMS的总览,增加工作状态、与PCS通讯、与EMS通讯的配置,及关联展示
5.增加批量导入单体电池点位的功能
6.修复计算点可能会出现id与code在一个池子内的问题,再次计算后数据正常
7.计算点增加小数位限制的功能,实时计算与7天历史接口都已经按照配置的小数位进行限制
8.统计报表中的功率曲线改为了按照分钟显示
9.功率曲线出现断点的问题是因为数据计算太密集了导致的,增加了前端连线不断的显示
10.PCS和电池堆的曲线与配置增加了关联设备显示
11.点位映射中的电池温度,增加了多设备
12.收益报表增加升序排列,合并当月所有合计
13.增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改
2026-04-12 15:18:00 +08:00

759 lines
27 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>
<div class="pcs-tags">
<el-tag
size="small"
:type="selectedClusterId ? 'info' : 'primary'"
:effect="selectedClusterId ? 'plain' : 'dark'"
class="pcs-tag-item"
@click="handleTagClick('')"
>
全部
</el-tag>
<el-tag
v-for="(item, index) in clusterDeviceList"
:key="index + 'clusterTag'"
size="small"
:type="selectedClusterId === (item.deviceId || item.id) ? 'primary' : 'info'"
:effect="selectedClusterId === (item.deviceId || item.id) ? 'dark' : 'plain'"
class="pcs-tag-item"
@click="handleTagClick(item.deviceId || item.id || '')"
>
{{ item.deviceName || item.name || item.deviceId || item.id || 'BMS电池簇' }}
</el-tag>
</div>
<div v-for="(baseInfo,index) in filteredBaseInfoList" :key="index+'bmsdccContainer'" style="margin-bottom:25px;">
<el-card shadow="always"
class="sbjk-card-container common-card-container-body-no-padding common-card-container-no-title-bg"
:class="handleCardClass(baseInfo)">
<div slot="header">
<span
class="large-title">{{
baseInfo.parentDeviceName ? `${baseInfo.parentDeviceName} -> ` : ''
}}{{ baseInfo.deviceName }}</span>
<div class="info">
<div>数据更新时间{{ baseInfo.dataUpdateTime || '-' }}</div>
</div>
<div class="alarm">
<el-button type="primary" round size="small" style="margin-right:20px;"
@click="pointDetail(baseInfo,'point')">详细
</el-button>
<el-badge :hidden="!baseInfo.alarmNum" :value="baseInfo.alarmNum || 0" class="item">
<i
class="el-icon-message-solid alarm-icon"
@click="pointDetail(baseInfo,'alarmPoint')"
></i>
</el-badge>
</div>
</div>
<div class="descriptions-main">
<el-descriptions direction="vertical" :column="3" :colon="false">
<el-descriptions-item
contentClassName="descriptions-direction work-status"
:span="1" label="工作状态">
<span
class="pointer"
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'workStatus') }"
@click="handleFieldClick(baseInfo, 'workStatus', '工作状态')"
>
{{ formatDictValue(clusterWorkStatusOptions, baseInfo.workStatus) }}
</span>
</el-descriptions-item>
<el-descriptions-item contentClassName="descriptions-direction"
:span="1" label="与PCS通信">
<span
class="pointer"
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'pcsCommunicationStatus') }"
@click="handleFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')"
>
{{ formatDictValue(clusterPcsCommunicationStatusOptions, baseInfo.pcsCommunicationStatus) }}
</span>
</el-descriptions-item>
<el-descriptions-item contentClassName="descriptions-direction"
:span="1" label="与EMS通信">
<span
class="pointer"
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'emsCommunicationStatus') }"
@click="handleFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')"
>
{{ formatDictValue(clusterEmsCommunicationStatusOptions, baseInfo.emsCommunicationStatus) }}
</span>
</el-descriptions-item>
</el-descriptions>
</div>
<div class="descriptions-main descriptions-main-bg-color">
<el-descriptions direction="vertical" :column="3" :colon="false">
<el-descriptions-item labelClassName="descriptions-label" contentClassName="descriptions-direction"
v-for="(item,index) in infoData" :key="index+'pcsInfoData'" :span="1"
:label="item.label">
<span
class="pointer"
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, item.attr) }"
@click="handleFieldClick(baseInfo, item.attr, item.label)"
>
<i v-if="isPointLoading(baseInfo[item.attr])" class="el-icon-loading point-loading-icon"></i>
<span v-else>{{ displayValue(baseInfo[item.attr]) | formatNumber }}</span>
<span v-if="item.unit" v-html="item.unit"></span>
</span>
</el-descriptions-item>
</el-descriptions>
<!-- 进度-->
<div class="process-container">
<div class="process-line-bg">
<div class="process-line" :style="{height:baseInfo.currentSoc+'%'}"></div>
</div>
<div
class="process pointer"
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'currentSoc') }"
@click="handleFieldClick(baseInfo, 'currentSoc', '当前SOC')"
>当前SOC :
{{ baseInfo.currentSoc }}%
</div>
</div>
</div>
<el-table
class="common-table"
:data="baseInfo.batteryDataList"
stripe
style="width: 100%;margin-top:25px;">
<el-table-column
prop="dataName"
label="名称">
<template slot-scope="scope">
<span v-html="scope.row.dataName+''+unitObj[scope.row.dataName]+''"></span>
</template>
</el-table-column>
<el-table-column
prop="avgData"
label="单体平均值"
>
<template slot-scope="scope">
<span class="pointer"
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
scope.row.avgData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="minData"
label="单体最小值">
<template slot-scope="scope">
<span class="pointer"
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
scope.row.minData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="minDataID"
label="单体最小值ID">
</el-table-column>
<el-table-column
prop="maxData"
label="单体最大值">
<template slot-scope="scope">
<span class="pointer "
:class="{ 'field-disabled': !hasTableFieldPointId(baseInfo, scope.row.dataName, scope.column.label) }"
@click="handleTableFieldClick(baseInfo, scope.row.dataName, scope.column.label)">{{
scope.row.maxData
}}</span>
</template>
</el-table-column>
<el-table-column
prop="maxDataID"
label="单体最大值ID">
</el-table-column>
</el-table>
</el-card>
</div>
<point-table ref="pointTable"/>
<el-dialog
:visible.sync="curveDialogVisible"
:title="curveDialogTitle"
width="1000px"
append-to-body
class="ems-dialog"
:close-on-click-modal="false"
destroy-on-close
@opened="handleCurveDialogOpened"
@closed="handleCurveDialogClosed"
>
<div class="curve-tools">
<el-date-picker
v-model="curveCustomRange"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
range-separator=""
start-placeholder="开始时间"
end-placeholder="结束时间"
style="width: 440px"
/>
<el-button type="primary" size="mini" :loading="curveLoading" @click="loadCurveData">查询</el-button>
</div>
<div v-loading="curveLoading" ref="curveChartRef" style="height: 380px;"></div>
</el-dialog>
</div>
</template>
<script>
import * as echarts from "echarts";
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
import {getProjectDisplayData, getStackNameList, getClusterNameList} from '@/api/ems/dzjk'
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
import intervalUpdate from "@/mixins/ems/intervalUpdate";
import {mapState} from "vuex";
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
export default {
name: 'DzjkSbjkBmsdcc',
mixins: [getQuerySiteId, intervalUpdate],
components: {PointTable},
computed: {
...mapState({
CLUSTERWorkStatusOptions: state => state?.ems?.CLUSTERWorkStatusOptions || {},
}),
clusterWorkStatusOptions() {
return this.getEnumOptions("CLUSTER", "workStatus", this.CLUSTERWorkStatusOptions || {});
},
clusterPcsCommunicationStatusOptions() {
return this.getEnumOptions("CLUSTER", "pcsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
},
clusterEmsCommunicationStatusOptions() {
return this.getEnumOptions("CLUSTER", "emsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
},
filteredBaseInfoList() {
if (!this.selectedClusterId) {
return this.baseInfoList || [];
}
return (this.baseInfoList || []).filter(item => item.deviceId === this.selectedClusterId);
},
},
data() {
return {
loading: false,
displayData: [],
clusterDeviceList: [],
siteEnumOptionMap: {},
selectedClusterId: "",
curveDialogVisible: false,
curveDialogTitle: "点位曲线",
curveChart: null,
curveLoading: false,
curveCustomRange: [],
curveQuery: {
siteId: "",
pointId: "",
pointType: "data",
rangeType: "custom",
startTime: "",
endTime: "",
},
unitObj: {
'电压': 'V',
'温度': '&#8451;',
'SOC': '%'
},
tablePointNameMap: {
'电压单体最小值': '最低单体电压',
'电压单体平均值': '电压平均值',
'电压单体最大值': '最高单体电压',
'温度单体最小值': '最低单体温度',
'温度单体平均值': '平均单体温度',
'温度单体最大值': '最高单体温度',
'SOC单体最小值': '最低单体SOC',
'SOC单体平均值': '当前SOC',
'SOC单体最大值': '最高单体SOC',
},
baseInfoList: [{
siteId: "",
deviceId: "",
parentDeviceName: "",
deviceName: "BMS电池簇",
dataUpdateTime: "-",
alarmNum: 0,
batteryDataList: [],
}],
infoData: [
{label: '簇电压', attr: 'clusterVoltage', unit: 'V', pointName: '簇电压'},
{label: '可充电量', attr: 'chargeableCapacity', unit: 'kWh', pointName: '可充电量'},
{label: '累计充电量', attr: 'totalChargedCapacity', unit: 'kWh', pointName: '累计充电量'},
{label: '簇电流', attr: 'clusterCurrent', unit: 'A', pointName: '簇电流'},
{label: '可放电量', attr: 'dischargeableCapacity', unit: 'kWh', pointName: '可放电量'},
{label: '累计放电量', attr: 'totalDischargedCapacity', unit: 'kWh', pointName: '累计放电量'},
{label: 'SOH', attr: 'soh', unit: '%', pointName: 'SOH'},
{label: '平均温度', attr: 'averageTemperature', unit: '&#8451;', pointName: '平均温度'},
{label: '绝缘电阻', attr: 'insulationResistance', unit: '&Omega;', pointName: '绝缘电阻'},
],
}
},
methods: {
displayValue(value) {
return value === undefined || value === null || value === "" ? "-" : value;
},
isPointLoading(value) {
return this.loading && (value === undefined || value === null || value === "" || value === "-");
},
normalizeDictKey(value) {
const raw = String(value == null ? "" : value).trim();
if (!raw) return "";
if (/^-?\d+(\.0+)?$/.test(raw)) {
return String(parseInt(raw, 10));
}
return raw;
},
formatDictValue(options, value) {
const dict = (options && typeof options === "object") ? options : {};
const key = this.normalizeDictKey(value);
if (!key) return "-";
return dict[key] || key;
},
buildEnumScopeKey(deviceCategory, matchField) {
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
},
buildSiteEnumOptionMap(mappings = []) {
return (mappings || []).reduce((acc, item) => {
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
const enumCode = this.normalizeDictKey(item?.enumCode);
const enumName = String(item?.enumName || "").trim();
const optionKey = dataEnumCode || enumCode;
if (!scopeKey || !optionKey || !enumName) {
return acc;
}
if (!acc[scopeKey]) {
acc[scopeKey] = {};
}
acc[scopeKey][optionKey] = enumName;
return acc;
}, {});
},
loadSiteEnumOptions() {
if (!this.siteId) {
this.siteEnumOptionMap = {};
return Promise.resolve({});
}
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
this.siteEnumOptionMap = optionMap;
return optionMap;
}).catch(() => {
this.siteEnumOptionMap = {};
return {};
});
},
getEnumOptions(deviceCategory, matchField, fallback = {}) {
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
const siteOptions = this.siteEnumOptionMap[scopeKey];
if (siteOptions && Object.keys(siteOptions).length > 0) {
return siteOptions;
}
return fallback || {};
},
handleCardClass(item) {
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
const statusOptions = (this.clusterWorkStatusOptions && typeof this.clusterWorkStatusOptions === "object")
? this.clusterWorkStatusOptions
: {};
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
return !hasStatus ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container';
},
// 查看设备电位表格
pointDetail(row, dataType) {
const {siteId, deviceId} = row
this.$refs.pointTable.showTable({siteId, deviceId, deviceCategory: 'CLUSTER'}, dataType)
},
hasFieldPointId(baseInfo, fieldName) {
const row = this.getFieldRow(baseInfo, fieldName);
return !!String(row?.dataPoint || "").trim();
},
hasTableFieldPointId(baseInfo, dataName, columnLabel) {
const pointName = this.tablePointNameMap[String(dataName || "") + String(columnLabel || "")];
if (!pointName) {
return false;
}
return this.hasFieldPointId(baseInfo, pointName);
},
getFieldRow(baseInfo, fieldName) {
const key = String(fieldName || "").trim();
const map = baseInfo?._fieldRowMap || {};
return map[key] || null;
},
handleFieldClick(baseInfo, fieldName, title) {
const row = this.getFieldRow(baseInfo, fieldName);
const pointId = String(row?.dataPoint || "").trim();
this.openCurveDialogByPointId(pointId, title || fieldName);
},
handleTableFieldClick(baseInfo, dataName, columnLabel) {
const pointName = this.tablePointNameMap[String(dataName || "") + String(columnLabel || "")];
if (!pointName) {
this.$message.warning("该字段未配置点位,无法查询曲线");
return;
}
this.handleFieldClick(baseInfo, pointName, pointName);
},
openCurveDialogByPointId(pointId, title) {
const normalizedPointId = String(pointId || "").trim();
if (!normalizedPointId) {
this.$message.warning("该字段未配置点位,无法查询曲线");
return;
}
const range = this.getDefaultCurveRange();
this.curveCustomRange = range;
this.curveDialogTitle = `点位曲线 - ${title || normalizedPointId}`;
this.curveQuery = {
siteId: this.siteId,
pointId: normalizedPointId,
pointType: "data",
rangeType: "custom",
startTime: range[0],
endTime: range[1],
};
this.curveDialogVisible = true;
},
handleCurveDialogOpened() {
if (!this.curveChart && this.$refs.curveChartRef) {
this.curveChart = echarts.init(this.$refs.curveChartRef);
}
this.loadCurveData();
},
handleCurveDialogClosed() {
if (this.curveChart) {
this.curveChart.dispose();
this.curveChart = null;
}
this.curveLoading = false;
},
getDefaultCurveRange() {
const end = new Date();
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000);
return [this.formatDateTime(start), this.formatDateTime(end)];
},
formatDateTime(date) {
const d = new Date(date);
const p = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
},
formatCurveTime(value) {
if (value === undefined || value === null || value === "") {
return "";
}
const raw = String(value).trim();
const normalized = raw
.replace("T", " ")
.replace(/\.\d+/, "")
.replace(/Z$/, "")
.replace(/([+-]\d{2}:?\d{2})$/, "")
.trim();
const matched = normalized.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2})/);
if (matched) {
return `${matched[1]} ${matched[2]}`;
}
return normalized.slice(0, 16);
},
loadCurveData() {
if (!this.curveQuery.siteId || !this.curveQuery.pointId) {
this.$message.warning("点位信息不完整,无法查询曲线");
return;
}
if (!this.curveCustomRange || this.curveCustomRange.length !== 2) {
this.$message.warning("请选择查询时间范围");
return;
}
this.curveQuery.startTime = this.curveCustomRange[0];
this.curveQuery.endTime = this.curveCustomRange[1];
const query = {
siteId: this.curveQuery.siteId,
pointId: this.curveQuery.pointId,
pointType: "data",
rangeType: "custom",
startTime: this.curveQuery.startTime,
endTime: this.curveQuery.endTime,
};
this.curveLoading = true;
getPointConfigCurve(query).then((response) => {
const rows = response?.data || [];
this.renderCurveChart(rows);
}).catch(() => {
this.renderCurveChart([]);
}).finally(() => {
this.curveLoading = false;
});
},
renderCurveChart(rows = []) {
if (!this.curveChart) return;
const xData = rows.map(item => this.formatCurveTime(item.dataTime));
const yData = rows.map(item => item.pointValue);
this.curveChart.clear();
this.curveChart.setOption({
legend: {},
grid: {
containLabel: true,
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "cross",
},
},
textStyle: {
color: "#333333",
},
xAxis: {
type: "category",
data: xData,
},
yAxis: {
type: "value",
},
dataZoom: [
{
type: "inside",
start: 0,
end: 100,
},
{
start: 0,
end: 100,
},
],
series: [
{
name: this.curveDialogTitle,
type: "line",
data: yData,
connectNulls: true,
},
],
});
if (!rows.length) {
this.$message.warning("当前时间范围暂无曲线数据");
}
},
handleTagClick(deviceId) {
this.selectedClusterId = deviceId || "";
},
init() {
this.updateData()
this.updateInterval(this.updateData)
},
getModuleRows(menuCode, sectionName) {
return (this.displayData || []).filter(item => item.menuCode === menuCode && item.sectionName === sectionName);
},
getFieldName(fieldCode) {
const raw = String(fieldCode || "").trim();
if (!raw) return "";
const index = raw.lastIndexOf("__");
return index >= 0 ? raw.slice(index + 2) : raw;
},
getFieldMap(rows = [], deviceId = "") {
const rowMap = this.getFieldRowMap(rows, deviceId);
return Object.keys(rowMap).reduce((acc, fieldName) => {
const row = rowMap[fieldName];
if (acc[fieldName] === undefined) {
acc[fieldName] = row?.fieldValue;
}
return acc;
}, {});
},
getFieldRowMap(rows = [], deviceId = "") {
const map = {};
const targetDeviceId = String(deviceId || "");
rows.forEach(item => {
if (!item || !item.fieldCode) return;
const itemDeviceId = String(item.deviceId || "");
if (itemDeviceId !== targetDeviceId) return;
const fieldName = this.getFieldName(item.fieldCode);
map[fieldName] = item;
const displayName = String(item.fieldName || "").trim();
if (displayName && !map[displayName]) {
map[displayName] = item;
}
});
rows.forEach(item => {
if (!item || !item.fieldCode) return;
const itemDeviceId = String(item.deviceId || "");
if (itemDeviceId !== "") return;
const fieldName = this.getFieldName(item.fieldCode);
if (map[fieldName] === undefined || map[fieldName] === null || map[fieldName] === "") {
map[fieldName] = item;
}
const displayName = String(item.fieldName || "").trim();
if (displayName && !map[displayName]) {
map[displayName] = item;
}
});
return map;
},
getLatestTime(menuCode) {
const times = (this.displayData || [])
.filter(item => item.menuCode === menuCode && item.valueTime)
.map(item => new Date(item.valueTime).getTime())
.filter(ts => !isNaN(ts));
if (times.length === 0) {
return '-';
}
const date = new Date(Math.max(...times));
const p = (n) => String(n).padStart(2, '0');
return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())} ${p(date.getHours())}:${p(date.getMinutes())}:${p(date.getSeconds())}`;
},
getClusterDeviceList() {
return getStackNameList(this.siteId)
.then(response => {
const stackList = response?.data || [];
if (!stackList.length) {
this.clusterDeviceList = [];
return;
}
const requests = stackList.map(stack => {
const stackDeviceId = stack.deviceId || stack.id || '';
return getClusterNameList({stackDeviceId, siteId: this.siteId})
.then(clusterResponse => {
const clusterList = clusterResponse?.data || [];
return clusterList.map(cluster => ({
...cluster,
parentDeviceName: stack.deviceName || stack.name || stackDeviceId || '',
}));
})
.catch(() => []);
});
return Promise.all(requests).then(results => {
this.clusterDeviceList = results.flat();
});
})
.catch(() => {
this.clusterDeviceList = [];
});
},
buildBaseInfoList() {
const devices = (this.clusterDeviceList && this.clusterDeviceList.length > 0)
? this.clusterDeviceList
: [{deviceId: this.siteId, deviceName: 'BMS电池簇', parentDeviceName: ''}];
this.baseInfoList = devices.map(device => ({
...(() => {
const id = device.deviceId || device.id || this.siteId;
const infoMap = this.getFieldMap(this.getModuleRows('SBJK_BMSDCC', '簇信息'), id);
const statusMap = this.getFieldMap(this.getModuleRows('SBJK_BMSDCC', '状态'), id);
const currentSoc = Number(infoMap.currentSoc);
return {
...infoMap,
workStatus: statusMap.workStatus,
pcsCommunicationStatus: statusMap.pcsCommunicationStatus,
emsCommunicationStatus: statusMap.emsCommunicationStatus,
currentSoc: isNaN(currentSoc) ? 0 : currentSoc,
_fieldRowMap: {
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '簇信息'), id),
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '状态'), id),
...this.getFieldRowMap(this.getModuleRows('SBJK_BMSDCC', '单体数据'), id),
},
};
})(),
siteId: this.siteId,
deviceId: device.deviceId || device.id || this.siteId,
parentDeviceName: device.parentDeviceName || '',
deviceName: device.deviceName || device.name || device.deviceId || device.id || 'BMS电池簇',
dataUpdateTime: this.getLatestTime('SBJK_BMSDCC'),
alarmNum: 0,
batteryDataList: [],
}));
},
updateData() {
this.loading = true
// 先渲染卡片框架,字段值走单点位 loading
this.buildBaseInfoList();
Promise.all([
getProjectDisplayData(this.siteId),
this.getClusterDeviceList(),
this.loadSiteEnumOptions(),
]).then(([response]) => {
this.displayData = response?.data || [];
this.buildBaseInfoList();
}).finally(() => {
this.loading = false
})
}
},
beforeDestroy() {
if (this.curveChart) {
this.curveChart.dispose();
this.curveChart = null;
}
},
}
</script>
<style scoped lang="scss">
.pcs-tags {
margin: 0 0 12px;
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: flex-start;
align-items: center;
}
.pcs-tag-item {
cursor: pointer;
}
::v-deep {
//描述列表样式
.descriptions-main {
padding: 24px 300px 24px 24px;
}
.descriptions-main-bottom {
padding: 14px 300px 14px 24px;
}
}
// 进度条样式
.process-container {
width: 100px;
position: absolute;
right: 70px;
top: 50%;
transform: translateY(-50%);
.process-line-bg {
position: relative;
width: 100%;
height: 110px;
background-color: #fff2cb;
border-radius: 6px;
box-shadow: 0 0 10px #fff2cb, 0 0 0 rgba(255, 242, 203, 0.5);
.process-line {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 25%;
background-color: #ffbf14;
border-radius: 0 0 6px 6px;
box-shadow: 0 0 10px #ffbf14, 0 0 0 rgba(255, 191, 20, 0.5);
}
}
.process {
margin-top: 15px;
color: #666666;
}
}
.point-loading-icon {
color: #409eff;
display: inline-block;
transform-origin: center;
animation: pointLoadingSpinPulse 1.1s linear infinite;
}
@keyframes pointLoadingSpinPulse {
0% { opacity: 0.45; transform: rotate(0deg) scale(0.9); }
50% { opacity: 1; transform: rotate(180deg) scale(1.08); }
100% { opacity: 0.45; transform: rotate(360deg) scale(0.9); }
}
</style>