1.一周充放曲线改为了时间聚合柱状图。
2.PCS最高温度修复bug展示多PCS设备的数据 3.PCS的状态根据状态枚举映射配置的内容显示 4.BMS的总览,增加工作状态、与PCS通讯、与EMS通讯的配置,及关联展示 5.增加批量导入单体电池点位的功能 6.修复计算点可能会出现id与code在一个池子内的问题,再次计算后数据正常 7.计算点增加小数位限制的功能,实时计算与7天历史接口都已经按照配置的小数位进行限制 8.统计报表中的功率曲线改为了按照分钟显示 9.功率曲线出现断点的问题是因为数据计算太密集了导致的,增加了前端连线不断的显示 10.PCS和电池堆的曲线与配置增加了关联设备显示 11.点位映射中的电池温度,增加了多设备 12.收益报表增加升序排列,合并当月所有合计 13.增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改
This commit is contained in:
@ -1,10 +1,17 @@
|
||||
<template>
|
||||
<el-card shadow="always" class="common-card-container common-card-container-body-no-padding time-range-card">
|
||||
<div slot="header" class="time-range-header">
|
||||
<span class="card-title">一周充放曲线</span>
|
||||
<span class="card-title">一周充放柱状图</span>
|
||||
<date-range-select ref="dateRangeSelect" :showIcon="true" :mini-time-picker="true" @updateDate="updateDate"/>
|
||||
</div>
|
||||
<div style="height: 310px" id="weekChart"></div>
|
||||
<div class="card-main">
|
||||
<div class="total-data">
|
||||
<div>总充电量:<span class="point">{{ formatMetricValue(summary.totalChargedCap) }}kWh</span></div>
|
||||
<div>总放电量:<span class="point">{{ formatMetricValue(summary.totalDisChargedCap) }}kWh</span></div>
|
||||
<div>综合效率:<span class="point">{{ formatPercentValue(summary.efficiency) }}%</span></div>
|
||||
</div>
|
||||
<div ref="weekChartRef" class="week-chart-canvas"></div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
@ -12,11 +19,21 @@
|
||||
import * as echarts from 'echarts'
|
||||
import resize from '@/mixins/ems/resize'
|
||||
import DateRangeSelect from '@/components/Ems/DateRangeSelect/index.vue'
|
||||
import {getPointConfigCurve} from '@/api/ems/site'
|
||||
import { getPointConfigCurve } from '@/api/ems/site'
|
||||
|
||||
const DAY = 24 * 60 * 60 * 1000
|
||||
|
||||
function createEmptySummary() {
|
||||
return {
|
||||
totalChargedCap: '',
|
||||
totalDisChargedCap: '',
|
||||
efficiency: ''
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
mixins: [resize],
|
||||
components: {DateRangeSelect},
|
||||
components: { DateRangeSelect },
|
||||
props: {
|
||||
displayData: {
|
||||
type: Array,
|
||||
@ -27,7 +44,8 @@ export default {
|
||||
return {
|
||||
chart: null,
|
||||
timeRange: [],
|
||||
siteId: ''
|
||||
siteId: '',
|
||||
summary: createEmptySummary()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -50,13 +68,12 @@ export default {
|
||||
this.chart = null
|
||||
},
|
||||
methods: {
|
||||
// 更新时间范围 重置图表
|
||||
updateDate(data) {
|
||||
this.timeRange = data
|
||||
this.getWeekKData()
|
||||
},
|
||||
getWeekKData() {
|
||||
const {siteId, timeRange} = this
|
||||
const { siteId, timeRange } = this
|
||||
const displayData = this.displayData || []
|
||||
const sectionRows = displayData.filter(item =>
|
||||
item && item.sectionName === '一周充放曲线' && item.useFixedDisplay !== 1 && item.dataPoint
|
||||
@ -75,6 +92,7 @@ export default {
|
||||
const list = curveResponse?.data || []
|
||||
return {
|
||||
name: row.fieldName || row.fieldCode || pointId,
|
||||
fieldCode: row.fieldCode || '',
|
||||
data: list
|
||||
.map(item => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter(item => item[0] && !Number.isNaN(item[1]))
|
||||
@ -86,14 +104,13 @@ export default {
|
||||
})
|
||||
},
|
||||
init(siteId) {
|
||||
//初始化 清空数据
|
||||
this.siteId = siteId
|
||||
this.timeRange = []
|
||||
this.deviceId = ''
|
||||
this.summary = createEmptySummary()
|
||||
this.$refs.dateRangeSelect.init()
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(document.querySelector('#weekChart'))
|
||||
this.chart = echarts.init(this.$refs.weekChartRef)
|
||||
},
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || '').trim()
|
||||
@ -106,41 +123,317 @@ export default {
|
||||
const t = new Date(value).getTime()
|
||||
return Number.isNaN(t) ? null : t
|
||||
},
|
||||
startOfDay(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date.getTime()
|
||||
},
|
||||
formatMetricValue(value) {
|
||||
const num = Number(value)
|
||||
if (value === '' || value == null || Number.isNaN(num)) return '--'
|
||||
return this.formatNumber(num)
|
||||
},
|
||||
formatPercentValue(value) {
|
||||
const num = Number(value)
|
||||
if (value === '' || value == null || Number.isNaN(num)) return '--'
|
||||
return this.formatNumber(num)
|
||||
},
|
||||
formatNumber(value) {
|
||||
const num = Number(value)
|
||||
if (Number.isNaN(num)) return '--'
|
||||
return num.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 2
|
||||
})
|
||||
},
|
||||
formatDateLabel(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${month}-${day}`
|
||||
},
|
||||
formatTooltipDate(timestamp) {
|
||||
const date = new Date(timestamp)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
},
|
||||
buildDatasetSource(labels = [], chargeData = [], dischargeData = [], efficiencyData = []) {
|
||||
const source = [['日期', '充电量', '放电量', '效率']]
|
||||
labels.forEach((label, index) => {
|
||||
source.push([
|
||||
label,
|
||||
Number(chargeData[index]?.value || 0),
|
||||
Number(dischargeData[index]?.value || 0),
|
||||
Number(efficiencyData[index]?.value || 0)
|
||||
])
|
||||
})
|
||||
return source
|
||||
},
|
||||
resolveSeriesType(item = {}) {
|
||||
const text = `${item?.name || ''} ${item?.fieldCode || ''}`.toLowerCase()
|
||||
if (text.includes('放') || text.includes('discharge') || text.includes('discharged')) {
|
||||
return 'discharge'
|
||||
}
|
||||
if (text.includes('充') || text.includes('charge') || text.includes('charged')) {
|
||||
return 'charge'
|
||||
}
|
||||
return ''
|
||||
},
|
||||
buildDailyChartData(seriesData = []) {
|
||||
const normalizedRange = this.timeRange || []
|
||||
const startTime = this.parseToTimestamp(this.normalizeDateTime(normalizedRange[0], false))
|
||||
const endTime = this.parseToTimestamp(this.normalizeDateTime(normalizedRange[1], true))
|
||||
if (!startTime || !endTime || endTime < startTime) {
|
||||
return {
|
||||
labels: [],
|
||||
chargeData: [],
|
||||
dischargeData: [],
|
||||
efficiencyData: [],
|
||||
summary: createEmptySummary()
|
||||
}
|
||||
}
|
||||
|
||||
const bucketStarts = []
|
||||
for (let cursor = this.startOfDay(startTime); cursor <= this.startOfDay(endTime); cursor += DAY) {
|
||||
bucketStarts.push(cursor)
|
||||
}
|
||||
|
||||
const chargeMap = {}
|
||||
const dischargeMap = {}
|
||||
|
||||
;(seriesData || []).forEach(item => {
|
||||
const seriesType = this.resolveSeriesType(item)
|
||||
if (!seriesType) return
|
||||
|
||||
;(item?.data || []).forEach(([timestamp, pointValue]) => {
|
||||
if (!timestamp || Number.isNaN(pointValue)) return
|
||||
if (timestamp < startTime || timestamp > endTime) return
|
||||
|
||||
const bucketStart = this.startOfDay(timestamp)
|
||||
const normalizedValue = Math.abs(Number(pointValue) || 0)
|
||||
if (seriesType === 'charge') {
|
||||
chargeMap[bucketStart] = (chargeMap[bucketStart] || 0) + normalizedValue
|
||||
} else if (seriesType === 'discharge') {
|
||||
dischargeMap[bucketStart] = (dischargeMap[bucketStart] || 0) + normalizedValue
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const labels = []
|
||||
const chargeData = []
|
||||
const dischargeData = []
|
||||
const efficiencyData = []
|
||||
|
||||
bucketStarts.forEach(bucketStart => {
|
||||
const chargedCap = Number(chargeMap[bucketStart] || 0)
|
||||
const disChargedCap = Number(dischargeMap[bucketStart] || 0)
|
||||
const dailyEfficiency = chargedCap > 0
|
||||
? Number(((disChargedCap / chargedCap) * 100).toFixed(2))
|
||||
: 0
|
||||
|
||||
labels.push(this.formatDateLabel(bucketStart))
|
||||
chargeData.push({
|
||||
value: chargedCap,
|
||||
bucketStart
|
||||
})
|
||||
dischargeData.push({
|
||||
value: disChargedCap,
|
||||
bucketStart
|
||||
})
|
||||
efficiencyData.push({
|
||||
value: dailyEfficiency,
|
||||
bucketStart
|
||||
})
|
||||
})
|
||||
|
||||
const totalChargedCap = chargeData.reduce((sum, item) => sum + Number(item.value || 0), 0)
|
||||
const totalDisChargedCap = dischargeData.reduce((sum, item) => sum + Number(item.value || 0), 0)
|
||||
const efficiency = totalChargedCap > 0
|
||||
? Number(((totalDisChargedCap / totalChargedCap) * 100).toFixed(2))
|
||||
: 0
|
||||
|
||||
return {
|
||||
labels,
|
||||
chargeData,
|
||||
dischargeData,
|
||||
efficiencyData,
|
||||
summary: {
|
||||
totalChargedCap,
|
||||
totalDisChargedCap,
|
||||
efficiency
|
||||
}
|
||||
}
|
||||
},
|
||||
resolveEfficiencyAxisMax(efficiencyData = []) {
|
||||
const maxValue = efficiencyData.reduce((max, item) => Math.max(max, Number(item?.value || 0)), 0)
|
||||
if (maxValue <= 100) return 100
|
||||
return Math.ceil(maxValue / 20) * 20
|
||||
},
|
||||
renderEmptyState(message = '暂无数据') {
|
||||
if (!this.chart) return
|
||||
this.chart.clear()
|
||||
this.chart.setOption({
|
||||
graphic: {
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
style: {
|
||||
text: message,
|
||||
fill: '#909399',
|
||||
fontSize: 14
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
color: ['#4472c4', '#70ad47'],//所有充放电颜色保持统一
|
||||
if (!this.chart) return
|
||||
|
||||
const { labels, chargeData, dischargeData, efficiencyData, summary } = this.buildDailyChartData(seriesData)
|
||||
const hasValue = chargeData.some(item => Number(item?.value || 0) > 0) || dischargeData.some(item => Number(item?.value || 0) > 0)
|
||||
|
||||
if (!labels.length || !hasValue) {
|
||||
this.summary = createEmptySummary()
|
||||
this.renderEmptyState()
|
||||
return
|
||||
}
|
||||
|
||||
this.summary = summary
|
||||
const efficiencyAxisMax = this.resolveEfficiencyAxisMax(efficiencyData)
|
||||
const source = this.buildDatasetSource(labels, chargeData, dischargeData, efficiencyData)
|
||||
|
||||
this.chart.clear()
|
||||
this.chart.setOption({
|
||||
color: ['#4472c4', '#70ad47', '#ffbd00'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'cross' }
|
||||
},
|
||||
grid: {
|
||||
containLabel: true
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: (params = []) => {
|
||||
if (!params.length) return ''
|
||||
const dataIndex = Number(params[0]?.dataIndex)
|
||||
const bucketStart = chargeData[dataIndex]?.bucketStart
|
||||
const lines = [this.formatTooltipDate(bucketStart)]
|
||||
params.forEach(item => {
|
||||
const rawValue = Array.isArray(item?.value) ? item.value[item.seriesIndex + 1] : item?.value
|
||||
const value = Number(rawValue || 0)
|
||||
const unit = item.seriesType === 'line' ? '%' : 'kWh'
|
||||
lines.push(`${item.marker}${item.seriesName}: ${this.formatNumber(value)}${unit}`)
|
||||
})
|
||||
return lines.join('<br/>')
|
||||
},
|
||||
extraCssText: 'max-width: 420px; white-space: normal;'
|
||||
},
|
||||
legend: {
|
||||
left: 'center',
|
||||
bottom: '15',
|
||||
bottom: 15
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time'
|
||||
grid: {
|
||||
top: 40,
|
||||
containLabel: true,
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 60
|
||||
},
|
||||
yAxis: [{
|
||||
type: 'value',
|
||||
name: '充电量/放电量kWh',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#333333',
|
||||
},
|
||||
onZero: false
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '充电量/放电量kWh',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#333333'
|
||||
}
|
||||
,
|
||||
onZero: false
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '效率%',
|
||||
max: efficiencyAxisMax,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#333333'
|
||||
},
|
||||
onZero: false
|
||||
}
|
||||
}
|
||||
],
|
||||
xAxis: [{
|
||||
type: 'category',
|
||||
name: '单位:日',
|
||||
nameLocation: 'center',
|
||||
nameGap: 30,
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
interval: 0
|
||||
}
|
||||
}],
|
||||
series: seriesData.map(item => ({
|
||||
name: item.name,
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
data: item.data
|
||||
}))
|
||||
dataset: {
|
||||
source
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '充电量',
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
color: '#4472c4',
|
||||
barMaxWidth: 22
|
||||
},
|
||||
{
|
||||
name: '放电量',
|
||||
yAxisIndex: 0,
|
||||
type: 'bar',
|
||||
color: '#70ad47',
|
||||
barMaxWidth: 22
|
||||
},
|
||||
{
|
||||
name: '效率',
|
||||
yAxisIndex: 1,
|
||||
type: 'line',
|
||||
color: '#ffbd00',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 7,
|
||||
lineStyle: {
|
||||
width: 2
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-main {
|
||||
padding: 0 16px 12px;
|
||||
}
|
||||
|
||||
.total-data {
|
||||
line-height: 18px;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
padding: 20px 0;
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.point {
|
||||
color: #05AEA3;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.week-chart-canvas {
|
||||
height: 310px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'workStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ CLUSTERWorkStatusOptions[baseInfo.workStatus] || '-' }}
|
||||
{{ formatDictValue(clusterWorkStatusOptions, baseInfo.workStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item contentClassName="descriptions-direction"
|
||||
@ -66,7 +66,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'pcsCommunicationStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')"
|
||||
>
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }}
|
||||
{{ formatDictValue(clusterPcsCommunicationStatusOptions, baseInfo.pcsCommunicationStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item contentClassName="descriptions-direction"
|
||||
@ -76,7 +76,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(baseInfo, 'emsCommunicationStatus') }"
|
||||
@click="handleFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')"
|
||||
>
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }}
|
||||
{{ formatDictValue(clusterEmsCommunicationStatusOptions, baseInfo.emsCommunicationStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@ -205,7 +205,7 @@ import {getProjectDisplayData, getStackNameList, getClusterNameList} from '@/api
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: 'DzjkSbjkBmsdcc',
|
||||
@ -215,6 +215,15 @@ export default {
|
||||
...mapState({
|
||||
CLUSTERWorkStatusOptions: state => state?.ems?.CLUSTERWorkStatusOptions || {},
|
||||
}),
|
||||
clusterWorkStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "workStatus", this.CLUSTERWorkStatusOptions || {});
|
||||
},
|
||||
clusterPcsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "pcsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
clusterEmsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("CLUSTER", "emsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedClusterId) {
|
||||
return this.baseInfoList || [];
|
||||
@ -227,6 +236,7 @@ export default {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
clusterDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
selectedClusterId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
@ -286,9 +296,69 @@ export default {
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
normalizeDictKey(value) {
|
||||
const raw = String(value == null ? "" : value).trim();
|
||||
if (!raw) return "";
|
||||
if (/^-?\d+(\.0+)?$/.test(raw)) {
|
||||
return String(parseInt(raw, 10));
|
||||
}
|
||||
return raw;
|
||||
},
|
||||
formatDictValue(options, value) {
|
||||
const dict = (options && typeof options === "object") ? options : {};
|
||||
const key = this.normalizeDictKey(value);
|
||||
if (!key) return "-";
|
||||
return dict[key] || key;
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const {workStatus = ''} = item
|
||||
return !(Object.keys(this.CLUSTERWorkStatusOptions).includes(item.workStatus)) ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container'
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.clusterWorkStatusOptions && typeof this.clusterWorkStatusOptions === "object")
|
||||
? this.clusterWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return !hasStatus ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container';
|
||||
},
|
||||
// 查看设备电位表格
|
||||
pointDetail(row, dataType) {
|
||||
@ -596,6 +666,7 @@ export default {
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getClusterDeviceList(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).then(([response]) => {
|
||||
this.displayData = response?.data || [];
|
||||
this.buildBaseInfoList();
|
||||
|
||||
@ -50,19 +50,19 @@
|
||||
contentClassName="descriptions-direction work-status"
|
||||
label="工作状态" labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'workStatus', '工作状态')">
|
||||
{{ STACKWorkStatusOptions[baseInfo.workStatus] || '-' }}
|
||||
{{ formatDictValue(stackWorkStatusOptions, baseInfo.workStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与PCS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'pcsCommunicationStatus', '与PCS通信')">
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.pcsCommunicationStatus] || '-' }}
|
||||
{{ formatDictValue(stackPcsCommunicationStatusOptions, baseInfo.pcsCommunicationStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" contentClassName="descriptions-direction" label="与EMS通信"
|
||||
labelClassName="descriptions-label">
|
||||
<span class="pointer" @click="handleStatusFieldClick(baseInfo, 'emsCommunicationStatus', '与EMS通信')">
|
||||
{{ (($store.state.ems && $store.state.ems.communicationStatusOptions) || {})[baseInfo.emsCommunicationStatus] || '-' }}
|
||||
{{ formatDictValue(stackEmsCommunicationStatusOptions, baseInfo.emsCommunicationStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@ -123,7 +123,7 @@
|
||||
<script>
|
||||
import * as echarts from "echarts";
|
||||
import {getProjectDisplayData, getStackNameList} from '@/api/ems/dzjk'
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import PointTable from "@/views/ems/site/sblb/PointTable.vue";
|
||||
@ -137,6 +137,15 @@ export default {
|
||||
...mapState({
|
||||
STACKWorkStatusOptions: state => state?.ems?.STACKWorkStatusOptions || {},
|
||||
}),
|
||||
stackWorkStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "workStatus", this.STACKWorkStatusOptions || {});
|
||||
},
|
||||
stackPcsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "pcsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
stackEmsCommunicationStatusOptions() {
|
||||
return this.getEnumOptions("STACK", "emsCommunicationStatus", (this.$store.state.ems && this.$store.state.ems.communicationStatusOptions) || {});
|
||||
},
|
||||
filteredBaseInfoList() {
|
||||
if (!this.selectedStackId) {
|
||||
return this.baseInfoList || [];
|
||||
@ -149,6 +158,7 @@ export default {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
stackDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
selectedStackId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
@ -191,9 +201,69 @@ export default {
|
||||
isPointLoading(value) {
|
||||
return this.loading && (value === undefined || value === null || value === "" || value === "-");
|
||||
},
|
||||
normalizeDictKey(value) {
|
||||
const raw = String(value == null ? "" : value).trim();
|
||||
if (!raw) return "";
|
||||
if (/^-?\d+(\.0+)?$/.test(raw)) {
|
||||
return String(parseInt(raw, 10));
|
||||
}
|
||||
return raw;
|
||||
},
|
||||
formatDictValue(options, value) {
|
||||
const dict = (options && typeof options === "object") ? options : {};
|
||||
const key = this.normalizeDictKey(value);
|
||||
if (!key) return "-";
|
||||
return dict[key] || key;
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const {workStatus = ''} = item
|
||||
return !Object.keys(this.STACKWorkStatusOptions).find(i => i === workStatus) ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container'
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.stackWorkStatusOptions && typeof this.stackWorkStatusOptions === "object")
|
||||
? this.stackWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return !hasStatus ? "timing-card-container" : workStatus === '9' ? 'warning-card-container' : 'running-card-container';
|
||||
},
|
||||
|
||||
// 查看设备电位表格
|
||||
@ -487,6 +557,7 @@ export default {
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getStackDeviceList(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildBaseInfoList();
|
||||
|
||||
@ -64,7 +64,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'workStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'workStatus', '工作状态')"
|
||||
>
|
||||
{{ formatDictValue((PCSWorkStatusOptions || {}), pcsItem.workStatus) }}
|
||||
{{ formatDictValue(pcsWorkStatusOptions, pcsItem.workStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
@ -79,7 +79,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'gridStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'gridStatus', '并网状态')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.gridStatusOptions) || {}), pcsItem.gridStatus) }}
|
||||
{{ formatDictValue(pcsGridStatusOptions, pcsItem.gridStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
@ -96,7 +96,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'deviceStatus') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'deviceStatus', '设备状态')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.deviceStatusOptions) || {}), pcsItem.deviceStatus) }}
|
||||
{{ formatDictValue(pcsDeviceStatusOptions, pcsItem.deviceStatus) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
@ -111,7 +111,7 @@
|
||||
:class="{ 'field-disabled': !hasFieldPointId(pcsItem, 'controlMode') }"
|
||||
@click="handlePcsFieldClick(pcsItem, 'controlMode', '控制模式')"
|
||||
>
|
||||
{{ formatDictValue((($store.state.ems && $store.state.ems.controlModeOptions) || {}), pcsItem.controlMode) }}
|
||||
{{ formatDictValue(pcsControlModeOptions, pcsItem.controlMode) }}
|
||||
</span>
|
||||
</el-descriptions-item
|
||||
>
|
||||
@ -242,7 +242,7 @@ import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getPcsNameList, getProjectDisplayData} from "@/api/ems/dzjk";
|
||||
import intervalUpdate from "@/mixins/ems/intervalUpdate";
|
||||
import {mapState} from "vuex";
|
||||
import {getPointConfigCurve} from "@/api/ems/site";
|
||||
import {getPointConfigCurve, getSingleMonitorWorkStatusEnumMappings} from "@/api/ems/site";
|
||||
|
||||
export default {
|
||||
name: "DzjkSbjkPcs",
|
||||
@ -252,6 +252,18 @@ export default {
|
||||
...mapState({
|
||||
PCSWorkStatusOptions: state => state?.ems?.PCSWorkStatusOptions || {},
|
||||
}),
|
||||
pcsWorkStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "workStatus", this.PCSWorkStatusOptions || {});
|
||||
},
|
||||
pcsGridStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "gridStatus", (this.$store.state.ems && this.$store.state.ems.gridStatusOptions) || {});
|
||||
},
|
||||
pcsDeviceStatusOptions() {
|
||||
return this.getEnumOptions("PCS", "deviceStatus", (this.$store.state.ems && this.$store.state.ems.deviceStatusOptions) || {});
|
||||
},
|
||||
pcsControlModeOptions() {
|
||||
return this.getEnumOptions("PCS", "controlMode", (this.$store.state.ems && this.$store.state.ems.controlModeOptions) || {});
|
||||
},
|
||||
filteredPcsList() {
|
||||
if (!this.selectedPcsId) {
|
||||
return this.pcsList || [];
|
||||
@ -264,6 +276,7 @@ export default {
|
||||
loading: false,
|
||||
displayData: [],
|
||||
pcsDeviceList: [],
|
||||
siteEnumOptionMap: {},
|
||||
selectedPcsId: "",
|
||||
curveDialogVisible: false,
|
||||
curveDialogTitle: "点位曲线",
|
||||
@ -388,10 +401,52 @@ export default {
|
||||
normalizeDeviceId(value) {
|
||||
return String(value == null ? "" : value).trim().toUpperCase();
|
||||
},
|
||||
buildEnumScopeKey(deviceCategory, matchField) {
|
||||
return `${String(deviceCategory || "").trim()}|${String(matchField || "").trim()}`;
|
||||
},
|
||||
buildSiteEnumOptionMap(mappings = []) {
|
||||
return (mappings || []).reduce((acc, item) => {
|
||||
const scopeKey = this.buildEnumScopeKey(item?.deviceCategory, item?.matchField);
|
||||
const dataEnumCode = this.normalizeDictKey(item?.dataEnumCode);
|
||||
const enumCode = this.normalizeDictKey(item?.enumCode);
|
||||
const enumName = String(item?.enumName || "").trim();
|
||||
const optionKey = dataEnumCode || enumCode;
|
||||
if (!scopeKey || !optionKey || !enumName) {
|
||||
return acc;
|
||||
}
|
||||
if (!acc[scopeKey]) {
|
||||
acc[scopeKey] = {};
|
||||
}
|
||||
acc[scopeKey][optionKey] = enumName;
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
loadSiteEnumOptions() {
|
||||
if (!this.siteId) {
|
||||
this.siteEnumOptionMap = {};
|
||||
return Promise.resolve({});
|
||||
}
|
||||
return getSingleMonitorWorkStatusEnumMappings(this.siteId).then(response => {
|
||||
const optionMap = this.buildSiteEnumOptionMap(response?.data || []);
|
||||
this.siteEnumOptionMap = optionMap;
|
||||
return optionMap;
|
||||
}).catch(() => {
|
||||
this.siteEnumOptionMap = {};
|
||||
return {};
|
||||
});
|
||||
},
|
||||
getEnumOptions(deviceCategory, matchField, fallback = {}) {
|
||||
const scopeKey = this.buildEnumScopeKey(deviceCategory, matchField);
|
||||
const siteOptions = this.siteEnumOptionMap[scopeKey];
|
||||
if (siteOptions && Object.keys(siteOptions).length > 0) {
|
||||
return siteOptions;
|
||||
}
|
||||
return fallback || {};
|
||||
},
|
||||
handleCardClass(item) {
|
||||
const workStatus = this.normalizeDictKey((item && item.workStatus) || "");
|
||||
const statusOptions = (this.PCSWorkStatusOptions && typeof this.PCSWorkStatusOptions === 'object')
|
||||
? this.PCSWorkStatusOptions
|
||||
const statusOptions = (this.pcsWorkStatusOptions && typeof this.pcsWorkStatusOptions === 'object')
|
||||
? this.pcsWorkStatusOptions
|
||||
: {};
|
||||
const hasStatus = Object.prototype.hasOwnProperty.call(statusOptions, workStatus);
|
||||
return workStatus === '1' || !hasStatus
|
||||
@ -651,6 +706,7 @@ export default {
|
||||
Promise.all([
|
||||
getProjectDisplayData(this.siteId),
|
||||
this.getPcsDeviceList(),
|
||||
this.loadSiteEnumOptions(),
|
||||
]).then(([displayResponse]) => {
|
||||
this.displayData = displayResponse?.data || [];
|
||||
this.buildPcsList();
|
||||
|
||||
@ -68,7 +68,7 @@ export default {
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: row.fieldName || row.fieldCode || pointId,
|
||||
name: (row.deviceName || "") + (row.fieldName || row.fieldCode || pointId),
|
||||
data: list
|
||||
.map((item) => [
|
||||
this.parseToTimestamp(item.dataTime),
|
||||
|
||||
@ -41,33 +41,38 @@ export default {
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveSoc" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
@ -80,7 +85,7 @@ export default {
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
@ -119,18 +124,16 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
name: `平均SOC`,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data,
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
],
|
||||
data: item.data
|
||||
})),
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -42,33 +42,38 @@ export default {
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curveBatteryAveTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
@ -81,7 +86,7 @@ export default {
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
@ -120,18 +125,16 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
name: `电池平均温度`,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data,
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
],
|
||||
data: item.data
|
||||
})),
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -27,7 +27,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
seriesName: "PCS最高温度"
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -43,34 +42,38 @@ export default {
|
||||
methods: {
|
||||
init(siteId,timeRange) {
|
||||
const [startTime='', endTime=''] = timeRange;
|
||||
const row = (this.displayData || []).find(
|
||||
const query = {
|
||||
siteId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
};
|
||||
const rows = (this.displayData || []).filter(
|
||||
(item) =>
|
||||
item &&
|
||||
item.fieldCode === "SBJK_SSYX__curvePcsMaxTemp" &&
|
||||
item.useFixedDisplay !== 1 &&
|
||||
item.dataPoint
|
||||
);
|
||||
const pointId = String(row?.dataPoint || "").trim();
|
||||
this.seriesName = row?.fieldName || "PCS最高温度";
|
||||
if (!pointId) {
|
||||
this.setOption([]);
|
||||
return;
|
||||
}
|
||||
getPointConfigCurve({
|
||||
siteId,
|
||||
pointId,
|
||||
rangeType: "custom",
|
||||
startTime: this.normalizeDateTime(startTime, false),
|
||||
endTime: this.normalizeDateTime(endTime, true)
|
||||
})
|
||||
.then((response) => {
|
||||
const list = response?.data || [];
|
||||
this.setOption(
|
||||
list
|
||||
.map((item) => [this.parseToTimestamp(item.dataTime), Number(item.pointValue)])
|
||||
.filter((item) => item[0] && !Number.isNaN(item[1]))
|
||||
);
|
||||
});
|
||||
const tasks = rows.map((row) => {
|
||||
const pointId = String(row.dataPoint || "").trim();
|
||||
if(!pointId) return Promise.resolve(null);
|
||||
return getPointConfigCurve({
|
||||
...query,
|
||||
pointId
|
||||
}).then((response) => {
|
||||
const list = response?.data || [];
|
||||
return {
|
||||
name: (row.deviceName || "") + (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);
|
||||
});
|
||||
Promise.all(tasks).then((series) => {
|
||||
this.setOption((series || []).filter(Boolean));
|
||||
});
|
||||
},
|
||||
normalizeDateTime(value, endOfDay) {
|
||||
const raw = String(value || "").trim();
|
||||
@ -83,7 +86,7 @@ export default {
|
||||
const t = new Date(value).getTime();
|
||||
return Number.isNaN(t) ? null : t;
|
||||
},
|
||||
setOption(data = []) {
|
||||
setOption(seriesData = []) {
|
||||
this.chart && this.chart.setOption({
|
||||
legend: {
|
||||
left: "center",
|
||||
@ -122,18 +125,16 @@ export default {
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: "line",
|
||||
name: this.seriesName,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data
|
||||
}
|
||||
],
|
||||
series: seriesData.map(item => ({
|
||||
type: "line",
|
||||
name: item.name,
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
opacity: 0.35
|
||||
},
|
||||
data: item.data
|
||||
})),
|
||||
},true);
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
<template>
|
||||
<div style="width:100%" v-loading="loading">
|
||||
<!-- 搜索栏-->
|
||||
<el-form :inline="true" class="select-container">
|
||||
<el-form-item label="时间选择">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
:default-value="defaultDateRange"
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
:default-value="defaultDateRange"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@ -25,108 +24,73 @@
|
||||
<el-button type="primary" @click="exportTable" native-type="button">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!--表格-->
|
||||
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<!-- 汇总列-->
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
stripe
|
||||
style="width: 100%; margin-top: 25px;"
|
||||
>
|
||||
<el-table-column label="汇总">
|
||||
<el-table-column
|
||||
prop="dataTime"
|
||||
label="日期"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataTime" label="日期" width="120"></el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
|
||||
<el-table-column label="充电量" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activePeakKwh"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeHighKwh"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeFlatKwh"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeValleyKwh"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeTotalKwh"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="activePeakKwh" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="activeHighKwh" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="activeFlatKwh" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="activeValleyKwh" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="activeTotalKwh" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
|
||||
<el-table-column label="放电量" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActivePeakKwh"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveHighKwh"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveFlatKwh"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveValleyKwh"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveTotalKwh"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="reActivePeakKwh" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveHighKwh" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveFlatKwh" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveValleyKwh" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveTotalKwh" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
<!-- 效率-->
|
||||
|
||||
<el-table-column label="效率(%)" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="effect">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="effect"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="备注" align="center" fixed="right" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<div class="remark-cell">
|
||||
<span class="remark-text">{{ scope.row.remark || "-" }}</span>
|
||||
<el-button type="text" @click="editRemark(scope.row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-show="tableData.length>0"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalSize"
|
||||
style="margin-top:15px;text-align: center"
|
||||
v-show="tableData.length > 0"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalSize"
|
||||
style="margin-top: 15px; text-align: center"
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getAmmeterData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import { batchGetBizRemark, getAmmeterData, saveBizRemark } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
|
||||
const BIZ_TYPE = "stats_report";
|
||||
const REPORT_KEY = "DBBB";
|
||||
|
||||
export default {
|
||||
name: 'DzjkTjbbDbbb',
|
||||
name: "DzjkTjbbDbbb",
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -136,81 +100,141 @@ export default {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange: [],//默认展示的时间
|
||||
defaultDateRange: [],
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
pageSize: 10,//分页栏当前每个数据总数
|
||||
pageNum: 1,//分页栏当前页数
|
||||
totalSize: 0,//table表格数据总数
|
||||
}
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 导出表格
|
||||
buildRemarkKey(dataTime) {
|
||||
return `${this.siteId}_${dataTime || ""}`;
|
||||
},
|
||||
loadRemarks(rows) {
|
||||
if (!rows.length) return Promise.resolve({});
|
||||
return batchGetBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2List: rows.map(row => this.buildRemarkKey(row.dataTime)),
|
||||
}).then(response => response?.data || {});
|
||||
},
|
||||
applyRemarks(rows, remarkMap) {
|
||||
rows.forEach(row => {
|
||||
this.$set(row, "remark", remarkMap[this.buildRemarkKey(row.dataTime)] || "");
|
||||
});
|
||||
},
|
||||
exportTable() {
|
||||
if (!this.dateRange?.length) return
|
||||
const [startTime, endTime] = this.dateRange
|
||||
this.download('ems/statsReport/exportAmmeterDataFromDaily', {
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
}, `电表报表_${startTime}-${endTime}.xlsx`)
|
||||
if (!this.dateRange?.length) return;
|
||||
const [startTime, endTime] = this.dateRange;
|
||||
this.download(
|
||||
"ems/statsReport/exportAmmeterDataFromDaily",
|
||||
{
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
},
|
||||
`电表报表_${startTime}-${endTime}.xlsx`
|
||||
);
|
||||
},
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
// 重置
|
||||
onReset() {
|
||||
this.dateRange = this.defaultDateRange
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
this.dateRange = this.defaultDateRange;
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.pageNum = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
// 获取数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
const {siteId, pageNum, pageSize} = this
|
||||
const [startTime = '', endTime = ''] = (this.dateRange || [])
|
||||
getAmmeterData({siteId: siteId, startTime, endTime, pageSize, pageNum}).then(response => {
|
||||
this.tableData = response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
editRemark(row) {
|
||||
this.$prompt("请输入备注", "编辑备注", {
|
||||
inputValue: row.remark || "",
|
||||
inputType: "textarea",
|
||||
inputPlaceholder: "可输入该日报表备注",
|
||||
confirmButtonText: "保存",
|
||||
cancelButtonText: "取消",
|
||||
})
|
||||
.then(({ value }) => {
|
||||
return saveBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2: this.buildRemarkKey(row.dataTime),
|
||||
remark: value || "",
|
||||
}).then(() => {
|
||||
this.$set(row, "remark", value || "");
|
||||
this.$message.success("备注保存成功");
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
const { siteId, pageNum, pageSize } = this;
|
||||
const [startTime = "", endTime = ""] = this.dateRange || [];
|
||||
getAmmeterData({ siteId, startTime, endTime, pageSize, pageNum })
|
||||
.then(response => {
|
||||
const rows = response?.rows || [];
|
||||
this.tableData = rows;
|
||||
this.totalSize = response?.total || 0;
|
||||
return this.loadRemarks(rows);
|
||||
})
|
||||
.then(remarkMap => {
|
||||
this.applyRemarks(this.tableData, remarkMap || {});
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.dateRange = []
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
this.pageSize = 10
|
||||
this.pageNum = 1
|
||||
let now = new Date(), lastDay = now.getTime(), firstDay = new Date(now.setDate(1)).getTime();
|
||||
this.dateRange = [];
|
||||
this.tableData = [];
|
||||
this.totalSize = 0;
|
||||
this.pageSize = 10;
|
||||
this.pageNum = 1;
|
||||
const now = new Date();
|
||||
const lastDay = now.getTime();
|
||||
const firstDay = new Date(new Date().setDate(1)).getTime();
|
||||
this.defaultDateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.dateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.getData()
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.common-table.el-table .el-table__header-wrapper th, .common-table.el-table .el-table__fixed-header-wrapper th {
|
||||
.common-table.el-table .el-table__header-wrapper th,
|
||||
.common-table.el-table .el-table__fixed-header-wrapper th {
|
||||
border-bottom: 1px solid #dfe6ec;
|
||||
}
|
||||
}
|
||||
|
||||
.remark-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -178,6 +178,7 @@ export default {
|
||||
return {
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
|
||||
@ -103,11 +103,11 @@ export default {
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
start: 0,
|
||||
start: data.length > 500 ? 90 : 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 0,
|
||||
start: data.length > 500 ? 90 : 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
@ -115,6 +115,7 @@ export default {
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
@ -122,6 +123,7 @@ export default {
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
@ -129,6 +131,7 @@ export default {
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
@ -136,6 +139,7 @@ export default {
|
||||
{
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
|
||||
@ -194,6 +194,7 @@ export default {
|
||||
return {
|
||||
type: "line",
|
||||
smooth: true,
|
||||
connectNulls: true,
|
||||
areaStyle: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
|
||||
@ -1,18 +1,17 @@
|
||||
<template>
|
||||
<div style="width:100%" v-loading="loading">
|
||||
<!-- 搜索栏-->
|
||||
<el-form :inline="true" class="select-container">
|
||||
<el-form-item label="时间选择">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
:default-value="defaultDateRange"
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="yyyy-MM-dd"
|
||||
:clearable="false"
|
||||
:picker-options="pickerOptions"
|
||||
:default-value="defaultDateRange"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@ -25,121 +24,77 @@
|
||||
<el-button type="primary" @click="exportTable" native-type="button">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!--表格-->
|
||||
|
||||
<el-table
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
show-summary
|
||||
stripe
|
||||
style="width: 100%;margin-top:25px;">
|
||||
<!-- 汇总列-->
|
||||
class="common-table"
|
||||
:data="tableData"
|
||||
:summary-method="getSummaries"
|
||||
show-summary
|
||||
stripe
|
||||
style="width: 100%; margin-top: 25px;"
|
||||
>
|
||||
<el-table-column label="汇总" min-width="100px" align="center">
|
||||
<el-table-column
|
||||
prop="dataTime"
|
||||
label="日期"
|
||||
min-width="100px" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="dayType"
|
||||
label="日期类型"
|
||||
min-width="100px" align="center">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="weatherDesc"
|
||||
label="天气情况"
|
||||
min-width="180px"
|
||||
align="center">
|
||||
</el-table-column>
|
||||
<el-table-column prop="dataTime" label="日期" min-width="100px" align="center"></el-table-column>
|
||||
<el-table-column prop="dayType" label="日期类型" min-width="100px" align="center"></el-table-column>
|
||||
<el-table-column prop="weatherDesc" label="天气情况" min-width="180px" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
|
||||
<el-table-column label="充电价格" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activePeakPrice"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeHighPrice"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeFlatPrice"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeValleyPrice"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="activeTotalPrice"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="activePeakPrice" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="activeHighPrice" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="activeFlatPrice" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="activeValleyPrice" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="activeTotalPrice" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
<!--充电量列-->
|
||||
|
||||
<el-table-column label="放电价格" align="center">
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActivePeakPrice"
|
||||
label="尖">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveHighPrice"
|
||||
label="峰">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveFlatPrice"
|
||||
label="平">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveValleyPrice"
|
||||
label="谷">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="center"
|
||||
prop="reActiveTotalPrice"
|
||||
label="总">
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="reActivePeakPrice" label="尖"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveHighPrice" label="峰"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveFlatPrice" label="平"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveValleyPrice" label="谷"></el-table-column>
|
||||
<el-table-column align="center" prop="reActiveTotalPrice" label="总"></el-table-column>
|
||||
</el-table-column>
|
||||
<!-- 实际收益-->
|
||||
<el-table-column label="" align="center" fixed="right">
|
||||
<el-table-column
|
||||
prop="actualRevenue"
|
||||
label="实际收益"
|
||||
align="center">
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="" align="center">
|
||||
<el-table-column prop="actualRevenue" label="实际收益" align="center"></el-table-column>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="备注" align="center" fixed="right" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<div class="remark-cell">
|
||||
<span class="remark-text">{{ scope.row.remark || "-" }}</span>
|
||||
<el-button type="text" @click="editRemark(scope.row)">编辑</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-show="tableData.length>0"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalSize"
|
||||
style="margin-top:15px;text-align: center"
|
||||
v-show="tableData.length > 0"
|
||||
background
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 40]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="totalSize"
|
||||
style="margin-top: 15px; text-align: center"
|
||||
>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import getQuerySiteId from "@/mixins/ems/getQuerySiteId";
|
||||
import {getAmmeterRevenueData} from '@/api/ems/dzjk'
|
||||
import {formatDate} from "@/filters/ems";
|
||||
import { batchGetBizRemark, getAmmeterRevenueData, saveBizRemark } from "@/api/ems/dzjk";
|
||||
import { formatDate } from "@/filters/ems";
|
||||
|
||||
const BIZ_TYPE = "stats_report";
|
||||
const REPORT_KEY = "SYBB";
|
||||
|
||||
export default {
|
||||
name: 'DzjkTjbbSybb',
|
||||
name: "DzjkTjbbSybb",
|
||||
mixins: [getQuerySiteId],
|
||||
data() {
|
||||
return {
|
||||
@ -149,81 +104,189 @@ export default {
|
||||
return time.getTime() > Date.now();
|
||||
},
|
||||
},
|
||||
defaultDateRange: [],//默认展示的时间
|
||||
defaultDateRange: [],
|
||||
dateRange: [],
|
||||
tableData: [],
|
||||
pageSize: 10,//分页栏当前每个数据总数
|
||||
pageNum: 1,//分页栏当前页数
|
||||
totalSize: 0,//table表格数据总数
|
||||
}
|
||||
summaryTotals: {},
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
totalSize: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
// 导出表格
|
||||
buildRemarkKey(dataTime) {
|
||||
return `${this.siteId}_${dataTime || ""}`;
|
||||
},
|
||||
loadRemarks(rows) {
|
||||
if (!rows.length) return Promise.resolve({});
|
||||
return batchGetBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2List: rows.map(row => this.buildRemarkKey(row.dataTime)),
|
||||
}).then(response => response?.data || {});
|
||||
},
|
||||
applyRemarks(rows, remarkMap) {
|
||||
rows.forEach(row => {
|
||||
this.$set(row, "remark", remarkMap[this.buildRemarkKey(row.dataTime)] || "");
|
||||
});
|
||||
},
|
||||
toScaledInt(value) {
|
||||
const num = Number(value);
|
||||
return Number.isFinite(num) ? Math.round(num * 1000) : 0;
|
||||
},
|
||||
sumRowsByProp(rows, prop) {
|
||||
const total = (rows || []).reduce((sum, row) => sum + this.toScaledInt(row?.[prop]), 0);
|
||||
return total / 1000;
|
||||
},
|
||||
formatSummaryNumber(value) {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num)) return "";
|
||||
return num.toFixed(3).replace(/\.?0+$/, "");
|
||||
},
|
||||
buildSummaryTotals(rows) {
|
||||
const numericProps = [
|
||||
"activePeakPrice",
|
||||
"activeHighPrice",
|
||||
"activeFlatPrice",
|
||||
"activeValleyPrice",
|
||||
"activeTotalPrice",
|
||||
"reActivePeakPrice",
|
||||
"reActiveHighPrice",
|
||||
"reActiveFlatPrice",
|
||||
"reActiveValleyPrice",
|
||||
"reActiveTotalPrice",
|
||||
"actualRevenue",
|
||||
];
|
||||
return numericProps.reduce((result, prop) => {
|
||||
result[prop] = this.sumRowsByProp(rows, prop);
|
||||
return result;
|
||||
}, {});
|
||||
},
|
||||
getSummaries({ columns, data }) {
|
||||
return columns.map((column, index) => {
|
||||
if (index === 0) return "合计";
|
||||
const prop = column.property;
|
||||
if (!prop) return "";
|
||||
if (Object.prototype.hasOwnProperty.call(this.summaryTotals, prop)) {
|
||||
return this.formatSummaryNumber(this.summaryTotals[prop]);
|
||||
}
|
||||
const hasNumericValue = (data || []).some(item => Number.isFinite(Number(item?.[prop])));
|
||||
return hasNumericValue ? this.formatSummaryNumber(this.sumRowsByProp(data, prop)) : "";
|
||||
});
|
||||
},
|
||||
exportTable() {
|
||||
if (!this.dateRange?.length) return
|
||||
const [startTime, endTime] = this.dateRange
|
||||
this.download('ems/statsReport/exportAmmeterRevenueData', {
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
}, `收益报表_${startTime}-${endTime}.xlsx`)
|
||||
if (!this.dateRange?.length) return;
|
||||
const [startTime, endTime] = this.dateRange;
|
||||
this.download(
|
||||
"ems/statsReport/exportAmmeterRevenueData",
|
||||
{
|
||||
siteId: this.siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
},
|
||||
`收益报表_${startTime}-${endTime}.xlsx`
|
||||
);
|
||||
},
|
||||
// 搜索
|
||||
onSearch() {
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
// 重置
|
||||
onReset() {
|
||||
this.dateRange = this.defaultDateRange
|
||||
this.pageNum = 1//每次搜索从1开始搜索
|
||||
this.getData()
|
||||
this.dateRange = this.defaultDateRange;
|
||||
this.pageNum = 1;
|
||||
this.getData();
|
||||
},
|
||||
// 分页
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.pageNum = val
|
||||
this.pageNum = val;
|
||||
this.$nextTick(() => {
|
||||
this.getData()
|
||||
})
|
||||
this.getData();
|
||||
});
|
||||
},
|
||||
// 获取数据
|
||||
getData() {
|
||||
this.loading = true
|
||||
const {siteId, pageNum, pageSize} = this
|
||||
const [startTime = '', endTime = ''] = (this.dateRange || [])
|
||||
getAmmeterRevenueData({siteId: siteId, startTime, endTime, pageSize, pageNum}).then(response => {
|
||||
const rows = response?.rows || [];
|
||||
this.totalSize = response?.total || 0
|
||||
this.tableData = rows
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
editRemark(row) {
|
||||
this.$prompt("请输入备注", "编辑备注", {
|
||||
inputValue: row.remark || "",
|
||||
inputType: "textarea",
|
||||
inputPlaceholder: "可输入该日报表备注",
|
||||
confirmButtonText: "保存",
|
||||
cancelButtonText: "取消",
|
||||
})
|
||||
.then(({ value }) => {
|
||||
return saveBizRemark({
|
||||
bizType: BIZ_TYPE,
|
||||
bizKey1: REPORT_KEY,
|
||||
bizKey2: this.buildRemarkKey(row.dataTime),
|
||||
remark: value || "",
|
||||
}).then(() => {
|
||||
this.$set(row, "remark", value || "");
|
||||
this.$message.success("备注保存成功");
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
const { siteId, pageNum, pageSize } = this;
|
||||
const [startTime = "", endTime = ""] = this.dateRange || [];
|
||||
|
||||
getAmmeterRevenueData({ siteId, startTime, endTime, pageSize, pageNum })
|
||||
.then(pageResponse => {
|
||||
const rows = pageResponse?.rows || [];
|
||||
const total = Number(pageResponse?.total || 0);
|
||||
this.totalSize = total;
|
||||
this.tableData = rows;
|
||||
|
||||
const tasks = [this.loadRemarks(rows)];
|
||||
if (total > 0) {
|
||||
tasks.push(
|
||||
getAmmeterRevenueData({
|
||||
siteId,
|
||||
startTime,
|
||||
endTime,
|
||||
pageNum: 1,
|
||||
pageSize: total,
|
||||
})
|
||||
);
|
||||
}
|
||||
return Promise.all(tasks);
|
||||
})
|
||||
.then(([remarkMap, allResponse]) => {
|
||||
this.applyRemarks(this.tableData, remarkMap || {});
|
||||
const allRows = allResponse?.rows || allResponse?.data || [];
|
||||
this.summaryTotals = this.buildSummaryTotals(allRows);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
init() {
|
||||
this.dateRange = []
|
||||
this.tableData = []
|
||||
this.totalSize = 0
|
||||
this.pageSize = 10
|
||||
this.pageNum = 1
|
||||
let now = new Date(), lastDay = now.getTime(), firstDay = new Date(now.setDate(1)).getTime();
|
||||
this.dateRange = [];
|
||||
this.tableData = [];
|
||||
this.summaryTotals = {};
|
||||
this.totalSize = 0;
|
||||
this.pageSize = 10;
|
||||
this.pageNum = 1;
|
||||
const now = new Date();
|
||||
const lastDay = now.getTime();
|
||||
const firstDay = new Date(new Date().setDate(1)).getTime();
|
||||
this.defaultDateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.dateRange = [formatDate(firstDay), formatDate(lastDay)];
|
||||
this.getData()
|
||||
this.getData();
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep {
|
||||
.common-table.el-table {
|
||||
.el-table__header-wrapper th, .common-table.el-table .el-table__fixed-header-wrapper th {
|
||||
.el-table__header-wrapper th,
|
||||
.common-table.el-table .el-table__fixed-header-wrapper th {
|
||||
border-bottom: 1px solid #dfe6ec;
|
||||
}
|
||||
|
||||
@ -234,7 +297,20 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.remark-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user