diff --git a/src/api/ems/dzjk.js b/src/api/ems/dzjk.js index 79b619f..41b52a6 100644 --- a/src/api/ems/dzjk.js +++ b/src/api/ems/dzjk.js @@ -330,6 +330,23 @@ export function configStrategy(data) { }) } +// 获取策略运行参数配置(按站点) +export function getStrategyRuntimeConfig(siteId) { + return request({ + url: `/system/strategyRuntimeConfig/getBySiteId?siteId=${siteId}`, + method: 'get' + }) +} + +// 保存策略运行参数配置(按站点) +export function saveStrategyRuntimeConfig(data) { + return request({ + url: `/system/strategyRuntimeConfig/save`, + method: 'post', + data + }) +} + //http://localhost:8089/strategy/temp/getTempNameList?strategyId=1&siteId=021_FXX_01 //获取策略下的所有模板列表 export function getTempNameList({siteId, strategyId}) { diff --git a/src/components/Ems/ZdBaseInfo/index.vue b/src/components/Ems/ZdBaseInfo/index.vue index 8028c33..6ccb1ec 100644 --- a/src/components/Ems/ZdBaseInfo/index.vue +++ b/src/components/Ems/ZdBaseInfo/index.vue @@ -4,7 +4,10 @@ {{ item.title }} - {{item.num | formatNumber}} + + + {{item.num | formatNumber}} + @@ -18,30 +21,35 @@ export default { title:'站点总数(座)', num:'', color:'#FFBD00', - attr:'siteNum' + attr:'siteNum', + loading: true },{ title:'装机功率(MW)', num:'', color:'#3C81FF', - attr:'installPower' + attr:'installPower', + loading: true },{ title:'装机容量(MW)', num:'', color:'#5AC7C0', - attr:'installCapacity' + attr:'installCapacity', + loading: true },{ title:'总充电量(KWh)', num:'', color:'#A696FF', - attr:'totalChargedCap' + attr:'totalChargedCap', + loading: true },{ title:'总放电量(KWh)', num:'', color:'#A696FF', - attr:'totalDischargedCap' + attr:'totalDischargedCap', + loading: true }] } @@ -50,6 +58,7 @@ export default { setData(res = {}){ this.data.forEach((item)=>{ item.num =res[item.attr] + item.loading = false }) } }, diff --git a/src/components/Ems/ZdSelect/index.vue b/src/components/Ems/ZdSelect/index.vue index bfe869f..8dbc6c7 100644 --- a/src/components/Ems/ZdSelect/index.vue +++ b/src/components/Ems/ZdSelect/index.vue @@ -2,8 +2,16 @@ - - + + @@ -15,7 +23,21 @@ + + 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 @@ + + + + + + + + + {{ scope.row.startTime || '-' }} + + + + {{ scope.row.endTime || '-' }} + + + + {{ scope.row.chargeDischargePower || '-' }} + + + + + + {{ scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%' }} + + + + + + {{ scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%' }} + - {{ chargeStatusOptions[scope.row.chargeStatus] }} + + + + {{ chargeStatusOptions[scope.row.chargeStatus] }} { 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下限"> - {{scope.row.sdcDown ? scope.row. sdcDown + '%' : '-'}} + {{scope.row.sdcDown === null || scope.row.sdcDown === undefined || scope.row.sdcDown === '' ? '-' : scope.row.sdcDown + '%'}} - {{scope.row.sdcUp ? scope.row.sdcUp + '%' : '-'}} + {{scope.row.sdcUp === null || scope.row.sdcUp === undefined || scope.row.sdcUp === '' ? '-' : scope.row.sdcUp + '%'}} { - 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 @@ - + @@ -21,13 +21,19 @@ - {{ info.siteAddress }} + + + {{ info.siteAddress || '-' }} + - {{ info.runningTime || '-' }} + + + {{ info.runningTime || '-' }} + @@ -38,7 +44,8 @@ 装机功率(MW) - {{ info.installPower | formatNumber }} + + {{ info.installPower | formatNumber }} @@ -49,7 +56,8 @@ 装机容量(MW) - {{ info.installCapacity | formatNumber }} + + {{ info.installCapacity | formatNumber }} @@ -67,7 +75,10 @@ 总累计运行数据 总收入 - {{ runningInfo.totalRevenue | formatNumber }} + + + {{ totalRevenueDisplayValue | formatNumber }} + 元 @@ -77,14 +88,15 @@ {{ item.title }} - {{ runningInfo[item.attr] | formatNumber }} + + {{ item.value | formatNumber }} @@ -104,7 +116,7 @@ diff --git a/src/views/ems/site/sblb/index.vue b/src/views/ems/site/sblb/index.vue index b3c6d07..be78262 100644 --- a/src/views/ems/site/sblb/index.vue +++ b/src/views/ems/site/sblb/index.vue @@ -1,13 +1,6 @@ - - - - - 新增设备 - 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 index bcdb7db..d5ee3f0 100644 --- a/src/views/ems/site/zdlb/MonitorPointMapping.vue +++ b/src/views/ems/site/zdlb/MonitorPointMapping.vue @@ -3,7 +3,10 @@ 单站监控项目点位配置 - 站点:{{ siteName || siteId || '-' }} + + 站点: + {{ siteName || siteId || '-' }} + 返回 @@ -34,15 +37,42 @@ - + + + + + + + + + + + + + + + 删除 + + + - + + + + + + + + + + + + + + + 删除 + + + @@ -85,17 +142,25 @@ export default { name: 'MonitorPointMapping', data() { return { + siteId: '', + siteName: '', activeTopMenu: 'HOME', activeChildMap: {}, - topMenuList: [] + topMenuList: [], + deletedFieldCodes: [] } }, - computed: { - siteId() { - return this.$route.query.siteId || '' - }, - siteName() { - return this.$route.query.siteName || '' + watch: { + '$route.query.siteId': { + immediate: false, + async handler(newSiteId) { + if (newSiteId === this.siteId) { + return + } + this.siteId = newSiteId || '' + this.siteName = this.$route.query.siteName || this.siteId + await this.initMenuStructure() + } } }, methods: { @@ -105,6 +170,7 @@ export default { async initMenuStructure() { if (!this.siteId) { this.topMenuList = [] + this.deletedFieldCodes = [] return } const response = await getSingleMonitorProjectPointMapping(this.siteId) @@ -131,7 +197,9 @@ export default { section: row.sectionName || '-', name: row.fieldName || '-', field: row.fieldCode || '', - point: row.dataPoint || '' + point: row.dataPoint || '', + fixedValue: row.fixedDataPoint || '', + useFixedDisplay: row.useFixedDisplay === 1 ? 1 : 0 } const topItem = topMap.get(topKey) const isTopDirect = moduleCode === menuCode || moduleCode === 'HOME' @@ -152,6 +220,7 @@ export default { const firstTop = this.topMenuList[0] this.activeTopMenu = firstTop ? firstTop.code : 'HOME' this.activeChildMap = {} + this.deletedFieldCodes = [] this.topMenuList.forEach(top => { if (top.children && top.children.length > 0) { this.$set(this.activeChildMap, top.code, top.children[0].code) @@ -168,6 +237,23 @@ export default { }) return rows }, + handleDeleteItem(index, list) { + if (!Array.isArray(list) || index < 0 || index >= list.length) { + return + } + this.$confirm('是否确认删除该条明细记录?删除后需点击“保存”才会生效。', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + const fieldCode = list[index] && list[index].field + if (fieldCode && !this.deletedFieldCodes.includes(fieldCode)) { + this.deletedFieldCodes.push(fieldCode) + } + list.splice(index, 1) + this.$message.success('已删除该条明细记录') + }).catch(() => {}) + }, async saveData() { if (!this.siteId) { this.$message.warning('缺少站点ID') @@ -175,13 +261,29 @@ export default { } const mappings = this.getAllMappingRows() .filter(item => item.field) - .map(item => ({ fieldCode: item.field, dataPoint: item.point || '' })) - await saveSingleMonitorProjectPointMapping({ siteId: this.siteId, mappings }) - this.$message.success('保存成功') + .map(item => ({ + fieldCode: item.field, + dataPoint: item.point || '', + fixedDataPoint: item.fixedValue || '', + useFixedDisplay: item.useFixedDisplay === 1 ? 1 : 0 + })) + const response = await saveSingleMonitorProjectPointMapping({ + siteId: this.siteId, + mappings, + deletedFieldCodes: this.deletedFieldCodes + }) + const affectedRows = Number(response?.data || 0) + if (affectedRows > 0) { + this.$message.success(`保存成功,已写入 ${affectedRows} 条`) + } else { + this.$message.warning('保存完成,但写入 0 条,请检查字段编码和点位值是否有效') + } await this.initMenuStructure() } }, async mounted() { + this.siteId = this.$route.query.siteId || '' + this.siteName = this.$route.query.siteName || this.siteId await this.initMenuStructure() } } @@ -209,6 +311,16 @@ export default { margin-top: 6px; font-size: 13px; color: #909399; + display: flex; + align-items: center; +} + +.site-label { + margin-right: 8px; +} + +.short-input { + width: 220px; } .child-menu-tabs { diff --git a/src/views/ems/site/zdlb/index.vue b/src/views/ems/site/zdlb/index.vue index e832e1d..28a1abc 100644 --- a/src/views/ems/site/zdlb/index.vue +++ b/src/views/ems/site/zdlb/index.vue @@ -59,11 +59,10 @@ 编辑 - 配置 @@ -266,15 +265,6 @@ export default { this.$refs.siteForm && this.$refs.siteForm.clearValidate() }) }, - openPointMappingPage(row) { - this.$router.push({ - path: '/ems/site/zdlb/monitor-point-mapping', - query: { - siteId: row.siteId, - siteName: row.siteName || '' - } - }) - }, submitSite() { this.$refs.siteForm.validate(valid => { if (!valid) {