From 7fdb6e2ad3e8f77abf857fda52860e7fa5cbf1a4 Mon Sep 17 00:00:00 2001 From: dashixiong Date: Thu, 12 Feb 2026 21:19:23 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/ems/dzjk.js | 25 + src/api/ems/search.js | 10 +- src/api/ems/site.js | 159 +++ src/router/ems.js | 6 +- src/router/index.js | 13 + src/store/modules/permission.js | 16 +- src/views/ems/search/index.vue | 706 ++++++---- src/views/ems/site/pointConfig/index.vue | 1235 +++++++++++++++++ src/views/ems/site/sblb/index.vue | 21 +- .../ems/site/zdlb/MonitorPointMapping.vue | 217 +++ src/views/ems/site/zdlb/index.vue | 223 ++- vue.config.js | 2 +- 12 files changed, 2320 insertions(+), 313 deletions(-) create mode 100644 src/views/ems/site/pointConfig/index.vue create mode 100644 src/views/ems/site/zdlb/MonitorPointMapping.vue diff --git a/src/api/ems/dzjk.js b/src/api/ems/dzjk.js index 56a36b0..79b619f 100644 --- a/src/api/ems/dzjk.js +++ b/src/api/ems/dzjk.js @@ -8,6 +8,31 @@ export function getDzjkHomeView(siteId) { }) } +// 单站监控项目点位配置(供单站监控功能查询) +export function getProjectPointMapping(siteId) { + return request({ + url: `/ems/siteMonitor/getProjectPointMapping?siteId=${siteId}`, + method: 'get' + }) +} + +// 单站监控项目展示数据(字段配置 + 最新值) +export function getProjectDisplayData(siteId) { + return request({ + url: `/ems/siteMonitor/getProjectDisplayData?siteId=${siteId}`, + method: 'get' + }) +} + +// 单站监控项目展示数据写入(批量) +export function saveProjectDisplayData(data) { + return request({ + url: `/ems/siteMonitor/saveProjectDisplayData`, + method: 'post', + data + }) +} + //站点首页 冲放曲线 export function getSevenChargeData({siteId, startDate, endDate}) { return request({ diff --git a/src/api/ems/search.js b/src/api/ems/search.js index 32bd1ea..e1cf73d 100644 --- a/src/api/ems/search.js +++ b/src/api/ems/search.js @@ -29,4 +29,12 @@ export function getAllBatteryIdsBySites(data) { url: `/ems/generalQuery/getAllBatteryIdsBySites/${data}`, method: 'get', }) -} \ No newline at end of file +} + +// 综合查询-按站点获取配置设备列表 +export function getGeneralQueryDeviceList(siteId) { + return request({ + url: `/ems/siteConfig/getDeviceList?siteId=${siteId}`, + method: 'get', + }) +} diff --git a/src/api/ems/site.js b/src/api/ems/site.js index 2f25903..60233a5 100644 --- a/src/api/ems/site.js +++ b/src/api/ems/site.js @@ -8,6 +8,24 @@ export function getSiteInfoList({siteName, startTime, endTime, pageSize, pageNum }) } +// 新增站点 +export function addSite(data) { + return request({ + url: `/ems/siteConfig/addSite`, + method: 'post', + data + }) +} + +// 编辑站点 +export function updateSite(data) { + return request({ + url: `/ems/siteConfig/updateSite`, + method: 'post', + data + }) +} + // 设备列表 export function getDeviceInfoList(data) { return request({ @@ -102,6 +120,23 @@ export function getDeviceListBySiteAndCategory({siteId, deviceCategory}) { }) } +// 获取单站监控项目点位映射 +export function getSingleMonitorProjectPointMapping(siteId) { + return request({ + url: `/ems/siteConfig/getSingleMonitorProjectPointMapping?siteId=${siteId}`, + method: 'get', + }) +} + +// 保存单站监控项目点位映射 +export function saveSingleMonitorProjectPointMapping(data) { + return request({ + url: `/ems/siteConfig/saveSingleMonitorProjectPointMapping`, + method: 'post', + data + }) +} + //新增设备保护 export function addProtectPlan(data) { return request({ @@ -163,6 +198,130 @@ export function importPointList(data) { }) } +// 按站点导入模板点位配置 +export function importPointTemplateBySite(data) { + return request({ + url: `/ems/pointConfig/importTemplateBySite`, + method: 'post', + data + }) +} + +// CSV导入点位配置 +export function importPointConfigCsv(data) { + return request({ + url: `/ems/pointConfig/importCsv`, + method: 'post', + data, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) +} + +// 点位配置列表 +export function getPointMatchList(params) { + return request({ + url: `/ems/pointConfig/list`, + method: 'get', + params + }) +} + +// 点位配置详情 +export function getPointMatchDetail(id) { + return request({ + url: `/ems/pointConfig/${id}`, + method: 'get', + }) +} + +// 新增点位配置 +export function addPointMatch(data) { + return request({ + url: `/ems/pointConfig`, + method: 'post', + data + }) +} + +// 编辑点位配置 +export function updatePointMatch(data) { + return request({ + url: `/ems/pointConfig`, + method: 'put', + data + }) +} + +// 删除点位配置 +export function deletePointMatch(ids) { + return request({ + url: `/ems/pointConfig/${ids}`, + method: 'delete', + }) +} + +// 点位配置-批量获取最新值(新接口) +export function getPointConfigLatestValues(data) { + return request({ + url: `/ems/pointConfig/latestValues`, + method: 'post', + data + }) +} + +// 点位配置-曲线数据(新接口) +export function getPointConfigCurve(data) { + return request({ + url: `/ems/pointConfig/curve`, + method: 'post', + data + }) +} + +// 计算点配置列表 +export function getPointCalcConfigList(params) { + return request({ + url: `/ems/pointCalcConfig/list`, + method: 'get', + params + }) +} + +// 计算点配置详情 +export function getPointCalcConfigDetail(id) { + return request({ + url: `/ems/pointCalcConfig/${id}`, + method: 'get', + }) +} + +// 新增计算点配置 +export function addPointCalcConfig(data) { + return request({ + url: `/ems/pointCalcConfig`, + method: 'post', + data + }) +} + +// 编辑计算点配置 +export function updatePointCalcConfig(data) { + return request({ + url: `/ems/pointCalcConfig`, + method: 'put', + data + }) +} + +// 删除计算点配置 +export function deletePointCalcConfig(ids) { + return request({ + url: `/ems/pointCalcConfig/${ids}`, + method: 'delete', + }) +} //mqtt export function getMqttList({pageSize, pageNum, mqttTopic, topicName, siteId}) { diff --git a/src/router/ems.js b/src/router/ems.js index f30c1e3..bda6551 100644 --- a/src/router/ems.js +++ b/src/router/ems.js @@ -9,13 +9,13 @@ export const dzjk = [ redirect: '/dzjk/home', meta: {title: '单站监控', icon: 'dashboard',}, alwaysShow: false, - name: 'Dzjk', + name: 'DzjkLocal', hidden: true, children: [ { path: '', component: () => import('@/views/ems/dzjk/index'), - name: 'Dzjk', + name: 'DzjkRoot', redirect: '/dzjk/home', hidden: true, children: [ @@ -301,5 +301,3 @@ export const dzjk = [ } ] - - diff --git a/src/router/index.js b/src/router/index.js index a3cf594..37d08b9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -89,6 +89,19 @@ export const constantRoutes = [ } ] }, + { + path: '/ems/site/zdlb', + component: Layout, + hidden: true, + children: [ + { + path: 'monitor-point-mapping', + component: () => import('@/views/ems/site/zdlb/MonitorPointMapping.vue'), + name: 'MonitorPointMapping', + meta: { title: '单站监控项目点位配置', activeMenu: '/ems/site/zdlb' } + } + ] + }, // EMS管理系统routers ...dzjk ] diff --git a/src/store/modules/permission.js b/src/store/modules/permission.js index 6484ed2..0265157 100644 --- a/src/store/modules/permission.js +++ b/src/store/modules/permission.js @@ -47,9 +47,12 @@ const permission = { const asyncRoutes = filterDynamicRoutes(dynamicRoutes) rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true }) router.addRoutes(asyncRoutes) - if(!hasDzjk){ + // 后端已下发 dzjk 菜单时,移除本地静态 dzjk 路由,避免重名/重复注册 + if (hasDzjk) { const index = constantRoutes.findIndex(i=>i.path.indexOf('dzjk')>-1) - constantRoutes.splice(index,1) + if (index > -1) { + constantRoutes.splice(index, 1) + } } commit('SET_ROUTES', rewriteRoutes) commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes)) @@ -121,11 +124,16 @@ export function filterDynamicRoutes(routes) { } export const loadView = (view) => { + const normalizedView = String(view || '') + .replace(/^\.\/+/, '') + .replace(/^\/+/, '') + .replace(/^@\/views\//, '') + if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'staging') { - return (resolve) => require([`@/views/${view}`], resolve) + return (resolve) => require([`@/views/${normalizedView}`], resolve) } else { // 使用 import 实现生产环境的路由懒加载 - return () => import(`@/views/${view}`) + return () => import(`@/views/${normalizedView}`) } } diff --git a/src/views/ems/search/index.vue b/src/views/ems/search/index.vue index 5938ede..f8f4d53 100644 --- a/src/views/ems/search/index.vue +++ b/src/views/ems/search/index.vue @@ -10,7 +10,7 @@ prop="siteIds" :rules="[{ required: true, message: '请选择站点' }]" > - + - - - - {{ item.name }} - - - +
+ + + + + + + +
+ + + +
+ 已选 {{ group.pointNames.length }} 个点位 +
+ + 刷新点位 + + + 清空选择 + +
+
+
+
+ +
+ + +
+
+ + @@ -50,22 +129,7 @@ @change="handleChildChange" > -
- - - -
+ 生成图表 @@ -98,8 +162,14 @@ 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 { + getAllBatteryIdsBySites, + getGeneralQueryDeviceList, + getPointValueList, + pointFuzzyQuery, +} from "@/api/ems/search"; import DateTimeSelect from "./DateTimeSelect.vue"; +import {debounce} from "@/utils"; export default { name: "Search", @@ -107,13 +177,19 @@ export default { components: {DateTimeSelect}, computed: { isDtdc() { - return this.form.deviceCategory === "BATTERY"; + return this.form.queryGroups?.[0]?.deviceCategory === "BATTERY"; }, }, watch: { "form.siteIds": { handler(newVal) { - newVal && this.isDtdc && this.getChildList(); + this.form.queryGroups = [this.createEmptyQueryGroup()]; + this.form.child = []; + this.childOptions = []; + this.deviceList = []; + if (newVal) { + this.getDeviceListBySite(newVal); + } }, }, isDtdc: { @@ -123,8 +199,7 @@ export default { }, }, "form.dataUnit": { - handler(newVal, oldVal) { - console.log("wacth到了dataUnit的变化", newVal, oldVal); + handler() { this.$nextTick(() => { this.$refs.dateTimeSelect.init(); this.getDate(); @@ -135,24 +210,96 @@ export default { data() { return { chart: null, - deviceCategoryList: [], //设备列表 - siteList: [], //站点列表 - childOptions: [], //二级设备列表 + deviceList: [], + siteList: [], + childOptions: [], + maxQueryGroups: 10, + groupKeySeed: 0, form: { - dataRange: [], //时间选择范围 + dataRange: [], child: [], - siteIds: "", //当前选中的站点id 默认选中第一个站点 - deviceCategory: "", //设备 - pointName: "", //点位 - dataUnit: 1, //横坐标 + siteIds: "", + queryGroups: [], + dataUnit: 1, }, loading: false, + debouncedPointSearchMap: {}, }; }, + created() { + this.form.queryGroups = [this.createEmptyQueryGroup()]; + }, methods: { - changeSiteIds(val) { - console.log("切换站点或设备清空点位", val); - val && (this.form.pointName = ""); + createEmptyQueryGroup() { + this.groupKeySeed += 1; + return { + key: this.groupKeySeed, + deviceId: "", + deviceCategory: "", + pointNames: [], + pointOptions: [], + pointOptionsCache: {}, + pointRequestId: 0, + pointLoading: false, + }; + }, + getQueryGroup(index) { + return this.form.queryGroups[index]; + }, + addQueryGroup() { + if (this.form.queryGroups.length >= this.maxQueryGroups) { + this.$message.warning(`最多只能添加 ${this.maxQueryGroups} 组`); + return; + } + this.form.queryGroups.push(this.createEmptyQueryGroup()); + }, + removeQueryGroup(index) { + if (this.form.queryGroups.length <= 1) return; + this.form.queryGroups.splice(index, 1); + if (index === 0) { + this.form.child = []; + } + }, + canSelectPoint(group) { + return !!(this.form.siteIds && group && group.deviceId); + }, + pointSelectPlaceholder(group) { + return this.canSelectPoint(group) + ? "支持关键字搜索,展开可查看设备点位" + : "请先选择站点和设备"; + }, + pointNoDataText(group) { + return this.canSelectPoint(group) ? "暂无匹配点位" : "请先选择站点和设备"; + }, + getDeviceListBySite(siteId) { + if (!siteId) return Promise.resolve([]); + return getGeneralQueryDeviceList(siteId).then((response) => { + this.deviceList = response?.data || []; + return this.deviceList; + }); + }, + handleDeviceChange(groupIndex, deviceId) { + const group = this.getQueryGroup(groupIndex); + if (!group) return; + const selected = this.deviceList.find((item) => item.deviceId === deviceId); + group.deviceCategory = selected?.deviceCategory || ""; + group.pointNames = []; + group.pointOptions = []; + group.pointOptionsCache = {}; + group.pointRequestId = 0; + group.pointLoading = false; + + if (groupIndex === 0) { + this.form.child = []; + this.childOptions = []; + if (this.isDtdc) { + this.getChildList(); + } + } + + if (this.canSelectPoint(group)) { + this.fetchPointOptions(groupIndex, ""); + } }, getChildList() { this.childOptions = []; @@ -173,8 +320,8 @@ export default { 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; + const start = (i - 1) * base + 1; + const end = i * base; options[index].children.push({ value: i, label: `${start}-${end}`, @@ -188,12 +335,10 @@ export default { } } }); - console.log("二级设备options", options); this.childOptions = options; }); }, handleChildChange(data) { - console.log("选择二级设备", data); this.form.child = data; }, showLoading() { @@ -210,27 +355,22 @@ 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) => { + data.forEach((item) => { item.deviceList.forEach((inner) => { dataset.push({ name: `${ @@ -242,44 +382,38 @@ export default { 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,24 +425,17 @@ export default { }); }); }); - console.log("折线图图表数据", dataset); + this.chart.setOption({ - legend: { - // left: 'center', - // top: '10', - }, + legend: {}, grid: { containLabel: true, }, tooltip: { trigger: "axis", axisPointer: { - type: 'cross', + type: "cross", }, - // axisPointer: { - // // 坐标轴指示器,坐标轴触发有效 - // type: "shadow", // 默认为直线,可选为:'line' | 'shadow' - // }, }, textStyle: { color: "#333333", @@ -333,7 +460,7 @@ export default { }, setBoxOption(data) { let dataset = []; - data.forEach((item, index) => { + data.forEach((item) => { item.deviceList.forEach((inner) => { dataset.push({ name: `${ @@ -342,86 +469,33 @@ export default { : "" }${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'), - // } - // } - // ] - // }, 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', - }, + legend: {}, grid: { containLabel: true, }, 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", @@ -447,105 +521,189 @@ export default { submitForm() { this.getDate(); }, - handleSelect(data) { - this.form.pointName = data.value; + getPointCacheKey(group, query = "") { + return `${this.form.siteIds}_${group.deviceId}_${query.trim()}`; }, - querySearchAsync(query, cb) { - console.log("查询数据", query); - if (!this.form.siteIds || !this.form.deviceCategory) { - this.$message({ - type: "warning", - message: "请先选择站点和设备", - }); - return cb([]); + normalizePointOptions(data = []) { + return (data || []).map((item) => { + if (typeof item === "string") { + return {value: item, label: item, pointName: item, dataKey: "", pointDesc: ""}; + } + const pointName = item?.pointName || item?.value || ""; + const dataKey = item?.dataKey || ""; + const pointDesc = item?.pointDesc || ""; + const labelParts = [pointName]; + if (dataKey && dataKey !== pointName) { + labelParts.push(`key:${dataKey}`); + } + if (pointDesc) { + labelParts.push(pointDesc); + } + return { + value: pointName, + label: labelParts.join(" | "), + pointName, + dataKey, + pointDesc, + }; + }).filter((item) => item.value); + }, + setPointOptions(group, data = []) { + const selected = group.pointNames || []; + const selectedOptions = selected.map((value) => ({value, label: value, pointName: value})); + const normalized = this.normalizePointOptions(data); + const merged = [...selectedOptions, ...normalized]; + const seen = {}; + group.pointOptions = merged.filter((item) => { + if (!item?.value || seen[item.value]) { + return false; + } + seen[item.value] = true; + return true; + }); + }, + fetchPointOptions(groupIndex, query = "", {force = false} = {}) { + const group = this.getQueryGroup(groupIndex); + if (!group || !this.canSelectPoint(group)) return Promise.resolve([]); + const normalizedQuery = (query || "").trim(); + const cacheKey = this.getPointCacheKey(group, normalizedQuery); + if (!force && group.pointOptionsCache[cacheKey]) { + this.setPointOptions(group, group.pointOptionsCache[cacheKey]); + return Promise.resolve(group.pointOptionsCache[cacheKey]); } - pointFuzzyQuery({ + const requestId = ++group.pointRequestId; + group.pointLoading = true; + return 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}; - }) - ); - }); - }, - // 获取所有设备 - getDeviceCategory() { - return getAllDeviceCategory().then((response) => { - this.deviceCategoryList = response?.data || []; - }); - }, - getZdList() { - return getAllSites() + deviceCategory: group.deviceCategory, + deviceId: group.deviceId, + pointName: normalizedQuery, + }) .then((response) => { - this.siteList = response.data || []; + if (requestId !== group.pointRequestId) return []; + const data = this.normalizePointOptions(response?.data || []); + group.pointOptionsCache[cacheKey] = data; + this.setPointOptions(group, data); + return data; }) .finally(() => { + if (requestId === group.pointRequestId) { + group.pointLoading = false; + } }); }, + getDebouncedPointSearch(groupIndex) { + if (!this.debouncedPointSearchMap[groupIndex]) { + this.debouncedPointSearchMap[groupIndex] = debounce((query) => { + this.fetchPointOptions(groupIndex, query); + }, 260); + } + return this.debouncedPointSearchMap[groupIndex]; + }, + remotePointSearch(groupIndex, query) { + const group = this.getQueryGroup(groupIndex); + if (!group || !this.canSelectPoint(group)) return; + this.getDebouncedPointSearch(groupIndex)(query); + }, + handlePointDropdownVisible(groupIndex, visible) { + const group = this.getQueryGroup(groupIndex); + if (visible && group && this.canSelectPoint(group)) { + this.fetchPointOptions(groupIndex, ""); + } + }, + refreshPointOptions(groupIndex) { + const group = this.getQueryGroup(groupIndex); + if (!group || !this.canSelectPoint(group)) return; + this.fetchPointOptions(groupIndex, "", {force: true}); + }, + clearPointSelection(groupIndex) { + const group = this.getQueryGroup(groupIndex); + if (!group) return; + group.pointNames = []; + }, + getZdList() { + return getAllSites().then((response) => { + this.siteList = response.data || []; + }); + }, + buildSiteDeviceMap() { + let siteDeviceMap = {}; + this.form.child.forEach(([first, second, third]) => { + if (siteDeviceMap[first]) { + siteDeviceMap[first].push(third); + } else { + siteDeviceMap[first] = [third]; + } + }); + return siteDeviceMap; + }, 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({ + if (!valid) { + return; + } + + const activeGroups = this.form.queryGroups + .map((group, index) => ({group, index})) + .filter(({group}) => group.deviceId && group.pointNames && group.pointNames.length > 0); + + if (activeGroups.length === 0) { + return this.$message.error("请至少选择1组设备和点位"); + } + + const invalidGroupIndex = this.form.queryGroups.findIndex( + (group) => group.deviceId && (!group.pointNames || group.pointNames.length === 0) + ); + if (invalidGroupIndex !== -1) { + return this.$message.error(`第 ${invalidGroupIndex + 1} 组请至少选择1个点位`); + } + + if (this.isDtdc && (this.form.child.length === 0 || this.form.child.length > 5)) { + return this.$message.error("请选择单体电池且不能超过5个"); + } + + const { + siteIds, + dataUnit, + dataRange: [start = "", end = ""], + } = this.form; + + if (!start || !end) return this.$message.error("请选择时间"); + + let startDate = start; + let endDate = end; + if (start && dataUnit === 3) { + startDate = start + " 00:00:00"; + } + if (end && dataUnit === 3) { + endDate = end + " 00:00:00"; + } + + this.loading = true; + + const requests = activeGroups.map(({group, index}) => { + const useChildMap = index === 0 && group.deviceCategory === "BATTERY"; + return getPointValueList({ siteIds: [siteIds], dataUnit, - deviceCategory, - pointName, + deviceId: group.deviceId, + deviceCategory: group.deviceCategory, + pointNames: group.pointNames, + pointName: group.pointNames.join(","), startDate, endDate, - siteDeviceMap, - }) - .then((response) => { - this.setOption(response?.data || []); - }) - .finally(() => { - this.loading = false; - }); - } + siteDeviceMap: useChildMap ? this.buildSiteDeviceMap() : {}, + }).then((response) => response?.data || []); + }); + + Promise.all(requests) + .then((groupResults) => { + const merged = groupResults.flat(); + this.setOption(merged); + }) + .finally(() => { + this.loading = false; + }); }); }, }, @@ -561,12 +719,58 @@ export default { this.$nextTick(() => { this.initChart(); this.$refs.dateTimeSelect.init(); - Promise.all([this.getDeviceCategory(), this.getZdList()]).finally( - () => (this.loading = false) - ); + Promise.all([this.getZdList()]).finally(() => (this.loading = false)); }); }, }; - + diff --git a/src/views/ems/site/pointConfig/index.vue b/src/views/ems/site/pointConfig/index.vue new file mode 100644 index 0000000..6297739 --- /dev/null +++ b/src/views/ems/site/pointConfig/index.vue @@ -0,0 +1,1235 @@ + + + + + diff --git a/src/views/ems/site/sblb/index.vue b/src/views/ems/site/sblb/index.vue index 9424372..b3c6d07 100644 --- a/src/views/ems/site/sblb/index.vue +++ b/src/views/ems/site/sblb/index.vue @@ -95,19 +95,6 @@ label="操作" width="250"> @@ -177,14 +163,13 @@ import {deleteService, getDeviceDetailInfo, getDeviceInfoList} from '@/api/ems/s import {getAllSites} from '@/api/ems/zddt' import {formatNumber} from "@/filters/ems"; import {getAllDeviceCategory} from '@/api/ems/search' -import PointTable from './PointTable.vue' import AddDevice from "./AddDevice.vue"; import PointUpload from "./PointUpload.vue"; // import PcsSwitch from "./PcsSwitch.vue"; export default { name: "Sblb", - components: {AddDevice, PointTable, PointUpload}, + components: {AddDevice, PointUpload}, data() { return { loading: false, @@ -230,10 +215,6 @@ export default { this.deviceCategoryList = response?.data || [] }) }, - // 查看设备电位表格 - pointDetail(row, dataType) { - this.$refs.pointTable.showTable(row, dataType) - }, // 下载点位清单 downloadPointDetail(command, isDetail = false) { const siteId = isDetail ? command.siteId : this.siteId diff --git a/src/views/ems/site/zdlb/MonitorPointMapping.vue b/src/views/ems/site/zdlb/MonitorPointMapping.vue new file mode 100644 index 0000000..bcdb7db --- /dev/null +++ b/src/views/ems/site/zdlb/MonitorPointMapping.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/src/views/ems/site/zdlb/index.vue b/src/views/ems/site/zdlb/index.vue index c4c3f27..e832e1d 100644 --- a/src/views/ems/site/zdlb/index.vue +++ b/src/views/ems/site/zdlb/index.vue @@ -1,4 +1,3 @@ - + + diff --git a/src/views/ems/dzjk/clpz/xftg/AddTemplate.vue b/src/views/ems/dzjk/clpz/xftg/AddTemplate.vue index c8f8dd1..9387e5e 100644 --- a/src/views/ems/dzjk/clpz/xftg/AddTemplate.vue +++ b/src/views/ems/dzjk/clpz/xftg/AddTemplate.vue @@ -9,14 +9,6 @@ - - - - - - - - 新增 @@ -64,6 +56,12 @@ + + + + + + + + + + + + + + + { const data = JSON.parse(JSON.stringify(response?.data || [])); if (data.length > 0) { - const {templateName, sdcLimit, sdcDown, sdcUp} = JSON.parse(JSON.stringify(data[0])); + const {templateName, sdcLimit} = JSON.parse(JSON.stringify(data[0])); this.formData.templateName = templateName this.formData.sdcLimit = sdcLimit - this.formData.sdcDown = sdcDown - this.formData.sdcUp = sdcUp } if (data.length === 1) { - const {startTime, endTime} = data; + const {startTime, endTime} = data[0]; if (!startTime || !endTime) { this.tableData = [] } else { @@ -242,15 +316,15 @@ export default { cancelAddTime() { this.$refs.addTimeForm.resetFields() this.showAddTime = false - this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', chargeStatus: ''}//startTime: '', endTime: '', + this.formInline = {timeRange: this.secondRange, chargeDischargePower: '', sdcDown: '', sdcUp: '', chargeStatus: ''}//startTime: '', endTime: '', }, saveTime() { //表单校验,校验成功,添加到tableData里 this.$refs.addTimeForm.validate(valid => { if (!valid) return - const {timeRange: [startTime, endTime], chargeDischargePower, chargeStatus} = this.formInline + const {timeRange: [startTime, endTime], chargeDischargePower, sdcDown, sdcUp, chargeStatus} = this.formInline - this.tableData.push({startTime, endTime, chargeDischargePower, chargeStatus}) + this.tableData.push({startTime, endTime, chargeDischargePower, sdcDown, sdcUp, chargeStatus}) this.$nextTick(() => { this.cancelAddTime() }) @@ -262,9 +336,14 @@ export default { saveDialog() { this.$refs.addTempForm.validate(valid => { if (!valid) return - const {templateName, sdcLimit, sdcDown, sdcUp} = this.formData + const {templateName, sdcLimit} = this.formData const {siteId, updateStrategyId} = this.$home - const {tableData} = this + const tableData = this.tableData.map(item => ({ + ...item, + sdcDown: this.normalizeSocValue(item.sdcDown), + sdcUp: this.normalizeSocValue(item.sdcUp) + })) + if (!this.validateTableData(tableData)) return if (this.mode === 'edit') { editStrategyTemp({ siteId, @@ -272,8 +351,6 @@ export default { templateId: this.editTempId, templateName, sdcLimit, - sdcDown, - sdcUp, timeConfigList: tableData }).then(response => { if (response?.code === 200) { @@ -288,8 +365,6 @@ export default { strategyId: updateStrategyId, templateName, sdcLimit, - sdcDown, - sdcUp, timeConfigList: tableData }).then(response => { if (response?.code === 200) { @@ -300,14 +375,64 @@ export default { } }) }, + normalizeSocValue(value) { + if (value === null || value === undefined) return null + const normalized = String(value).replace('%', '').trim() + return normalized === '' ? null : normalized + }, + toMinutes(timeValue) { + if (!timeValue || String(timeValue).indexOf(':') < 0) return -1 + const [h, m] = String(timeValue).split(':') + const hour = Number(h), minute = Number(m) + if (!Number.isInteger(hour) || !Number.isInteger(minute)) return -1 + if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return -1 + return hour * 60 + minute + }, + validateTableData(list = []) { + const numberPattern = /^-?\d+(\.\d+)?$/ + const socPattern = /^(0|[1-9]\d*)(\.\d+)?$/ + for (let i = 0; i < list.length; i++) { + const row = list[i] + const rowNo = i + 1 + if (!row.startTime || !row.endTime) { + this.$message.error(`第${rowNo}行:开始时间和结束时间不能为空`) + return false + } + const startMinute = this.toMinutes(row.startTime) + const endMinute = this.toMinutes(row.endTime) + if (startMinute < 0 || endMinute < 0 || startMinute >= endMinute) { + this.$message.error(`第${rowNo}行:时间范围不合法`) + return false + } + if (!numberPattern.test(String(row.chargeDischargePower ?? '').trim())) { + this.$message.error(`第${rowNo}行:充放功率格式不正确`) + return false + } + if (!socPattern.test(String(row.sdcDown ?? '').trim())) { + this.$message.error(`第${rowNo}行:SOC下限格式不正确`) + return false + } + if (!socPattern.test(String(row.sdcUp ?? '').trim())) { + this.$message.error(`第${rowNo}行:SOC上限格式不正确`) + return false + } + if (Number(row.sdcDown) > Number(row.sdcUp)) { + this.$message.error(`第${rowNo}行:SOC下限不能大于SOC上限`) + return false + } + if (row.chargeStatus === undefined || row.chargeStatus === null || row.chargeStatus === '') { + this.$message.error(`第${rowNo}行:请选择充电状态`) + return false + } + } + return true + }, closeDialog() { // 清空所有数据 this.$refs.addTempForm.resetFields() this.formData = { templateName: '', sdcLimit: 0, - sdcDown: '', - sdcUp: '', } this.tableData = [] this.cancelAddTime() @@ -322,4 +447,4 @@ export default { max-height: 90vh; overflow-y: auto; } - \ No newline at end of file + diff --git a/src/views/ems/dzjk/clpz/xftg/TempTable.vue b/src/views/ems/dzjk/clpz/xftg/TempTable.vue index f79cf9b..3ad3043 100644 --- a/src/views/ems/dzjk/clpz/xftg/TempTable.vue +++ b/src/views/ems/dzjk/clpz/xftg/TempTable.vue @@ -39,14 +39,14 @@ prop="sdcDown" label="SOC下限"> { - this.setOption(response?.data || []) + getProjectDisplayData(siteId).then(response => { + const displayData = response?.data || [] + const sectionRows = displayData.filter(item => + item && item.sectionName === '当日功率曲线' && item.useFixedDisplay !== 1 && item.dataPoint + ) + const tasks = sectionRows.map(row => { + const pointId = String(row.dataPoint || '').trim() + if (!pointId) return Promise.resolve(null) + return getPointConfigCurve({ + siteId, + pointId, + pointType: 'data', + rangeType: 'custom', + startTime: this.normalizeDateTime(timeRange[0], false), + endTime: this.normalizeDateTime(timeRange[1], true) + }).then(curveResponse => { + const list = curveResponse?.data || [] + return { + name: row.fieldName || row.fieldCode || pointId, + data: list + .map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)]) + .filter(item => item[0] && !Number.isNaN(item[1])) + } + }).catch(() => null) + }) + return Promise.all(tasks) + }).then(series => { + this.setOption((series || []).filter(Boolean)) }).finally(() => this.hideLoading()) }, init(siteId) { @@ -70,12 +97,18 @@ export default { hideLoading() { this.chart && this.chart.hideLoading() }, - setOption(data) { - const source = [['日期', '电网功率', '负载功率', '储能功率', '光伏功率', 'soc平均值', 'soh平均值', '电池平均温度平均值']] - console.log('source.slice(1)', source[0].slice(1)) - this.chart && data.forEach((item) => { - source.push([item.statisDate, item.gridPower, item.loadPower, item.storagePower, item.pvPower, item.avgSoc, item.avgSoh, item.avgTemp]) - }) + normalizeDateTime(value, endOfDay) { + const raw = String(value || '').trim() + if (!raw) return '' + if (raw.includes(' ')) return raw + return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}` + }, + parseToTimestamp(value) { + if (!value) return null + const t = new Date(value).getTime() + return Number.isNaN(t) ? null : t + }, + setOption(seriesData = []) { this.chart.setOption({ grid: { containLabel: true @@ -86,35 +119,28 @@ export default { }, tooltip: { trigger: 'axis', - axisPointer: { // 坐标轴指示器,坐标轴触发有效 - type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' - } + axisPointer: { type: 'cross' } }, textStyle: { color: "#333333", }, xAxis: { - type: 'category', + type: 'time', }, - yAxis: [ - { - type: 'value', - }, - { - type: 'value', - }, - ], - dataset: {source}, - series: source[0].slice(1).map((item, index) => { + yAxis: [{ + type: 'value', + }], + series: seriesData.map((item) => { return { - type: 'line',//index === 5 ? 'bar' : 'line', + name: item.name, + type: 'line', showSymbol: false, symbolSize: 2, smooth: true, areaStyle: { opacity: 0.5, }, - yAxisIndex: index <= 4 ? 0 : 1 + data: item.data } }) }) @@ -124,4 +150,3 @@ export default { } - diff --git a/src/views/ems/dzjk/home/WeekChart.vue b/src/views/ems/dzjk/home/WeekChart.vue index 592be06..9176d5a 100644 --- a/src/views/ems/dzjk/home/WeekChart.vue +++ b/src/views/ems/dzjk/home/WeekChart.vue @@ -12,7 +12,8 @@ import * as echarts from 'echarts' import resize from '@/mixins/ems/resize' import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue' -import {getSevenChargeData} from '@/api/ems/dzjk' +import {getProjectDisplayData} from '@/api/ems/dzjk' +import {getPointConfigCurve} from '@/api/ems/site' export default { mixins: [resize], @@ -45,8 +46,34 @@ export default { getWeekKData() { this.showLoading() const {siteId, timeRange} = this - getSevenChargeData({siteId, startDate: timeRange[0], endDate: timeRange[1]}).then(response => { - this.setOption(response?.data || []) + getProjectDisplayData(siteId).then(response => { + const displayData = response?.data || [] + const sectionRows = displayData.filter(item => + item && item.sectionName === '一周充放曲线' && item.useFixedDisplay !== 1 && item.dataPoint + ) + const tasks = sectionRows.map(row => { + const pointId = String(row.dataPoint || '').trim() + if (!pointId) return Promise.resolve(null) + return getPointConfigCurve({ + siteId, + pointId, + pointType: 'data', + rangeType: 'custom', + startTime: this.normalizeDateTime(timeRange[0], false), + endTime: this.normalizeDateTime(timeRange[1], true) + }).then(curveResponse => { + const list = curveResponse?.data || [] + return { + name: row.fieldName || row.fieldCode || pointId, + data: list + .map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)]) + .filter(item => item[0] && !Number.isNaN(item[1])) + } + }).catch(() => null) + }) + return Promise.all(tasks) + }).then(series => { + this.setOption((series || []).filter(Boolean)) }).finally(() => this.hideLoading()) }, init(siteId) { @@ -65,18 +92,23 @@ export default { hideLoading() { this.chart && this.chart.hideLoading() }, - setOption(data, unit) { - const source = [['日期', '充电量', '放电量']] - data.forEach(item => { - source.push([item.ammeterDate, item.chargedCap, item.disChargedCap]) - }) + normalizeDateTime(value, endOfDay) { + const raw = String(value || '').trim() + if (!raw) return '' + if (raw.includes(' ')) return raw + return `${raw} ${endOfDay ? '23:59:59' : '00:00:00'}` + }, + parseToTimestamp(value) { + if (!value) return null + const t = new Date(value).getTime() + return Number.isNaN(t) ? null : t + }, + setOption(seriesData = []) { this.chart && this.chart.setOption({ color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一 tooltip: { trigger: 'axis', - axisPointer: { // 坐标轴指示器,坐标轴触发有效 - type: 'shadow' // 默认为直线,可选为:'line' | 'shadow' - } + axisPointer: { type: 'cross' } }, grid: { containLabel: true @@ -86,9 +118,7 @@ export default { bottom: '15', }, xAxis: { - type: 'category', - name: unit, - nameLocation: 'center' + type: 'time' }, yAxis: [{ type: 'value', @@ -100,19 +130,12 @@ export default { onZero: false } }], - dataset: { - source - }, - series: [ - { - yAxisIndex: 0, - type: 'bar', - }, - { - yAxisIndex: 0, - type: 'bar', - }, - ] + series: seriesData.map(item => ({ + name: item.name, + yAxisIndex: 0, + type: 'bar', + data: item.data + })) }) } } diff --git a/src/views/ems/dzjk/home/index.vue b/src/views/ems/dzjk/home/index.vue index b2f9b1b..fae1a3e 100644 --- a/src/views/ems/dzjk/home/index.vue +++ b/src/views/ems/dzjk/home/index.vue @@ -1,5 +1,5 @@ diff --git a/src/views/ems/dzjk/sbjk/bmszl/index.vue b/src/views/ems/dzjk/sbjk/bmszl/index.vue index 0815165..2ce8bf5 100644 --- a/src/views/ems/dzjk/sbjk/bmszl/index.vue +++ b/src/views/ems/dzjk/sbjk/bmszl/index.vue @@ -49,15 +49,21 @@ - {{ STACKWorkStatusOptions[baseInfo.workStatus] || '-' }} + + {{ STACKWorkStatusOptions[baseInfo.workStatus] || '-' }} + - {{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }} + + {{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }} + - {{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }} + + {{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }} + @@ -66,7 +72,7 @@ - + {{ displayValue(baseInfo[item.attr]) | formatNumber }} @@ -78,7 +84,7 @@
-
当前SOC : +
当前SOC : {{ baseInfo.stackSoc }}%
@@ -98,21 +104,21 @@ >
@@ -134,7 +140,7 @@ prop="minVoltage"> @@ -147,7 +153,7 @@ label="单体最高温度"> @@ -161,7 +167,7 @@ prop="minTemperature"> @@ -174,22 +180,47 @@ - + +
+ + 查询 +
+
+
@@ -363,6 +609,13 @@ export default { cursor: pointer; } +.curve-tools { + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 10px; +} + ::v-deep { //描述列表样式 .descriptions-main { diff --git a/src/views/ems/dzjk/sbjk/db/index.vue b/src/views/ems/dzjk/sbjk/db/index.vue index afa7e4f..9103933 100644 --- a/src/views/ems/dzjk/sbjk/db/index.vue +++ b/src/views/ems/dzjk/sbjk/db/index.vue @@ -41,23 +41,53 @@ :key="dataIndex + 'dbField'" :span="8" class="device-info-col" + :class="{ 'field-disabled': !item.pointId }" > - {{ item.fieldName }} - - - {{ displayValue(item.fieldValue) | formatNumber }} - +
+ {{ item.fieldName }} + + + {{ displayValue(item.fieldValue) | formatNumber }} + +
+ + +
+ + 查询 +
+
+
@@ -265,6 +463,26 @@ export default { cursor: pointer; } +.device-info-col { + cursor: pointer; +} + +.field-click-wrapper { + width: 100%; +} + +.device-info-col.field-disabled { + cursor: not-allowed; + opacity: 0.8; +} + +.curve-tools { + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 10px; +} + .point-loading-icon { color: #409eff; display: inline-block; diff --git a/src/views/ems/dzjk/sbjk/dtdc/List.vue b/src/views/ems/dzjk/sbjk/dtdc/List.vue index a638107..e9c2ae0 100644 --- a/src/views/ems/dzjk/sbjk/dtdc/List.vue +++ b/src/views/ems/dzjk/sbjk/dtdc/List.vue @@ -15,10 +15,10 @@ {{ item.clusterDeviceId }}
#{{ item.deviceId }}
-
+
{{ item.voltage }}V
-
+
{{ item.temperature }}℃
@@ -102,9 +102,8 @@ export default { return className; }, //查看表格行图表 - chartDetail(row, dataType = "") { - const { clusterDeviceId, deviceId } = row; - this.$emit("chart", { ...row, dataType }); + chartDetail(row, fieldKey = "") { + this.$emit("chart", { ...row, fieldKey }); }, }, }; diff --git a/src/views/ems/dzjk/sbjk/dtdc/Table.vue b/src/views/ems/dzjk/sbjk/dtdc/Table.vue index e8d9537..ce63cf1 100644 --- a/src/views/ems/dzjk/sbjk/dtdc/Table.vue +++ b/src/views/ems/dzjk/sbjk/dtdc/Table.vue @@ -11,7 +11,7 @@