Files
emsapp/pages/index.vue

577 lines
14 KiB
Vue
Raw Normal View History

2026-02-08 17:26:51 +08:00
<template>
<view class="home-container">
<view class="site-sections-list">
2026-03-05 16:34:25 +08:00
<uni-data-picker placeholder="请选择" popup-title="业态选择" :step-searh="true" :value="siteId" :clear-icon="false"
:localdata="siteTypeOptions" :ellipsis="false" @change="selectedSite">
</uni-data-picker>
<view class="info">
<view class="list">
<uni-icons type="location" color="#fff" size="20"></uni-icons>
{{ baseInfo.siteAddress || '-' }}
</view>
<view class="list">
<uni-icons type="calendar" color="#fff" size="20"></uni-icons>
{{ baseInfo.runningTime || '-' }}
</view>
</view>
2026-02-08 17:26:51 +08:00
</view>
<view class="base-info">
2026-02-08 21:51:10 +08:00
<view class="map-card">
<image v-if="mapUrl" class="site-map" :src="mapUrl" mode="aspectFill"></image>
<view v-else class="map-empty">暂无站点位置</view>
</view>
2026-02-08 17:26:51 +08:00
<view class="total-card">
<view class="total-header">
<view class="title">总累计运行数据</view>
<view class="total-revenue">
<text class="label">总收入</text>
2026-03-05 16:34:25 +08:00
<text class="value">{{ format2(totalRevenueDisplayValue) }}</text>
2026-02-08 17:26:51 +08:00
<text class="unit"></text>
</view>
</view>
<uni-grid :column="2" :showBorder="false" :square="false" :highlight="false">
2026-03-05 16:34:25 +08:00
<uni-grid-item v-for="(item, index) in runningDataCards" :key="index + 'sjglData'">
2026-02-08 17:26:51 +08:00
<view class="grid-item-box">
<view class="title">{{ item.title }}</view>
<view class="text" :style="{ color: item.color }">
2026-03-05 16:34:25 +08:00
{{ format2(item.value) }}
2026-02-08 17:26:51 +08:00
</view>
</view>
</uni-grid-item>
</uni-grid>
</view>
<uni-section title="收入曲线" type="line" class="sections-list">
<view style="width:100%;height: 220px;">
2026-02-08 21:51:10 +08:00
<qiun-data-charts type="column" :chartData="revenueChartData" :optsWatch='true'
2026-02-08 17:26:51 +08:00
:inScrollView="true" :pageScrollTop="pageScrollTop" :opts="revenueOptions" :ontouch="true" />
</view>
</uni-section>
</view>
</view>
</template>
<script>
import {
mapGetters
} from 'vuex'
import {
formatDate
} from '@/utils/filters'
import {
getAllSites,
2026-03-05 16:34:25 +08:00
getSingleSiteBaseInfo,
2026-02-08 17:26:51 +08:00
getDzjkHomeView,
2026-03-05 16:34:25 +08:00
getProjectDisplayData,
2026-02-08 17:26:51 +08:00
getAmmeterRevenueData
} from '@/api/ems/site.js'
export default {
data() {
return {
pageScrollTop: 0,
siteOptions: [],
2026-03-05 16:34:25 +08:00
siteTypeOptions: [{
text: '储能',
value: 'cn',
children: []
},
{
text: '光能',
value: 'gn',
disable: true,
children: []
},
{
text: '岸电',
value: 'ad',
disable: true,
children: []
}
],
2026-02-08 21:51:10 +08:00
siteId: '',
mapUrl: '',
2026-03-05 16:34:25 +08:00
baseInfo: {},
2026-02-08 17:26:51 +08:00
runningInfo: {},
2026-03-05 16:34:25 +08:00
runningDisplayData: [],
2026-02-08 17:26:51 +08:00
revenueChartData: {},
revenueOptions: {
padding: [10, 5, 0, 10],
dataLabel: false,
enableScroll: false,
xAxis: {
scrollShow: false,
itemCount: 5,
disableGrid: true
},
yAxis: {
disabled: false,
splitNumber: 4
},
2026-02-08 21:51:10 +08:00
extra: {}
2026-02-08 17:26:51 +08:00
},
2026-03-05 16:34:25 +08:00
fallbackSjglData: [{
2026-02-08 17:26:51 +08:00
title: "今日充电量kWh",
attr: "dayChargedCap",
color: '#4472c4'
},
{
title: "今日放电量kWh",
attr: "dayDisChargedCap",
color: '#70ad47'
},
{
title: "总充电量kWh",
attr: "totalChargedCap",
color: '#4472c4'
},
{
title: "今日实时收入(元)",
attr: "dayRevenue",
color: '#f67438'
},
{
title: "昨日充电量kWh",
attr: "yesterdayChargedCap",
color: '#4472c4'
},
{
title: "昨日放电量kWh",
attr: "yesterdayDisChargedCap",
color: '#70ad47'
},
{
title: "总放电量kWh",
attr: "totalDischargedCap",
color: '#70ad47'
},
{
title: "昨日实时收入(元)",
attr: "yesterdayRevenue",
color: '#f67438'
}
]
}
},
computed: {
2026-03-05 16:34:25 +08:00
...mapGetters(['belongSite', 'currentSiteId']),
totalRunningSectionData() {
return (this.runningDisplayData || []).filter(item => item.sectionName === '总累计运行数据')
},
totalRevenueDisplayItem() {
const sectionData = this.totalRunningSectionData || []
const byFieldCode = sectionData.find(item => item.fieldCode === 'totalRevenue')
if (byFieldCode) {
return byFieldCode
}
return sectionData.find(item => item.fieldName === '总收入')
},
totalRevenueDisplayValue() {
return this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldValue : this.runningInfo.totalRevenue
},
runningDataCards() {
const sectionData = this.totalRunningSectionData || []
if (sectionData.length > 0) {
const revenueFieldCode = this.totalRevenueDisplayItem ? this.totalRevenueDisplayItem.fieldCode : ''
return sectionData
.filter(item => item.fieldCode !== revenueFieldCode)
.map((item, index) => ({
title: item.fieldName,
value: item.fieldValue,
color: this.getCardColor(index)
}))
}
return this.fallbackSjglData.map(item => ({
title: item.title,
value: this.runningInfo[item.attr],
color: item.color
}))
}
2026-02-08 17:26:51 +08:00
},
methods: {
2026-03-05 16:34:25 +08:00
isAvailableSite(siteId) {
const site = this.siteOptions.find(item => item.value === siteId)
return !!(site && !site.disable)
},
getCardColor(index) {
const colors = ['#4472c4', '#70ad47', '#4472c4', '#f67438', '#4472c4', '#70ad47', '#70ad47', '#f67438']
return colors[index % colors.length]
},
2026-02-08 17:26:51 +08:00
format2(value) {
const num = Number(value || 0)
return Number.isFinite(num) ? num.toFixed(2) : '0.00'
},
getLastDaysRange(days = 7) {
const end = new Date()
const start = new Date(end.getTime() - (days - 1) * 24 * 60 * 60 * 1000)
return [formatDate(start), formatDate(end)]
},
buildDateList(start, end) {
const list = []
const startTime = new Date(start).getTime()
const endTime = new Date(end).getTime()
const dayMs = 24 * 60 * 60 * 1000
for (let t = startTime; t <= endTime; t += dayMs) {
list.push(formatDate(t))
}
return list
},
2026-03-05 16:34:25 +08:00
selectedSite(data) {
const siteObj = (data.detail.value || [])[1]
const value = siteObj?.value
if (!value) return
2026-02-08 21:51:10 +08:00
if (value === this.siteId) return
this.siteId = value
2026-03-05 16:34:25 +08:00
this.$store.commit('SET_CURRENTSITEID', value)
2026-02-08 17:26:51 +08:00
this.updateSiteInfo()
},
updateSiteInfo() {
2026-02-08 21:51:10 +08:00
if (!this.siteId) {
2026-03-05 16:34:25 +08:00
this.baseInfo = {}
2026-02-08 17:26:51 +08:00
this.runningInfo = {}
2026-03-05 16:34:25 +08:00
this.runningDisplayData = []
2026-02-08 17:26:51 +08:00
this.revenueChartData = {}
2026-02-08 21:51:10 +08:00
this.mapUrl = ''
2026-02-08 17:26:51 +08:00
return
}
2026-02-08 21:51:10 +08:00
this.updateMapCenter()
2026-03-05 16:34:25 +08:00
this.getSiteBaseInfo()
2026-02-08 17:26:51 +08:00
this.getRunningInfo()
this.getRevenueChartData()
},
2026-03-05 16:34:25 +08:00
getSiteBaseInfo() {
return getSingleSiteBaseInfo({
siteId: this.siteId
}).then(response => {
this.baseInfo = response?.data || {}
})
},
2026-02-08 17:26:51 +08:00
getSiteList() {
getAllSites().then(response => {
const data = response?.data || []
2026-03-05 16:34:25 +08:00
const children = data.map(item => {
2026-02-08 17:26:51 +08:00
return {
text: item.siteName,
value: item.siteId,
2026-03-05 16:34:25 +08:00
id: item.id,
2026-02-08 21:51:10 +08:00
longitude: Number(item.longitude || 0),
latitude: Number(item.latitude || 0),
2026-02-08 17:26:51 +08:00
disable: !this.belongSite || this.belongSite.length === 0 || this.belongSite.includes('all') ? false : !this
.belongSite.includes(item.siteId)
}
})
2026-03-05 16:34:25 +08:00
this.siteTypeOptions.find(i => i.value === 'cn').children = children
this.siteOptions = children
const defaultSiteId = this.isAvailableSite(this.currentSiteId) ? this.currentSiteId : (children.find(item => !item
.disable)?.value || '')
if (defaultSiteId) {
this.siteId = defaultSiteId
this.$store.commit('SET_CURRENTSITEID', defaultSiteId)
this.updateSiteInfo()
}
2026-02-08 17:26:51 +08:00
})
},
2026-02-08 21:51:10 +08:00
updateMapCenter() {
const site = this.siteOptions.find(item => item.value === this.siteId)
if (!site || !site.latitude || !site.longitude) {
this.mapUrl = ''
return
}
const lat = Number(site.latitude)
const lon = Number(site.longitude)
const zoom = 12
const width = 640
const height = 360
const tk = '01e99ab4472430e1c7dbfe4b5db99787'
const layers = 'vec_c,cva_c'
this.mapUrl = `https://api.tianditu.gov.cn/staticimage?center=${lon},${lat}&width=${width}&height=${height}&zoom=${zoom}&layers=${layers}&markers=${lon},${lat}&tk=${tk}`
},
2026-02-08 17:26:51 +08:00
getRunningInfo() {
2026-03-05 16:34:25 +08:00
return Promise.all([
getDzjkHomeView(this.siteId),
getProjectDisplayData(this.siteId)
]).then(([homeResponse, displayResponse]) => {
this.runningInfo = homeResponse?.data || {}
this.runningDisplayData = displayResponse?.data || []
2026-02-08 17:26:51 +08:00
})
},
getRevenueChartData() {
const [startTime, endTime] = this.getLastDaysRange(7)
const dateList = this.buildDateList(startTime, endTime)
2026-02-08 21:51:10 +08:00
const requests = [getAmmeterRevenueData({
siteId: this.siteId,
2026-02-08 17:26:51 +08:00
startTime,
endTime,
pageSize: 200,
pageNum: 1
2026-02-08 21:51:10 +08:00
}).then(response => response?.rows || [])]
2026-02-08 17:26:51 +08:00
Promise.all(requests).then(list => {
2026-02-08 21:51:10 +08:00
const rows = list[0] || []
2026-02-08 17:26:51 +08:00
const categories = dateList.map(day => day.slice(5))
2026-02-08 21:51:10 +08:00
const sumMap = {}
dateList.forEach(day => {
sumMap[day] = 0
})
rows.forEach(row => {
const day = row.dataTime || row.statisDate || row.date
if (!day) return
if (!Object.prototype.hasOwnProperty.call(sumMap, day)) {
2026-02-08 17:26:51 +08:00
sumMap[day] = 0
}
2026-02-08 21:51:10 +08:00
const value = Number(row.actualRevenue || row.revenue || 0)
sumMap[day] += Number.isFinite(value) ? value : 0
2026-02-08 17:26:51 +08:00
})
2026-02-08 21:51:10 +08:00
const series = [{
name: '收入',
data: dateList.map(day => Number((sumMap[day] || 0).toFixed(2)))
}]
const values = series[0].data
let minVal = Math.min(...values)
let maxVal = Math.max(...values)
if (!Number.isFinite(minVal)) minVal = 0
if (!Number.isFinite(maxVal)) maxVal = 0
if (minVal === maxVal) {
minVal -= 1
maxVal += 1
}
const lower = Math.min(minVal, 0)
const upper = Math.max(maxVal, 0)
const padding = Math.max((upper - lower) * 0.1, 1)
this.revenueOptions = {
...this.revenueOptions,
yAxis: {
...(this.revenueOptions.yAxis || {}),
data: [{
min: lower - padding,
max: upper + padding,
format: (val) => Number(val).toFixed(2)
}]
},
extra: {
...(this.revenueOptions.extra || {}),
markLine: {
type: 'solid',
data: [{
value: 0,
lineColor: '#ff4d4f',
showLabel: true,
labelText: '0',
labelFontColor: '#ff4d4f',
labelBgColor: '#fff1f0',
labelBgOpacity: 0.9,
labelAlign: 'left'
}]
}
}
}
2026-02-08 17:26:51 +08:00
this.revenueChartData = JSON.parse(JSON.stringify({
categories,
series
}))
})
}
},
2026-03-05 16:34:25 +08:00
watch: {
currentSiteId(newSiteId) {
if (!newSiteId || newSiteId === this.siteId) return
if (!this.isAvailableSite(newSiteId)) return
this.siteId = newSiteId
this.updateSiteInfo()
}
},
2026-02-08 17:26:51 +08:00
onLoad() {
this.$nextTick(() => {
this.getSiteList()
})
},
onPageScroll(e) {
this.pageScrollTop = e.scrollTop
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #fff;
min-height: 100%;
height: auto;
font-size: 26rpx;
line-height: 30rpx;
}
view {
line-height: inherit;
}
/* #endif */
.home-container {
background-color: #fff;
2026-02-08 21:51:10 +08:00
padding-top: 0;
2026-02-08 17:26:51 +08:00
}
.site-sections-list {
background: linear-gradient(to right, #547ef4, #679ff5);
2026-02-08 21:51:10 +08:00
padding: 30rpx 30rpx;
padding-bottom: 100rpx;
2026-02-08 17:26:51 +08:00
color: #fff;
2026-03-05 16:34:25 +08:00
.info {
color: #fff;
font-size: 26rpx;
line-height: 30rpx;
vertical-align: middle;
margin-top: 20rpx;
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
>.list {
display: flex;
justify-content: flex-start;
align-items: center;
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
&:not(:last-child) {
margin-bottom: 20rpx;
}
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
>.uni-icons {
margin-right: 10rpx;
}
2026-02-08 17:26:51 +08:00
}
2026-03-05 16:34:25 +08:00
}
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
.uni-data-tree {
::v-deep {
.input-value {
border: none;
padding-left: 0;
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
.selected-area {
width: 90%;
flex: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
2026-02-08 17:26:51 +08:00
2026-03-05 16:34:25 +08:00
.selected-list {
color: #fff;
}
}
.text-color {
color: #fff;
font-size: 34rpx;
line-height: 36rpx;
font-weight: bolder;
}
.arrow-area {
transform: rotate(-135deg);
.input-arrow {
border-color: #fff;
}
}
}
2026-02-08 17:26:51 +08:00
}
}
}
.base-info {
margin-top: -80rpx;
border-radius: 80rpx 80rpx 0 0;
padding: 30rpx;
background-color: #fff;
2026-02-08 21:51:10 +08:00
.map-card {
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.08);
margin-top: 30rpx;
background: #f3f5f8;
.site-map {
width: 100%;
height: 220px;
}
.map-empty {
height: 220px;
display: flex;
align-items: center;
justify-content: center;
color: #8a8f98;
font-size: 24rpx;
}
}
2026-02-08 17:26:51 +08:00
.total-card {
margin-top: 30rpx;
border-radius: 16rpx;
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.08);
padding: 20rpx;
.total-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
.title {
font-weight: 700;
font-size: 28rpx;
}
.total-revenue {
display: flex;
align-items: baseline;
gap: 6rpx;
font-size: 22rpx;
color: #19242d;
.value {
font-size: 28rpx;
font-weight: 700;
color: #f67438;
}
}
}
.grid-item-box {
padding-top: 6rpx;
padding-bottom: 6rpx;
text-align: left;
.title {
font-size: 22rpx;
color: #666;
}
.text {
margin-top: 10rpx;
font-size: 26rpx;
font-weight: 600;
}
}
}
.sections-list {
margin-bottom: 10rpx;
::v-deep &>.uni-section-header {
font-weight: 700;
font-size: 26rpx;
line-height: 30rpx;
}
}
.sections-list:not(:first-child) {
margin-top: 40rpx;
}
}
</style>