数据概览
-
+
-
+
-
+
-
+
-
+
@@ -47,24 +47,45 @@ export default {
},
data() {
return {
- loading:false,
+ chartLoading: {
+ dlzbchart: false,
+ xtxlchart: false,
+ gjqsChart: false,
+ sbgjzbChart: false,
+ gjdjfbChart: false
+ }
}
},
methods: {
-
+ setChartLoading(loading) {
+ this.chartLoading = {
+ dlzbchart: loading,
+ xtxlchart: loading,
+ gjqsChart: loading,
+ sbgjzbChart: loading,
+ gjdjfbChart: loading
+ }
+ },
+ hideChartLoading(key) {
+ this.$set(this.chartLoading, key, false)
+ }
},
mounted() {
- this.loading = true
+ this.setChartLoading(true)
dataList().then(response => {
const data = JSON.parse(JSON.stringify(response?.data || {}))
this.$refs.dlzbchart.initChart(data?.elecDataList || [])
+ this.hideChartLoading('dlzbchart')
this.$refs.xtxlchart.initChart(data?.sysEfficList || [])
+ this.hideChartLoading('xtxlchart')
this.$refs.gjqsChart.initChart(data?.alarmDataList || [])
+ this.hideChartLoading('gjqsChart')
this.$refs.sbgjzbChart.initChart(data?.deviceAlarmList || [])
+ this.hideChartLoading('sbgjzbChart')
this.$refs.gjdjfbChart.initChart(data?.alarmLevelList || [])
-
- }).finally(() => {
- this.loading = false
+ this.hideChartLoading('gjdjfbChart')
+ }).catch(() => {
+ this.setChartLoading(false)
})
}
}
diff --git a/src/views/ems/search/index.vue b/src/views/ems/search/index.vue
index 5938ede..2c0fc8d 100644
--- a/src/views/ems/search/index.vue
+++ b/src/views/ems/search/index.vue
@@ -1,73 +1,77 @@
-
+
-
-
-
- {{ item.siteName }}
-
-
-
-
-
-
- {{ item.name }}
-
-
-
-
-
-
-
-
-
-
+
+ 当前 {{ form.queryGroups.length }} 个点位
+ 新增点位
+
+
+
+
+
handlePointChange(index, value)"
+ @visible-change="(visible) => handlePointDropdownVisible(index, visible)"
+ >
+
+
+
+
+
+
+
+
生成图表
+ 导出数据
import * as echarts from "echarts";
import resize from "@/mixins/ems/resize";
-import {getAllSites} from "@/api/ems/zddt";
-import {getAllBatteryIdsBySites, getAllDeviceCategory, getPointValueList, pointFuzzyQuery,} from "@/api/ems/search";
+import {getPointValueList, pointFuzzyQuery} from "@/api/ems/search";
import DateTimeSelect from "./DateTimeSelect.vue";
export default {
name: "Search",
mixins: [resize],
components: {DateTimeSelect},
- computed: {
- isDtdc() {
- return this.form.deviceCategory === "BATTERY";
- },
- },
watch: {
- "form.siteIds": {
+ "$route.query.siteId": {
handler(newVal) {
- newVal && this.isDtdc && this.getChildList();
- },
- },
- isDtdc: {
- handler(newVal) {
- newVal && this.form.siteIds && this.getChildList();
- !newVal && (this.form.child = []);
+ this.syncQuerySiteIds(newVal);
},
},
"form.dataUnit": {
- handler(newVal, oldVal) {
- console.log("wacth到了dataUnit的变化", newVal, oldVal);
+ handler() {
this.$nextTick(() => {
this.$refs.dateTimeSelect.init();
this.getDate();
@@ -135,66 +126,157 @@ export default {
data() {
return {
chart: null,
- deviceCategoryList: [], //设备列表
- siteList: [], //站点列表
- childOptions: [], //二级设备列表
form: {
- dataRange: [], //时间选择范围
- child: [],
- siteIds: "", //当前选中的站点id 默认选中第一个站点
- deviceCategory: "", //设备
- pointName: "", //点位
- dataUnit: 1, //横坐标
+ dataRange: [],
+ siteIds: [],
+ queryGroups: [],
+ dataUnit: 1,
},
- loading: false,
+ queryGroupSeed: 0,
+ lastQueryResult: [],
+ sitePointOptionsCache: {},
+ sitePointRequestId: 0,
};
},
+ created() {
+ this.form.queryGroups = [];
+ for (let i = 0; i < 5; i += 1) {
+ this.addQueryGroup();
+ }
+ },
methods: {
- changeSiteIds(val) {
- console.log("切换站点或设备清空点位", val);
- val && (this.form.pointName = "");
+ getChartColor(index = 0) {
+ const palette = [
+ "#5470C6",
+ "#91CC75",
+ "#FAC858",
+ "#EE6666",
+ "#73C0DE",
+ "#3BA272",
+ "#FC8452",
+ "#9A60B4",
+ "#EA7CCC",
+ ];
+ return palette[index % palette.length];
},
- getChildList() {
- this.childOptions = [];
- this.form.child = [];
- const {siteIds} = this.form;
- getAllBatteryIdsBySites([siteIds]).then((response) => {
- const data = response?.data || {};
- const base = 50;
- let options = [];
- Object.entries(data).forEach(([key, value], index) => {
- if (!value) value = [];
- options.push({
- value: key,
- label: this.siteList.find((s) => s.siteId === key)?.siteName || "",
- children: [],
- });
- const length = value.length;
- const num = Math.ceil(length / base);
- if (num === 0) return;
- for (let i = 1; i <= num; i++) {
- const start = (i - 1) * base + 1,
- end = i * base;
- options[index].children.push({
- value: i,
- label: `${start}-${end}`,
- children: [],
- });
- for (let s = start; s <= Math.min(length, end); s++) {
- options[index].children[i - 1].children.push({
- value: value[s - 1],
- label: value[s - 1],
- });
- }
- }
- });
- console.log("二级设备options", options);
- this.childOptions = options;
+ getSelectedPointAxisKeys() {
+ const keys = [];
+ const seen = new Set();
+ (this.form.queryGroups || []).forEach((group) => {
+ const key = String(this.resolveSelectedPointName(group) || group?.pointId || "").trim();
+ if (!key || seen.has(key)) return;
+ seen.add(key);
+ keys.push(key);
});
+ return keys;
},
- handleChildChange(data) {
- console.log("选择二级设备", data);
- this.form.child = data;
+ buildDynamicYAxisConfig(data = []) {
+ const axisIndexMap = {};
+ const yAxis = [];
+ const orderedAxisKeys = [];
+ const seenAxisKeys = new Set();
+
+ const pushAxisKey = (key) => {
+ const normalized = String(key || "").trim();
+ if (!normalized || seenAxisKeys.has(normalized)) return;
+ seenAxisKeys.add(normalized);
+ orderedAxisKeys.push(normalized);
+ };
+
+ this.getSelectedPointAxisKeys().forEach((key) => pushAxisKey(key));
+
+ data.forEach((item) => {
+ const pointId = String(item?.pointId || "").trim();
+ const pointName = String(item?.pointName || "").trim();
+ pushAxisKey(pointName || pointId);
+
+ (item?.deviceList || []).forEach((inner) => {
+ pushAxisKey(inner?.deviceId);
+ });
+ });
+
+ orderedAxisKeys.forEach((pointKey) => {
+ const axisIndex = yAxis.length;
+ const sideIndex = Math.floor(axisIndex / 2);
+ const position = axisIndex % 2 === 0 ? "left" : "right";
+ const color = this.getChartColor(axisIndex);
+ axisIndexMap[pointKey] = axisIndex;
+
+ yAxis.push({
+ type: "value",
+ name: pointKey || `点位${axisIndex + 1}`,
+ position,
+ offset: sideIndex * 55,
+ alignTicks: true,
+ axisLine: {
+ show: true,
+ lineStyle: {color},
+ },
+ axisLabel: {
+ color,
+ },
+ nameTextStyle: {
+ color,
+ },
+ splitLine: {
+ show: axisIndex === 0,
+ },
+ });
+ });
+
+ if (yAxis.length === 0) {
+ yAxis.push({type: "value"});
+ }
+
+ const axisCount = yAxis.length;
+ const leftCount = Math.ceil(axisCount / 2);
+ const rightCount = Math.floor(axisCount / 2);
+
+ return {
+ yAxis,
+ axisIndexMap,
+ grid: {
+ containLabel: true,
+ left: 40 + Math.max(0, leftCount - 1) * 55,
+ right: 40 + Math.max(0, rightCount - 1) * 55,
+ },
+ };
+ },
+ createEmptyQueryGroup(key) {
+ return {
+ key,
+ pointId: "",
+ selectedPointName: "",
+ pointOptions: [],
+ pointOptionsCache: {},
+ pointRequestId: 0,
+ pointLoading: false,
+ };
+ },
+ addQueryGroup() {
+ this.queryGroupSeed += 1;
+ this.form.queryGroups.push(this.createEmptyQueryGroup(this.queryGroupSeed));
+ },
+ removeQueryGroup(groupIndex) {
+ if (this.form.queryGroups.length <= 1) {
+ this.$message.warning("至少保留1个点位");
+ return;
+ }
+ this.form.queryGroups.splice(groupIndex, 1);
+ },
+ getQueryGroup(index) {
+ return this.form.queryGroups[index];
+ },
+ canSelectPoint(group) {
+ return !!(group && this.form.siteIds && this.form.siteIds.length > 0);
+ },
+ pointSelectPlaceholder(group) {
+ return this.canSelectPoint(group)
+ ? "支持关键字搜索,展开可查看点位列表"
+ : "暂无可用站点点位";
+ },
+ pointNoDataText(group) {
+ return this.canSelectPoint(group) ? "暂无匹配点位" : "暂无可用站点点位";
},
showLoading() {
this.chart && this.chart.showLoading();
@@ -210,76 +292,75 @@ export default {
this.getDate();
},
setOption(data) {
- // 点位类型 dataType 1-瞬时值 2-累计值 仅当值为2展示差值
- // 图表类型 chartType 1-曲线图,2-箱线图
if (!this.chart) return;
this.chart.clear();
- console.log("返回的数据", data);
if (!data || data.length <= 0) {
this.$message.warning("暂无数据");
+ return;
}
- console.log('展示的图表类型chartType', data[0].chartType)
if (data[0].chartType === 2) {
- // 箱型图
- this.setBoxOption(data)
+ this.setBoxOption(data);
} else {
- //折线图
- this.setLineOption(data)
+ this.setLineOption(data);
}
},
setLineOption(data) {
let dataset = [];
- data.forEach((item, index) => {
+ const axisConfig = this.buildDynamicYAxisConfig(data);
+ data.forEach((item) => {
+ const pointId = String(item?.pointId || "").trim();
+ const pointName = String(item?.pointName || "").trim();
+ const pointKey = pointId || pointName;
item.deviceList.forEach((inner) => {
+ const seriesKey = String(inner?.deviceId || "").trim() || pointKey;
+ const yAxisIndex = axisConfig.axisIndexMap[seriesKey] ?? 0;
+ const axisColor = this.getChartColor(yAxisIndex);
dataset.push({
- name: `${
- this.isDtdc
- ? `${inner.parentDeviceId ? inner.parentDeviceId + "-" : ""}`
- : ""
- }${inner.deviceId}`,
+ name: inner.deviceId,
type: "line",
+ yAxisIndex,
+ itemStyle: {
+ color: axisColor,
+ },
+ lineStyle: {
+ color: axisColor,
+ },
markPoint: {
symbolSize: 30,
emphasis: {
- disabled: false//打开 鼠标高亮
+ disabled: false,
},
- data: [//最大值、最小值
+ data: [
{
- // type: 'max',
- name: `最大值`,
+ name: "最大值",
coord: [inner.maxDate, inner.maxValue],
- relativeTo: 'coordinate',
+ relativeTo: "coordinate",
label: {
position: "top",
- formatter: item.dataType === 2 ? ([
- `最大值:${inner.maxValue}`,
- // `平均值:${inner.avgValue}`,
- `差值:${inner.diffValue}`,
- ]).join('\n') : ([
- `最大值:${inner.maxValue}`,
- // `平均值:${inner.avgValue}`,
- ]).join('\n'),
+ formatter: item.dataType === 2
+ ? [
+ `最大值:${inner.maxValue}`,
+ `差值:${inner.diffValue}`,
+ ].join("\n")
+ : [`最大值:${inner.maxValue}`].join("\n"),
},
},
{
- // type: 'min',
- name: `最小值`,
+ name: "最小值",
coord: [inner.minDate, inner.minValue],
- relativeTo: 'coordinate',
+ relativeTo: "coordinate",
label: {
position: "top",
- formatter: item.dataType === 2 ? ([
- `最小值:${inner.minValue}`,
- // `平均值:${inner.avgValue}`,
- `差值:${inner.diffValue}`,
- ]).join('\n') : ([
- `最小值:${inner.minValue}`,
- // `平均值:${inner.avgValue}`,
- ]).join('\n'),
- }
- }
- ]
+ formatter: item.dataType === 2
+ ? [
+ `最小值:${inner.minValue}`,
+ `差值:${inner.diffValue}`,
+ ].join("\n")
+ : [`最小值:${inner.minValue}`].join("\n"),
+ },
+ },
+ ],
},
xdata: [],
data: [],
@@ -291,32 +372,21 @@ export default {
});
});
});
- console.log("折线图图表数据", dataset);
+
this.chart.setOption({
- legend: {
- // left: 'center',
- // top: '10',
- },
- grid: {
- containLabel: true,
- },
+ legend: {},
+ grid: axisConfig.grid,
tooltip: {
trigger: "axis",
axisPointer: {
- type: 'cross',
+ type: "cross",
},
- // axisPointer: {
- // // 坐标轴指示器,坐标轴触发有效
- // type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
- // },
},
textStyle: {
color: "#333333",
},
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
- yAxis: {
- type: "value",
- },
+ yAxis: axisConfig.yAxis,
dataZoom: [
{
type: "inside",
@@ -333,103 +403,53 @@ export default {
},
setBoxOption(data) {
let dataset = [];
- data.forEach((item, index) => {
+ const axisConfig = this.buildDynamicYAxisConfig(data);
+ data.forEach((item) => {
+ const pointId = String(item?.pointId || "").trim();
+ const pointName = String(item?.pointName || "").trim();
+ const pointKey = pointId || pointName;
item.deviceList.forEach((inner) => {
+ const seriesKey = String(inner?.deviceId || "").trim() || pointKey;
+ const yAxisIndex = axisConfig.axisIndexMap[seriesKey] ?? 0;
+ const axisColor = this.getChartColor(yAxisIndex);
dataset.push({
- name: `${
- this.isDtdc
- ? `${inner.parentDeviceId ? inner.parentDeviceId + "-" : ""}`
- : ""
- }${inner.deviceId}`,
+ name: inner.deviceId,
type: "boxplot",
- // markPoint: {
- // symbolSize: 30,
- // emphasis: {
- // disabled: false//打开 鼠标高亮
- // },
- // data: [//最大值、最小值
- // {
- // // type: 'max',
- // name: `最大值`,
- // coord: [inner.maxDate, inner.maxValue],
- // relativeTo: 'coordinate',
- // label: {
- // position: "top",
- // formatter: item.dataType === 2 ? ([
- // `最大值:${inner.maxValue}`,
- // // `平均值:${inner.avgValue}`,
- // `差值:${inner.diffValue}`,
- // ]).join('\n') : ([
- // `最大值:${inner.maxValue}`,
- // // `平均值:${inner.avgValue}`,
- // ]).join('\n'),
- // },
- // },
- // {
- // // type: 'min',
- // name: `最小值`,
- // coord: [inner.minDate, inner.minValue],
- // relativeTo: 'coordinate',
- // label: {
- // position: "top",
- // formatter: item.dataType === 2 ? ([
- // `最小值:${inner.minValue}`,
- // // `平均值:${inner.avgValue}`,
- // `差值:${inner.diffValue}`,
- // ]).join('\n') : ([
- // `最小值:${inner.minValue}`,
- // // `平均值:${inner.avgValue}`,
- // ]).join('\n'),
- // }
- // }
- // ]
- // },
+ yAxisIndex,
+ itemStyle: {
+ color: axisColor,
+ },
xdata: [],
data: [],
});
const length = dataset.length;
inner.pointValueList.forEach((value) => {
- const {valueDate, min, q1, median, q3, max} = value
+ const {valueDate, min, q1, median, q3, max} = value;
dataset[length - 1].xdata.push(valueDate);
dataset[length - 1].data.push([min, q1, median, q3, max]);
});
});
});
- console.log("箱型图图表数据", dataset);
+
this.chart.setOption({
- legend: {
- // left: 'center',
- // top: '10',
- },
- grid: {
- containLabel: true,
- },
+ legend: {},
+ grid: axisConfig.grid,
tooltip: {
- trigger: 'item',
+ trigger: "item",
formatter: function (params) {
- let data = params.data;
- let result = params.marker + params.name + ' ' + params.seriesName + '
';
- result += '最小值: ' + data[1] + '
';
- result += '平均值: ' + data[3] + '
';
- result += '最大值: ' + data[5];
+ let itemData = params.data;
+ let result = params.marker + params.name + " " + params.seriesName + "
";
+ result += "最小值: " + itemData[1] + "
";
+ result += "平均值: " + itemData[3] + "
";
+ result += "最大值: " + itemData[5];
return result;
- }
- // trigger: "axis",
- // axisPointer: {
- // type: 'cross',
- // },
- // axisPointer: {
- // // 坐标轴指示器,坐标轴触发有效
- // type: "shadow", // 默认为直线,可选为:'line' | 'shadow'
- // },
+ },
},
textStyle: {
color: "#333333",
},
xAxis: {type: "category", data: dataset?.[0]?.xdata || []},
- yAxis: {
- type: "value",
- },
+ yAxis: axisConfig.yAxis,
dataZoom: [
{
type: "inside",
@@ -447,105 +467,389 @@ export default {
submitForm() {
this.getDate();
},
- handleSelect(data) {
- this.form.pointName = data.value;
- },
- querySearchAsync(query, cb) {
- console.log("查询数据", query);
- if (!this.form.siteIds || !this.form.deviceCategory) {
- this.$message({
- type: "warning",
- message: "请先选择站点和设备",
- });
- return cb([]);
+ buildQueryPayload() {
+ const activeGroups = this.form.queryGroups
+ .map((group) => ({group}))
+ .filter(({group}) => !!group.pointId);
+
+ if (activeGroups.length === 0) {
+ this.$message.error("请至少选择1组点位");
+ return null;
}
- pointFuzzyQuery({
- siteIds: [this.form.siteIds],
- deviceCategory: this.form.deviceCategory,
- pointName: query,
- }).then((response) => {
- const data = response?.data || [];
- cb(
- data.map((item) => {
- return {name: item, value: item};
- })
- );
+
+ if (!this.form.siteIds || this.form.siteIds.length === 0) {
+ this.$message.error("请先在顶部选择站点");
+ return null;
+ }
+
+ const {
+ siteIds,
+ dataUnit,
+ dataRange: [start = "", end = ""],
+ } = this.form;
+
+ if (!start || !end) {
+ this.$message.error("请选择时间");
+ return null;
+ }
+
+ let startDate = start;
+ let endDate = end;
+ if (start && dataUnit === 3) {
+ startDate = `${start} 00:00:00`;
+ }
+ if (end && dataUnit === 3) {
+ endDate = `${end} 00:00:00`;
+ }
+
+ const selectedPoints = [];
+ const pointIdSet = new Set();
+ activeGroups.forEach(({group}) => {
+ const pointId = String(group.pointId || "").trim();
+ if (!pointId || pointIdSet.has(pointId)) return;
+ pointIdSet.add(pointId);
+ selectedPoints.push({
+ pointId,
+ pointName: this.resolveSelectedPointName(group) || pointId,
+ });
+ });
+
+ if (selectedPoints.length === 0) {
+ this.$message.error("请至少选择1组点位");
+ return null;
+ }
+
+ const pointIds = selectedPoints.map((item) => item.pointId);
+ const pointNames = selectedPoints.map((item) => item.pointName);
+ return {
+ siteIds,
+ dataUnit,
+ pointIds,
+ pointNames,
+ pointId: pointIds.join(","),
+ startDate,
+ endDate,
+ };
+ },
+ requestPointData(payload) {
+ return getPointValueList(payload).then((response) => response?.data || []);
+ },
+ resolveExportPointValue(value = {}) {
+ if (value?.pointValue !== undefined && value?.pointValue !== null && value?.pointValue !== "") {
+ return value.pointValue;
+ }
+ if (value?.median !== undefined && value?.median !== null && value?.median !== "") {
+ return value.median;
+ }
+ return "";
+ },
+ normalizeExportRows(data = [], payload = {}) {
+ const pointIds = payload?.pointIds || [];
+ const pointNames = payload?.pointNames || [];
+ const selectedPoints = pointIds
+ .map((pointId, index) => {
+ const normalizedId = String(pointId || "").trim();
+ if (!normalizedId) return null;
+ return {
+ pointId: normalizedId,
+ pointName: String(pointNames[index] || normalizedId).trim(),
+ pointOrder: index,
+ };
+ })
+ .filter((item) => !!item);
+ const selectedPointById = {};
+ const selectedPointByName = {};
+ selectedPoints.forEach((point) => {
+ selectedPointById[point.pointId] = point;
+ if (point.pointName) {
+ selectedPointByName[point.pointName] = point;
+ }
+ });
+
+ const groups = [];
+ const groupMap = {};
+ const rowMap = {};
+ const rowDates = [];
+ data.forEach((item, itemIndex) => {
+ const itemPointId = String(item?.pointId || "").trim();
+ const itemPointName = String(item?.pointName || "").trim();
+ const pointByItemId = itemPointId ? selectedPointById[itemPointId] : null;
+ const pointByItemName = itemPointName ? selectedPointByName[itemPointName] : null;
+
+ (item?.deviceList || []).forEach((device) => {
+ const deviceId = String(device?.deviceId || "").trim();
+ const pointByDeviceId = deviceId ? selectedPointById[deviceId] : null;
+ const pointByDeviceName = deviceId ? selectedPointByName[deviceId] : null;
+ const pointByOrder = selectedPoints[itemIndex] || null;
+ const matchedPoint = pointByItemId || pointByItemName || pointByDeviceId || pointByDeviceName || pointByOrder || null;
+
+ const pointId = itemPointId || matchedPoint?.pointId || "";
+ const pointName = itemPointName || matchedPoint?.pointName || pointId;
+ const pointOrder = matchedPoint?.pointOrder ?? Number.MAX_SAFE_INTEGER;
+ const pointKey = pointId || pointName || `point_${itemIndex + 1}`;
+ const deviceKey = deviceId || "unknown";
+ const groupKey = `${pointKey}__${deviceKey}`;
+
+ if (!groupMap[groupKey]) {
+ groupMap[groupKey] = true;
+ groups.push({
+ groupKey,
+ pointId,
+ pointName,
+ deviceId,
+ pointOrder,
+ });
+ }
+ (device?.pointValueList || []).forEach((value) => {
+ const valueDate = String(value?.valueDate || "").trim();
+ if (!valueDate) return;
+ if (!rowMap[valueDate]) {
+ rowMap[valueDate] = {valueDate};
+ rowDates.push(valueDate);
+ }
+ rowMap[valueDate][groupKey] = this.resolveExportPointValue(value);
+ });
+ });
+ });
+
+ groups.sort((a, b) => {
+ const ao = a.pointOrder === -1 ? Number.MAX_SAFE_INTEGER : a.pointOrder;
+ const bo = b.pointOrder === -1 ? Number.MAX_SAFE_INTEGER : b.pointOrder;
+ if (ao !== bo) return ao - bo;
+ if (a.pointId !== b.pointId) return a.pointId.localeCompare(b.pointId);
+ return a.deviceId.localeCompare(b.deviceId);
+ });
+
+ return {
+ groups,
+ rows: rowDates.sort().map((valueDate) => rowMap[valueDate]),
+ };
+ },
+ csvCell(value) {
+ const text = value === undefined || value === null ? "" : String(value);
+ return `"${text.replace(/"/g, '""')}"`;
+ },
+ exportRowsToCsv({groups = [], rows = []} = {}) {
+ const headers = ["时间"];
+ groups.forEach((_, index) => {
+ const order = index + 1;
+ headers.push(`点位ID${order}`, `点位名称${order}`, `设备ID${order}`, `点位值${order}`);
+ });
+ const lines = [headers.map((item) => this.csvCell(item)).join(",")];
+ rows.forEach((row) => {
+ const line = [row.valueDate || ""];
+ groups.forEach((group) => {
+ line.push(group.pointId || "", group.pointName || "", group.deviceId || "", row[group.groupKey] ?? "");
+ });
+ lines.push(line.map((item) => this.csvCell(item)).join(","));
+ });
+ const csv = `\uFEFF${lines.join("\n")}`;
+ this.$download.saveAs(new Blob([csv], {type: "text/csv;charset=utf-8;"}), `综合查询数据_${new Date().getTime()}.csv`);
+ },
+ handleExportData() {
+ this.$refs.form.validate((valid) => {
+ if (!valid) return;
+ const payload = this.buildQueryPayload();
+ if (!payload) return;
+
+ this.requestPointData(payload).then((data) => {
+ if (!data || data.length === 0) {
+ this.$message.warning("暂无可导出数据");
+ return;
+ }
+ const exportData = this.normalizeExportRows(data, payload);
+ if (!exportData?.rows?.length || !exportData?.groups?.length) {
+ this.$message.warning("暂无可导出数据");
+ return;
+ }
+ this.exportRowsToCsv(exportData);
+ this.$message.success("导出成功");
+ }).catch((error) => {
+ if (error?.code === "ECONNABORTED") {
+ this.$message.error("查询超时,请缩短时间范围后重试");
+ return;
+ }
+ this.$message.error("导出失败,请稍后重试");
+ });
});
},
- // 获取所有设备
- getDeviceCategory() {
- return getAllDeviceCategory().then((response) => {
- this.deviceCategoryList = response?.data || [];
+ getPointCacheKey(query = "") {
+ return `${this.form.siteIds.join(",")}_${query.trim()}`;
+ },
+ getSitePointCacheKey() {
+ return this.form.siteIds.join(",");
+ },
+ formatPointLabel({pointId = "", pointName = "", dataKey = ""} = {}) {
+ return `${pointId || "-"}-${pointName || "-"}(${dataKey || "-"})`;
+ },
+ normalizePointOptions(data = []) {
+ return (data || []).map((item) => {
+ if (typeof item === "string") {
+ return {value: item, label: this.formatPointLabel({pointName: item}), pointId: "", pointName: item, dataKey: "", pointDesc: ""};
+ }
+ const pointId = item?.pointId || "";
+ const pointName = item?.pointName || item?.value || "";
+ const dataKey = item?.dataKey || "";
+ const pointDesc = item?.pointDesc || "";
+ const label = this.formatPointLabel({pointId, pointName, dataKey});
+ return {
+ value: pointId || pointName,
+ label,
+ pointId,
+ pointName,
+ dataKey,
+ pointDesc,
+ };
+ }).filter((item) => item.value);
+ },
+ fuzzyFilterPointOptions(options = [], query = "") {
+ const keyword = String(query || "").trim().toLowerCase();
+ if (!keyword) return options;
+ return (options || []).filter((item) => {
+ const marker = `${item?.pointId || ""} ${item?.pointName || ""} ${item?.dataKey || ""} ${item?.pointDesc || ""}`.toLowerCase();
+ return marker.includes(keyword);
});
},
- getZdList() {
- return getAllSites()
+ setPointOptions(group, data = []) {
+ const normalized = this.normalizePointOptions(data);
+ const selected = group.pointId
+ ? (group.pointOptions || []).find((item) => item?.value === group.pointId)
+ || {
+ value: group.pointId,
+ label: this.formatPointLabel({pointId: group.pointId}),
+ pointId: group.pointId,
+ pointName: group.selectedPointName || "",
+ dataKey: "",
+ pointDesc: "",
+ }
+ : null;
+ const nextOptions = selected ? [...normalized, selected] : normalized;
+ const seen = {};
+ group.pointOptions = nextOptions.filter((item) => {
+ if (!item?.value || seen[item.value]) return false;
+ seen[item.value] = true;
+ return true;
+ });
+ },
+ applyPointOptionsToGroups(pointOptions = []) {
+ (this.form.queryGroups || []).forEach((group) => {
+ if (!group) return;
+ const baseCacheKey = this.getPointCacheKey("");
+ group.pointOptionsCache[baseCacheKey] = pointOptions;
+ this.setPointOptions(group, pointOptions);
+ });
+ },
+ loadSitePointOptions({force = false} = {}) {
+ const siteCacheKey = this.getSitePointCacheKey();
+ if (!siteCacheKey) {
+ return Promise.resolve([]);
+ }
+ if (!force && this.sitePointOptionsCache[siteCacheKey]) {
+ const cached = this.sitePointOptionsCache[siteCacheKey];
+ this.applyPointOptionsToGroups(cached);
+ return Promise.resolve(cached);
+ }
+ const requestId = ++this.sitePointRequestId;
+ (this.form.queryGroups || []).forEach((group) => {
+ group.pointLoading = true;
+ });
+ return pointFuzzyQuery({
+ siteIds: this.form.siteIds,
+ pointName: "",
+ })
.then((response) => {
- this.siteList = response.data || [];
+ if (requestId !== this.sitePointRequestId) return [];
+ const data = this.normalizePointOptions(response?.data || []);
+ this.sitePointOptionsCache[siteCacheKey] = data;
+ this.applyPointOptionsToGroups(data);
+ return data;
})
.finally(() => {
+ if (requestId !== this.sitePointRequestId) return;
+ (this.form.queryGroups || []).forEach((group) => {
+ group.pointLoading = false;
+ });
});
},
+ remotePointSearch(groupIndex, query) {
+ const group = this.getQueryGroup(groupIndex);
+ if (!group || !this.canSelectPoint(group)) return;
+ const baseCacheKey = this.getPointCacheKey("");
+ const baseOptions = group.pointOptionsCache?.[baseCacheKey] || group.pointOptions || [];
+ const localFiltered = this.fuzzyFilterPointOptions(baseOptions, query);
+ this.setPointOptions(group, localFiltered);
+ },
+ handlePointDropdownVisible(groupIndex, visible) {
+ const group = this.getQueryGroup(groupIndex);
+ if (visible && group && this.canSelectPoint(group)) {
+ this.loadSitePointOptions();
+ }
+ },
+ refreshPointOptions(groupIndex) {
+ const group = this.getQueryGroup(groupIndex);
+ if (!group || !this.canSelectPoint(group)) return;
+ this.loadSitePointOptions({force: true});
+ },
+ clearPointSelection(groupIndex) {
+ const group = this.getQueryGroup(groupIndex);
+ if (!group) return;
+ group.pointId = "";
+ group.selectedPointName = "";
+ },
+ resolveSelectedPointName(group) {
+ if (!group) return "";
+ if (group.selectedPointName) return String(group.selectedPointName).trim();
+ const selectedOption = (group.pointOptions || []).find((item) => item?.value === group.pointId);
+ if (selectedOption?.pointName) {
+ return String(selectedOption.pointName).trim();
+ }
+ return "";
+ },
+ handlePointChange(groupIndex, value) {
+ const group = this.getQueryGroup(groupIndex);
+ if (!group) return;
+ if (!value) {
+ group.selectedPointName = "";
+ return;
+ }
+ const selectedOption = (group.pointOptions || []).find((item) => item?.value === value);
+ group.selectedPointName = String(selectedOption?.pointName || "").trim();
+ },
+ syncQuerySiteIds(routeSiteId) {
+ const siteId = routeSiteId || this.$route?.query?.siteId;
+ const normalizedSiteId = siteId === undefined || siteId === null ? "" : String(siteId).trim();
+ const prevSiteIds = (this.form.siteIds || []).join(",");
+ this.form.siteIds = normalizedSiteId ? [normalizedSiteId] : [];
+ const nextSiteIds = (this.form.siteIds || []).join(",");
+ if (prevSiteIds !== nextSiteIds) {
+ (this.form.queryGroups || []).forEach((group) => {
+ group.pointOptions = [];
+ group.pointOptionsCache = {};
+ group.pointLoading = false;
+ });
+ }
+ if (nextSiteIds) {
+ this.loadSitePointOptions();
+ }
+ },
getDate() {
this.$refs.form.validate((valid) => {
- if (valid) {
- if (!this.form.pointName) {
- return this.$message.error("请输入正确的点位");
- }
- if (
- this.isDtdc &&
- (this.form.child.length === 0 || this.form.child.length > 5)
- ) {
- return this.$message.error("请选择单体电池且不能超过5个");
- }
- const {
- siteIds,
- dataUnit,
- deviceCategory,
- pointName,
- dataRange: [start = "", end = ""],
- child,
- } = this.form;
- if (!start || !end) return this.$message.error("请选择时间");
- let siteDeviceMap = {};
- child.forEach(([first, second, third]) => {
- if (siteDeviceMap[first]) {
- siteDeviceMap[first].push(third);
- } else {
- siteDeviceMap[first] = [];
- siteDeviceMap[first].push(third);
- }
- });
- let startDate, endDate;
- if (start && dataUnit === 3) {
- // startDate= start + `${dataUnit === 2 ? ':00' : ' 00:00:00'}`
- startDate = start + " 00:00:00";
- } else {
- startDate = start;
- }
- if (end && dataUnit === 3) {
- // endDate= end + `${dataUnit === 2 ? ':00' : ' 00:00:00'}`
- endDate = end + " 00:00:00";
- } else {
- endDate = end;
- }
- this.loading = true;
- getPointValueList({
- siteIds: [siteIds],
- dataUnit,
- deviceCategory,
- pointName,
- startDate,
- endDate,
- siteDeviceMap,
- })
- .then((response) => {
- this.setOption(response?.data || []);
- })
- .finally(() => {
- this.loading = false;
- });
+ if (!valid) {
+ return;
}
+ const payload = this.buildQueryPayload();
+ if (!payload) return;
+
+ this.requestPointData(payload).then((data) => {
+ this.lastQueryResult = data || [];
+ this.setOption(this.lastQueryResult);
+ }).catch((error) => {
+ if (error?.code === "ECONNABORTED") {
+ this.$message.error("查询超时,请缩短时间范围后重试");
+ return;
+ }
+ this.$message.error("查询失败,请稍后重试");
+ });
});
},
},
@@ -557,16 +861,86 @@ export default {
this.chart = null;
},
mounted() {
- this.loading = true;
this.$nextTick(() => {
this.initChart();
this.$refs.dateTimeSelect.init();
- Promise.all([this.getDeviceCategory(), this.getZdList()]).finally(
- () => (this.loading = false)
- );
+ this.syncQuerySiteIds();
});
},
};
-
+
diff --git a/src/views/ems/site/dataCorrection/AddChargeDataCorrection.vue b/src/views/ems/site/dataCorrection/AddChargeDataCorrection.vue
new file mode 100644
index 0000000..b6562e8
--- /dev/null
+++ b/src/views/ems/site/dataCorrection/AddChargeDataCorrection.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/dataCorrection/AddDataCorrection.vue b/src/views/ems/site/dataCorrection/AddDataCorrection.vue
new file mode 100644
index 0000000..a34c848
--- /dev/null
+++ b/src/views/ems/site/dataCorrection/AddDataCorrection.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 确定
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/dataCorrection/DailyChargeDataTab.vue b/src/views/ems/site/dataCorrection/DailyChargeDataTab.vue
new file mode 100644
index 0000000..7be3f36
--- /dev/null
+++ b/src/views/ems/site/dataCorrection/DailyChargeDataTab.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
新增
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/dataCorrection/DailyEnergyDataTab.vue b/src/views/ems/site/dataCorrection/DailyEnergyDataTab.vue
new file mode 100644
index 0000000..6e012d6
--- /dev/null
+++ b/src/views/ems/site/dataCorrection/DailyEnergyDataTab.vue
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
新增
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/dataCorrection/index.vue b/src/views/ems/site/dataCorrection/index.vue
new file mode 100644
index 0000000..ff5b5a8
--- /dev/null
+++ b/src/views/ems/site/dataCorrection/index.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/mqtt/AddMqtt.vue b/src/views/ems/site/mqtt/AddMqtt.vue
index 104a5aa..0a2d8d2 100644
--- a/src/views/ems/site/mqtt/AddMqtt.vue
+++ b/src/views/ems/site/mqtt/AddMqtt.vue
@@ -2,9 +2,7 @@
-
-
-
+
@@ -30,13 +28,10 @@
+
+
diff --git a/src/views/ems/site/powerTariff/AddPowerTariff.vue b/src/views/ems/site/powerTariff/AddPowerTariff.vue
index 7ee2bca..34297d4 100644
--- a/src/views/ems/site/powerTariff/AddPowerTariff.vue
+++ b/src/views/ems/site/powerTariff/AddPowerTariff.vue
@@ -5,9 +5,7 @@
@@ -103,15 +101,12 @@
diff --git a/src/views/ems/site/sbbh/AddDevice.vue b/src/views/ems/site/sbbh/AddDevice.vue
deleted file mode 100644
index 15e9453..0000000
--- a/src/views/ems/site/sbbh/AddDevice.vue
+++ /dev/null
@@ -1,815 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 等级1
- 等级2
- 等级3
-
-
-
-
-
-
- 新增保护前提
-
-
-
-
-
-
设备类型
-
点位
-
故障值比较符号
-
故障值
-
释放值比较符号
-
释放值
-
关系
-
操作
-
-
-
- handleChange(v,'protectionSettings',index)"
- >
-
-
- handleSelect(v, index, 'protectionSettings')"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
- 新增保护方案
-
-
-
-
-
-
设备类型
-
点位
-
故障值比较符号
-
故障值
-
操作
-
-
-
- handleChange(v,'protectionPlan',index)"
- >
-
-
- handleSelect(v, index, 'protectionPlan')"
- >
-
-
-
=
-
-
-
-
-
-
- 删除
-
-
-
-
-
-
-
-
- 取消
- 确定
-
-
-
-
-
diff --git a/src/views/ems/site/sbbh/AddPlan.vue b/src/views/ems/site/sbbh/AddPlan.vue
new file mode 100644
index 0000000..1e8f241
--- /dev/null
+++ b/src/views/ems/site/sbbh/AddPlan.vue
@@ -0,0 +1,1090 @@
+
+
+
+
+
基础信息
+
+
+
+
+
+ 启用告警
+
+
+
+ 等级1
+ 等级2
+ 等级3
+
+
+
+
+
+
+
+
+
+
故障保护
+
+
+
+
{{ index + 1 }}
+
+
+
点位
+
handlePointChange(index, 'faultProtectionSettings', value)"
+ @visible-change="(visible) => handlePointDropdownVisible(index, 'faultProtectionSettings', visible)"
+ :remote-method="(query) => remotePointSearch(index, 'faultProtectionSettings', query)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
释放保护
+
+
+
+
{{ index + 1 }}
+
+
+
点位
+
handlePointChange(index, 'releaseProtectionSettings', value)"
+ @visible-change="(visible) => handlePointDropdownVisible(index, 'releaseProtectionSettings', visible)"
+ :remote-method="(query) => remotePointSearch(index, 'releaseProtectionSettings', query)"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
执行保护
+
+
+
设置触发后的执行动作(降功率、停机、禁充放等)
+
+
+
+ 新增执行保护
+
+
+
+
+
+
{{ index + 1 }}
+
+
+
执行动作
+
handleProtectionActionChange(index, value)"
+ >
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/sbbh/index.vue b/src/views/ems/site/sbbh/index.vue
index 1ed331c..d69aa37 100644
--- a/src/views/ems/site/sbbh/index.vue
+++ b/src/views/ems/site/sbbh/index.vue
@@ -1,160 +1,185 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
+ style="width: 220px"
+ />
+
+
搜索
重置
-
-
- 新增设备
-
-
-
-
- 等级{{scope.row.faultLevel}}
-
-
- {{scope.row.isAlert === 1 ? '是' : '否'}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 编辑
-
-
- 删除
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ 等级{{ scope.row.faultLevel }}
+
+
+ {{ scope.row.isAlert === 1 ? '是' : '否' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
-
+
diff --git a/src/views/ems/site/sblb/AddDevice.vue b/src/views/ems/site/sblb/AddDevice.vue
index f0110ae..9f8ec8b 100644
--- a/src/views/ems/site/sblb/AddDevice.vue
+++ b/src/views/ems/site/sblb/AddDevice.vue
@@ -3,133 +3,124 @@
:show-close="false" destroy-on-close lock-scroll append-to-body width="800px" class="ems-dialog"
:title="mode === 'add'?'新增设备':`编辑设备` ">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PCS配置
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PCS配置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
取消
@@ -139,7 +130,6 @@
diff --git a/src/views/ems/site/sblb/PointTable.vue b/src/views/ems/site/sblb/PointTable.vue
index 5b41dc4..11de1d5 100644
--- a/src/views/ems/site/sblb/PointTable.vue
+++ b/src/views/ems/site/sblb/PointTable.vue
@@ -110,6 +110,11 @@
sortable="custom"
>
+
+
+ 选择
+
+
-
-
-
-
-
新增设备
-
downloadPointDetail(val,false)">
-
- 下载点位清单
-
-
-
- {{ item.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $store.state.ems.deviceStatusOptions[scope.row.deviceStatus] }}
-
-
-
-
-
-
+ width="180">
-
- 点位清单
-
-
- 报警点位清单
-
-
-
- 下载点位清单
-
-
- 上传点位清单
-
-
-
-
diff --git a/src/views/ems/site/zdlb/MonitorPointMapping.vue b/src/views/ems/site/zdlb/MonitorPointMapping.vue
new file mode 100644
index 0000000..37bbf23
--- /dev/null
+++ b/src/views/ems/site/zdlb/MonitorPointMapping.vue
@@ -0,0 +1,1697 @@
+
+
+
+
+
+
+
+
+
+
+ 列表配置
+ 快速配置
+ 分组填报
+
+
{{ saveStatusText }}
+
+
+ 导入配置
+ 导出配置
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ sectionBlock.section }}
+ {{ sectionBlock.fields.length }} 个字段
+
+
+
+
+
+
+
{{ row.deviceName || '默认映射' }}
+
+
+
固定展示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ row.deviceName || '默认映射' }}
+
+
+
固定展示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.row.deviceName || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+ 说明:按上方状态项分别维护“站点上送值 -> 系统状态编码/名称”的映射关系。
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 选择
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/site/zdlb/index.vue b/src/views/ems/site/zdlb/index.vue
index c4c3f27..ea64531 100644
--- a/src/views/ems/site/zdlb/index.vue
+++ b/src/views/ems/site/zdlb/index.vue
@@ -1,4 +1,3 @@
-
@@ -24,6 +23,7 @@
搜索
重置
+ 新增站点
+
+
+
+
+ 编辑
+ 同步天气
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/ems/zddt/index.vue b/src/views/ems/zddt/index.vue
index 0ef2058..b1ce6bc 100644
--- a/src/views/ems/zddt/index.vue
+++ b/src/views/ems/zddt/index.vue
@@ -1,33 +1,55 @@
-
+
-
-
-
-
-
-
-
-
基本信息
-
查看详情
+
+
+
+
+
{{ item.siteName || '-' }}
+
+ 电站位置
+ {{ formatSiteCardField(item.siteAddress) }}
+
+
+ 投运时间
+ {{ formatSiteCardDate(item.runningTime) }}
+
+
+ 装机功率(MWh)
+ {{ formatSiteCardField(item.installPower) }}
+
+
+ 装机容量(MWh)
+ {{ formatSiteCardField(item.installCapacity) }}
+
-
{{singleSiteName}}
-
-
-
-
-
-
-
-
- {{item.value | formatNumber }}
-
-
-
-
-
+
+
+
+
+
@@ -36,94 +58,103 @@