From 074bcb8394fab66ce531c219e1e7de7c142ff2d5 Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期五, 29 五月 2026 11:07:10 +0800
Subject: [PATCH] 新增智能电表、空调管理
---
admin/src/components/common/Menu.vue | 49
admin/src/views/business/components/YwElectricalRemote.vue | 239 ++++
admin/public/template/yw_device.xlsx | 0
server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java | 25
admin/src/components/business/DailyEnergyTrendPanel.vue | 240 ++++
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwWorkDeskEnergyService.java | 15
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java | 3
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwWorkDeskEnergyServiceImpl.java | 228 +++
admin/src/views/business/ywcustomerrecharge.vue | 12
admin/src/components/business/OperaDeviceImportWindow.vue | 86 +
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwCustomerRechargeCloudController.java | 3
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java | 293 +++-
server/db/business.yw_device.permissions.sql | 1
admin/src/components/business/ElectricalWarningWorkbench.vue | 310 +++++
admin/src/views/operation/device.vue | 7
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwWorkDeskCloutController.java | 36
admin/src/components/business/commonFunctions.vue | 25
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwDeviceCloudController.java | 15
server/visits/dmvisit_service/src/main/java/com/doumee/dao/admin/request/DeviceImport.java | 64 +
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwDeviceService.java | 6
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java | 33
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/DailyEnergyStatVO.java | 24
admin/src/utils/menu.js | 68 +
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalWarningService.java | 3
admin/src/views/index.vue | 1002 ++++++++++------
admin/src/api/Inspection/device.js | 5
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java | 2
admin/src/api/ywWorkDesk.js | 22
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/WarningTypeStatVO.java | 22
admin/src/views/business/ywelectricalactions.vue | 4
server/db/business.yw_device.importExcel.grant.sql | 12
admin/src/views/business/components/YwCustomerConditionerTab.vue | 245 +++-
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java | 2
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwDeviceServiceImpl.java | 332 +++++
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java | 8
35 files changed, 2,849 insertions(+), 592 deletions(-)
diff --git a/admin/public/template/yw_device.xlsx b/admin/public/template/yw_device.xlsx
new file mode 100644
index 0000000..4dda1d0
--- /dev/null
+++ b/admin/public/template/yw_device.xlsx
Binary files differ
diff --git a/admin/src/api/Inspection/device.js b/admin/src/api/Inspection/device.js
index d565e90..9cd6a09 100644
--- a/admin/src/api/Inspection/device.js
+++ b/admin/src/api/Inspection/device.js
@@ -52,3 +52,8 @@
export function getDeviceCateData (data) {
return request.post('/visitsAdmin/cloudService/business/ywDevice/getDeviceCateData', data)
}
+
+// 鎵归噺瀵煎叆
+export function importExcel (data) {
+ return request.post('/visitsAdmin/cloudService/business/ywDevice/importExcel', data)
+}
diff --git a/admin/src/api/ywWorkDesk.js b/admin/src/api/ywWorkDesk.js
index 11b291f..51d7bec 100644
--- a/admin/src/api/ywWorkDesk.js
+++ b/admin/src/api/ywWorkDesk.js
@@ -24,3 +24,25 @@
export function workDeskData () {
return request.get('/visitsAdmin/cloudService/business/ywWorkDesk/workDeskData')
}
+
+// 鐢佃〃鎶ヨ绫诲瀷缁熻
+export function electricalWarningStats () {
+ return request.get('/visitsAdmin/cloudService/business/ywWorkDesk/electricalWarningStats')
+}
+
+// 鐢佃〃鎶ヨ鍒嗛〉
+export function electricalWarningPage (data) {
+ return request.post('/visitsAdmin/cloudService/business/ywWorkDesk/electricalWarningPage', data, {
+ trim: true
+ })
+}
+
+// 杩�30澶╂櫤鑳界數琛ㄦ瘡鏃ョ數閲�/鐢佃垂
+export function electricalDailyEnergyStats () {
+ return request.get('/visitsAdmin/cloudService/business/ywWorkDesk/electricalDailyEnergyStats')
+}
+
+// 杩�30澶╃┖璋冨鑱旀満姣忔棩鐢甸噺/鐢佃垂
+export function conditionerDailyEnergyStats () {
+ return request.get('/visitsAdmin/cloudService/business/ywWorkDesk/conditionerDailyEnergyStats')
+}
diff --git a/admin/src/components/business/DailyEnergyTrendPanel.vue b/admin/src/components/business/DailyEnergyTrendPanel.vue
new file mode 100644
index 0000000..211e067
--- /dev/null
+++ b/admin/src/components/business/DailyEnergyTrendPanel.vue
@@ -0,0 +1,240 @@
+<template>
+ <div class="daily-energy-panel">
+ <div class="panel-header">
+ <h3 class="panel-header__title">{{ title }}</h3>
+ </div>
+ <div class="panel_body" v-loading="loading">
+ <div class="chart_box">
+ <div ref="mixChart" class="chart_item"></div>
+ <div v-if="!loading && !hasData" class="chart_empty">鏆傛棤鏁版嵁</div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+export default {
+ name: 'DailyEnergyTrendPanel',
+ props: {
+ title: {
+ type: String,
+ required: true
+ },
+ loadData: {
+ type: Function,
+ required: true
+ }
+ },
+ data () {
+ return {
+ loading: false,
+ statList: [],
+ chartInstance: null
+ }
+ },
+ computed: {
+ hasData () {
+ return this.statList.some(item => Number(item.totalKwh) > 0 || Number(item.totalFee) > 0)
+ }
+ },
+ mounted () {
+ this.fetchStats()
+ window.addEventListener('resize', this.handleResize)
+ },
+ beforeDestroy () {
+ window.removeEventListener('resize', this.handleResize)
+ this.disposeChart()
+ },
+ methods: {
+ fetchStats () {
+ this.loading = true
+ this.loadData()
+ .then(res => {
+ this.statList = res || []
+ this.$nextTick(() => {
+ this.renderChart()
+ })
+ })
+ .finally(() => {
+ this.loading = false
+ })
+ },
+ disposeChart () {
+ if (this.chartInstance) {
+ this.chartInstance.dispose()
+ this.chartInstance = null
+ }
+ },
+ renderChart () {
+ const chartDom = this.$refs.mixChart
+ if (!chartDom) {
+ return
+ }
+ if (!this.chartInstance) {
+ this.chartInstance = echarts.init(chartDom)
+ }
+ const dates = this.statList.map(item => item.statDate.substring(5))
+ const kwhData = this.statList.map(item => Number(item.totalKwh || 0))
+ const feeData = this.statList.map(item => Number(item.totalFee || 0))
+ this.chartInstance.setOption({
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'cross' },
+ backgroundColor: 'rgba(255,255,255,0.96)',
+ borderColor: '#EEF2F8',
+ borderWidth: 1,
+ textStyle: { color: '#333', fontSize: 12 },
+ extraCssText: 'border-radius: 6px;'
+ },
+ legend: {
+ data: ['鎬荤數閲�(kWh)', '鎬荤數璐�(鍏�)'],
+ top: 0,
+ right: 0,
+ textStyle: { fontSize: 12, color: '#666' }
+ },
+ grid: {
+ left: 50,
+ right: 50,
+ top: 36,
+ bottom: 28
+ },
+ xAxis: {
+ type: 'category',
+ data: dates,
+ axisLabel: {
+ fontSize: 11,
+ color: '#999',
+ interval: 4
+ },
+ axisLine: { lineStyle: { color: '#E8E8E8' } }
+ },
+ yAxis: [
+ {
+ type: 'value',
+ name: 'kWh',
+ nameTextStyle: { color: '#999', fontSize: 11 },
+ axisLabel: { fontSize: 11, color: '#999' },
+ splitLine: { lineStyle: { color: '#F0F0F0' } }
+ },
+ {
+ type: 'value',
+ name: '鍏�',
+ nameTextStyle: { color: '#999', fontSize: 11 },
+ axisLabel: { fontSize: 11, color: '#999' },
+ splitLine: { show: false }
+ }
+ ],
+ series: [
+ {
+ name: '鎬荤數閲�(kWh)',
+ type: 'bar',
+ barMaxWidth: 24,
+ itemStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 0, color: '#5B9CF5' },
+ { offset: 1, color: '#3E80EF' }
+ ]
+ },
+ borderRadius: [3, 3, 0, 0]
+ },
+ data: kwhData
+ },
+ {
+ name: '鎬荤數璐�(鍏�)',
+ type: 'line',
+ smooth: true,
+ yAxisIndex: 1,
+ symbol: 'circle',
+ symbolSize: 5,
+ itemStyle: { color: '#FFB020' },
+ lineStyle: { width: 2, color: '#FFB020' },
+ areaStyle: {
+ color: {
+ type: 'linear',
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 1,
+ colorStops: [
+ { offset: 0, color: 'rgba(255, 176, 32, 0.25)' },
+ { offset: 1, color: 'rgba(255, 176, 32, 0.02)' }
+ ]
+ }
+ },
+ data: feeData
+ }
+ ]
+ }, true)
+ },
+ handleResize () {
+ if (this.chartInstance) {
+ this.chartInstance.resize()
+ }
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.daily-energy-panel {
+ flex: 1;
+ min-width: 0;
+ background: #FFFFFF;
+ border-radius: 10px;
+ padding: 20px;
+ box-sizing: border-box;
+
+ .panel-header {
+ margin-bottom: 16px;
+
+ &__title {
+ margin: 0;
+ padding-left: 10px;
+ font-weight: 600;
+ font-size: 18px;
+ color: #222222;
+ line-height: 1.4;
+ border-left: 3px solid #3E80EF;
+ }
+ }
+
+ .panel_body {
+ min-height: 280px;
+ }
+
+ .chart_box {
+ position: relative;
+ width: 100%;
+ padding: 12px;
+ background: #FAFBFE;
+ border-radius: 8px;
+ border: 1px solid #EEF2F8;
+ box-sizing: border-box;
+
+ .chart_item {
+ width: 100%;
+ height: 280px;
+ }
+
+ .chart_empty {
+ position: absolute;
+ inset: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #999;
+ font-size: 14px;
+ background: #FAFBFE;
+ border-radius: 6px;
+ }
+ }
+}
+</style>
diff --git a/admin/src/components/business/ElectricalWarningWorkbench.vue b/admin/src/components/business/ElectricalWarningWorkbench.vue
new file mode 100644
index 0000000..353e8cd
--- /dev/null
+++ b/admin/src/components/business/ElectricalWarningWorkbench.vue
@@ -0,0 +1,310 @@
+<template>
+ <div class="electrical-warning-panel">
+ <div class="panel-header">
+ <h3 class="panel-header__title">鏃ュ父鐢甸噺绠$悊鎶ヨ淇℃伅</h3>
+ </div>
+ <div class="panel_body">
+ <div class="chart_box" v-loading="chartLoading">
+ <div ref="warningChart" class="warning_chart"></div>
+ <div v-if="!chartLoading && !chartData.length" class="chart_empty">鏆傛棤鎶ヨ鏁版嵁</div>
+ </div>
+ <div class="list_box">
+ <el-table
+ v-loading="listLoading"
+ :data="warningList"
+ stripe
+ highlight-current-row
+ class="warning_table"
+ @row-click="handleRowClick"
+ >
+ <el-table-column label="搴忓彿" width="60" align="center">
+ <template slot-scope="{ $index }">
+ {{ (pagination.page - 1) * pagination.pageSize + $index + 1 }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="electricalName" label="鐢佃〃鍚嶇О" min-width="120" align="center" show-overflow-tooltip />
+ <el-table-column prop="deviceAddress" label="鐢佃〃鍦板潃" min-width="120" align="center" show-overflow-tooltip />
+ <el-table-column label="鎶ヨ椤�" min-width="160" align="center">
+ <template slot-scope="{ row }">
+ <span v-if="row.warningName" class="warn-tag">{{ row.warningName }}</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎶ヨ鏃堕棿" min-width="150" align="center">
+ <template slot-scope="{ row }">
+ {{ formatDateTime(row.startTime) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="msg" label="鎶ヨ璇︽儏" min-width="160" align="center" show-overflow-tooltip />
+ </el-table>
+ <div class="pagination_wrap">
+ <Pagination
+ @size-change="handleSizeChange"
+ @current-change="loadWarningList"
+ :pagination="pagination"
+ />
+ </div>
+ </div>
+ </div>
+ <YwElectricalRemote ref="remoteWindow" />
+ </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import dayjs from 'dayjs'
+import Pagination from '@/components/common/Pagination'
+import { electricalWarningStats, electricalWarningPage } from '@/api/ywWorkDesk'
+import YwElectricalRemote from '@/views/business/components/YwElectricalRemote'
+
+const CHART_COLORS = ['#8EC5FF', '#FF8C69', '#6B5BFF', '#52C41A', '#FFB020', '#9254DE', '#36CFC9', '#F759AB']
+
+export default {
+ name: 'ElectricalWarningWorkbench',
+ components: { Pagination, YwElectricalRemote },
+ data () {
+ return {
+ chartLoading: false,
+ listLoading: false,
+ chartData: [],
+ warningList: [],
+ chartInstance: null,
+ pagination: {
+ page: 1,
+ pageSize: 10,
+ total: 0
+ }
+ }
+ },
+ mounted () {
+ this.loadStats()
+ this.loadWarningList(1)
+ window.addEventListener('resize', this.handleResize)
+ },
+ beforeDestroy () {
+ window.removeEventListener('resize', this.handleResize)
+ if (this.chartInstance) {
+ this.chartInstance.dispose()
+ this.chartInstance = null
+ }
+ },
+ methods: {
+ loadStats () {
+ this.chartLoading = true
+ electricalWarningStats()
+ .then(res => {
+ this.chartData = (res || []).map((item, index) => ({
+ name: `銆愮數鑳借〃銆�${item.warningName}(${item.count})`,
+ value: item.count,
+ itemStyle: { color: CHART_COLORS[index % CHART_COLORS.length] }
+ }))
+ this.$nextTick(() => {
+ this.renderChart()
+ })
+ })
+ .finally(() => {
+ this.chartLoading = false
+ })
+ },
+ loadWarningList (page) {
+ this.listLoading = true
+ if (page) {
+ this.pagination.page = page
+ }
+ electricalWarningPage({
+ page: this.pagination.page,
+ capacity: this.pagination.pageSize,
+ model: {}
+ })
+ .then(res => {
+ this.warningList = res.records || []
+ this.pagination.total = res.total || 0
+ })
+ .finally(() => {
+ this.listLoading = false
+ })
+ },
+ handleSizeChange (size) {
+ this.pagination.pageSize = size
+ this.loadWarningList(1)
+ },
+ renderChart () {
+ const chartDom = this.$refs.warningChart
+ if (!chartDom) {
+ return
+ }
+ if (!this.chartInstance) {
+ this.chartInstance = echarts.init(chartDom)
+ }
+ this.chartInstance.setOption({
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c} ({d}%)',
+ backgroundColor: 'rgba(255,255,255,0.96)',
+ borderColor: '#EEF2F8',
+ borderWidth: 1,
+ textStyle: { color: '#333', fontSize: 12 }
+ },
+ legend: {
+ type: 'scroll',
+ orient: 'vertical',
+ bottom: 8,
+ left: 'center',
+ height: 80,
+ pageButtonItemGap: 6,
+ pageIconSize: 10,
+ itemWidth: 12,
+ itemHeight: 12,
+ itemGap: 8,
+ textStyle: {
+ fontSize: 12,
+ color: '#666'
+ }
+ },
+ series: [
+ {
+ type: 'pie',
+ radius: ['45%', '62%'],
+ center: ['50%', '38%'],
+ avoidLabelOverlap: true,
+ label: {
+ show: false
+ },
+ labelLine: {
+ show: false
+ },
+ data: this.chartData
+ }
+ ]
+ }, true)
+ },
+ handleResize () {
+ if (this.chartInstance) {
+ this.chartInstance.resize()
+ }
+ },
+ handleRowClick (row) {
+ if (!row.electricalId) {
+ this.$message.warning('璇ユ姤璀︽湭鍏宠仈鐢佃〃锛屾棤娉曟墦寮�杩滅▼鎺у埗')
+ return
+ }
+ this.$refs.remoteWindow.open({ id: row.electricalId }, 'basic')
+ },
+ formatDateTime (value) {
+ if (!value) {
+ return '-'
+ }
+ return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.electrical-warning-panel {
+ width: 100%;
+ background: #FFFFFF;
+ border-radius: 10px;
+ padding: 20px;
+ box-sizing: border-box;
+
+ .panel-header {
+ margin-bottom: 16px;
+
+ &__title {
+ margin: 0;
+ padding-left: 10px;
+ font-weight: 600;
+ font-size: 18px;
+ color: #222222;
+ line-height: 1.4;
+ border-left: 3px solid #3E80EF;
+ }
+ }
+
+ .panel_body {
+ display: flex;
+ align-items: stretch;
+ gap: 20px;
+ }
+
+ .chart_box {
+ position: relative;
+ width: 400px;
+ flex-shrink: 0;
+ min-height: 340px;
+ padding: 12px;
+ background: #FAFBFE;
+ border-radius: 8px;
+ border: 1px solid #EEF2F8;
+ box-sizing: border-box;
+
+ .warning_chart {
+ width: 100%;
+ height: 316px;
+ }
+
+ .chart_empty {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #999;
+ font-size: 14px;
+ }
+ }
+
+ .list_box {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ padding-left: 4px;
+ border-left: 1px solid #EEF2F8;
+ }
+
+ .warning_table {
+ width: 100%;
+
+ ::v-deep tbody tr {
+ cursor: pointer;
+ }
+ }
+
+ .pagination_wrap {
+ margin-top: 12px;
+ }
+
+ .warn-tag {
+ display: inline-block;
+ color: #f56c6c;
+ border: 1px solid #f56c6c;
+ border-radius: 4px;
+ padding: 0 6px;
+ margin: 2px;
+ font-size: 12px;
+ line-height: 20px;
+ white-space: nowrap;
+ }
+}
+
+@media (max-width: 1200px) {
+ .electrical-warning-panel {
+ .panel_body {
+ flex-direction: column;
+ }
+
+ .chart_box {
+ width: 100%;
+ }
+
+ .list_box {
+ padding-left: 0;
+ border-left: none;
+ padding-top: 4px;
+ border-top: 1px solid #EEF2F8;
+ }
+ }
+}
+</style>
diff --git a/admin/src/components/business/OperaDeviceImportWindow.vue b/admin/src/components/business/OperaDeviceImportWindow.vue
new file mode 100644
index 0000000..58f0daa
--- /dev/null
+++ b/admin/src/components/business/OperaDeviceImportWindow.vue
@@ -0,0 +1,86 @@
+<template>
+ <el-dialog
+ class="center-title"
+ :title="title"
+ width="500px"
+ top="30vh"
+ :visible.sync="visible"
+ :confirm-working="isWorking"
+ @confirm="confirm"
+ >
+ <p class="tip-warn"><i class="el-icon-warning"></i>瀵煎叆璇存槑锛�<br>
+ 1.璇峰厛涓嬭浇鏂囦欢妯℃澘锛屽苟鎸夌収妯℃澘瑕佹眰濉啓琛ㄦ牸鍐呭;<br>
+ 2.甯�*涓哄繀濉」锛屽叧鑱旀埧婧愭牸寮忎负锛氭ゼ瀹�/妤煎眰/鎴挎簮;<br>
+ 3.璁惧鍒嗙被鏍煎紡涓猴細涓�绾у垎绫�/浜岀骇鍒嗙被;<br>
+ </p>
+ <el-form class="demo-form-inline">
+ <el-form-item label="璁惧鍚嶅崟" required>
+ <div style="width: 100%;display: flex;align-items: center;">
+ <el-button type="primary" @click="clickRef">鐐瑰嚮涓婁紶</el-button>
+ <el-button type="text" @click="exportTemplate">鐐瑰嚮涓嬭浇妯$増.EXCEL</el-button>
+ </div>
+ <div style="font-size: 14px; color: black;" v-if="fileName">{{ fileName }}</div>
+ </el-form-item>
+ </el-form>
+ <input type="file" style="position: fixed; left: 0; top: -50px;" accept=".xlsx" ref="fileExcel" @change="result" />
+ <template v-slot:footer>
+ <el-button @click="visible=false">杩斿洖</el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script>
+import BaseOpera from '@/components/base/BaseOpera'
+import GlobalWindow from '@/components/common/GlobalWindow'
+import { importExcel } from '@/api/Inspection/device'
+export default {
+ name: 'OperaDeviceImportWindow',
+ extends: BaseOpera,
+ // eslint-disable-next-line vue/no-unused-components
+ components: { GlobalWindow },
+ data () {
+ return {
+ importing: false,
+ fileName: ''
+ }
+ },
+ methods: {
+ open (title) {
+ this.title = title
+ this.fileName = ''
+ this.visible = true
+ },
+ exportTemplate () {
+ window.open('/template/yw_device.xlsx')
+ },
+ clickRef () {
+ this.$refs.fileExcel.click()
+ },
+ result (e) {
+ const file = e.target.files[0]
+ if (!file) {
+ return
+ }
+ this.fileName = file.name
+ const data = new FormData()
+ data.append('file', file)
+ importExcel(data)
+ .then(res => {
+ this.$message.success(res || '鎿嶄綔鎴愬姛')
+ this.$emit('success')
+ this.visible = false
+ })
+ .catch(() => {
+ this.fileName = ''
+ })
+ .finally(() => {
+ this.$refs.fileExcel.value = null
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>
diff --git a/admin/src/components/business/commonFunctions.vue b/admin/src/components/business/commonFunctions.vue
index 15ce0d7..b3eb492 100644
--- a/admin/src/components/business/commonFunctions.vue
+++ b/admin/src/components/business/commonFunctions.vue
@@ -9,7 +9,7 @@
<span>璇锋牴鎹娇鐢ㄤ範鎯嚜瀹氫箟甯哥敤鍔熻兘锛屾敮鎸佹嫋鍔ㄦ帓</span>
<draggable v-model="filterList" chosenClass="chosen" forceFallback="true" group="people" animation="1000">
<transition-group class="dra">
- <div class="list" v-for="(item, index) in filterList" :key="index">
+ <div class="list" v-for="item in filterList" :key="item.path || item.id">
<div class="list_checkbox">
<el-checkbox v-model="item.checked"> </el-checkbox>
</div>
@@ -25,7 +25,7 @@
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
- import { updMyYwQuickModel, getDefaultYwQuickList } from '@/api/ywWorkDesk'
+ import { updMyYwQuickModel, getDefaultYwQuickList, getYwQuickList } from '@/api/ywWorkDesk'
import draggable from 'vuedraggable'
export default {
name: 'commonFunctions',
@@ -39,14 +39,25 @@
methods: {
open (title) {
this.title = title
- getDefaultYwQuickList({})
- .then(res => {
- res.forEach(item => {
- item.checked = false
+ Promise.all([getDefaultYwQuickList(), getYwQuickList()])
+ .then(([allList, myList]) => {
+ const selectedPaths = (myList || []).map(item => item.path)
+ const pathOrder = new Map(selectedPaths.map((path, index) => [path, index]))
+ const selected = []
+ const unselected = []
+ ;(allList || []).forEach(item => {
+ const entry = { ...item, checked: selectedPaths.includes(item.path) }
+ if (entry.checked) {
+ selected.push(entry)
+ } else {
+ unselected.push(entry)
+ }
})
- this.filterList = res
+ selected.sort((a, b) => (pathOrder.get(a.path) || 0) - (pathOrder.get(b.path) || 0))
+ this.filterList = [...selected, ...unselected]
this.visible = true
})
+ .catch(e => this.$tip.apiFailed(e))
},
confirm () {
this.isWorking = true
diff --git a/admin/src/components/common/Menu.vue b/admin/src/components/common/Menu.vue
index 75a48d1..6690926 100644
--- a/admin/src/components/common/Menu.vue
+++ b/admin/src/components/common/Menu.vue
@@ -27,33 +27,50 @@
import { mapState } from 'vuex'
import MenuItems from './MenuItems'
import Scrollbar from './Scrollbar'
+import { findMenuByUrl, getOpenMenuIndexes } from '@/utils/menu'
export default {
name: 'Menu',
components: { Scrollbar, MenuItems },
computed: {
...mapState(['menuData']),
// 閫変腑鐨勮彍鍗昳ndex
- activeIndex() {
- let path = this.$route.path
- if (path.endsWith('/')) {
- path = path.substring(0, path.length - 1)
- }
- const menuConfig = this.__getMenuConfig(path, 'index', this.menuData.list)
- if (menuConfig == null) {
- return null
- } else {
- this.$store.commit('pushtags', menuConfig)
- }
- return menuConfig.index
+ activeIndex () {
+ const menuConfig = findMenuByUrl(this.$route.path, this.menuData.list)
+ return menuConfig ? menuConfig.index : null
},
// 榛樿灞曞紑鐨勮彍鍗昳ndex
- defaultOpeneds() {
- // return this.menuData.list.map(menu => menu.index)
-
- return [this.menuData.list[0].index]
+ defaultOpeneds () {
+ const openeds = getOpenMenuIndexes(this.$route.path, this.menuData.list)
+ if (openeds.length > 0) {
+ return openeds
+ }
+ return this.menuData.list.length > 0 ? [this.menuData.list[0].index] : []
+ }
+ },
+ watch: {
+ '$route': {
+ handler () {
+ this.syncMenuState()
+ },
+ immediate: true
}
},
methods: {
+ syncMenuState () {
+ const menuConfig = findMenuByUrl(this.$route.path, this.menuData.list)
+ if (menuConfig) {
+ this.$store.commit('pushtags', menuConfig)
+ }
+ this.$nextTick(() => {
+ const menuRef = this.$refs.menu
+ if (!menuRef) {
+ return
+ }
+ getOpenMenuIndexes(this.$route.path, this.menuData.list).forEach(index => {
+ menuRef.open(index)
+ })
+ })
+ },
// 澶勭悊鑿滃崟閫変腑
handleSelect(menuIndex) {
const menuConfig = this.__getMenuConfig(menuIndex, 'index', this.menuData.list)
diff --git a/admin/src/utils/menu.js b/admin/src/utils/menu.js
new file mode 100644
index 0000000..6c69abb
--- /dev/null
+++ b/admin/src/utils/menu.js
@@ -0,0 +1,68 @@
+function normalizePath (path) {
+ if (!path) {
+ return ''
+ }
+ let normalized = path
+ if (normalized.endsWith('/')) {
+ normalized = normalized.substring(0, normalized.length - 1)
+ }
+ return normalized
+}
+
+export function findMenuByUrl (url, menus) {
+ const target = normalizePath(url)
+ for (const menu of menus || []) {
+ if (menu.url && normalizePath(menu.url) === target) {
+ return menu
+ }
+ if (menu.children && menu.children.length > 0) {
+ const child = findMenuByUrl(target, menu.children)
+ if (child) {
+ return child
+ }
+ }
+ }
+ return null
+}
+
+export function findMenuPathByUrl (url, menus, ancestors = []) {
+ const target = normalizePath(url)
+ for (const menu of menus || []) {
+ const chain = [...ancestors, menu]
+ if (menu.url && normalizePath(menu.url) === target) {
+ return chain
+ }
+ if (menu.children && menu.children.length > 0) {
+ const childChain = findMenuPathByUrl(target, menu.children, chain)
+ if (childChain) {
+ return childChain
+ }
+ }
+ }
+ return null
+}
+
+export function getOpenMenuIndexes (url, menus) {
+ const chain = findMenuPathByUrl(url, menus)
+ if (!chain || chain.length <= 1) {
+ return []
+ }
+ return chain.slice(0, -1).map(menu => menu.index)
+}
+
+export function navigateByMenu (router, store, path, menus) {
+ const menuConfig = findMenuByUrl(path, menus)
+ if (menuConfig) {
+ if (menuConfig.params) {
+ router.push({
+ path: menuConfig.url,
+ query: { index: menuConfig.index, param: menuConfig.params }
+ })
+ } else {
+ router.push(menuConfig.url)
+ }
+ store.commit('pushtags', menuConfig)
+ return
+ }
+ router.push({ path })
+}
diff --git a/admin/src/views/business/components/YwCustomerConditionerTab.vue b/admin/src/views/business/components/YwCustomerConditionerTab.vue
index e731bc2..7a1e404 100644
--- a/admin/src/views/business/components/YwCustomerConditionerTab.vue
+++ b/admin/src/views/business/components/YwCustomerConditionerTab.vue
@@ -1,24 +1,34 @@
<template>
<div v-loading="loading" class="conditioner-tab">
- <section class="config-section">
- <div class="section-title">璁¤垂閰嶇疆</div>
- <el-form label-width="150px" size="small" class="config-form">
- <el-row :gutter="24">
+ <el-form ref="configForm" :model="form" :rules="rules" label-width="150px" size="small" class="config-form">
+ <section class="config-section">
+ <div class="section-title">璁¤垂閰嶇疆</div>
+ <el-row :gutter="24" class="config-first-row">
<el-col :span="12">
- <el-form-item label="璁¤垂寮�鍏�">
- <el-switch v-model="form.isPwr" :active-value="1" :inactive-value="0" active-text="寮�鍚�" inactive-text="鍏抽棴"/>
- </el-form-item>
- </el-col>
- <el-col :span="12">
- <el-form-item label="18:00-09:00 涓嶅仠鏈�">
- <el-switch v-model="form.isRestStop" :active-value="1" :inactive-value="0" active-text="鏄�" inactive-text="鍚�"/>
+ <el-form-item label="娆犺垂棰濆害" prop="stopMoney" class="stop-money-item">
+ <div class="el-input-group stop-money-group">
+ <el-input-number
+ v-model="form.stopMoney"
+ :min="0"
+ :precision="2"
+ :step="10"
+ controls-position="right"
+ class="stop-money-input"
+ />
+ <div class="el-input-group__append">鍏�</div>
+ </div>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
- <el-form-item label="娆犺垂棰濆害锛堝厓锛�">
- <el-input-number v-model="form.stopMoney" :min="0" :precision="2" :step="10" controls-position="right" style="width: 100%"/>
+ <el-form-item label="璁¤垂寮�鍏�" prop="isPwr">
+ <el-switch v-model="form.isPwr" :active-value="1" :inactive-value="0" active-text="寮�鍚�" inactive-text="鍏抽棴"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="18:00-09:00 涓嶅仠鏈�" prop="isRestStop">
+ <el-switch v-model="form.isRestStop" :active-value="1" :inactive-value="0" active-text="鏄�" inactive-text="鍚�"/>
</el-form-item>
</el-col>
</el-row>
@@ -32,36 +42,38 @@
placeholder="璇疯緭鍏ュ娉紙閫夊~锛�"
/>
</el-form-item>
- </el-form>
- </section>
+ </section>
- <section class="config-section">
- <div class="section-header">
- <span class="section-title">鍏宠仈鍐呮満</span>
- <el-button type="primary" size="small" icon="el-icon-plus" @click="openSelector">娣诲姞鍐呮満</el-button>
- </div>
- <el-table :data="form.conditioners" stripe size="small" class="device-table" empty-text="鏆傛湭鍏宠仈鍐呮満锛岃鐐瑰嚮娣诲姞">
- <el-table-column label="璁惧" min-width="200" align="left" show-overflow-tooltip>
- <template slot-scope="{ row }">{{ deviceLabel(row) }}</template>
- </el-table-column>
- <el-table-column prop="platformDevId" label="骞冲彴璁惧ID" min-width="110" align="center"/>
- <el-table-column label="鍦ㄧ嚎" min-width="80" align="center">
- <template slot-scope="{ row }">
- <span :class="row.online === '鍦ㄧ嚎' ? 'green' : 'red'">{{ row.online || '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column label="鐢佃垂鍗犳瘮%" min-width="130" align="center">
- <template slot-scope="{ row }">
- <el-input-number v-model="row.devRatio" :min="1" :max="100" size="small" controls-position="right"/>
- </template>
- </el-table-column>
- <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
- <template slot-scope="{ $index }">
- <el-button type="text" class="red" @click="form.conditioners.splice($index, 1)">绉婚櫎</el-button>
- </template>
- </el-table-column>
- </el-table>
- </section>
+ <section class="config-section">
+ <div class="section-header">
+ <span class="section-title required-title">鍏宠仈鍐呮満</span>
+ <el-button type="primary" size="small" icon="el-icon-plus" @click="openSelector">娣诲姞鍐呮満</el-button>
+ </div>
+ <el-form-item prop="conditioners" label-width="0" class="conditioners-form-item">
+ <el-table :data="form.conditioners" stripe size="small" class="device-table" empty-text="鏆傛湭鍏宠仈鍐呮満锛岃鐐瑰嚮娣诲姞">
+ <el-table-column label="璁惧" min-width="200" align="left" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ deviceLabel(row) }}</template>
+ </el-table-column>
+ <el-table-column prop="platformDevId" label="骞冲彴璁惧ID" min-width="110" align="center"/>
+ <el-table-column label="鍦ㄧ嚎" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span :class="row.online === '鍦ㄧ嚎' ? 'green' : 'red'">{{ row.online || '-' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢佃垂鍗犳瘮%" min-width="130" align="center">
+ <template slot-scope="{ row }">
+ <el-input-number v-model="row.devRatio" :min="1" :max="100" size="small" controls-position="right"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
+ <template slot-scope="{ $index }">
+ <el-button type="text" class="red" @click="removeConditioner($index)">绉婚櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </section>
+ </el-form>
<div class="footer-btns">
<el-button type="primary" :loading="saving" v-permissions="['business:ywcustomerrecharge:bindDevice']" @click="save">淇濆瓨閰嶇疆</el-button>
@@ -105,11 +117,46 @@
loading: false,
saving: false,
form: {
- isPwr: 1,
- isRestStop: 0,
- stopMoney: 0,
+ isPwr: null,
+ isRestStop: null,
+ stopMoney: undefined,
gsBz: '',
conditioners: []
+ },
+ rules: {
+ stopMoney: [{
+ validator: (rule, value, callback) => {
+ if (value === null || value === undefined || value === '') {
+ callback(new Error('璇疯緭鍏ユ瑺璐归搴�'))
+ } else if (Number(value) < 0) {
+ callback(new Error('娆犺垂棰濆害涓嶈兘灏忎簬0'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'blur'
+ }],
+ isPwr: [{
+ validator: (rule, value, callback) => {
+ if (value === 0 || value === 1) callback()
+ else callback(new Error('璇烽�夋嫨璁¤垂寮�鍏�'))
+ },
+ trigger: 'change'
+ }],
+ isRestStop: [{
+ validator: (rule, value, callback) => {
+ if (value === 0 || value === 1) callback()
+ else callback(new Error('璇烽�夋嫨鏄惁鍋滄満'))
+ },
+ trigger: 'change'
+ }],
+ conditioners: [{
+ validator: (rule, value, callback) => {
+ if (value && value.length) callback()
+ else callback(new Error('璇疯嚦灏戝叧鑱斾竴鍙板唴鏈�'))
+ },
+ trigger: 'change'
+ }]
},
selectorVisible: false,
selectorLoading: false,
@@ -143,14 +190,14 @@
rechargeApi.conditionerPage(this.customerId, { page: 1, capacity: 500, model: {} })
]).then(([gs, page]) => {
if (gs) {
- this.form.isPwr = gs.isPwr != null ? gs.isPwr : 1
- this.form.isRestStop = gs.isRestStop != null ? gs.isRestStop : 0
- this.form.stopMoney = gs.stopMoney != null ? gs.stopMoney : 0
+ this.form.isPwr = gs.isPwr != null ? gs.isPwr : null
+ this.form.isRestStop = gs.isRestStop != null ? gs.isRestStop : null
+ this.form.stopMoney = gs.stopMoney != null ? gs.stopMoney : undefined
this.form.gsBz = gs.gsBz || ''
} else {
- this.form.isPwr = 1
- this.form.isRestStop = 0
- this.form.stopMoney = 0
+ this.form.isPwr = null
+ this.form.isRestStop = null
+ this.form.stopMoney = undefined
this.form.gsBz = ''
}
this.form.conditioners = (page.records || []).map(c => ({
@@ -162,29 +209,41 @@
online: c.online,
devRatio: c.devRatio != null ? c.devRatio : 100
}))
+ this.$nextTick(() => {
+ if (this.$refs.configForm) {
+ this.$refs.configForm.clearValidate()
+ }
+ })
}).catch(e => this.$tip.apiFailed(e)).finally(() => { this.loading = false })
},
save () {
- if (!this.form.conditioners.length) {
- this.$tip.warning('璇疯嚦灏戝叧鑱斾竴鍙板唴鏈�')
- return
- }
- this.saving = true
- rechargeApi.saveGsConfig({
- customerId: this.customerId,
- isPwr: this.form.isPwr,
- isRestStop: this.form.isRestStop,
- stopMoney: this.form.stopMoney,
- gsBz: this.form.gsBz,
- conditioners: this.form.conditioners.map(c => ({
- conditionerId: c.conditionerId,
- devRatio: c.devRatio
- }))
- }).then(() => {
- this.$tip.success('淇濆瓨鎴愬姛')
- this.loadConfig()
- this.$emit('success')
- }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.saving = false })
+ this.$refs.configForm.validate(valid => {
+ if (!valid) return
+ this.saving = true
+ rechargeApi.saveGsConfig({
+ customerId: this.customerId,
+ isPwr: this.form.isPwr,
+ isRestStop: this.form.isRestStop,
+ stopMoney: this.form.stopMoney,
+ gsBz: this.form.gsBz,
+ conditioners: this.form.conditioners.map(c => ({
+ conditionerId: c.conditionerId,
+ devRatio: c.devRatio
+ }))
+ }).then(() => {
+ this.$tip.success('淇濆瓨鎴愬姛')
+ this.loadConfig()
+ this.$emit('success')
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.saving = false })
+ })
+ },
+ removeConditioner (index) {
+ this.form.conditioners.splice(index, 1)
+ this.$nextTick(() => {
+ if (this.$refs.configForm) {
+ this.$refs.configForm.validateField('conditioners')
+ }
+ })
},
openSelector () {
this.selectorVisible = true
@@ -226,6 +285,11 @@
})
})
this.selectorVisible = false
+ this.$nextTick(() => {
+ if (this.$refs.configForm) {
+ this.$refs.configForm.validateField('conditioners')
+ }
+ })
}
}
}
@@ -265,6 +329,31 @@
.config-form {
margin-bottom: 0;
}
+.config-first-row {
+ margin-bottom: 4px;
+ padding-bottom: 4px;
+ border-bottom: 1px dashed #ebeef5;
+}
+.config-form ::v-deep .stop-money-item {
+ margin-bottom: 8px;
+}
+.stop-money-group {
+ display: inline-flex;
+ width: 100%;
+ max-width: 280px;
+ vertical-align: middle;
+}
+.stop-money-group ::v-deep .stop-money-input {
+ flex: 1;
+ width: auto;
+}
+.stop-money-group ::v-deep .el-input-number {
+ width: 100%;
+}
+.stop-money-group ::v-deep .el-input__inner {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
.config-form ::v-deep .el-form-item {
margin-bottom: 12px;
}
@@ -277,6 +366,20 @@
.config-form ::v-deep .el-switch {
width: auto;
}
+.required-title::before {
+ content: '*';
+ color: #f56c6c;
+ margin-right: 4px;
+}
+.conditioners-form-item {
+ margin-bottom: 0;
+}
+.conditioners-form-item ::v-deep .el-form-item__content {
+ margin-left: 0 !important;
+}
+.conditioners-form-item ::v-deep .el-form-item__error {
+ padding-top: 4px;
+}
.device-table {
width: 100%;
}
diff --git a/admin/src/views/business/components/YwElectricalRemote.vue b/admin/src/views/business/components/YwElectricalRemote.vue
index 6261d0e..571c9e1 100644
--- a/admin/src/views/business/components/YwElectricalRemote.vue
+++ b/admin/src/views/business/components/YwElectricalRemote.vue
@@ -1,6 +1,6 @@
<template>
<GlobalWindow title="鐢佃〃杩滅▼鎺у埗" :visible.sync="visible" width="780px" :show-confirm="false">
- <el-tabs v-model="activeTab">
+ <el-tabs v-model="activeTab" @tab-click="onTabClick">
<el-tab-pane label="鍩烘湰鎿嶄綔" name="basic">
<div class="info-block">
<p>閲囬泦鍣ㄥ彿锛歿{ info.collectorId }} <span :class="info.online === 1 ? 'green' : 'red'">{{ info.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span></p>
@@ -9,11 +9,23 @@
<p>鐢佃〃绫诲瀷锛歿{ info.type }}</p>
<p>鍏宠仈鎴块棿锛歿{ info.roomNames }}</p>
</div>
+ <div class="meter-data-block" v-loading="infoLoading">
+ <div class="meter-data-block__title">鏈�鏂版妱琛ㄦ暟鎹�</div>
+ <p>褰撳墠鎬荤數閲忥細{{ totalPower }}</p>
+ <p>鐢甸噺鍚屾鏃堕棿锛歿{ latest && latest.addTime ? latest.addTime : '-' }}</p>
+ <p>褰撳墠鍓╀綑閲戦(鍏�)锛歿{ latest && latest.ye != null ? latest.ye : (info.balance != null ? info.balance : '0.0000') }}</p>
+ <p>浣欓鍚屾鏃堕棿锛歿{ balanceSyncTime }}</p>
+ </div>
<div class="btn-row">
<el-button type="primary" :loading="isOperating" @click="doOperate('resetPrepay', '纭娓呴浂骞跺垏鎹㈠埌棰勪粯璐规ā寮忓悧锛�')">娓呴浂骞跺垏鎹㈠埌棰勪粯璐规ā寮�</el-button>
<el-button type="primary" :loading="isOperating" @click="doOperate('resetPostpay', '纭娓呴浂骞跺垏鎹㈠埌鍚庝粯璐规ā寮忓悧锛�')">娓呴浂骞跺垏鎹㈠埌鍚庝粯璐规ā寮�</el-button>
- <el-button type="primary" :loading="isOperating" @click="doOperate('trip', '纭鎷夐椄鍚楋紵')">鎷夐椄</el-button>
- <el-button type="primary" :loading="isOperating" @click="doOperate('close', '纭鍚堥椄鍚楋紵')">鍚堥椄</el-button>
+ </div>
+ <div class="btn-row btn-row--relay">
+ <span class="btn-row__label">鎷夊悎闂告搷浣�</span>
+ <el-button type="danger" :loading="isOperating" @click="doRelayOperate('trip')">鎷夐椄</el-button>
+ <el-button type="primary" :loading="isOperating" @click="doRelayOperate('close')">鍚堥椄</el-button>
+ <el-button type="warning" :loading="isOperating" @click="doRelayOperate('powerProtect')">淇濈數</el-button>
+ <el-button type="danger" plain :loading="isOperating" @click="doRelayOperate('powerProtectRelease')">瑙i櫎淇濈數</el-button>
</div>
</el-tab-pane>
<el-tab-pane label="寮�鎴�" name="open">
@@ -39,18 +51,86 @@
@confirm="doOperate('recharge', '纭鍏呭�煎悧锛�')"
/>
</el-tab-pane>
+ <el-tab-pane label="鎺у埗璁板綍" name="records">
+ <el-table v-loading="recordsLoading" :data="recordsList" stripe size="small" class="records-table">
+ <el-table-column label="鎿嶄綔绫诲瀷" min-width="100" align="center">
+ <template slot-scope="{ row }">{{ formatActionType(row.actionType) }}</template>
+ </el-table-column>
+ <el-table-column prop="oprId" label="鎿嶄綔ID" min-width="150" align="center" show-overflow-tooltip />
+ <el-table-column label="鐘舵��" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span :class="statusClass(row.status)">{{ formatStatus(row.status) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="resultMsg" label="鎵ц缁撴灉" min-width="140" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.resultMsg || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="createDate" label="鎿嶄綔鏃堕棿" min-width="150" align="center" />
+ <el-table-column label="鎶ユ枃" min-width="120" align="center">
+ <template slot-scope="{ row }">
+ <el-button type="text" :disabled="!row.requestBody" @click="openJson('璇锋眰鎶ユ枃', row.requestBody)">璇锋眰</el-button>
+ <el-button type="text" :disabled="!row.responseBody" @click="openJson('鍝嶅簲鎶ユ枃', row.responseBody)">鍝嶅簲</el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" min-width="100" align="center" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button
+ v-if="row.status === 0 && row.oprId"
+ type="text"
+ :loading="queryLoadingId === row.id"
+ v-permissions="['business:ywelectricalactions:queryResult']"
+ @click="handleQueryResult(row)"
+ >鏌ヨ缁撴灉</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ small
+ @size-change="handleRecordsSizeChange"
+ @current-change="loadActionRecords"
+ :pagination="recordsPagination"
+ />
+ </el-tab-pane>
</el-tabs>
+ <OperaInterfaceLogWindow ref="jsonWindow" />
+ <template slot="footer">
+ <el-button type="primary" icon="el-icon-refresh" :loading="isRefreshing" @click="refreshMeterData">鍒锋柊鐢佃〃鏁版嵁</el-button>
+ <el-button @click="visible = false">杩斿洖</el-button>
+ </template>
</GlobalWindow>
</template>
<script>
import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import OperaInterfaceLogWindow from '@/components/business/OperaInterfaceLogWindow'
import { getRemoteInfo, operate } from '@/api/business/ywelectrical'
+import * as actionsApi from '@/api/business/ywelectricalactions'
import AccountRechargePanel from './AccountRechargePanel'
+
+const RELAY_ACTIONS = {
+ trip: { label: '鎷夐椄', message: '纭瀵硅鐢佃〃鎵ц鎷夐椄鎿嶄綔鍚楋紵' },
+ close: { label: '鍚堥椄', message: '纭瀵硅鐢佃〃鎵ц鍚堥椄鎿嶄綔鍚楋紵' },
+ powerProtect: { label: '淇濈數', message: '纭瀵硅鐢佃〃鎵ц淇濈數鎿嶄綔鍚楋紵' },
+ powerProtectRelease: { label: '瑙i櫎淇濈數', message: '纭瀵硅鐢佃〃鎵ц瑙i櫎淇濈數鎿嶄綔鍚楋紵' }
+}
+
+const ACTION_TYPE_MAP = {
+ 1: '棰勪粯璐规竻闆�',
+ 2: '鍚庝粯璐规竻闆�',
+ 3: '杩滅▼閿�鎴�',
+ 4: '鎷夐椄',
+ 5: '鍚堥椄',
+ 6: '寮�鎴�',
+ 7: '鍏呭��',
+ 8: '鎶勮〃',
+ 9: '淇濈數',
+ 10: '瑙i櫎淇濈數'
+}
export default {
name: 'YwElectricalRemote',
- components: { GlobalWindow, AccountRechargePanel },
+ components: { GlobalWindow, AccountRechargePanel, Pagination, OperaInterfaceLogWindow },
data () {
return {
visible: false,
@@ -60,7 +140,23 @@
latest: null,
purchaseCount: '0',
form: { money: 0, remark: '' },
- isOperating: false
+ isOperating: false,
+ isRefreshing: false,
+ infoLoading: false,
+ recordsLoading: false,
+ recordsList: [],
+ recordsPagination: { pageIndex: 1, pageSize: 10, total: 0 },
+ queryLoadingId: null
+ }
+ },
+ computed: {
+ totalPower () {
+ if (!this.latest) return '0.00kWh'
+ const v = this.latest.zhygzdl || '0'
+ return String(v).toLowerCase().indexOf('kwh') >= 0 ? v : v + 'kWh'
+ },
+ balanceSyncTime () {
+ return (this.latest && this.latest.addTime) ? this.latest.addTime : (this.info.balanceTime || '-')
}
},
methods: {
@@ -68,21 +164,86 @@
this.electricalId = row.id
this.activeTab = tab || 'basic'
this.form = { money: 0, remark: '' }
+ this.recordsPagination.pageIndex = 1
+ this.recordsList = []
this.visible = true
this.loadInfo()
+ if (this.activeTab === 'records') {
+ this.loadActionRecords(1)
+ }
+ },
+ onTabClick (tab) {
+ if (tab.name === 'records' && this.recordsList.length === 0) {
+ this.loadActionRecords(1)
+ }
},
loadInfo () {
+ if (!this.electricalId) return
+ this.infoLoading = true
getRemoteInfo(this.electricalId).then(res => {
this.info = res.electrical || {}
this.latest = res.latestData
this.purchaseCount = res.purchaseCount || '0'
- }).catch(e => this.$tip.apiFailed(e))
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.infoLoading = false })
+ },
+ refreshMeterData () {
+ if (!this.electricalId) return
+ this.isRefreshing = true
+ operate({
+ electricalId: this.electricalId,
+ action: 'readMeter'
+ })
+ .then(res => {
+ this.$tip.apiSuccess(res || '鎶勮〃璇锋眰宸叉彁浜わ紝姝e湪鍒锋柊鏁版嵁')
+ this.loadInfo()
+ this.$emit('success')
+ if (this.activeTab === 'records') {
+ this.loadActionRecords(this.recordsPagination.pageIndex)
+ }
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.isRefreshing = false })
+ },
+ loadActionRecords (page) {
+ if (!this.electricalId) return
+ if (page) {
+ this.recordsPagination.pageIndex = page
+ }
+ this.recordsLoading = true
+ actionsApi.fetchList({
+ page: this.recordsPagination.pageIndex,
+ capacity: this.recordsPagination.pageSize,
+ model: { electricalId: this.electricalId }
+ })
+ .then(data => {
+ this.recordsList = data.records || []
+ this.recordsPagination.total = data.total || 0
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.recordsLoading = false })
+ },
+ handleRecordsSizeChange (size) {
+ this.recordsPagination.pageSize = size
+ this.loadActionRecords(1)
},
readMeter () {
this.submitOperate('readMeter', true)
},
doOperate (action, msg) {
this.$dialog.actionConfirm(msg, '鎿嶄綔纭')
+ .then(() => this.submitOperate(action, false))
+ .catch(() => {})
+ },
+ relayMeterTip () {
+ const name = this.info.name || '-'
+ const address = this.info.address || '-'
+ return `琛ㄥ悕绉帮細${name}锛岃〃鍦板潃锛�${address}`
+ },
+ doRelayOperate (action) {
+ const cfg = RELAY_ACTIONS[action]
+ if (!cfg) return
+ const meterTip = this.relayMeterTip()
+ this.$dialog.actionConfirm(`${cfg.message}锛�${meterTip}锛塦, '鎿嶄綔纭')
.then(() => this.submitOperate(action, false))
.catch(() => {})
},
@@ -98,15 +259,79 @@
this.$tip.apiSuccess(res || (silent ? '鎶勮〃璇锋眰宸叉彁浜�' : '鎻愪氦鎴愬姛锛岃鍦ㄣ�愭棩甯哥敤鐢电鐞�-鍏呭�艰褰曘�戜腑鏌ョ湅鍏呭�肩粨鏋�'))
this.loadInfo()
this.$emit('success')
+ if (this.activeTab === 'records') {
+ this.loadActionRecords(this.recordsPagination.pageIndex)
+ }
})
.catch(e => this.$tip.apiFailed(e))
.finally(() => { this.isOperating = false })
+ },
+ formatActionType (val) {
+ return ACTION_TYPE_MAP[val] || val || '-'
+ },
+ formatStatus (val) {
+ if (val === 0 || val === '0') return '澶勭悊涓�'
+ if (val === 1 || val === '1') return '鎴愬姛'
+ if (val === 2 || val === '2') return '澶辫触'
+ return val == null || val === '' ? '-' : String(val)
+ },
+ statusClass (val) {
+ if (val === 1 || val === '1') return 'green'
+ if (val === 2 || val === '2') return 'red'
+ if (val === 0 || val === '0') return 'orange'
+ return ''
+ },
+ openJson (title, content) {
+ if (!content) return
+ this.$refs.jsonWindow.open(title, { content })
+ },
+ handleQueryResult (row) {
+ this.queryLoadingId = row.id
+ actionsApi.queryResult(row.id)
+ .then(msg => {
+ this.$tip.success(msg || '鏌ヨ瀹屾垚')
+ this.loadActionRecords(this.recordsPagination.pageIndex)
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.queryLoadingId = null })
}
}
}
</script>
<style scoped>
-.info-block { margin-bottom: 16px; line-height: 28px; color: #303133; }
+.info-block { margin-bottom: 12px; line-height: 28px; color: #303133; }
+.meter-data-block {
+ margin-bottom: 16px;
+ padding: 12px 14px;
+ background: #fafbfe;
+ border: 1px solid #eef2f8;
+ border-radius: 8px;
+ line-height: 26px;
+ color: #303133;
+}
+.meter-data-block__title {
+ font-weight: 600;
+ font-size: 14px;
+ margin-bottom: 6px;
+ color: #222;
+}
+.meter-data-block p { margin: 0; }
.btn-row .el-button { margin: 0 8px 8px 0; }
+.btn-row--relay {
+ margin-top: 4px;
+ padding-top: 12px;
+ border-top: 1px dashed #ebeef5;
+}
+.btn-row__label {
+ display: block;
+ width: 100%;
+ margin-bottom: 8px;
+ font-size: 13px;
+ color: #909399;
+}
+.records-table { width: 100%; margin-top: 4px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.orange { color: #e6a23c; }
</style>
diff --git a/admin/src/views/business/ywcustomerrecharge.vue b/admin/src/views/business/ywcustomerrecharge.vue
index c5c0e8a..ecc712b 100644
--- a/admin/src/views/business/ywcustomerrecharge.vue
+++ b/admin/src/views/business/ywcustomerrecharge.vue
@@ -113,14 +113,20 @@
capacity: this.tableData.pagination.pageSize,
model: this.buildSearchModel()
}).then(data => {
- this.tableData.list = data.records
- this.tableData.pagination.total = data.total
- }).catch(() => {}).finally(() => { this.isWorking.search = false })
+ this.tableData.list = (data && data.records) || []
+ this.tableData.pagination.total = (data && data.total) || 0
+ }).catch(e => {
+ this.$tip.apiFailed(e)
+ }).finally(() => { this.isWorking.search = false })
},
search () {
this.tableData.pagination.pageIndex = 1
this.loadList()
},
+ handleSizeChange (size) {
+ this.tableData.pagination.pageSize = size
+ this.loadList()
+ },
buildSearchModel () {
const model = {}
if (this.searchForm.nameKeyword) model.nameKeyword = this.searchForm.nameKeyword
diff --git a/admin/src/views/business/ywelectricalactions.vue b/admin/src/views/business/ywelectricalactions.vue
index 82bae2c..b233f26 100644
--- a/admin/src/views/business/ywelectricalactions.vue
+++ b/admin/src/views/business/ywelectricalactions.vue
@@ -96,7 +96,9 @@
5: '鍚堥椄',
6: '寮�鎴�',
7: '鍏呭��',
- 8: '鎶勮〃'
+ 8: '鎶勮〃',
+ 9: '淇濈數',
+ 10: '瑙i櫎淇濈數'
}
export default {
diff --git a/admin/src/views/index.vue b/admin/src/views/index.vue
index 51f20e9..6df214f 100644
--- a/admin/src/views/index.vue
+++ b/admin/src/views/index.vue
@@ -1,109 +1,139 @@
<template>
- <div class="main">
- <div class="main_left">
- <div class="main_left_head">
- <div class="head_item">
- <div class="head_item_left">
- <span>寰呭姙宸ュ崟</span>
- <span>{{obj.waitDealWorkOrderSize || 0}}</span>
- <span @click="jump(1)">鏌ョ湅鏇村</span>
- </div>
- <img class="head_item_icon" src="@/assets/indexIcon/ic_daibangongdan@2x.png" />
- </div>
- <div class="head_item">
- <div class="head_item_left">
- <span>寰呭贰妫�</span>
- <span>{{obj.waitTaskSize || 0}}</span>
- <span @click="jump(2)">鏌ョ湅鏇村</span>
- </div>
- <img class="head_item_icon" src="@/assets/indexIcon/ic_daixuncha@2x.png" />
- </div>
- <div class="head_item">
- <div class="head_item_left">
- <span>寰呯洏鐐�</span>
- <span>{{obj.stocktakingSize || 0}}</span>
- <span @click="jump(3)">鏌ョ湅鏇村</span>
- </div>
- <img class="head_item_icon" src="@/assets/indexIcon/ic_daipandian@2x.png" />
- </div>
- </div>
- <div class="main_left_menu">
- <div class="main_left_menu_title">
- <span>甯哥敤鍔熻兘</span>
- <div class="main_left_menu_title_edit" @click="$refs.commonFunctions.open('甯哥敤鍔熻兘绠$悊')">
- <img src="@/assets/indexIcon/ic_daibangongdan@2x.png" />
- <span>鑷畾涔夊姛鑳�</span>
- </div>
- </div>
- <div class="main_left_menu_list">
- <div class="list_item" v-for="(item, index) in list" :key="index" @click="jump1(item.path)">
- <div class="list_item_left">
- <img :src="item.icoPath" />
- <span>{{item.name}}</span>
+ <div class="dashboard">
+ <section class="dashboard-top">
+ <div class="dashboard-top__left">
+ <div class="stat-cards card">
+ <div class="stat-cards__item head_item--workorder" @click="jump(1)">
+ <div class="stat-cards__content">
+ <span class="stat-cards__label">寰呭姙宸ュ崟</span>
+ <span class="stat-cards__value">{{ obj.waitDealWorkOrderSize || 0 }}</span>
+ <span class="stat-cards__link">鏌ョ湅鏇村</span>
</div>
- <i class="el-icon-arrow-right"></i>
+ <img class="stat-cards__icon" src="@/assets/indexIcon/ic_daibangongdan@2x.png" alt="" />
</div>
- <div class="list_item1"></div>
- <div class="list_item1"></div>
- <div class="list_item1"></div>
+ <div class="stat-cards__item head_item--inspect" @click="jump(2)">
+ <div class="stat-cards__content">
+ <span class="stat-cards__label">寰呭贰妫�</span>
+ <span class="stat-cards__value">{{ obj.waitTaskSize || 0 }}</span>
+ <span class="stat-cards__link">鏌ョ湅鏇村</span>
+ </div>
+ <img class="stat-cards__icon" src="@/assets/indexIcon/ic_daixuncha@2x.png" alt="" />
+ </div>
+ <div class="stat-cards__item head_item--stock" @click="jump(3)">
+ <div class="stat-cards__content">
+ <span class="stat-cards__label">寰呯洏鐐�</span>
+ <span class="stat-cards__value">{{ obj.stocktakingSize || 0 }}</span>
+ <span class="stat-cards__link">鏌ョ湅鏇村</span>
+ </div>
+ <img class="stat-cards__icon" src="@/assets/indexIcon/ic_daipandian@2x.png" alt="" />
+ </div>
</div>
- </div>
- </div>
- <div class="main_right">
- <div class="main_right_title">鏃ョ▼</div>
- <div class="main_right_search">
- <el-select v-model="nian" style="width: 150px; margin-right: 15px;" @change="getMonthNoticess" placeholder="璇烽�夋嫨">
- <el-option
- v-for="item in yearList"
- :key="item.val"
- :label="item.name"
- :value="item.val">
- </el-option>
- </el-select>
- <el-select v-model="yue" style="width: 150px;" @change="getMonthNoticess" placeholder="璇烽�夋嫨">
- <el-option
- v-for="(item, index) in 12"
- :key="index"
- :label="item + '鏈�'"
- :value="item > 9 ? item : `0${item}`">
- </el-option>
- </el-select>
- </div>
- <div class="main_right_date">
- <Calendar
- ref="Calendar"
- v-on:choseDay="clickDay"
- :markDateMore="markDateMore"
- ></Calendar>
- </div>
- <div class="main_right_rc">
- <div class="main_right_rc_title">褰撴棩鏃ョ▼锛坽{dataList.length}}锛�</div>
- <div class="main_right_rc_list" v-loading="loading">
- <div class="main_right_rc_list_row" v-for="(item, index) in dataList" :key="index">
- <div class="top">
- <div class="top_left">
- <div class="top_left_dian"></div>
- <div class="top_left_title">{{item.title}}</div>
+
+ <div class="quick-menu card">
+ <div class="section-header">
+ <h3 class="section-header__title">甯哥敤鍔熻兘</h3>
+ <div class="section-header__action" @click="$refs.commonFunctions.open('甯哥敤鍔熻兘绠$悊')">
+ <i class="el-icon-setting"></i>
+ <span>鑷畾涔夊姛鑳�</span>
+ </div>
+ </div>
+ <div class="quick-menu__grid">
+ <div
+ class="quick-menu__item"
+ v-for="(item, index) in list"
+ :key="index"
+ @click="jump1(item.path)"
+ >
+ <div class="quick-menu__item-left">
+ <img :src="item.icoPath" alt="" />
+ <span>{{ item.name }}</span>
</div>
- <div class="top_date">{{item.param1}}</div>
- </div>
- <div class="bottom">
- {{item.content}}
+ <i class="el-icon-arrow-right quick-menu__arrow"></i>
</div>
</div>
</div>
</div>
- </div>
+
+ <aside class="schedule-panel card">
+ <div class="section-header">
+ <h3 class="section-header__title">鏃ョ▼</h3>
+ </div>
+ <div class="schedule-panel__filters">
+ <el-select v-model="nian" class="schedule-panel__select" @change="getMonthNoticess" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="item in yearList"
+ :key="item.val"
+ :label="item.name"
+ :value="item.val"
+ />
+ </el-select>
+ <el-select v-model="yue" class="schedule-panel__select" @change="getMonthNoticess" placeholder="璇烽�夋嫨">
+ <el-option
+ v-for="(item, index) in 12"
+ :key="index"
+ :label="item + '鏈�'"
+ :value="item > 9 ? item : `0${item}`"
+ />
+ </el-select>
+ </div>
+ <div class="schedule-panel__calendar">
+ <Calendar
+ ref="Calendar"
+ v-on:choseDay="clickDay"
+ :markDateMore="markDateMore"
+ />
+ </div>
+ <div class="schedule-panel__list-wrap">
+ <div class="schedule-panel__list-title">褰撴棩鏃ョ▼锛坽{ dataList.length }}锛�</div>
+ <div class="schedule-panel__list" v-loading="loading">
+ <template v-if="dataList.length">
+ <div class="schedule-item" v-for="(item, index) in dataList" :key="index">
+ <div class="schedule-item__top">
+ <div class="schedule-item__title-wrap">
+ <div class="schedule-item__dot"></div>
+ <div class="schedule-item__title">{{ item.title }}</div>
+ </div>
+ <div class="schedule-item__time">{{ item.param1 }}</div>
+ </div>
+ <div class="schedule-item__content">{{ item.content }}</div>
+ </div>
+ </template>
+ <div v-else class="schedule-empty">褰撴棩鏆傛棤鏃ョ▼</div>
+ </div>
+ </div>
+ </aside>
+ </section>
+
+ <section class="dashboard-warning">
+ <ElectricalWarningWorkbench />
+ </section>
+
+ <section class="dashboard-energy">
+ <DailyEnergyTrendPanel
+ title="杩�30澶╂櫤鑳界數琛ㄦ瘡鏃ユ�荤數閲�/鎬荤數璐�"
+ :load-data="electricalDailyEnergyStats"
+ />
+ <DailyEnergyTrendPanel
+ title="杩�30澶╃┖璋冨鑱旀満姣忔棩鎬荤數閲�/鎬荤數璐�"
+ :load-data="conditionerDailyEnergyStats"
+ />
+ </section>
+
<CommonFunctions ref="commonFunctions" @success="getYwQuickLists" />
</div>
</template>
<script>
import CommonFunctions from '@/components/business/commonFunctions'
-import { getYwQuickList, getMonthNotices, workDeskData } from '@/api/ywWorkDesk'
+import ElectricalWarningWorkbench from '@/components/business/ElectricalWarningWorkbench'
+import DailyEnergyTrendPanel from '@/components/business/DailyEnergyTrendPanel'
+import { getYwQuickList, getMonthNotices, workDeskData, electricalDailyEnergyStats, conditionerDailyEnergyStats } from '@/api/ywWorkDesk'
+import { navigateByMenu } from '@/utils/menu'
+import { mapState } from 'vuex'
import Calendar from 'vue-calendar-component'
+
export default {
- data() {
+ data () {
return {
list: [],
value: new Date(),
@@ -116,16 +146,21 @@
loading: false
}
},
- components: { CommonFunctions, Calendar },
+ components: { CommonFunctions, Calendar, ElectricalWarningWorkbench, DailyEnergyTrendPanel },
+ computed: {
+ ...mapState(['menuData'])
+ },
created () {
this.getWorkDeskData()
this.getYwQuickLists()
this.getYear()
},
methods: {
- getYear() {
- let currentYear = new Date().getFullYear();
- let currentMonth = new Date().getMonth() + 1;
+ electricalDailyEnergyStats,
+ conditionerDailyEnergyStats,
+ getYear () {
+ const currentYear = new Date().getFullYear()
+ const currentMonth = new Date().getMonth() + 1
this.nian = currentYear
this.yue = currentMonth > 9 ? currentMonth : `0${currentMonth}`
for (let i = currentYear - 10; i <= currentYear; i++) {
@@ -133,40 +168,27 @@
}
this.getMonthNoticess()
},
- // 鑾峰彇浠e姙
- getWorkDeskData() {
- workDeskData({})
- .then(res => {
- this.obj = res
- })
- },
- clickDay(e) {
- this.loading = true
- let date = e.replace("/\\//g", "-")
- let arr = this.markDateMore.filter(item => {
- if (item.date === date) {
- return item
- }
+ getWorkDeskData () {
+ workDeskData({}).then(res => {
+ this.obj = res
})
- if (arr.length > 0) {
- this.dataList = arr[0].noticeList
- } else {
- this.dataList = []
- }
+ },
+ clickDay (e) {
+ this.loading = true
+ const date = e.replace('/\\//g', '-')
+ const arr = this.markDateMore.filter(item => item.date === date)
+ this.dataList = arr.length > 0 ? arr[0].noticeList : []
this.loading = false
},
- // 鑾峰彇鏃ョ▼
- getMonthNoticess() {
+ getMonthNoticess () {
getMonthNotices(this.nian + '-' + this.yue).then(res => {
- let arr = res.filter(item => {
- if (item.noticeList && item.noticeList.length > 0) {
- return item
- }
- })
- this.markDateMore = arr.map(item => {
- return { date: item.monthDate.replace("/-0/g", "-"), className: 'markRed', noticeList: item.noticeList }
- })
- let toDay = this.getDay()
+ const arr = res.filter(item => item.noticeList && item.noticeList.length > 0)
+ this.markDateMore = arr.map(item => ({
+ date: item.monthDate.replace('/-0/g', '-'),
+ className: 'markRed',
+ noticeList: item.noticeList
+ }))
+ const toDay = this.getDay()
this.markDateMore.forEach(item => {
if (item.date === toDay) {
this.dataList = item.noticeList
@@ -175,301 +197,477 @@
this.$refs.Calendar.ChoseMonth(`${this.nian}-${this.yue}`, false)
})
},
- getYwQuickLists() {
- getYwQuickList({})
- .then(res => {
- this.list = res
- })
+ getYwQuickLists () {
+ getYwQuickList({}).then(res => {
+ this.list = res
+ })
},
- getDay() {
- let date = new Date();
- let year = date.getFullYear(); // 鑾峰彇骞翠唤
- let month = String(date.getMonth() + 1).padStart(2, '0'); // 鑾峰彇鏈堜唤锛屽苟琛ラ浂
- let day = String(date.getDate()).padStart(2, '0'); // 鑾峰彇鏃ユ湡锛屽苟琛ラ浂
-
- return `${year}-${month}-${day}`;
+ getDay () {
+ const date = new Date()
+ 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}`
},
- jump(type) {
- if (type === 1) {
- this.$router.push({ path: '/workorder/workorderList' })
- } else if (type === 2) {
- this.$router.push({ path: '/Inspection/task' })
- } else if (type === 3) {
- this.$router.push({ path: '/stock/check' })
+ jump (type) {
+ const pathMap = {
+ 1: '/workorder/workorderList',
+ 2: '/Inspection/task',
+ 3: '/stock/check'
}
+ this.navigateToMenu(pathMap[type])
},
- jump1(path) {
- this.$router.push({ path })
+ jump1 (path) {
+ this.navigateToMenu(path)
+ },
+ navigateToMenu (path) {
+ navigateByMenu(this.$router, this.$store, path, this.menuData.list)
}
}
}
</script>
+
<style lang="scss" scoped>
- .main {
- width: 100%;
- padding: 15px;
- box-sizing: border-box;
- background: #F4F7FC;
+.dashboard {
+ --dash-primary: #3E80EF;
+ --dash-primary-light: rgba(62, 128, 239, 0.08);
+ --dash-text: #222222;
+ --dash-text-secondary: #666666;
+ --dash-text-muted: #999999;
+ --dash-bg: #F4F7FC;
+ --dash-card-bg: #FFFFFF;
+ --dash-border: #EEF2F8;
+ --dash-radius: 10px;
+ --dash-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
+ --dash-gap: 16px;
+
+ width: 100%;
+ min-height: 100%;
+ padding: var(--dash-gap);
+ box-sizing: border-box;
+ background: var(--dash-bg);
+ display: flex;
+ flex-direction: column;
+ gap: var(--dash-gap);
+}
+
+.card {
+ background: var(--dash-card-bg);
+ border-radius: var(--dash-radius);
+ border: 1px solid var(--dash-border);
+ box-shadow: var(--dash-shadow);
+}
+
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 16px;
+
+ &__title {
+ margin: 0;
+ padding-left: 10px;
+ font-weight: 600;
+ font-size: 18px;
+ color: var(--dash-text);
+ line-height: 1.4;
+ border-left: 3px solid var(--dash-primary);
+ }
+
+ &__action {
+ cursor: pointer;
display: flex;
- align-items: self-start;
+ align-items: center;
+ gap: 4px;
+ font-size: 14px;
+ color: var(--dash-primary);
+ transition: opacity 0.2s;
+
+ &:hover {
+ opacity: 0.8;
+ }
+
+ i {
+ font-size: 15px;
+ }
+ }
+}
+
+.dashboard-top {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--dash-gap);
+
+ &__left {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: var(--dash-gap);
+ }
+}
+
+.stat-cards {
+ display: flex;
+ align-items: stretch;
+ gap: 12px;
+ padding: 20px;
+
+ &__item {
+ flex: 1;
+ display: flex;
+ align-items: center;
justify-content: space-between;
- .main_left {
- flex: 1;
- height: 100%;
- display: flex;
- flex-direction: column;
- .main_left_head {
- width: 100%;
- height: 199px;
- background: #FFFFFF;
- border-radius: 8px;
- display: flex;
- align-items: center;
- padding: 20px;
- box-sizing: border-box;
- .head_item {
- flex: 1;
- height: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- background: #F1F5FF;
- border-radius: 8px;
- margin-right: 15px;
- padding: 28px 20px;
- box-sizing: border-box;
- &:last-child {
- margin: 0 !important;
- }
- .head_item_left {
- flex: 1;
- display: flex;
- flex-direction: column;
- span {
- &:nth-child(1) {
- font-weight: 600;
- font-size: 17px;
- color: #222222;
- }
- &:nth-child(2) {
- font-weight: 600;
- font-size: 30px;
- color: #111111;
- margin-top: 6px;
- margin-bottom: 14px;
- }
- &:nth-child(3) {
- font-weight: 400;
- font-size: 13px;
- color: #999999;
- cursor: pointer;
- }
- }
- }
- .head_item_icon {
- width: 60px;
- height: 60px;
- }
- }
- }
- .main_left_menu {
- width: 100%;
- height: calc(100% - 214px);
- background: #FFFFFF;
- border-radius: 8px;
- margin-top: 15px;
- padding: 20px;
- box-sizing: border-box;
- .main_left_menu_title {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- span {
- font-weight: 600;
- font-size: 18px;
- color: #222222;
- }
- .main_left_menu_title_edit {
- cursor: pointer;
- display: flex;
- align-items: center;
- img {
- width: 15px;
- height: 15px;
- }
- span {
- font-weight: 400;
- font-size: 14px;
- color: #3E80EF;
- margin-left: 5px;
- }
- }
- }
- .main_left_menu_list {
- width: 100%;
- margin-top: 20px;
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- justify-content: space-between;
- .list_item1 {
- width: 24%;
- height: 0;
- }
- .list_item {
- cursor: pointer;
- width: 24%;
- height: 70px;
- background: #FFFFFF;
- border-radius: 8px;
- border: 1px solid #EEEEEE;
- padding: 0 16px;
- box-sizing: border-box;
- margin-bottom: 15px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .list_item_left {
- display: flex;
- align-items: center;
- img {
- width: 40px;
- height: 40px;
- margin-right: 5px;
- }
- span {
- font-weight: 500;
- font-size: 15px;
- color: #222222;
- }
- }
- }
- }
+ padding: 24px 20px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: transform 0.2s, box-shadow 0.2s;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+
+ .stat-cards__icon {
+ transform: scale(1.05);
}
}
- .main_right {
- flex-shrink: 0;
- width: 500px;
- height: 100%;
- margin-left: 15px;
- background: #FEFEFF;
- border-radius: 8px;
- padding: 20px;
- box-sizing: border-box;
- .main_right_title {
- font-weight: 600;
- font-size: 18px;
- color: #222222;
- margin-bottom: 12px;
+
+ &.head_item--workorder {
+ background: linear-gradient(135deg, #EEF4FF 0%, #F5F8FF 100%);
+
+ .stat-cards__value {
+ color: #3E80EF;
}
- .main_right_rc {
- width: 100%;
- display: flex;
- flex-direction: column;
- .main_right_rc_title {
- font-weight: 500;
- font-size: 16px;
- color: #222222;
- margin-bottom: 15px;
- }
- .main_right_rc_list {
- width: 100%;
- height: 300px;
- overflow-y: scroll;
- .main_right_rc_list_row {
- width: 100%;
- padding: 15px;
- box-sizing: border-box;
- background: #F4F7FC;
- border-radius: 2px;
- margin-bottom: 10px;
- .top {
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
- .top_left {
- display: flex;
- align-items: center;
- .top_left_dian {
- width: 10px;
- height: 10px;
- border-radius: 50%;
- background: #FF9E00;
- margin-right: 10px;
- }
- .top_left_title {
- font-weight: 500;
- font-size: 15px;
- color: #222222;
- }
- }
- .top_date {
- font-weight: 400;
- font-size: 12px;
- color: #999999;
- }
- }
- .bottom {
- font-weight: 400;
- font-size: 13px;
- color: #666666;
- margin-top: 8px;
- }
- }
- }
+
+ .stat-cards__link:hover {
+ color: #3E80EF;
}
- .main_right_date {
- width: 100%;
- padding-top: 15px;
- box-sizing: border-box;
- ::v-deep .wh_content_item {
- height: 50px;
- color: #222222;
- font-weight: 400;
- font-size: 15px;
- }
- ::v-deep .wh_item_date {
- width: 30px;
- height: 30px;
- font-size: 15px;
- }
- ::v-deep .wh_content_all {
- background-color: #ffffff;
- }
- ::v-deep .wh_top_changge {
- display: none;
- }
- ::v-deep .wh_container {
- max-width: 100%;
- }
- ::v-deep .wh_content_item .wh_isToday {
- background-color: rgba(62, 128, 239, 0.47);
- color: #ffffff;
- }
- ::v-deep .wh_item_date:hover {
- background-color: #3E80EF;
- border-radius: 50%;
- color: #ffffff;
- }
- ::v-deep .wh_content_item .wh_chose_day {
- background: #3E80EF;
- color: #fff;
- }
- ::v-deep .markRed {
- position: relative;
- }
- ::v-deep .markRed::after {
- content: '鈼�';
- color: #FF9E00;
- font-size: 11px;
- position: absolute;
- bottom: -30px;
- left: 50%;
- transform: translate(-50%, 0);
- }
+ }
+
+ &.head_item--inspect {
+ background: linear-gradient(135deg, #E8FAF0 0%, #F2FBF6 100%);
+
+ .stat-cards__value {
+ color: #36B37E;
+ }
+
+ .stat-cards__link:hover {
+ color: #36B37E;
+ }
+ }
+
+ &.head_item--stock {
+ background: linear-gradient(135deg, #FFF6E8 0%, #FFFAF2 100%);
+
+ .stat-cards__value {
+ color: #FF9E00;
+ }
+
+ .stat-cards__link:hover {
+ color: #FF9E00;
}
}
}
+
+ &__content {
+ display: flex;
+ flex-direction: column;
+ }
+
+ &__label {
+ font-weight: 600;
+ font-size: 16px;
+ color: var(--dash-text);
+ }
+
+ &__value {
+ font-weight: 700;
+ font-size: 32px;
+ line-height: 1.2;
+ margin: 6px 0 12px;
+ }
+
+ &__link {
+ font-size: 13px;
+ color: var(--dash-text-muted);
+ transition: color 0.2s;
+ }
+
+ &__icon {
+ width: 56px;
+ height: 56px;
+ flex-shrink: 0;
+ transition: transform 0.2s;
+ }
+}
+
+.quick-menu {
+ padding: 20px;
+
+ &__grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+ }
+
+ &__item {
+ cursor: pointer;
+ height: 72px;
+ background: var(--dash-card-bg);
+ border-radius: 8px;
+ border: 1px solid #EEEEEE;
+ padding: 0 16px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
+
+ &:hover {
+ border-color: var(--dash-primary);
+ box-shadow: 0 2px 8px rgba(62, 128, 239, 0.12);
+ transform: translateY(-1px);
+
+ .quick-menu__arrow {
+ color: var(--dash-primary);
+ }
+ }
+ }
+
+ &__item-left {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+
+ img {
+ width: 40px;
+ height: 40px;
+ margin-right: 8px;
+ flex-shrink: 0;
+ }
+
+ span {
+ font-weight: 500;
+ font-size: 14px;
+ color: var(--dash-text);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ &__arrow {
+ color: #C0C4CC;
+ font-size: 14px;
+ flex-shrink: 0;
+ transition: color 0.2s;
+ }
+}
+
+.schedule-panel {
+ flex-shrink: 0;
+ width: 480px;
+ padding: 20px;
+
+ &__filters {
+ display: flex;
+ gap: 12px;
+ margin-bottom: 12px;
+ }
+
+ &__select {
+ width: 140px;
+ }
+
+ &__calendar {
+ padding: 12px;
+ background: #FAFBFE;
+ border-radius: 8px;
+ border: 1px solid var(--dash-border);
+ }
+
+ &__list-wrap {
+ margin-top: 16px;
+ }
+
+ &__list-title {
+ font-weight: 500;
+ font-size: 15px;
+ color: var(--dash-text);
+ margin-bottom: 12px;
+ }
+
+ &__list {
+ height: 280px;
+ overflow-y: auto;
+ padding-right: 4px;
+
+ &::-webkit-scrollbar {
+ width: 5px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: #D8DCE6;
+ border-radius: 3px;
+ }
+ }
+}
+
+.schedule-item {
+ padding: 14px 14px 14px 12px;
+ background: #FAFBFE;
+ border-radius: 8px;
+ border: 1px solid var(--dash-border);
+ border-left: 3px solid #FF9E00;
+ margin-bottom: 10px;
+
+ &__top {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ }
+
+ &__title-wrap {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+ }
+
+ &__dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #FF9E00;
+ margin-right: 8px;
+ flex-shrink: 0;
+ }
+
+ &__title {
+ font-weight: 500;
+ font-size: 14px;
+ color: var(--dash-text);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &__time {
+ font-size: 12px;
+ color: var(--dash-text-muted);
+ flex-shrink: 0;
+ }
+
+ &__content {
+ font-size: 13px;
+ color: var(--dash-text-secondary);
+ margin-top: 8px;
+ line-height: 1.5;
+ }
+}
+
+.schedule-empty {
+ height: 120px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--dash-text-muted);
+ font-size: 14px;
+}
+
+.dashboard-warning,
+.dashboard-energy {
+ display: flex;
+ flex-direction: column;
+ gap: var(--dash-gap);
+}
+
+::v-deep .electrical-warning-panel,
+::v-deep .daily-energy-panel {
+ margin-top: 0 !important;
+ border: 1px solid var(--dash-border);
+ box-shadow: var(--dash-shadow);
+ border-radius: var(--dash-radius);
+}
+
+.schedule-panel__calendar {
+ ::v-deep .wh_content_item {
+ height: 50px;
+ color: var(--dash-text);
+ font-size: 14px;
+ }
+
+ ::v-deep .wh_item_date {
+ width: 30px;
+ height: 30px;
+ font-size: 14px;
+ }
+
+ ::v-deep .wh_content_all {
+ background-color: transparent;
+ }
+
+ ::v-deep .wh_top_changge {
+ display: none;
+ }
+
+ ::v-deep .wh_container {
+ max-width: 100%;
+ }
+
+ ::v-deep .wh_content_item .wh_isToday {
+ background-color: rgba(62, 128, 239, 0.47);
+ color: #ffffff;
+ }
+
+ ::v-deep .wh_item_date:hover {
+ background-color: var(--dash-primary);
+ border-radius: 50%;
+ color: #ffffff;
+ }
+
+ ::v-deep .wh_content_item .wh_chose_day {
+ background: var(--dash-primary);
+ color: #fff;
+ }
+
+ ::v-deep .markRed {
+ position: relative;
+ }
+
+ ::v-deep .markRed::after {
+ content: '鈼�';
+ color: #FF9E00;
+ font-size: 11px;
+ position: absolute;
+ bottom: -30px;
+ left: 50%;
+ transform: translate(-50%, 0);
+ }
+}
+
+@media (max-width: 1400px) {
+ .dashboard-top {
+ flex-direction: column;
+ }
+
+ .schedule-panel {
+ width: 100%;
+ }
+}
+
+@media (max-width: 1200px) {
+ .quick-menu__grid {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .stat-cards {
+ flex-direction: column;
+ }
+}
+
+@media (max-width: 768px) {
+ .quick-menu__grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
</style>
diff --git a/admin/src/views/operation/device.vue b/admin/src/views/operation/device.vue
index 43a7339..767a196 100644
--- a/admin/src/views/operation/device.vue
+++ b/admin/src/views/operation/device.vue
@@ -4,6 +4,8 @@
<div class="mt20">
<el-button type="primary" @click="handleEdit()" icon="el-icon-plus"
v-permissions="['business:ywpatrolline:create']">鏂板缓</el-button>
+ <el-button type="primary" v-permissions="['business:ywdevice:importExcel']"
+ @click="$refs.deviceImport.open('璁惧鎵归噺瀵煎叆')">鎵归噺瀵煎叆</el-button>
</div>
<el-table v-loading="loading" :data="list" stripe>
<el-table-column prop="code" label="璁惧缂栧彿" min-width="100" show-overflow-tooltip />
@@ -35,6 +37,7 @@
</div>
<Edit v-if="showEdit" ref="EditRef" @success="getList" @close="showEdit = false" />
<Detail ref="DetailRef" />
+ <OperaDeviceImportWindow ref="deviceImport" @success="getList(1)" />
</div>
</template>
@@ -43,13 +46,15 @@
import QueryForm from '@/components/common/QueryForm'
import Edit from './components/deviceEdit'
import Detail from './components/deviceDetail'
+import OperaDeviceImportWindow from '@/components/business/OperaDeviceImportWindow'
import { fetchList, deleteById } from '@/api/Inspection/device'
export default {
components: {
Pagination,
QueryForm,
Edit,
- Detail
+ Detail,
+ OperaDeviceImportWindow
},
data() {
return {
diff --git a/server/db/business.yw_device.importExcel.grant.sql b/server/db/business.yw_device.importExcel.grant.sql
new file mode 100644
index 0000000..a768adc
--- /dev/null
+++ b/server/db/business.yw_device.importExcel.grant.sql
@@ -0,0 +1,12 @@
+-- 璁惧绠$悊鎵归噺瀵煎叆锛氫负瓒呯骇绠$悊鍛樿鑹茶ˉ鍏� importExcel 鏉冮檺锛堝彲閲嶅鎵ц锛�
+-- 鎵ц鍚庤閲嶆柊鐧诲綍浠ュ埛鏂� Redis 涓殑鏉冮檺缂撳瓨
+
+INSERT INTO `SYSTEM_ROLE_PERMISSION` (`ROLE_ID`, `PERMISSION_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, p.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_PERMISSION` p ON p.`CODE` = 'business:ywdevice:importExcel' AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` rp
+ WHERE rp.`ROLE_ID` = r.`ID` AND rp.`PERMISSION_ID` = p.`ID` AND rp.`DELETED` = 0
+ );
diff --git a/server/db/business.yw_device.permissions.sql b/server/db/business.yw_device.permissions.sql
index c5f64e5..8767c92 100644
--- a/server/db/business.yw_device.permissions.sql
+++ b/server/db/business.yw_device.permissions.sql
@@ -3,4 +3,5 @@
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywdevice:update', '淇敼杩愮淮璁惧淇℃伅琛�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywdevice:query', '鏌ヨ杩愮淮璁惧淇℃伅琛�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywdevice:exportExcel', '瀵煎嚭杩愮淮璁惧淇℃伅琛�(Excel)', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywdevice:importExcel', '瀵煎叆杩愮淮璁惧淇℃伅琛�(Excel)', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwCustomerRechargeCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwCustomerRechargeCloudController.java
index da8fe3d..8d2aec4 100644
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwCustomerRechargeCloudController.java
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwCustomerRechargeCloudController.java
@@ -34,6 +34,9 @@
public ApiResponse<PageData<YwCustomerRechargeMerchantVO>> merchantPage(
@RequestBody PageWrap<YwCustomerRechargeQueryDTO> pageWrap,
@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ if (pageWrap == null) {
+ pageWrap = new PageWrap<>();
+ }
return ApiResponse.success(ywCustomerRechargeBizService.findMerchantPage(pageWrap));
}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwDeviceCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwDeviceCloudController.java
index db601da..adf98ad 100644
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwDeviceCloudController.java
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwDeviceCloudController.java
@@ -13,12 +13,17 @@
import com.doumee.dao.business.vo.YwDeviceStatusDataVO;
import com.doumee.service.business.YwDeviceService;
import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import com.doumee.config.annotation.CloudRequiredPermission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -90,6 +95,16 @@
ExcelExporter.build(YwDevice.class).export(ywDeviceService.findPage(pageWrap).getRecords(), "杩愮淮璁惧淇℃伅琛�", response);
}
+ @ApiOperation("璁惧鎵归噺瀵煎叆")
+ @PostMapping("/importExcel")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "file", value = "file", required = true, paramType = "query", dataType = "file", dataTypeClass = File.class),
+ })
+ @CloudRequiredPermission("business:ywdevice:importExcel")
+ public ApiResponse<String> importExcel(@ApiParam(value = "file") MultipartFile file, @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywDeviceService.importBatch(file, this.getLoginUser(token)));
+ }
+
@ApiOperation("鏍规嵁ID鏌ヨ")
@GetMapping("/{id}")
@CloudRequiredPermission("business:ywdevice:query")
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwWorkDeskCloutController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwWorkDeskCloutController.java
index e7c3479..4dcee74 100644
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwWorkDeskCloutController.java
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwWorkDeskCloutController.java
@@ -10,10 +10,15 @@
import com.doumee.core.utils.Constants;
import com.doumee.dao.business.model.YwPatrolTaskRecord;
import com.doumee.dao.business.model.YwQuickModel;
+import com.doumee.dao.business.model.YwElectricalWarning;
+import com.doumee.dao.business.vo.DailyEnergyStatVO;
import com.doumee.dao.business.vo.MonthDataResponse;
+import com.doumee.dao.business.vo.WarningTypeStatVO;
import com.doumee.dao.business.vo.WorkDeskDataResponse;
import com.doumee.dao.system.model.Notices;
import com.doumee.service.business.WorkbenchesService;
+import com.doumee.service.business.YwElectricalWarningService;
+import com.doumee.service.business.YwWorkDeskEnergyService;
import com.doumee.service.business.YwPatrolTaskRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -36,6 +41,12 @@
@Autowired
private WorkbenchesService workbenchesService;
+
+ @Autowired
+ private YwElectricalWarningService ywElectricalWarningService;
+
+ @Autowired
+ private YwWorkDeskEnergyService ywWorkDeskEnergyService;
@ApiOperation("鑾峰彇蹇嵎鑿滃崟妯″潡淇℃伅")
@GetMapping("/getYwQuickList")
@@ -75,4 +86,29 @@
return ApiResponse.success(workbenchesService.getMyNotices(pageWrap,getLoginUser(token)));
}
+ @ApiOperation("宸ヤ綔鍙�-鐢佃〃鎶ヨ绫诲瀷缁熻")
+ @GetMapping("/electricalWarningStats")
+ public ApiResponse<List<WarningTypeStatVO>> electricalWarningStats(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywElectricalWarningService.warningTypeStats());
+ }
+
+ @ApiOperation("宸ヤ綔鍙�-鐢佃〃鎶ヨ鍒嗛〉")
+ @PostMapping("/electricalWarningPage")
+ public ApiResponse<PageData<YwElectricalWarning>> electricalWarningPage(@RequestBody PageWrap<YwElectricalWarning> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywElectricalWarningService.findPage(pageWrap));
+ }
+
+ @ApiOperation("宸ヤ綔鍙�-杩�30澶╂櫤鑳界數琛ㄦ瘡鏃ョ數閲�/鐢佃垂")
+ @GetMapping("/electricalDailyEnergyStats")
+ public ApiResponse<List<DailyEnergyStatVO>> electricalDailyEnergyStats(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywWorkDeskEnergyService.electricalDailyStats());
+ }
+
+ @ApiOperation("宸ヤ綔鍙�-杩�30澶╃┖璋冨鑱旀満姣忔棩鐢甸噺/鐢佃垂")
+ @GetMapping("/conditionerDailyEnergyStats")
+ public ApiResponse<List<DailyEnergyStatVO>> conditionerDailyEnergyStats(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywWorkDeskEnergyService.conditionerDailyStats());
+ }
+
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java
index 0872b5e..45f4b3f 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java
@@ -18,6 +18,8 @@
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.math.BigDecimal;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -290,12 +292,8 @@
JSONObject body = new JSONObject(true);
putSessionFields(body, req);
body.put("is_pwr", req.getIs_pwr() != null ? req.getIs_pwr() : 1);
- if (req.getLi_dev() != null && !req.getLi_dev().isEmpty()) {
- body.put("li_dev", req.getLi_dev());
- }
- if (req.getD_dev() != null && !req.getD_dev().isEmpty()) {
- body.put("d_dev", req.getD_dev());
- }
+ body.put("li_dev", req.getLi_dev() != null ? req.getLi_dev() : Collections.emptyList());
+ body.put("d_dev", req.getD_dev() != null ? req.getD_dev() : Collections.emptyMap());
body.put("gs_name", req.getGs_name());
body.put("is_rest_stop", req.getIs_rest_stop() != null ? req.getIs_rest_stop() : 0);
body.put("gs_bz", StringUtils.defaultString(req.getGs_bz()));
@@ -309,6 +307,7 @@
}
req.fillSessionDefaults();
JSONObject body = new JSONObject(true);
+ putSessionFields(body, req);
body.put("id", req.getId());
body.put("is_pwr", req.getIs_pwr() != null ? req.getIs_pwr() : 1);
body.put("is_rest_stop", req.getIs_rest_stop() != null ? req.getIs_rest_stop() : 0);
@@ -317,16 +316,22 @@
body.put("left_money", req.getLeft_money());
}
body.put("is_stop", req.getIs_stop() != null ? req.getIs_stop() : 0);
- body.put("li_dev", req.getLi_dev());
- body.put("d_dev", req.getD_dev());
+ body.put("li_dev", req.getLi_dev() != null ? req.getLi_dev() : Collections.emptyList());
+ body.put("d_dev", req.getD_dev() != null ? req.getD_dev() : Collections.emptyMap());
body.put("gs_bz", StringUtils.defaultString(req.getGs_bz()));
putStopMoney(body, req.getStop_money());
return postJsonBody("/changeGs", body, Object.class);
}
private static void putStopMoney(JSONObject body, Object stopMoney) {
- if (stopMoney != null) {
- body.put("stop_money", stopMoney);
+ if (stopMoney == null) {
+ body.put("stop_money", "0");
+ return;
+ }
+ if (stopMoney instanceof BigDecimal) {
+ body.put("stop_money", ((BigDecimal) stopMoney).toPlainString());
+ } else {
+ body.put("stop_money", String.valueOf(stopMoney));
}
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java
index 7876274..02cb5c2 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java
@@ -11,6 +11,6 @@
@EqualsAndHashCode(callSuper = true)
public class EleControlApiRequest extends OpenAccountRequest {
- @ApiModelProperty("10鎷夐椄 11鍚堥椄")
+ @ApiModelProperty("10鎷夐椄 11鍚堥椄 63淇濈數 220瑙i櫎淇濈數")
private int type;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/admin/request/DeviceImport.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/admin/request/DeviceImport.java
new file mode 100644
index 0000000..f97c65b
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/admin/request/DeviceImport.java
@@ -0,0 +1,64 @@
+package com.doumee.dao.admin.request;
+
+import com.doumee.core.annotation.excel.ExcelColumn;
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+/**
+ * 杩愮淮璁惧鎵归噺瀵煎叆
+ */
+@Data
+@ApiModel("杩愮淮璁惧瀵煎叆")
+public class DeviceImport {
+
+ @ExcelColumn(name = "*璁惧缂栧彿", value = "code", index = 0)
+ private String code;
+
+ @ExcelColumn(name = "*璁惧鍚嶇О", value = "name", index = 1)
+ private String name;
+
+ @ExcelColumn(name = "*璁惧鍒嗙被", value = "categoryPath", index = 2)
+ private String categoryPath;
+
+ @ExcelColumn(name = "璁惧鍨嬪彿", value = "modelNo", index = 3)
+ private String modelNo;
+
+ @ExcelColumn(name = "璁惧绠$悊鍛�", value = "adminUserName", index = 4)
+ private String adminUserName;
+
+ @ExcelColumn(name = "鎵�鍦ㄤ綅缃�", value = "addr", index = 5)
+ private String addr;
+
+ @ExcelColumn(name = "*鎵�灞為」鐩�", value = "projectName", index = 6)
+ private String projectName;
+
+ @ExcelColumn(name = "*鍏宠仈鎴挎簮", value = "roomPath", index = 7)
+ private String roomPath;
+
+ @ExcelColumn(name = "璐叆鏃堕棿", value = "buyDate", index = 8)
+ private String buyDate;
+
+ @ExcelColumn(name = "璁惧鐘舵��", value = "statusText", index = 9)
+ private String statusText;
+
+ @ExcelColumn(name = "杩愮淮鍐呭", value = "content", index = 10)
+ private String content;
+
+ @ExcelColumn(name = "璁惧渚涘簲鍟�", value = "supplier", index = 11)
+ private String supplier;
+
+ @ExcelColumn(name = "渚涘簲鍟嗚仈绯讳汉", value = "supplierLinker", index = 12)
+ private String supplierLinker;
+
+ @ExcelColumn(name = "渚涘簲鍟嗚仈绯绘柟寮�", value = "supplierPhone", index = 13)
+ private String supplierPhone;
+
+ @ExcelColumn(name = "缁翠繚璐熻矗浜�", value = "maintenanceUserName", index = 14)
+ private String maintenanceUserName;
+
+ @ExcelColumn(name = "缁翠繚鍒版湡鏃�", value = "maintenanceOverDate", index = 15)
+ private String maintenanceOverDate;
+
+ @ExcelColumn(name = "缁翠繚璇存槑", value = "maintenanceContent", index = 16)
+ private String maintenanceContent;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java
index f6ba30f..9c0c736 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java
@@ -28,7 +28,7 @@
private String remark;
@ApiModelProperty("鐢佃〃ID")
private Integer electricalId;
- @ApiModelProperty("鎿嶄綔绫诲瀷 1棰勪粯璐规竻闆� 2鍚庝粯璐规竻闆� 3杩滅▼閿�鎴� 4鎷夐椄 5鍚堥椄 6寮�鎴� 7鍏呭�� 8鎶勮〃")
+ @ApiModelProperty("鎿嶄綔绫诲瀷 1棰勪粯璐规竻闆� 2鍚庝粯璐规竻闆� 3杩滅▼閿�鎴� 4鎷夐椄 5鍚堥椄 6寮�鎴� 7鍏呭�� 8鎶勮〃 9淇濈數 10瑙i櫎淇濈數")
private Integer actionType;
private String oprId;
private String requestBody;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/DailyEnergyStatVO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/DailyEnergyStatVO.java
new file mode 100644
index 0000000..2ceaf72
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/DailyEnergyStatVO.java
@@ -0,0 +1,24 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 鎸夋棩姹囨�荤數閲�/鐢佃垂
+ */
+@Data
+@ApiModel("鎸夋棩姹囨�荤數閲�/鐢佃垂")
+public class DailyEnergyStatVO {
+
+ @ApiModelProperty("鏃ユ湡 yyyy-MM-dd")
+ private String statDate;
+
+ @ApiModelProperty("鎬荤數閲� kWh")
+ private BigDecimal totalKwh;
+
+ @ApiModelProperty("鎬荤數璐� 鍏�")
+ private BigDecimal totalFee;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/WarningTypeStatVO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/WarningTypeStatVO.java
new file mode 100644
index 0000000..e9edd7b
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/WarningTypeStatVO.java
@@ -0,0 +1,22 @@
+package com.doumee.dao.business.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 鐢佃〃鎶ヨ绫诲瀷缁熻
+ */
+@Data
+@ApiModel("鐢佃〃鎶ヨ绫诲瀷缁熻")
+public class WarningTypeStatVO {
+
+ @ApiModelProperty("鎶ヨ浜嬩欢id")
+ private Integer warningDefId;
+
+ @ApiModelProperty("鎶ヨ椤瑰悕绉�")
+ private String warningName;
+
+ @ApiModelProperty("鏁伴噺")
+ private Long count;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwDeviceService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwDeviceService.java
index ef46e18..e5c5808 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwDeviceService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwDeviceService.java
@@ -8,6 +8,7 @@
import com.doumee.dao.business.vo.YwDeviceDataVO;
import com.doumee.dao.business.vo.YwDeviceParentCateDataVO;
import com.doumee.dao.business.vo.YwDeviceStatusDataVO;
+import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Set;
@@ -112,4 +113,9 @@
Set<YwDeviceParentCateDataVO> getDeviceCateData(YwDevice model);
YwDeviceStatusDataVO getDeviceStatus(YwDevice model);
+
+ /**
+ * 鎵归噺瀵煎叆璁惧
+ */
+ String importBatch(MultipartFile file, LoginUserInfo user);
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalWarningService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalWarningService.java
index ab39583..0fce338 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalWarningService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalWarningService.java
@@ -4,6 +4,7 @@
import com.doumee.core.model.PageWrap;
import com.doumee.dao.business.dto.WarningDefOptionDTO;
import com.doumee.dao.business.model.YwElectricalWarning;
+import com.doumee.dao.business.vo.WarningTypeStatVO;
import java.util.List;
@@ -19,4 +20,6 @@
String syncFromPlatform();
List<WarningDefOptionDTO> listWarningDefOptions();
+
+ List<WarningTypeStatVO> warningTypeStats();
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwWorkDeskEnergyService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwWorkDeskEnergyService.java
new file mode 100644
index 0000000..dd64e4c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwWorkDeskEnergyService.java
@@ -0,0 +1,15 @@
+package com.doumee.service.business;
+
+import com.doumee.dao.business.vo.DailyEnergyStatVO;
+
+import java.util.List;
+
+/**
+ * 宸ヤ綔鍙扮數閲�/鐢佃垂缁熻
+ */
+public interface YwWorkDeskEnergyService {
+
+ List<DailyEnergyStatVO> electricalDailyStats();
+
+ List<DailyEnergyStatVO> conditionerDailyStats();
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
index 8cfe1f1..66dc3c4 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
@@ -27,6 +27,7 @@
import com.doumee.service.business.YwCustomerRechargeBizService;
import com.doumee.service.business.YwElectricalBizService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -40,14 +41,13 @@
import java.util.stream.Collectors;
@Service
+@Slf4j
public class YwCustomerRechargeBizServiceImpl implements YwCustomerRechargeBizService {
private static final String ONLINE_TEXT = "鍦ㄧ嚎";
@Autowired
private YwCustomerMapper ywCustomerMapper;
- @Autowired
- private MemberMapper memberMapper;
@Autowired
private YwCustomerGsMapper ywCustomerGsMapper;
@Autowired
@@ -69,14 +69,14 @@
@Override
public PageData<YwCustomerRechargeMerchantVO> findMerchantPage(PageWrap<YwCustomerRechargeQueryDTO> pageWrap) {
+ if (pageWrap == null) {
+ pageWrap = new PageWrap<>();
+ }
YwCustomerRechargeQueryDTO query = pageWrap.getModel() != null ? pageWrap.getModel() : new YwCustomerRechargeQueryDTO();
boolean hasDeviceFilter = query.getElectricalStatusFilter() != null || query.getConditionerStatusFilter() != null;
if (hasDeviceFilter) {
- List<YwCustomer> all = ywCustomerMapper.selectList(new QueryWrapper<YwCustomer>().lambda()
- .eq(YwCustomer::getIsdeleted, Constants.ZERO)
- .like(StringUtils.isNotBlank(query.getNameKeyword()), YwCustomer::getName, query.getNameKeyword())
- .orderByDesc(YwCustomer::getCreateDate));
+ List<YwCustomer> all = ywCustomerMapper.selectJoinList(YwCustomer.class, buildMerchantCustomerWrapper(query));
List<YwCustomerRechargeMerchantVO> enriched = enrichMerchantList(all);
List<YwCustomerRechargeMerchantVO> filtered = enriched.stream()
.filter(vo -> matchDeviceFilter(vo, query))
@@ -85,10 +85,7 @@
}
IPage<YwCustomer> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
- IPage<YwCustomer> result = ywCustomerMapper.selectPage(page, new QueryWrapper<YwCustomer>().lambda()
- .eq(YwCustomer::getIsdeleted, Constants.ZERO)
- .like(StringUtils.isNotBlank(query.getNameKeyword()), YwCustomer::getName, query.getNameKeyword())
- .orderByDesc(YwCustomer::getCreateDate));
+ IPage<YwCustomer> result = ywCustomerMapper.selectJoinPage(page, YwCustomer.class, buildMerchantCustomerWrapper(query));
List<YwCustomerRechargeMerchantVO> list = enrichMerchantList(result.getRecords());
PageData<YwCustomerRechargeMerchantVO> data = new PageData<>();
data.setRecords(list);
@@ -96,6 +93,17 @@
data.setPage(result.getCurrent());
data.setCapacity(result.getSize());
return data;
+ }
+
+ private MPJLambdaWrapper<YwCustomer> buildMerchantCustomerWrapper(YwCustomerRechargeQueryDTO query) {
+ return new MPJLambdaWrapper<YwCustomer>()
+ .selectAll(YwCustomer.class)
+ .selectAs(Member::getName, YwCustomer::getMemberName)
+ .selectAs(Member::getPhone, YwCustomer::getMemberPhone)
+ .leftJoin(Member.class, Member::getId, YwCustomer::getMemberId)
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .like(StringUtils.isNotBlank(query.getNameKeyword()), YwCustomer::getName, query.getNameKeyword())
+ .orderByDesc(YwCustomer::getCreateDate);
}
private PageData<YwCustomerRechargeMerchantVO> manualPage(List<YwCustomerRechargeMerchantVO> list, long page, long capacity) {
@@ -146,45 +154,23 @@
}
List<Integer> customerIds = customers.stream().map(YwCustomer::getId).collect(Collectors.toList());
- Map<Integer, YwCustomerGs> gsMap = ywCustomerGsMapper.selectList(new QueryWrapper<YwCustomerGs>().lambda()
- .eq(YwCustomerGs::getIsdeleted, Constants.ZERO)
- .in(YwCustomerGs::getCustomerId, customerIds))
- .stream().collect(Collectors.toMap(YwCustomerGs::getCustomerId, g -> g, (a, b) -> a));
+ Map<Integer, YwCustomerGs> gsMap = loadGsMap(customerIds);
- List<YwCustomerElectrical> relE = ywCustomerElectricalMapper.selectList(new QueryWrapper<YwCustomerElectrical>().lambda()
- .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO)
- .in(YwCustomerElectrical::getCustomerId, customerIds));
+ List<YwCustomerElectrical> relE = loadCustomerElectricalRels(customerIds);
Map<Integer, List<Integer>> customerElectricalIds = relE.stream()
.collect(Collectors.groupingBy(YwCustomerElectrical::getCustomerId,
Collectors.mapping(YwCustomerElectrical::getElectricalId, Collectors.toList())));
- List<YwCustomerConditioner> relC = ywCustomerConditionerMapper.selectList(new QueryWrapper<YwCustomerConditioner>().lambda()
- .eq(YwCustomerConditioner::getIsdeleted, Constants.ZERO)
- .in(YwCustomerConditioner::getCustomerId, customerIds));
+ List<YwCustomerConditioner> relC = loadCustomerConditionerRels(customerIds);
Map<Integer, List<Integer>> customerConditionerIds = relC.stream()
.collect(Collectors.groupingBy(YwCustomerConditioner::getCustomerId,
Collectors.mapping(YwCustomerConditioner::getConditionerId, Collectors.toList())));
Set<Integer> allElectricalIds = relE.stream().map(YwCustomerElectrical::getElectricalId).collect(Collectors.toSet());
- Map<Integer, YwElectrical> electricalMap = allElectricalIds.isEmpty() ? Collections.emptyMap()
- : ywElectricalMapper.selectBatchIds(allElectricalIds).stream()
- .filter(e -> !Objects.equals(e.getIsdeleted(), Constants.ONE))
- .collect(Collectors.toMap(YwElectrical::getId, e -> e, (a, b) -> a));
+ Map<Integer, YwElectrical> electricalMap = loadElectricalMap(allElectricalIds);
Set<Integer> allConditionerIds = relC.stream().map(YwCustomerConditioner::getConditionerId).collect(Collectors.toSet());
- Map<Integer, YwConditioner> conditionerMap = allConditionerIds.isEmpty() ? Collections.emptyMap()
- : ywConditionerMapper.selectBatchIds(allConditionerIds).stream()
- .filter(c -> !Objects.equals(c.getIsdeleted(), Constants.ONE))
- .collect(Collectors.toMap(YwConditioner::getId, c -> c, (a, b) -> a));
-
- Set<Integer> memberIds = customers.stream()
- .map(YwCustomer::getMemberId)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
- Map<Integer, Member> memberMap = memberIds.isEmpty() ? Collections.emptyMap()
- : memberMapper.selectBatchIds(memberIds).stream()
- .filter(m -> !Objects.equals(m.getIsdeleted(), Constants.ONE))
- .collect(Collectors.toMap(Member::getId, m -> m, (a, b) -> a));
+ Map<Integer, YwConditioner> conditionerMap = loadConditionerMap(allConditionerIds);
List<YwCustomerRechargeMerchantVO> list = new ArrayList<>();
for (YwCustomer c : customers) {
@@ -194,11 +180,8 @@
vo.setName(c.getName());
vo.setPhone(c.getPhone());
vo.setCreateDate(c.getCreateDate());
- Member member = c.getMemberId() != null ? memberMap.get(c.getMemberId()) : null;
- if (member != null) {
- vo.setMemberName(member.getName());
- vo.setMemberPhone(member.getPhone());
- }
+ vo.setMemberName(c.getMemberName());
+ vo.setMemberPhone(StringUtils.defaultIfBlank(c.getMemberPhone(), c.getPhone()));
List<Integer> eIds = customerElectricalIds.getOrDefault(c.getId(), Collections.emptyList());
vo.setElectricalCount(eIds.size());
@@ -258,6 +241,86 @@
list.add(vo);
}
return list;
+ }
+
+ private Map<Integer, YwCustomerGs> loadGsMap(List<Integer> customerIds) {
+ if (CollectionUtils.isEmpty(customerIds)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return ywCustomerGsMapper.selectList(new QueryWrapper<YwCustomerGs>().lambda()
+ .select(YwCustomerGs::getId, YwCustomerGs::getCustomerId,
+ YwCustomerGs::getLeftMoney, YwCustomerGs::getSyncDate, YwCustomerGs::getPlatformGsId)
+ .eq(YwCustomerGs::getIsdeleted, Constants.ZERO)
+ .in(YwCustomerGs::getCustomerId, customerIds))
+ .stream().collect(Collectors.toMap(YwCustomerGs::getCustomerId, g -> g, (a, b) -> a));
+ } catch (Exception e) {
+ log.warn("load yw_customer_gs failed, skip gs stats: {}", e.getMessage());
+ return Collections.emptyMap();
+ }
+ }
+
+ private List<YwCustomerElectrical> loadCustomerElectricalRels(List<Integer> customerIds) {
+ if (CollectionUtils.isEmpty(customerIds)) {
+ return Collections.emptyList();
+ }
+ try {
+ return ywCustomerElectricalMapper.selectList(new QueryWrapper<YwCustomerElectrical>().lambda()
+ .select(YwCustomerElectrical::getCustomerId, YwCustomerElectrical::getElectricalId)
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO)
+ .in(YwCustomerElectrical::getCustomerId, customerIds));
+ } catch (Exception e) {
+ log.warn("load yw_customer_electrical failed, skip electrical stats: {}", e.getMessage());
+ return Collections.emptyList();
+ }
+ }
+
+ private List<YwCustomerConditioner> loadCustomerConditionerRels(List<Integer> customerIds) {
+ if (CollectionUtils.isEmpty(customerIds)) {
+ return Collections.emptyList();
+ }
+ try {
+ return ywCustomerConditionerMapper.selectList(new QueryWrapper<YwCustomerConditioner>().lambda()
+ .select(YwCustomerConditioner::getCustomerId, YwCustomerConditioner::getConditionerId)
+ .eq(YwCustomerConditioner::getIsdeleted, Constants.ZERO)
+ .in(YwCustomerConditioner::getCustomerId, customerIds));
+ } catch (Exception e) {
+ log.warn("load yw_customer_conditioner failed, skip conditioner stats: {}", e.getMessage());
+ return Collections.emptyList();
+ }
+ }
+
+ private Map<Integer, YwElectrical> loadElectricalMap(Set<Integer> electricalIds) {
+ if (CollectionUtils.isEmpty(electricalIds)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return ywElectricalMapper.selectList(new QueryWrapper<YwElectrical>().lambda()
+ .select(YwElectrical::getId, YwElectrical::getName, YwElectrical::getAddress,
+ YwElectrical::getBalance, YwElectrical::getOnline, YwElectrical::getIsdeleted)
+ .in(YwElectrical::getId, electricalIds)
+ .eq(YwElectrical::getIsdeleted, Constants.ZERO))
+ .stream().collect(Collectors.toMap(YwElectrical::getId, e -> e, (a, b) -> a));
+ } catch (Exception e) {
+ log.warn("load electrical for merchant page failed: {}", e.getMessage());
+ return Collections.emptyMap();
+ }
+ }
+
+ private Map<Integer, YwConditioner> loadConditionerMap(Set<Integer> conditionerIds) {
+ if (CollectionUtils.isEmpty(conditionerIds)) {
+ return Collections.emptyMap();
+ }
+ try {
+ return ywConditionerMapper.selectList(new QueryWrapper<YwConditioner>().lambda()
+ .select(YwConditioner::getId, YwConditioner::getOnline, YwConditioner::getIsdeleted)
+ .in(YwConditioner::getId, conditionerIds)
+ .eq(YwConditioner::getIsdeleted, Constants.ZERO))
+ .stream().collect(Collectors.toMap(YwConditioner::getId, c -> c, (a, b) -> a));
+ } catch (Exception e) {
+ log.warn("load conditioner for merchant page failed: {}", e.getMessage());
+ return Collections.emptyMap();
+ }
}
@Override
@@ -367,19 +430,30 @@
@Override
public YwCustomerGs getCustomerGsConfig(Integer customerId) {
- return ywCustomerGsMapper.selectOne(new QueryWrapper<YwCustomerGs>().lambda()
- .eq(YwCustomerGs::getCustomerId, customerId)
- .eq(YwCustomerGs::getIsdeleted, Constants.ZERO)
- .last("limit 1"));
+ try {
+ return ywCustomerGsMapper.selectOne(new QueryWrapper<YwCustomerGs>().lambda()
+ .eq(YwCustomerGs::getCustomerId, customerId)
+ .eq(YwCustomerGs::getIsdeleted, Constants.ZERO)
+ .last("limit 1"));
+ } catch (Exception e) {
+ log.warn("load yw_customer_gs failed, retry without stop_money: {}", e.getMessage());
+ return ywCustomerGsMapper.selectOne(new QueryWrapper<YwCustomerGs>().lambda()
+ .select(YwCustomerGs::getId, YwCustomerGs::getCustomerId, YwCustomerGs::getPlatformGsId,
+ YwCustomerGs::getIsPwr, YwCustomerGs::getIsRestStop, YwCustomerGs::getGsBz,
+ YwCustomerGs::getLeftMoney, YwCustomerGs::getSyncDate,
+ YwCustomerGs::getCreator, YwCustomerGs::getCreateDate,
+ YwCustomerGs::getEditor, YwCustomerGs::getEditDate, YwCustomerGs::getIsdeleted)
+ .eq(YwCustomerGs::getCustomerId, customerId)
+ .eq(YwCustomerGs::getIsdeleted, Constants.ZERO)
+ .last("limit 1"));
+ }
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveCustomerGsConfig(YwCustomerGsConfigDTO dto, LoginUserInfo user) {
YwCustomer customer = requireCustomer(dto.getCustomerId());
- if (CollectionUtils.isEmpty(dto.getConditioners())) {
- throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璇疯嚦灏戝叧鑱斾竴鍙扮┖璋冨唴鏈�");
- }
+ validateGsConfigRequired(dto);
conditionerBizService.ensureLogin();
List<Integer> conditionerIds = dto.getConditioners().stream()
@@ -413,10 +487,10 @@
}
gs.setEditor(user.getId());
gs.setEditDate(new Date());
- gs.setIsPwr(dto.getIsPwr() != null ? dto.getIsPwr() : Constants.ONE);
- gs.setIsRestStop(dto.getIsRestStop() != null ? dto.getIsRestStop() : Constants.ZERO);
+ gs.setIsPwr(dto.getIsPwr());
+ gs.setIsRestStop(dto.getIsRestStop());
gs.setGsBz(StringUtils.defaultString(dto.getGsBz()));
- gs.setStopMoney(dto.getStopMoney() != null ? dto.getStopMoney() : BigDecimal.ZERO);
+ gs.setStopMoney(dto.getStopMoney());
if (StringUtils.isBlank(customer.getName())) {
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀹㈡埛鍚嶇О涓嶈兘涓虹┖");
@@ -432,6 +506,8 @@
companyReq.setIs_rest_stop(gs.getIsRestStop());
companyReq.setGs_bz(gs.getGsBz());
companyReq.setStop_money(gs.getStopMoney());
+ companyReq.setLi_dev(liDev);
+ companyReq.setD_dev(dDev);
if (gs.getPlatformGsId() == null) {
ConditionerBaseResponse<Object> addResp = ConditionerUtil.addGs(companyReq);
@@ -464,29 +540,88 @@
refreshGsLeftMoney(gs);
if (gs.getId() == null) {
- ywCustomerGsMapper.insert(gs);
+ try {
+ ywCustomerGsMapper.insert(gs);
+ } catch (Exception e) {
+ if (gs.getStopMoney() != null) {
+ log.warn("insert yw_customer_gs with stop_money failed, retry without stop_money: {}", e.getMessage());
+ gs.setStopMoney(null);
+ ywCustomerGsMapper.insert(gs);
+ } else {
+ throw e;
+ }
+ }
} else {
- ywCustomerGsMapper.updateById(gs);
+ try {
+ ywCustomerGsMapper.updateById(gs);
+ } catch (Exception e) {
+ if (gs.getStopMoney() != null) {
+ log.warn("update yw_customer_gs with stop_money failed, retry without stop_money: {}", e.getMessage());
+ BigDecimal stopMoney = gs.getStopMoney();
+ gs.setStopMoney(null);
+ ywCustomerGsMapper.updateById(gs);
+ gs.setStopMoney(stopMoney);
+ } else {
+ throw e;
+ }
+ }
}
- ywCustomerConditionerMapper.update(null, new UpdateWrapper<YwCustomerConditioner>().lambda()
- .set(YwCustomerConditioner::getIsdeleted, Constants.ONE)
- .set(YwCustomerConditioner::getEditDate, new Date())
- .set(YwCustomerConditioner::getEditor, user.getId())
- .eq(YwCustomerConditioner::getCustomerId, dto.getCustomerId())
- .eq(YwCustomerConditioner::getIsdeleted, Constants.ZERO));
+ saveCustomerConditionerRels(dto, user);
+ }
+
+ /**
+ * 鍏宠仈鍐呮満 upsert锛氳〃涓婃湁 uk(customer_id, conditioner_id)锛屼笉鑳借蒋鍒犲悗閲嶅 insert銆�
+ */
+ private void saveCustomerConditionerRels(YwCustomerGsConfigDTO dto, LoginUserInfo user) {
+ List<YwCustomerConditioner> existingRels = ywCustomerConditionerMapper.selectList(
+ new QueryWrapper<YwCustomerConditioner>().lambda()
+ .eq(YwCustomerConditioner::getCustomerId, dto.getCustomerId()));
+ Map<Integer, YwCustomerConditioner> relByCondId = existingRels.stream()
+ .collect(Collectors.toMap(YwCustomerConditioner::getConditionerId, r -> r, (a, b) -> a));
+
+ Set<Integer> targetIds = dto.getConditioners().stream()
+ .map(YwCustomerGsConfigDTO.ConditionerItem::getConditionerId)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+
+ Date now = new Date();
+ for (YwCustomerConditioner rel : existingRels) {
+ if (!targetIds.contains(rel.getConditionerId())
+ && Objects.equals(rel.getIsdeleted(), Constants.ZERO)) {
+ ywCustomerConditionerMapper.update(null, new UpdateWrapper<YwCustomerConditioner>().lambda()
+ .set(YwCustomerConditioner::getIsdeleted, Constants.ONE)
+ .set(YwCustomerConditioner::getEditDate, now)
+ .set(YwCustomerConditioner::getEditor, user.getId())
+ .eq(YwCustomerConditioner::getId, rel.getId()));
+ }
+ }
for (YwCustomerGsConfigDTO.ConditionerItem item : dto.getConditioners()) {
- YwCustomerConditioner rel = new YwCustomerConditioner();
- rel.setCreator(user.getId());
- rel.setCreateDate(new Date());
- rel.setEditor(user.getId());
- rel.setEditDate(new Date());
- rel.setIsdeleted(Constants.ZERO);
- rel.setCustomerId(dto.getCustomerId());
- rel.setConditionerId(item.getConditionerId());
- rel.setDevRatio(item.getDevRatio() != null ? item.getDevRatio() : 100);
- ywCustomerConditionerMapper.insert(rel);
+ if (item.getConditionerId() == null) {
+ continue;
+ }
+ int ratio = item.getDevRatio() != null ? item.getDevRatio() : 100;
+ YwCustomerConditioner rel = relByCondId.get(item.getConditionerId());
+ if (rel != null) {
+ rel.setIsdeleted(Constants.ZERO);
+ rel.setDevRatio(ratio);
+ rel.setEditor(user.getId());
+ rel.setEditDate(now);
+ ywCustomerConditionerMapper.updateById(rel);
+ } else {
+ YwCustomerConditioner created = new YwCustomerConditioner();
+ created.setCreator(user.getId());
+ created.setCreateDate(now);
+ created.setEditor(user.getId());
+ created.setEditDate(now);
+ created.setIsdeleted(Constants.ZERO);
+ created.setCustomerId(dto.getCustomerId());
+ created.setConditionerId(item.getConditionerId());
+ created.setDevRatio(ratio);
+ ywCustomerConditionerMapper.insert(created);
+ relByCondId.put(item.getConditionerId(), created);
+ }
}
}
@@ -838,6 +973,24 @@
}
}
+ private void validateGsConfigRequired(YwCustomerGsConfigDTO dto) {
+ if (dto.getStopMoney() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "娆犺垂棰濆害涓嶈兘涓虹┖");
+ }
+ if (dto.getStopMoney().compareTo(BigDecimal.ZERO) < 0) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "娆犺垂棰濆害涓嶈兘灏忎簬0");
+ }
+ if (dto.getIsPwr() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璁¤垂寮�鍏充笉鑳戒负绌�");
+ }
+ if (dto.getIsRestStop() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏄惁鍋滄満涓嶈兘涓虹┖");
+ }
+ if (CollectionUtils.isEmpty(dto.getConditioners())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璇疯嚦灏戝叧鑱斾竴鍙扮┖璋冨唴鏈�");
+ }
+ }
+
private String apiMsg(ConditionerBaseResponse<?> resp, String def) {
return resp != null && StringUtils.isNotBlank(resp.getMessage()) ? resp.getMessage() : def;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwDeviceServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwDeviceServiceImpl.java
index c308645..4754f5a 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwDeviceServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwDeviceServiceImpl.java
@@ -9,14 +9,19 @@
import com.doumee.core.utils.Constants;
import com.doumee.core.utils.DateUtil;
import com.doumee.core.utils.Utils;
+import com.doumee.core.annotation.excel.ExcelImporter;
+import com.doumee.dao.admin.request.DeviceImport;
+import com.doumee.dao.business.*;
import com.doumee.dao.business.YwDeviceMapper;
import com.doumee.dao.business.YwDeviceRecordMapper;
+import com.doumee.dao.business.join.MemberJoinMapper;
import com.doumee.dao.business.model.*;
import com.doumee.dao.business.vo.YwDeviceCateDataVO;
import com.doumee.dao.business.vo.YwDeviceDataVO;
import com.doumee.dao.business.vo.YwDeviceParentCateDataVO;
import com.doumee.dao.business.vo.YwDeviceStatusDataVO;
import com.doumee.dao.system.MultifileMapper;
+import com.doumee.dao.system.SystemUserMapper;
import com.doumee.dao.system.model.Multifile;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.YwDeviceRecordService;
@@ -31,8 +36,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
+import org.springframework.web.multipart.MultipartFile;
+import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@@ -57,6 +65,22 @@
@Autowired
private RedisTemplate<String,Object> redisTemplate;
+ @Autowired
+ private CategoryMapper categoryMapper;
+ @Autowired
+ private YwProjectMapper ywProjectMapper;
+ @Autowired
+ private YwBuildingMapper ywBuildingMapper;
+ @Autowired
+ private YwFloorMapper ywFloorMapper;
+ @Autowired
+ private YwRoomMapper ywRoomMapper;
+ @Autowired
+ private MemberJoinMapper memberJoinMapper;
+ @Autowired
+ private SystemUserMapper systemUserMapper;
+
+ private static final int IMPORT_EXCEL_ROW_OFFSET = 3;
@Override
public Integer create(YwDevice ywDevice) {
@@ -434,7 +458,313 @@
return ywDeviceStatusDataVO;
}
-
+
+ @Override
+ @Transactional(rollbackFor = {BusinessException.class, Exception.class})
+ public String importBatch(MultipartFile file, LoginUserInfo loginUserInfo) {
+ List<DeviceImport> dataList;
+ try {
+ ExcelImporter ie = new ExcelImporter(file, 1, 0);
+ dataList = ie.getDataList(DeviceImport.class, null);
+ } catch (Exception e) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝瀵煎叆鏂囦欢瑙f瀽澶辫触锛岃妫�鏌ヨ〃鏍兼牸寮忥紒");
+ }
+ if (CollectionUtils.isEmpty(dataList)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝褰曞叆鏁版嵁涓虹┖锛�");
+ }
+ DeviceImportCache cache = buildImportCache();
+ List<YwDevice> insertList = new ArrayList<>();
+ List<YwDevice> updateList = new ArrayList<>();
+ Set<String> fileCodes = new HashSet<>();
+ Date now = new Date();
+ for (int i = 0; i < dataList.size(); i++) {
+ DeviceImport row = dataList.get(i);
+ if (isImportBlankRow(row)) {
+ continue;
+ }
+ YwDevice device = checkImportRow(row, i, cache, fileCodes, loginUserInfo, now);
+ if (device.getId() != null) {
+ updateList.add(device);
+ } else {
+ insertList.add(device);
+ }
+ }
+ if (CollectionUtils.isEmpty(insertList) && CollectionUtils.isEmpty(updateList)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝褰曞叆鏈夋晥鏁版嵁涓虹┖锛�");
+ }
+ if (!CollectionUtils.isEmpty(insertList)) {
+ ywDeviceMapper.insert(insertList);
+ }
+ for (YwDevice device : updateList) {
+ ywDeviceMapper.updateById(device);
+ }
+ return String.format("鎿嶄綔鎴愬姛锛屾湰娆″鍏� 鏂板%d涓澶囷紝鏇存柊%d涓澶�", insertList.size(), updateList.size());
+ }
+
+ private boolean isImportBlankRow(DeviceImport row) {
+ return StringUtils.isBlank(row.getCode())
+ && StringUtils.isBlank(row.getName())
+ && StringUtils.isBlank(row.getProjectName())
+ && StringUtils.isBlank(row.getRoomPath());
+ }
+
+ private DeviceImportCache buildImportCache() {
+ DeviceImportCache cache = new DeviceImportCache();
+ List<Category> categories = categoryMapper.selectList(new QueryWrapper<Category>().lambda()
+ .eq(Category::getIsdeleted, Constants.ZERO)
+ .eq(Category::getType, Constants.FIVE));
+ Map<Integer, Category> categoryById = categories.stream()
+ .collect(Collectors.toMap(Category::getId, c -> c, (a, b) -> a));
+ for (Category category : categories) {
+ if (category.getParentId() == null || category.getParentId() <= 0) {
+ continue;
+ }
+ Category parent = categoryById.get(category.getParentId());
+ if (parent == null || StringUtils.isBlank(parent.getName()) || StringUtils.isBlank(category.getName())) {
+ continue;
+ }
+ cache.categoryPathMap.put(parent.getName() + "/" + category.getName(), category.getId());
+ }
+ List<YwProject> projects = ywProjectMapper.selectList(new QueryWrapper<YwProject>().lambda()
+ .eq(YwProject::getIsdeleted, Constants.ZERO));
+ for (YwProject project : projects) {
+ if (StringUtils.isNotBlank(project.getName())) {
+ cache.projectNameMap.put(project.getName(), project.getId());
+ }
+ }
+ List<YwBuilding> buildings = ywBuildingMapper.selectList(new QueryWrapper<YwBuilding>().lambda()
+ .eq(YwBuilding::getIsdeleted, Constants.ZERO));
+ for (YwBuilding building : buildings) {
+ if (building.getProjectId() != null && StringUtils.isNotBlank(building.getName())) {
+ cache.buildingMap.put(building.getProjectId() + "|" + building.getName(), building.getId());
+ }
+ }
+ List<YwFloor> floors = ywFloorMapper.selectList(new QueryWrapper<YwFloor>().lambda()
+ .eq(YwFloor::getIsdeleted, Constants.ZERO));
+ for (YwFloor floor : floors) {
+ if (floor.getBuildingId() != null && StringUtils.isNotBlank(floor.getName())) {
+ cache.floorMap.put(floor.getBuildingId() + "|" + floor.getName(), floor.getId());
+ }
+ }
+ List<YwRoom> rooms = ywRoomMapper.selectList(new QueryWrapper<YwRoom>().lambda()
+ .eq(YwRoom::getIsdeleted, Constants.ZERO));
+ for (YwRoom room : rooms) {
+ if (room.getProjectId() == null || room.getBuildingId() == null || room.getFloor() == null) {
+ continue;
+ }
+ if (StringUtils.isNotBlank(room.getCode())) {
+ cache.roomMap.put(roomKey(room.getProjectId(), room.getBuildingId(), room.getFloor(), room.getCode()), room.getId());
+ }
+ if (StringUtils.isNotBlank(room.getRoomNum())) {
+ cache.roomMap.put(roomKey(room.getProjectId(), room.getBuildingId(), room.getFloor(), room.getRoomNum()), room.getId());
+ }
+ }
+ List<YwDevice> devices = ywDeviceMapper.selectList(new QueryWrapper<YwDevice>().lambda()
+ .eq(YwDevice::getIsdeleted, Constants.ZERO)
+ .select(YwDevice::getId, YwDevice::getCode));
+ for (YwDevice device : devices) {
+ if (StringUtils.isNotBlank(device.getCode())) {
+ cache.existingCodeIdMap.put(device.getCode(), device.getId());
+ }
+ }
+ List<Member> members = memberJoinMapper.selectJoinList(Member.class, new MPJLambdaWrapper<Member>()
+ .select(Member::getId, Member::getName)
+ .leftJoin(Company.class, Company::getId, Member::getCompanyId)
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .eq(Company::getType, Constants.ONE));
+ if (!CollectionUtils.isEmpty(members)) {
+ List<Integer> memberIds = members.stream().map(Member::getId).filter(Objects::nonNull).collect(Collectors.toList());
+ if (!CollectionUtils.isEmpty(memberIds)) {
+ List<SystemUser> systemUsers = systemUserMapper.selectList(new QueryWrapper<SystemUser>().lambda()
+ .eq(SystemUser::getDeleted, Boolean.FALSE)
+ .in(SystemUser::getMemberId, memberIds));
+ Map<Integer, Integer> memberUserMap = systemUsers.stream()
+ .filter(u -> u.getMemberId() != null)
+ .collect(Collectors.toMap(SystemUser::getMemberId, SystemUser::getId, (a, b) -> a));
+ for (Member member : members) {
+ if (StringUtils.isBlank(member.getName())) {
+ continue;
+ }
+ Integer userId = memberUserMap.get(member.getId());
+ if (userId == null) {
+ continue;
+ }
+ if (cache.internalUserNameMap.containsKey(member.getName())) {
+ cache.duplicateInternalUserNames.add(member.getName());
+ }
+ cache.internalUserNameMap.put(member.getName(), userId);
+ }
+ }
+ }
+ return cache;
+ }
+
+ private String roomKey(Integer projectId, Integer buildingId, Integer floorId, String roomName) {
+ return projectId + "|" + buildingId + "|" + floorId + "|" + roomName;
+ }
+
+ private YwDevice checkImportRow(DeviceImport row, int index, DeviceImportCache cache, Set<String> fileCodes,
+ LoginUserInfo loginUserInfo, Date now) {
+ int rowNum = index + IMPORT_EXCEL_ROW_OFFSET;
+ if (StringUtils.isBlank(row.getCode())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囩紪鍙蜂笉鑳戒负绌猴紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ if (StringUtils.isBlank(row.getName())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囧悕绉颁笉鑳戒负绌猴紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ if (StringUtils.isBlank(row.getProjectName())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屾墍灞為」鐩笉鑳戒负绌猴紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ if (StringUtils.isBlank(row.getRoomPath())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屽叧鑱旀埧婧愪笉鑳戒负绌猴紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ String code = row.getCode().trim();
+ if (fileCodes.contains(code)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囩紪鍙枫��" + code + "銆戦噸澶嶏紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ fileCodes.add(code);
+ Integer existingId = cache.existingCodeIdMap.get(code);
+
+ Integer cateId = null;
+ if (StringUtils.isNotBlank(row.getCategoryPath())) {
+ String categoryPath = row.getCategoryPath().trim();
+ String[] cateParts = categoryPath.split("/");
+ if (cateParts.length < 2 || StringUtils.isBlank(cateParts[1])) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囧垎绫汇��" + categoryPath + "銆戞牸寮忎笉姝g‘锛岃浣跨敤涓�绾�/浜岀骇鏍煎紡锛�");
+ }
+ cateId = cache.categoryPathMap.get(categoryPath);
+ if (cateId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囧垎绫汇��" + categoryPath + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ }
+
+ Integer projectId = cache.projectNameMap.get(row.getProjectName().trim());
+ if (projectId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屾墍灞為」鐩��" + row.getProjectName() + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+
+ String[] roomParts = row.getRoomPath().trim().split("/");
+ if (roomParts.length < 3 || StringUtils.isAnyBlank(roomParts[0], roomParts[1], roomParts[2])) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屽叧鑱旀埧婧愩��" + row.getRoomPath() + "銆戞牸寮忎笉姝g‘锛岃浣跨敤妤煎畤/妤煎眰/鎴挎簮鏍煎紡锛�");
+ }
+ Integer buildingId = cache.buildingMap.get(projectId + "|" + roomParts[0].trim());
+ if (buildingId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屽叧鑱旀埧婧愩��" + row.getRoomPath() + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ Integer floorId = cache.floorMap.get(buildingId + "|" + roomParts[1].trim());
+ if (floorId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屽叧鑱旀埧婧愩��" + row.getRoomPath() + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ Integer roomId = cache.roomMap.get(roomKey(projectId, buildingId, floorId, roomParts[2].trim()));
+ if (roomId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛屽叧鑱旀埧婧愩��" + row.getRoomPath() + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+
+ Integer status = parseImportStatus(row.getStatusText(), rowNum);
+
+ Integer userId = resolveInternalUserId(row.getAdminUserName(), rowNum, "璁惧绠$悊鍛�", cache);
+ Integer maintenanceUserId = resolveInternalUserId(row.getMaintenanceUserName(), rowNum, "缁翠繚璐熻矗浜�", cache);
+
+ YwDevice device = new YwDevice();
+ device.setCode(code);
+ device.setName(row.getName().trim());
+ device.setCateId(cateId);
+ device.setModelNo(StringUtils.trimToNull(row.getModelNo()));
+ device.setUserId(userId);
+ device.setAddr(StringUtils.trimToNull(row.getAddr()));
+ device.setProjectId(projectId);
+ device.setBuildingId(buildingId);
+ device.setFloorId(floorId);
+ device.setRoomId(roomId);
+ device.setBuyDate(parseImportDate(row.getBuyDate(), rowNum, "璐叆鏃堕棿"));
+ device.setStatus(status);
+ device.setContent(StringUtils.trimToNull(row.getContent()));
+ device.setSupplier(StringUtils.trimToNull(row.getSupplier()));
+ device.setSupplierLinker(StringUtils.trimToNull(row.getSupplierLinker()));
+ device.setSupplierPhone(StringUtils.trimToNull(row.getSupplierPhone()));
+ device.setMaintenanceUserId(maintenanceUserId);
+ device.setMaintenanceOverDate(parseImportDate(row.getMaintenanceOverDate(), rowNum, "缁翠繚鍒版湡鏃�"));
+ device.setMaintenanceContent(StringUtils.trimToNull(row.getMaintenanceContent()));
+ if (existingId != null) {
+ device.setId(existingId);
+ device.setEditor(loginUserInfo.getId());
+ device.setEditDate(now);
+ } else {
+ device.setCreator(loginUserInfo.getId());
+ device.setCreateDate(now);
+ device.setIsdeleted(Constants.ZERO);
+ }
+ return device;
+ }
+
+ private Integer resolveInternalUserId(String userName, int rowNum, String fieldLabel, DeviceImportCache cache) {
+ if (StringUtils.isBlank(userName)) {
+ return null;
+ }
+ String name = userName.trim();
+ if (cache.duplicateInternalUserNames.contains(name)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛�" + fieldLabel + "銆�" + name + "銆戝瓨鍦ㄩ噸鍚嶏紝璇锋鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ Integer userId = cache.internalUserNameMap.get(name);
+ if (userId == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛�" + fieldLabel + "銆�" + name + "銆戞湭鎵惧埌锛岃妫�鏌ヨ〃鏍煎唴瀹癸紒");
+ }
+ return userId;
+ }
+
+ private Integer parseImportStatus(String statusText, int rowNum) {
+ if (StringUtils.isBlank(statusText)) {
+ return Constants.ZERO;
+ }
+ String text = statusText.trim();
+ if ("姝e父".equals(text)) {
+ return Constants.ZERO;
+ }
+ if ("鎹熷潖".equals(text)) {
+ return Constants.ONE;
+ }
+ if ("鎶ュ簾".equals(text)) {
+ return Constants.TWO;
+ }
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "瀵逛笉璧凤紝绗�" + rowNum + "琛岃澶囩姸鎬併��" + statusText + "銆戜笉姝g‘锛屼粎鏀寔姝e父/鎹熷潖/鎶ュ簾锛�");
+ }
+
+ private Date parseImportDate(String dateText, int rowNum, String fieldLabel) {
+ if (StringUtils.isBlank(dateText)) {
+ return null;
+ }
+ String text = dateText.trim();
+ if (StringUtils.endsWith(text, ".0")) {
+ text = StringUtils.substringBefore(text, ".0");
+ }
+ Date date = DateUtil.parseFromFormats(text);
+ if (date != null) {
+ return date;
+ }
+ try {
+ return new SimpleDateFormat("yyyy-MM-dd").parse(text);
+ } catch (Exception ignored) {
+ // try excel serial number below
+ }
+ try {
+ return org.apache.poi.ss.usermodel.DateUtil.getJavaDate(Double.parseDouble(text));
+ } catch (Exception ignored) {
+ // fall through to error
+ }
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),
+ "瀵逛笉璧凤紝绗�" + rowNum + "琛�" + fieldLabel + "銆�" + dateText + "銆戞牸寮忎笉姝g‘锛岃浣跨敤yyyy-MM-dd鏍煎紡锛�");
+ }
+
+ private static class DeviceImportCache {
+ private final Map<String, Integer> categoryPathMap = new HashMap<>();
+ private final Map<String, Integer> projectNameMap = new HashMap<>();
+ private final Map<String, Integer> buildingMap = new HashMap<>();
+ private final Map<String, Integer> floorMap = new HashMap<>();
+ private final Map<String, Integer> roomMap = new HashMap<>();
+ private final Map<String, Integer> existingCodeIdMap = new HashMap<>();
+ private final Map<String, Integer> internalUserNameMap = new HashMap<>();
+ private final Set<String> duplicateInternalUserNames = new HashSet<>();
+ }
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java
index 3eb58fd..bcfd258 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java
@@ -47,6 +47,9 @@
if (model.getActionType() != null) {
queryWrapper.eq(YwElectricalActions::getActionType, model.getActionType());
}
+ if (model.getElectricalId() != null) {
+ queryWrapper.eq(YwElectricalActions::getElectricalId, model.getElectricalId());
+ }
if (model.getOperateTimeBegin() != null) {
queryWrapper.ge(YwElectricalActions::getCreateDate, Utils.Date.getStart(model.getOperateTimeBegin()));
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
index c9911dd..8ab9565 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
@@ -52,6 +52,8 @@
public static final int ACTION_OPEN = 6;
public static final int ACTION_RECHARGE = 7;
public static final int ACTION_READ = 8;
+ public static final int ACTION_POWER_PROTECT = 9;
+ public static final int ACTION_POWER_PROTECT_RELEASE = 10;
private static final long FIRST_STATUS_QUERY_DELAY_MS = 30_000L;
private static final long STATUS_QUERY_MIN_INTERVAL_MS = 3_600_000L;
@@ -164,6 +166,10 @@
return doEleControl(e, 10, ACTION_TRIP, user);
case "close":
return doEleControl(e, 11, ACTION_CLOSE, user);
+ case "powerProtect":
+ return doEleControl(e, 63, ACTION_POWER_PROTECT, user);
+ case "powerProtectRelease":
+ return doEleControl(e, 220, ACTION_POWER_PROTECT_RELEASE, user);
case "openAccount":
return doOpenAccount(e, dto, user);
case "recharge":
@@ -209,7 +215,7 @@
List<OpenAccountRequest> list = new ArrayList<>();
list.add(req);
ElectronicBaseResponse resp = ElectronicToolUtil.eleControl(list);
- return finishAsync(e, actionType, oprId, "/Api_v2/ele_security/ele_control", reqJson, resp, user);
+ return finishAsync(e, actionType, oprId, "/Api_v2/ele_control", reqJson, resp, user);
}
private String doOpenAccount(YwElectrical e, YwElectricalOperateDTO dto, LoginUserInfo user) {
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java
index 8a06b32..1f12943 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java
@@ -22,6 +22,7 @@
import com.doumee.dao.business.model.YwElectricalRoom;
import com.doumee.dao.business.model.YwElectricalWarning;
import com.doumee.dao.business.model.YwRoom;
+import com.doumee.dao.business.vo.WarningTypeStatVO;
import com.doumee.service.business.YwElectricalWarningService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.commons.lang3.StringUtils;
@@ -159,6 +160,38 @@
return list;
}
+ @Override
+ public List<WarningTypeStatVO> warningTypeStats() {
+ QueryWrapper<YwElectricalWarning> wrapper = new QueryWrapper<>();
+ wrapper.select("warning_def_id", "count(1) as cnt")
+ .eq("isdeleted", Constants.ZERO)
+ .eq("device_type", ELECTRICAL_DEVICE_TYPE)
+ .isNotNull("warning_def_id")
+ .groupBy("warning_def_id")
+ .orderByDesc("cnt");
+ List<Map<String, Object>> rows = ywElectricalWarningMapper.selectMaps(wrapper);
+ List<WarningTypeStatVO> list = new ArrayList<>();
+ if (CollectionUtils.isEmpty(rows)) {
+ return list;
+ }
+ for (Map<String, Object> row : rows) {
+ Object defIdObj = row.get("warning_def_id");
+ if (defIdObj == null) {
+ continue;
+ }
+ Integer warningDefId = Integer.parseInt(String.valueOf(defIdObj));
+ Object cntObj = row.get("cnt");
+ long count = cntObj == null ? 0L : Long.parseLong(String.valueOf(cntObj));
+ WarningTypeStatVO stat = new WarningTypeStatVO();
+ stat.setWarningDefId(warningDefId);
+ stat.setCount(count);
+ ElectronicConstant.warningDefId def = ElectronicConstant.warningDefId.getByKey(warningDefId);
+ stat.setWarningName(def != null ? def.getName() : "鏈煡鎶ヨ");
+ list.add(stat);
+ }
+ return list;
+ }
+
private MPJLambdaWrapper<YwElectricalWarning> buildPageQuery(PageWrap<YwElectricalWarning> pageWrap) {
MPJLambdaWrapper<YwElectricalWarning> queryWrapper = new MPJLambdaWrapper<>();
YwElectricalWarning model = pageWrap.getModel() == null ? new YwElectricalWarning() : pageWrap.getModel();
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwWorkDeskEnergyServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwWorkDeskEnergyServiceImpl.java
new file mode 100644
index 0000000..aa2d11c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwWorkDeskEnergyServiceImpl.java
@@ -0,0 +1,228 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.YwConditionerUsageMapper;
+import com.doumee.dao.business.YwElectricalDataMapper;
+import com.doumee.dao.business.model.YwConditionerUsage;
+import com.doumee.dao.business.model.YwElectricalData;
+import com.doumee.dao.business.vo.DailyEnergyStatVO;
+import com.doumee.service.business.YwWorkDeskEnergyService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class YwWorkDeskEnergyServiceImpl implements YwWorkDeskEnergyService {
+
+ private static final int DAYS = 30;
+ private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+ private static final String READ_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ @Autowired
+ private YwElectricalDataMapper ywElectricalDataMapper;
+
+ @Autowired
+ private YwConditionerUsageMapper ywConditionerUsageMapper;
+
+ @Override
+ public List<DailyEnergyStatVO> electricalDailyStats() {
+ LocalDate end = LocalDate.now();
+ LocalDate start = end.minusDays(DAYS - 1L);
+ LocalDate queryStart = start.minusDays(1L);
+ Map<String, DailyEnergyStatVO> bucket = initDailyBucket(start, end);
+
+ List<YwElectricalData> rows = ywElectricalDataMapper.selectList(new QueryWrapper<YwElectricalData>().lambda()
+ .eq(YwElectricalData::getIsdeleted, Constants.ZERO)
+ .ge(YwElectricalData::getCreateDate, java.sql.Date.valueOf(queryStart))
+ .le(YwElectricalData::getCreateDate, java.sql.Date.valueOf(end.plusDays(1))));
+
+ SimpleDateFormat dayFmt = new SimpleDateFormat("yyyy-MM-dd");
+ SimpleDateFormat readTimeFmt = new SimpleDateFormat(READ_TIME_PATTERN);
+ Map<String, Map<String, MeterDayReading>> meterDayLatest = new HashMap<>();
+
+ for (YwElectricalData row : rows) {
+ String meterKey = resolveMeterKey(row);
+ if (StringUtils.isBlank(meterKey)) {
+ continue;
+ }
+ String dayKey = resolveElectricalDayKey(row, dayFmt);
+ if (StringUtils.isBlank(dayKey)) {
+ continue;
+ }
+ BigDecimal totalEnergy = resolveTotalEnergy(row);
+ if (totalEnergy.compareTo(BigDecimal.ZERO) < 0) {
+ continue;
+ }
+ long readingTime = resolveReadingTime(row, readTimeFmt);
+ MeterDayReading snapshot = new MeterDayReading(totalEnergy, parseDecimal(row.getDqdj()), readingTime);
+ upsertLatestReading(meterDayLatest, meterKey, dayKey, snapshot);
+ }
+
+ for (LocalDate day = start; !day.isAfter(end); day = day.plusDays(1)) {
+ String todayKey = day.format(DATE_FMT);
+ String yesterdayKey = day.minusDays(1).format(DATE_FMT);
+ DailyEnergyStatVO stat = bucket.get(todayKey);
+ for (Map<String, MeterDayReading> dayReadings : meterDayLatest.values()) {
+ MeterDayReading todayReading = dayReadings.get(todayKey);
+ MeterDayReading yesterdayReading = dayReadings.get(yesterdayKey);
+ if (todayReading == null || yesterdayReading == null) {
+ continue;
+ }
+ BigDecimal usage = todayReading.totalEnergy.subtract(yesterdayReading.totalEnergy);
+ if (usage.compareTo(BigDecimal.ZERO) <= 0) {
+ continue;
+ }
+ stat.setTotalKwh(stat.getTotalKwh().add(usage));
+ stat.setTotalFee(stat.getTotalFee().add(usage.multiply(todayReading.price)));
+ }
+ }
+ return normalizeBucket(bucket);
+ }
+
+ @Override
+ public List<DailyEnergyStatVO> conditionerDailyStats() {
+ LocalDate end = LocalDate.now();
+ LocalDate start = end.minusDays(DAYS - 1L);
+ Map<String, DailyEnergyStatVO> bucket = initDailyBucket(start, end);
+
+ List<YwConditionerUsage> rows = ywConditionerUsageMapper.selectList(new QueryWrapper<YwConditionerUsage>().lambda()
+ .eq(YwConditionerUsage::getIsdeleted, Constants.ZERO)
+ .ge(YwConditionerUsage::getUsageDate, java.sql.Date.valueOf(start))
+ .le(YwConditionerUsage::getUsageDate, java.sql.Date.valueOf(end)));
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ for (YwConditionerUsage row : rows) {
+ if (row.getUsageDate() == null) {
+ continue;
+ }
+ String dayKey = sdf.format(row.getUsageDate());
+ if (!bucket.containsKey(dayKey)) {
+ continue;
+ }
+ DailyEnergyStatVO stat = bucket.get(dayKey);
+ stat.setTotalKwh(stat.getTotalKwh().add(nullToZero(row.getSumDl())));
+ stat.setTotalFee(stat.getTotalFee().add(nullToZero(row.getSumDf())));
+ }
+ return normalizeBucket(bucket);
+ }
+
+ private static void upsertLatestReading(Map<String, Map<String, MeterDayReading>> meterDayLatest,
+ String meterKey,
+ String dayKey,
+ MeterDayReading snapshot) {
+ Map<String, MeterDayReading> dayMap = meterDayLatest.computeIfAbsent(meterKey, key -> new HashMap<>());
+ MeterDayReading existing = dayMap.get(dayKey);
+ if (existing == null || snapshot.readingTime >= existing.readingTime) {
+ dayMap.put(dayKey, snapshot);
+ }
+ }
+
+ private static String resolveMeterKey(YwElectricalData row) {
+ if (StringUtils.isNotBlank(row.getAddress())) {
+ return row.getAddress().trim();
+ }
+ if (StringUtils.isNotBlank(row.getDeviceId())) {
+ return "dev:" + row.getDeviceId().trim();
+ }
+ if (StringUtils.isNotBlank(row.getMid())) {
+ return "mid:" + row.getMid().trim();
+ }
+ return null;
+ }
+
+ private static BigDecimal resolveTotalEnergy(YwElectricalData row) {
+ BigDecimal total = parseDecimal(row.getZhygzdl());
+ if (total.compareTo(BigDecimal.ZERO) <= 0) {
+ total = parseDecimal(row.getZyje());
+ }
+ return total;
+ }
+
+ private static long resolveReadingTime(YwElectricalData row, SimpleDateFormat readTimeFmt) {
+ if (StringUtils.isNotBlank(row.getAddTime())) {
+ String addTime = row.getAddTime().trim();
+ if (addTime.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
+ try {
+ return readTimeFmt.parse(addTime).getTime();
+ } catch (ParseException ignored) {
+ // fallback to createDate
+ }
+ }
+ }
+ Date createDate = row.getCreateDate();
+ return createDate == null ? 0L : createDate.getTime();
+ }
+
+ private static List<DailyEnergyStatVO> normalizeBucket(Map<String, DailyEnergyStatVO> bucket) {
+ List<DailyEnergyStatVO> list = new ArrayList<>(bucket.values());
+ for (DailyEnergyStatVO stat : list) {
+ stat.setTotalKwh(stat.getTotalKwh().setScale(2, RoundingMode.HALF_UP));
+ stat.setTotalFee(stat.getTotalFee().setScale(2, RoundingMode.HALF_UP));
+ }
+ return list;
+ }
+
+ private static Map<String, DailyEnergyStatVO> initDailyBucket(LocalDate start, LocalDate end) {
+ Map<String, DailyEnergyStatVO> bucket = new LinkedHashMap<>();
+ for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
+ DailyEnergyStatVO vo = new DailyEnergyStatVO();
+ vo.setStatDate(d.format(DATE_FMT));
+ vo.setTotalKwh(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
+ vo.setTotalFee(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
+ bucket.put(vo.getStatDate(), vo);
+ }
+ return bucket;
+ }
+
+ private static String resolveElectricalDayKey(YwElectricalData row, SimpleDateFormat sdf) {
+ if (StringUtils.isNotBlank(row.getAddTime())) {
+ String addTime = row.getAddTime().trim();
+ if (addTime.length() >= 10) {
+ return addTime.substring(0, 10);
+ }
+ }
+ Date createDate = row.getCreateDate();
+ return createDate == null ? null : sdf.format(createDate);
+ }
+
+ private static BigDecimal parseDecimal(String val) {
+ if (StringUtils.isBlank(val)) {
+ return BigDecimal.ZERO;
+ }
+ try {
+ return new BigDecimal(val.trim());
+ } catch (NumberFormatException e) {
+ return BigDecimal.ZERO;
+ }
+ }
+
+ private static BigDecimal nullToZero(BigDecimal val) {
+ return val == null ? BigDecimal.ZERO : val;
+ }
+
+ private static class MeterDayReading {
+ private final BigDecimal totalEnergy;
+ private final BigDecimal price;
+ private final long readingTime;
+
+ private MeterDayReading(BigDecimal totalEnergy, BigDecimal price, long readingTime) {
+ this.totalEnergy = totalEnergy;
+ this.price = price;
+ this.readingTime = readingTime;
+ }
+ }
+}
--
Gitblit v1.9.3