From 74190ebc24e6e850d418ad0ce041fd91b795c23e Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期一, 25 五月 2026 18:32:21 +0800
Subject: [PATCH] 新增智能电表、空调管理
---
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalData.java | 25
admin/src/views/business/components/YwElectricalRemote.vue | 112 +
server/db/business.yw_electrical_data.menu.sql | 34
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicConstant.java | 25
admin/src/views/business/ywelectrical.vue | 65
server/db/business.yw_electrical.menu.sql | 79 +
server/db/business.yw_electrical_charge.permissions.sql | 2
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/ElectronicToolUtil.java | 93 +
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalActionsService.java | 13
admin/src/views/business/ywelectricalcharge.vue | 114 +
admin/src/views/business/ywelectricaldata.vue | 238 +++
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectrical.java | 5
server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java | 6
server/db/business.yw_electrical_actions.menu.sql | 30
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalDataService.java | 5
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalDataCloudController.java | 10
server/db/business.yw_electrical_actions.permissions.sql | 1
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java | 57
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalEditDTO.java | 14
server/visits/admin_timer/src/main/java/com/doumee/job/ElectricalScheduleJob.java | 48
admin/src/api/business/ywelectrical.js | 2
admin/src/views/business/ywelectricalactions.vue | 176 ++
server/db/business.yw_electrical_data.permissions.sql | 3
server/db/business.yw_electrical_param.permissions.sql | 5
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java | 16
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalActionsCloudController.java | 37
server/system_service/src/main/java/com/doumee/core/utils/Constants.java | 7
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ElectronicConfigService.java | 52
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwElectricalActionsMapper.java | 9
server/db/yw_electrical.electrical_param_id.sql | 3
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java | 53
server/system_service/src/main/java/com/doumee/core/utils/ImageBase64Util.java | 6
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalOperateDTO.java | 15
admin/src/views/business/components/YwElectricalParamEdit.vue | 598 +++++++++
server/db/yw_electrical_actions.sql | 21
admin/src/api/business/ywelectricaldata.js | 15
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicNotifyStatus.java | 55
admin/src/api/business/ywelectricalcharge.js | 11
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalBizService.java | 34
server/db/business.yw_electrical_param.menu.sql | 36
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalServiceImpl.java | 8
server/db/electrical_param.dict.sql | 49
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalWarningServiceImpl.java | 40
admin/src/views/business/components/YwElectricalEdit.vue | 199 +++
admin/src/api/business/ywelectricalparam.js | 31
admin/src/views/business/components/AccountRechargePanel.vue | 82 +
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalDataServiceImpl.java | 150 +-
server/system_service/src/main/java/com/doumee/core/utils/FtpUtil.java | 1
admin/src/api/business/ywelectricalactions.js | 7
server/db/ELECTRICAL_INTEGRATION.md | 172 ++
server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleReadRequest.java | 2
admin/src/views/business/ywelectricalparam.vue | 136 ++
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java | 939 ++++++++++++++
53 files changed, 3,819 insertions(+), 127 deletions(-)
diff --git a/admin/src/api/business/ywelectrical.js b/admin/src/api/business/ywelectrical.js
index f51e909..4456f9e 100644
--- a/admin/src/api/business/ywelectrical.js
+++ b/admin/src/api/business/ywelectrical.js
@@ -34,6 +34,6 @@
return request.post('/visitsAdmin/cloudService/business/ywElectricalParam/page', data || {
page: 1,
capacity: 500,
- model: { isdeleted: 0 }
+ model: {}
}, { trim: true })
}
diff --git a/admin/src/api/business/ywelectricalactions.js b/admin/src/api/business/ywelectricalactions.js
new file mode 100644
index 0000000..b1c430c
--- /dev/null
+++ b/admin/src/api/business/ywelectricalactions.js
@@ -0,0 +1,7 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywElectricalActions'
+
+export function fetchList (data) {
+ return request.post(base + '/page', data, { trim: true })
+}
diff --git a/admin/src/api/business/ywelectricalcharge.js b/admin/src/api/business/ywelectricalcharge.js
new file mode 100644
index 0000000..04229fa
--- /dev/null
+++ b/admin/src/api/business/ywelectricalcharge.js
@@ -0,0 +1,11 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywElectricalCharge'
+
+export function fetchList (data) {
+ return request.post(base + '/page', data, { trim: true })
+}
+
+export function exportExcel (data) {
+ return request.post(base + '/exportExcel', data, { trim: true, download: true })
+}
diff --git a/admin/src/api/business/ywelectricaldata.js b/admin/src/api/business/ywelectricaldata.js
new file mode 100644
index 0000000..93bde78
--- /dev/null
+++ b/admin/src/api/business/ywelectricaldata.js
@@ -0,0 +1,15 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywElectricalData'
+
+export function fetchList (data) {
+ return request.post(base + '/page', data, { trim: true })
+}
+
+export function exportExcel (data) {
+ return request.post(base + '/exportExcel', data, { trim: true, download: true })
+}
+
+export function syncAll (data) {
+ return request.post(base + '/syncAll', data || {})
+}
diff --git a/admin/src/api/business/ywelectricalparam.js b/admin/src/api/business/ywelectricalparam.js
new file mode 100644
index 0000000..4f8bab4
--- /dev/null
+++ b/admin/src/api/business/ywelectricalparam.js
@@ -0,0 +1,31 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywElectricalParam'
+
+export function fetchList (data) {
+ return request.post(base + '/page', data, { trim: true })
+}
+
+export function exportExcel (data) {
+ return request.post(base + '/exportExcel', data, { trim: true, download: true })
+}
+
+export function create (data) {
+ return request.post(base + '/create', data)
+}
+
+export function getInfoById (id) {
+ return request.get(base + '/' + id)
+}
+
+export function updateById (data) {
+ return request.post(base + '/updateById', data)
+}
+
+export function deleteById (id) {
+ return request.get(base + '/delete/' + id)
+}
+
+export function deleteByIdInBatch (ids) {
+ return request.get(base + '/delete/batch', { params: { ids } })
+}
diff --git a/admin/src/views/business/components/AccountRechargePanel.vue b/admin/src/views/business/components/AccountRechargePanel.vue
new file mode 100644
index 0000000..262f11d
--- /dev/null
+++ b/admin/src/views/business/components/AccountRechargePanel.vue
@@ -0,0 +1,82 @@
+<template>
+ <div>
+ <div class="info-block">
+ <p>閲囬泦鍣ㄥ彿锛歿{ info.collectorId }} <span :class="info.online === 1 ? 'green' : 'red'">{{ info.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span></p>
+ <p>琛ㄥ湴鍧�锛歿{ info.address }}</p>
+ <p>琛ㄥ悕绉帮細{{ info.name }}</p>
+ <p>鐢佃〃绫诲瀷锛歿{ info.type }}</p>
+ <p>鍏宠仈鎴块棿锛歿{ info.roomNames }}</p>
+ <p>鐢佃〃鐘舵�侊細{{ info.accountStatus === 1 ? '宸插紑鎴�' : '鏈紑鎴�' }}</p>
+ <p v-if="mode === 'recharge'">璐數娆℃暟锛歿{ purchaseCount }}</p>
+ </div>
+ <el-form label-width="150px">
+ <el-form-item :label="mode === 'open' ? '寮�鎴烽噾棰�' : '鍏呭�奸噾棰�'">
+ <el-input-number v-model="form.money" :min="0" :precision="4" style="width: 200px"/>
+ </el-form-item>
+ <el-form-item :label="mode === 'open' ? '寮�鎴峰娉�' : '鍏呭�煎娉�'">
+ <el-input v-model="form.remark" :placeholder="mode === 'open' ? '寮�鎴峰娉紝鏈�澶�50涓瓧绗�' : '鍏呭�煎娉紝鏈�澶�300涓瓧绗�'" :maxlength="mode === 'open' ? 50 : 300" style="width: 400px"/>
+ </el-form-item>
+ <el-form-item label="褰撳墠鎬荤數閲�">
+ <span class="meter-read-row">
+ <span class="meter-read-value">{{ totalPower }}</span>
+ <el-button type="primary" size="small" class="read-meter-btn" :loading="isOperating" @click="$emit('read')">绔嬪嵆鎶勮〃骞舵洿鏂扮敤閲忎綑棰�</el-button>
+ </span>
+ </el-form-item>
+ <el-form-item label="鐢甸噺鍚屾鏃堕棿">{{ latest && latest.addTime ? latest.addTime : '-' }}</el-form-item>
+ <el-form-item label="褰撳墠鍓╀綑閲戦(鍏�)">
+ <span class="meter-read-row">
+ <span class="meter-read-value">{{ latest && latest.ye != null ? latest.ye : '0.0000' }}</span>
+ <el-button type="primary" size="small" class="read-meter-btn" :loading="isOperating" @click="$emit('read')">绔嬪嵆鎶勮〃骞舵洿鏂扮敤閲忎綑棰�</el-button>
+ </span>
+ </el-form-item>
+ <el-form-item label="鍓╀綑閲戦鍚屾鏃堕棿">{{ syncTime }}</el-form-item>
+ </el-form>
+ <div class="footer-btns">
+ <el-button type="primary" :loading="isOperating" @click="$emit('confirm')">{{ mode === 'open' ? '纭寮�鎴�' : '纭鍏呭��' }}</el-button>
+ </div>
+ </div>
+</template>
+
+<script>
+export default {
+ name: 'AccountRechargePanel',
+ props: {
+ info: { type: Object, default: () => ({}) },
+ latest: { type: Object, default: null },
+ form: { type: Object, required: true },
+ isOperating: Boolean,
+ mode: { type: String, default: 'open' },
+ purchaseCount: { type: String, default: '0' }
+ },
+ computed: {
+ totalPower () {
+ if (!this.latest) return '0.00kWh'
+ const v = this.latest.zhygzdl || '0'
+ return String(v).toLowerCase().indexOf('kwh') >= 0 ? v : v + 'kWh'
+ },
+ syncTime () {
+ return (this.latest && this.latest.addTime) ? this.latest.addTime : (this.info.balanceTime || '-')
+ }
+ }
+}
+</script>
+
+<style scoped>
+.info-block p { margin: 4px 0; line-height: 26px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.footer-btns { text-align: right; margin-top: 16px; }
+.meter-read-row {
+ display: inline-flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ max-width: 100%;
+}
+.meter-read-value { margin-right: 12px; flex-shrink: 0; }
+.read-meter-btn {
+ flex-shrink: 0;
+ width: auto;
+ white-space: nowrap;
+}
+</style>
diff --git a/admin/src/views/business/components/YwElectricalEdit.vue b/admin/src/views/business/components/YwElectricalEdit.vue
new file mode 100644
index 0000000..8522326
--- /dev/null
+++ b/admin/src/views/business/components/YwElectricalEdit.vue
@@ -0,0 +1,199 @@
+<template>
+ <GlobalWindow title="缂栬緫鐢佃〃" :visible.sync="visible" width="920px" :confirm-working="isWorking" @confirm="confirm">
+ <el-form ref="form" :model="form" :rules="rules" label-width="120px" class="electrical-edit-form">
+ <el-form-item label="閲囬泦鍣ㄥ彿">
+ <span>{{ form.collectorId }}</span>
+ <span :class="form.online === 1 ? 'green' : 'red'" style="margin-left: 12px">{{ form.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span>
+ </el-form-item>
+ <el-form-item label="琛ㄥ湴鍧�">{{ form.address }}</el-form-item>
+ <el-form-item label="鐢佃〃绫诲瀷">{{ form.type || '-' }}</el-form-item>
+ <el-form-item label="缁х數鍣�">
+ <span v-if="form.relayStatus === '0' || form.relayStatus === 0" class="red">鏂紑</span>
+ <span v-else-if="form.relayStatus === '1' || form.relayStatus === 1" class="green">闂悎</span>
+ <span v-else>-</span>
+ </el-form-item>
+ <el-form-item label="鐢佃〃鍚嶇О">
+ <span>{{ form.name || '-' }}</span>
+ </el-form-item>
+ <el-form-item label="鐢佃〃鍙傛暟" prop="electricalParamId">
+ <el-select v-model="form.electricalParamId" clearable placeholder="璇烽�夋嫨鐢佃〃鍙傛暟" style="width: 400px">
+ <el-option v-for="p in paramList" :key="p.id" :label="p.name" :value="p.id"></el-option>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鐢佃〃鍊嶇巼" prop="rate">
+ <el-input-number v-model="form.rate" :min="0.01" :precision="2" :step="0.1" style="width: 400px"/>
+ <div class="form-tip">閽堝浜掓劅琛ㄩ渶瑕侀厤缃紝鍏跺畠鍗曠浉琛ㄩ粯璁ら厤缃�1鍗冲彲</div>
+ </el-form-item>
+ <el-form-item label="璇烽�夋嫨鎴挎簮">
+ <el-tree
+ ref="roomTree"
+ :data="houseList"
+ show-checkbox
+ node-key="idd"
+ :props="{ children: 'projectDataVOList', label: 'name' }"
+ :default-checked-keys="checkedKeys"
+ @check="onRoomCheck"
+ style="max-height: 320px; overflow: auto; width: 400px; border: 1px solid #dcdfe6; padding: 8px;"
+ />
+ </el-form-item>
+ <el-form-item label="宸查�夋埧婧�">
+ <div class="selected-room-list">
+ <template v-if="selectedRooms.length">
+ <el-tag
+ v-for="room in selectedRooms"
+ :key="room.idd"
+ closable
+ class="selected-room-tag"
+ @close="removeSelectedRoom(room)"
+ >{{ room.roomPath }}</el-tag>
+ </template>
+ <span v-else class="selected-room-empty">-</span>
+ </div>
+ </el-form-item>
+ </el-form>
+ </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import { tree } from '@/api/project/ywProject'
+import { getDetail, saveDetail, fetchParamList } from '@/api/business/ywelectrical'
+
+export default {
+ name: 'YwElectricalEdit',
+ components: { GlobalWindow },
+ data () {
+ return {
+ visible: false,
+ isWorking: false,
+ form: {},
+ paramList: [],
+ houseList: [],
+ checkedKeys: [],
+ selectedRooms: [],
+ rules: {
+ }
+ }
+ },
+ methods: {
+ open (row) {
+ this.visible = true
+ this.checkedKeys = []
+ this.selectedRooms = []
+ this.loadParams()
+ this.loadHouseTree()
+ getDetail(row.id).then(data => {
+ this.form = { ...data, rate: data.rate != null ? Number(data.rate) : 1 }
+ if (data.roomIds && data.roomIds.length) {
+ this.checkedKeys = data.roomIds.map(id => '3-' + id)
+ this.$nextTick(() => {
+ if (this.$refs.roomTree) {
+ this.$refs.roomTree.setCheckedKeys(this.checkedKeys)
+ this.syncSelectedRoomsFromTree()
+ }
+ })
+ }
+ }).catch(e => this.$tip.apiFailed(e))
+ },
+ loadParams () {
+ fetchParamList().then(res => {
+ this.paramList = (res && res.records) ? res.records : []
+ }).catch(() => {})
+ },
+ loadHouseTree () {
+ tree({}).then(res => {
+ this.markTreeNodes(res || [])
+ this.houseList = res || []
+ this.$nextTick(() => {
+ if (this.checkedKeys.length && this.$refs.roomTree) {
+ this.$refs.roomTree.setCheckedKeys(this.checkedKeys)
+ this.syncSelectedRoomsFromTree()
+ }
+ })
+ })
+ },
+ markTreeNodes (arr, ancestors) {
+ if (!arr) return
+ arr.forEach(node => {
+ node.idd = node.lv + '-' + node.id
+ node.disabled = node.lv !== 3
+ const names = (ancestors || []).concat(node.name)
+ if (node.lv === 3) {
+ node.roomPath = names.length >= 3 ? names.slice(-3).join('/') : names.join('/')
+ }
+ if (node.projectDataVOList && node.projectDataVOList.length) {
+ this.markTreeNodes(node.projectDataVOList, names)
+ }
+ })
+ },
+ syncSelectedRoomsFromTree () {
+ const tree = this.$refs.roomTree
+ if (!tree) {
+ this.selectedRooms = []
+ return
+ }
+ const nodes = tree.getCheckedNodes(true).filter(n => n.lv === 3)
+ this.selectedRooms = nodes.map(n => ({
+ id: n.id,
+ idd: n.idd,
+ roomPath: n.roomPath || n.name
+ }))
+ this.form.roomIds = nodes.map(n => n.id)
+ this.checkedKeys = nodes.map(n => n.idd)
+ },
+ removeSelectedRoom (room) {
+ const tree = this.$refs.roomTree
+ if (!tree || !room) return
+ tree.setChecked(room.idd, false, true)
+ this.syncSelectedRoomsFromTree()
+ },
+ onRoomCheck () {
+ this.syncSelectedRoomsFromTree()
+ },
+ confirm () {
+ this.$refs.form.validate(valid => {
+ if (!valid) return
+ this.isWorking = true
+ saveDetail({
+ id: this.form.id,
+ electricalParamId: this.form.electricalParamId,
+ rate: this.form.rate,
+ roomIds: this.form.roomIds || []
+ })
+ .then(() => {
+ this.$tip.success('淇濆瓨鎴愬姛')
+ this.visible = false
+ this.$emit('success')
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.isWorking = false })
+ })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.form-tip { font-size: 12px; color: #909399; margin-top: 4px; }
+.selected-room-list {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ max-width: 560px;
+ min-height: 32px;
+ gap: 8px;
+}
+.selected-room-tag {
+ max-width: 100%;
+ white-space: normal;
+ height: auto;
+ line-height: 1.5;
+ padding-top: 4px;
+ padding-bottom: 4px;
+}
+.selected-room-empty {
+ color: #909399;
+}
+</style>
diff --git a/admin/src/views/business/components/YwElectricalParamEdit.vue b/admin/src/views/business/components/YwElectricalParamEdit.vue
new file mode 100644
index 0000000..642df5f
--- /dev/null
+++ b/admin/src/views/business/components/YwElectricalParamEdit.vue
@@ -0,0 +1,598 @@
+<template>
+ <GlobalWindow
+ :title="title"
+ :visible.sync="visible"
+ :confirm-working="isWorking"
+ width="920px"
+ @confirm="confirm"
+ @close="onClose"
+ >
+ <el-form
+ v-loading="detailLoading"
+ :model="form"
+ ref="form"
+ :rules="rules"
+ label-width="150px"
+ class="electrical-param-form"
+ >
+ <el-form-item label="閰嶇疆鍚嶇О" prop="name">
+ <el-input
+ v-model="form.name"
+ class="param-input"
+ maxlength="100"
+ placeholder="璇疯緭鍏ラ厤缃悕绉�"
+ clearable
+ v-trim
+ />
+ </el-form-item>
+
+ <el-form-item label="閫忔敮閲戦" prop="tzMoney">
+ <div class="param-field">
+ <div class="param-field__row">
+ <el-input-number
+ v-model="form.tzMoney"
+ class="param-input-number param-input-number--wide"
+ :min="0"
+ :max="190000"
+ :precision="2"
+ controls-position="right"
+ placeholder="0 - 190000"
+ />
+ <span class="param-field__unit">鍏�</span>
+ </div>
+ <p class="param-field__tip">鐢佃〃鏈�澶у彲<span class="param-tip-highlight">閫忔敮</span>閲戦锛岃缃悗鍏佽璐熸暟鍓╀綑閲戦</p>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="涓�绾ф姤璀﹂噾棰�" prop="yjbjMoney">
+ <div class="param-field">
+ <div class="param-field__row">
+ <el-input-number
+ v-model="form.yjbjMoney"
+ class="param-input-number"
+ :min="0"
+ :precision="2"
+ controls-position="right"
+ placeholder="0"
+ />
+ <span class="param-field__unit">鍏�</span>
+ </div>
+ <p class="param-field__tip">浣庝簬璁剧疆鍊煎睆骞曟樉绀烘彁绀�</p>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="浜岀骇鎶ヨ閲戦" prop="ejbjMoney">
+ <div class="param-field">
+ <div class="param-field__row">
+ <el-input-number
+ v-model="form.ejbjMoney"
+ class="param-input-number"
+ :min="0"
+ :precision="2"
+ controls-position="right"
+ placeholder="0"
+ />
+ <span class="param-field__unit">鍏�</span>
+ </div>
+ <p class="param-field__tip">浣庝簬璁剧疆鍊奸棯鐏厜鎶ヨ鎻愮ず</p>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="璐熻嵎闄愬埗鍔熺巼" prop="limitFhgl">
+ <div class="param-field">
+ <div class="param-field__row">
+ <el-input-number
+ v-model="form.limitFhgl"
+ class="param-input-number"
+ :min="0"
+ :max="80"
+ :precision="4"
+ controls-position="right"
+ placeholder="0 - 80"
+ />
+ <span class="param-field__unit param-field__unit--suffix">(4浣嶅皬鏁�) KW</span>
+ </div>
+ <div class="param-field__info">
+ <p class="param-field__info-title">鍚勭瑙勬牸琛ㄦ渶澶у彲璁剧疆鐨勫姛鐜囷細</p>
+ <ul class="param-field__info-list">
+ <li>
+ <strong>鍗曠浉琛�</strong>锛氭寜瑙勬牸鏈�澶у畨鍩规暟 脳 鐢靛帇銆備緥濡� 10(40)A 琛ㄥ彲璁剧疆鏈�澶у姛鐜囦负 40A 脳 220V = <em>8.8KW</em>
+ </li>
+ <li>
+ <strong>涓夌浉琛�</strong>锛氭寜鏈�澶ц鏍肩數娴� 脳 220V 脳 3銆備緥濡� 10(40)A 琛ㄥ彲璁剧疆鏈�澶у姛鐜囦负 40A 脳 220V 脳 3 = <em>26.4KW</em>
+ </li>
+ <li>
+ <strong>涓夌浉浜掓劅寮忚〃</strong>锛氭棤闇�鍐嶄箻浜掓劅姣斻�備緥濡傛敮鎸佹渶澶х數娴� 5A锛屽彲璁剧疆鏈�澶у姛鐜囦负 5A 脳 220V 脳 3 = <em>3.3KW</em>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="瓒呰礋鑽峰欢鏃堕棿" prop="limitFhTime">
+ <div class="param-field__row">
+ <el-input-number
+ v-model="form.limitFhTime"
+ class="param-input-number"
+ :min="0"
+ :max="99"
+ :precision="0"
+ controls-position="right"
+ placeholder="0 - 99"
+ />
+ <span class="param-field__unit">鍒嗛挓</span>
+ </div>
+ </el-form-item>
+
+ <!-- 鐢典环锛欰PP 灞曠ず涓鸿绠楃粨鏋滐紝闈炲繀濉緭鍏� -->
+ <el-form-item label="鐢典环" class="price-section">
+ <template slot="label">
+ <span>鐢典环</span>
+ <el-tooltip content="鐢典环 = 鍩虹浠� + 闄勫姞鍗曚环锛屼繚瀛樺悗鍐欏叆鍙傛暟妗f" placement="top">
+ <i class="el-icon-question price-section__help"/>
+ </el-tooltip>
+ </template>
+ <div class="param-field">
+ <div class="price-display">
+ <span class="price-display__value">{{ displayPriceText }}</span>
+ <span class="param-field__unit param-field__unit--suffix">(4浣嶅皬鏁�) 鍏�/KWH</span>
+ <span class="price-display__tag">APP绔睍绀虹數浠�</span>
+ </div>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="" prop="extraPrice" class="price-formula-item">
+ <div class="price-formula">
+ <span class="price-formula__eq">= 鍩虹浠�*</span>
+ <el-input-number
+ v-model="form.basePrice"
+ class="param-input-number param-input-number--formula"
+ :min="0"
+ :precision="4"
+ controls-position="right"
+ placeholder="0"
+ @change="onPricePartChange"
+ />
+ <span class="price-formula__unit">鍏�/KWH + 闄勫姞鍗曚环*</span>
+ <el-input-number
+ v-model="form.extraPrice"
+ class="param-input-number param-input-number--formula"
+ :min="0"
+ :precision="4"
+ controls-position="right"
+ placeholder="0"
+ @change="onPricePartChange"
+ />
+ <span class="price-formula__unit">鍏�/KWH</span>
+ </div>
+ </el-form-item>
+
+ <el-form-item label="闄勫姞浠锋牸缁勬垚璇存槑" prop="remark">
+ <el-input
+ v-model="form.remark"
+ class="param-textarea"
+ type="textarea"
+ :rows="4"
+ maxlength="500"
+ placeholder="闄勫姞浠锋牸缁勬垚璇存槑"
+ />
+ </el-form-item>
+ </el-form>
+ </GlobalWindow>
+</template>
+
+<script>
+import BaseOpera from '@/components/base/BaseOpera'
+import GlobalWindow from '@/components/common/GlobalWindow'
+import * as paramApi from '@/api/business/ywelectricalparam'
+
+const numRequired = (label) => [
+ {
+ validator (rule, value, callback) {
+ if (value === undefined || value === null || value === '') {
+ callback(new Error('璇峰~鍐�' + label))
+ } else {
+ callback()
+ }
+ },
+ trigger: ['blur', 'change']
+ }
+]
+
+const PARAM_FORM_DEFAULTS = {
+ id: null,
+ name: '',
+ tzMoney: 0,
+ yjbjMoney: 0,
+ ejbjMoney: 0,
+ limitFhgl: 0,
+ limitFhTime: 0,
+ basePrice: 1,
+ extraPrice: 0,
+ price: 1,
+ status: 0,
+ remark: ''
+}
+
+const PARAM_NUMERIC_KEYS = ['tzMoney', 'yjbjMoney', 'ejbjMoney', 'limitFhgl', 'limitFhTime', 'basePrice', 'extraPrice', 'price']
+
+const hasValue = (v) => v !== null && v !== undefined && v !== ''
+
+const toNumber = (v) => {
+ if (!hasValue(v)) return null
+ const n = Number(v)
+ return isNaN(n) ? null : n
+}
+
+export default {
+ name: 'YwElectricalParamEdit',
+ extends: BaseOpera,
+ components: { GlobalWindow },
+ data () {
+ return {
+ title: '',
+ visible: false,
+ api: paramApi,
+ configData: {
+ 'field.id': 'id'
+ },
+ isWorking: false,
+ detailLoading: false,
+ form: { ...PARAM_FORM_DEFAULTS },
+ rules: {
+ name: [{ required: true, message: '璇疯緭鍏ュ弬鏁拌缃悕绉�', trigger: 'blur' }],
+ tzMoney: numRequired('閫忔敮閲戦'),
+ yjbjMoney: numRequired('涓�绾ф姤璀﹂噾棰�'),
+ ejbjMoney: numRequired('浜岀骇鎶ヨ閲戦'),
+ limitFhgl: numRequired('璐熻嵎闄愬埗鍔熺巼'),
+ limitFhTime: numRequired('瓒呰礋鑽峰欢鏃堕棿')
+ }
+ }
+ },
+ computed: {
+ displayPrice () {
+ const base = Number(this.form.basePrice)
+ const extra = Number(this.form.extraPrice)
+ if (isNaN(base) || isNaN(extra)) {
+ return ''
+ }
+ return (base + extra).toFixed(4)
+ },
+ displayPriceText () {
+ return this.displayPrice === '' ? '-' : this.displayPrice
+ }
+ },
+ created () {
+ this.config({
+ api: '/business/ywelectricalparam',
+ 'field.id': 'id'
+ })
+ if (!this.api) {
+ this.api = paramApi
+ }
+ this.$set(this.rules, 'extraPrice', [{ validator: this.validatePriceParts, trigger: ['blur', 'change'] }])
+ },
+ methods: {
+ /** 灏嗗垪琛ㄨ/鎺ュ彛鏁版嵁鏄犲皠涓鸿〃鍗曪紙缂栬緫鏃朵繚鐣欏師濮嬩笟鍔″�硷級 */
+ mapApiToForm (raw, { isNew = false } = {}) {
+ if (isNew || raw == null) {
+ return { ...PARAM_FORM_DEFAULTS }
+ }
+ const merged = { ...PARAM_FORM_DEFAULTS }
+ Object.keys(PARAM_FORM_DEFAULTS).forEach((key) => {
+ if (Object.prototype.hasOwnProperty.call(raw, key)) {
+ merged[key] = raw[key]
+ }
+ })
+
+ PARAM_NUMERIC_KEYS.forEach((key) => {
+ const n = toNumber(merged[key])
+ if (n !== null) {
+ merged[key] = key === 'limitFhTime' ? Math.round(n) : n
+ }
+ })
+
+ ;['tzMoney', 'yjbjMoney', 'ejbjMoney', 'limitFhgl', 'limitFhTime', 'extraPrice'].forEach((key) => {
+ if (!hasValue(merged[key])) {
+ merged[key] = 0
+ }
+ })
+
+ const priceNum = toNumber(raw.price) != null ? toNumber(raw.price) : toNumber(merged.price)
+ if (!hasValue(merged.basePrice) && !hasValue(merged.extraPrice) && priceNum != null) {
+ merged.basePrice = priceNum
+ merged.extraPrice = 0
+ } else {
+ if (!hasValue(merged.extraPrice)) {
+ merged.extraPrice = 0
+ }
+ if (!hasValue(merged.basePrice)) {
+ if (priceNum != null && hasValue(merged.extraPrice)) {
+ merged.basePrice = Number((priceNum - Number(merged.extraPrice)).toFixed(4))
+ } else if (priceNum != null) {
+ merged.basePrice = priceNum
+ merged.extraPrice = 0
+ } else {
+ merged.basePrice = 0
+ }
+ }
+ }
+
+ if (!hasValue(merged.name)) merged.name = raw.name || ''
+ if (merged.remark == null) merged.remark = raw.remark || ''
+ if (merged.status == null || merged.status === '') merged.status = raw.status != null ? raw.status : 0
+
+ if (toNumber(raw.price) != null) {
+ merged.price = toNumber(raw.price)
+ } else {
+ const base = Number(merged.basePrice) || 0
+ const extra = Number(merged.extraPrice) || 0
+ merged.price = Number((base + extra).toFixed(4))
+ }
+ return merged
+ },
+ applyForm (formData) {
+ this.form = formData
+ this.$nextTick(() => {
+ if (this.$refs.form) {
+ this.$refs.form.clearValidate()
+ }
+ })
+ },
+ validatePriceParts (rule, value, callback) {
+ const base = this.form.basePrice
+ const extra = this.form.extraPrice
+ if (base === undefined || base === null || base === '') {
+ callback(new Error('璇峰~鍐欏熀纭�浠�'))
+ } else if (extra === undefined || extra === null || extra === '') {
+ callback(new Error('璇峰~鍐欓檮鍔犲崟浠�'))
+ } else {
+ callback()
+ }
+ },
+ onPricePartChange () {
+ this.syncPrice()
+ this.$nextTick(() => {
+ if (this.$refs.form) {
+ this.$refs.form.validateField('extraPrice')
+ }
+ })
+ },
+ syncPrice () {
+ const base = Number(this.form.basePrice) || 0
+ const extra = Number(this.form.extraPrice) || 0
+ this.form.price = Number((base + extra).toFixed(4))
+ },
+ confirm () {
+ this.syncPrice()
+ this.form.status = this.form.status != null ? this.form.status : 0
+ if (this.form.id == null || this.form.id === '') {
+ this.__confirmCreate()
+ return
+ }
+ this.__confirmEdit()
+ },
+ onClose () {
+ this.isWorking = false
+ this.detailLoading = false
+ this.visible = false
+ this.$emit('close')
+ },
+ open (title, target) {
+ this.title = title
+ this.isWorking = false
+ this.detailLoading = false
+ if (target == null) {
+ this.visible = true
+ this.applyForm(this.mapApiToForm(null, { isNew: true }))
+ return
+ }
+ this.visible = true
+ // 浼樺厛鐢ㄥ垪琛ㄩ�変腑琛屽洖鏄撅紝涓庤〃鏍煎睍绀轰竴鑷�
+ this.applyForm(this.mapApiToForm(target, { isNew: false }))
+ const id = target.id
+ if (id == null || id === '') {
+ return
+ }
+ this.detailLoading = true
+ paramApi.getInfoById(id)
+ .then(data => {
+ if (data == null) {
+ return
+ }
+ // 鍒楄〃琛屽瓧娈典紭鍏堬紝閬垮厤鎺ュ彛缂哄瓧娈垫椂琚粯璁ゅ�艰鐩�
+ this.applyForm(this.mapApiToForm(Object.assign({}, data, target), { isNew: false }))
+ })
+ .catch(() => {})
+ .finally(() => { this.detailLoading = false })
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+.electrical-param-form {
+ padding: 8px 0 16px;
+ max-width: 680px;
+}
+
+.electrical-param-form ::v-deep .el-form-item {
+ margin-bottom: 20px;
+}
+
+.electrical-param-form ::v-deep .el-form-item__label {
+ color: #303133;
+ font-weight: 500;
+ line-height: 32px;
+}
+
+.electrical-param-form ::v-deep .el-form-item__content {
+ line-height: normal;
+}
+
+.electrical-param-form ::v-deep .el-form-item__content > .param-input,
+.electrical-param-form ::v-deep .el-form-item__content > .param-field,
+.electrical-param-form ::v-deep .el-form-item__content > .param-field__row,
+.electrical-param-form ::v-deep .el-form-item__content > .param-textarea,
+.electrical-param-form ::v-deep .el-form-item__content > .price-formula {
+ width: 100% !important;
+ max-width: 520px;
+}
+
+.param-input {
+ width: 400px !important;
+ max-width: 100%;
+}
+
+.param-input ::v-deep .el-input__inner {
+ height: 32px;
+ line-height: 32px;
+}
+
+.param-field__row {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.param-input-number {
+ width: 160px !important;
+ flex-shrink: 0;
+}
+
+.param-input-number--wide {
+ width: 200px !important;
+}
+
+.param-input-number--formula {
+ width: 120px !important;
+}
+
+.param-input-number ::v-deep .el-input {
+ width: 100% !important;
+}
+
+.param-input-number ::v-deep .el-input__inner {
+ height: 32px;
+ line-height: 32px;
+ text-align: left;
+}
+
+.param-textarea {
+ width: 400px !important;
+ max-width: 100%;
+}
+
+.param-textarea ::v-deep .el-textarea__inner {
+ min-height: 88px;
+ padding: 8px 12px;
+}
+
+.param-field__unit {
+ margin-left: 8px;
+ color: #606266;
+ font-size: 14px;
+ white-space: nowrap;
+}
+
+.param-field__unit--suffix {
+ margin-left: 6px;
+ color: #909399;
+ font-size: 12px;
+}
+
+.param-field__tip {
+ margin: 6px 0 0;
+ font-size: 12px;
+ color: #909399;
+ line-height: 1.6;
+ width: 100%;
+}
+
+.param-tip-highlight {
+ color: #f56c6c;
+ font-weight: 600;
+}
+
+.param-field__info {
+ margin-top: 10px;
+ padding: 10px 12px;
+ background: #f4f8fc;
+ border: 1px solid #e4ebf3;
+ border-radius: 4px;
+ max-width: 100%;
+}
+
+.param-field__info-title {
+ margin: 0 0 8px;
+ font-size: 12px;
+ color: #606266;
+ font-weight: 600;
+}
+
+.param-field__info-list {
+ margin: 0;
+ padding-left: 18px;
+ font-size: 12px;
+ color: #606266;
+ line-height: 1.75;
+}
+
+.param-field__info-list em {
+ font-style: normal;
+ color: #409eff;
+ font-weight: 600;
+}
+
+.price-section__help {
+ margin-left: 4px;
+ color: #909399;
+ cursor: pointer;
+}
+
+.price-display {
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+}
+
+.price-display__value {
+ font-size: 16px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 32px;
+ white-space: nowrap;
+}
+
+.price-display__tag {
+ margin-left: 12px;
+ font-size: 12px;
+ color: #909399;
+}
+
+.price-formula-item ::v-deep .el-form-item__content {
+ margin-left: 150px !important;
+}
+
+.price-formula {
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ gap: 6px 8px;
+ font-size: 14px;
+ color: #303133;
+ white-space: nowrap;
+ overflow-x: auto;
+}
+
+.price-formula__eq,
+.price-formula__unit {
+ flex-shrink: 0;
+ white-space: nowrap;
+ line-height: 32px;
+}
+
+.price-formula ::v-deep .param-input-number--formula {
+ flex-shrink: 0;
+}
+</style>
diff --git a/admin/src/views/business/components/YwElectricalRemote.vue b/admin/src/views/business/components/YwElectricalRemote.vue
new file mode 100644
index 0000000..6261d0e
--- /dev/null
+++ b/admin/src/views/business/components/YwElectricalRemote.vue
@@ -0,0 +1,112 @@
+<template>
+ <GlobalWindow title="鐢佃〃杩滅▼鎺у埗" :visible.sync="visible" width="780px" :show-confirm="false">
+ <el-tabs v-model="activeTab">
+ <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>
+ <p>琛ㄥ湴鍧�锛歿{ info.address }}</p>
+ <p>琛ㄥ悕绉帮細{{ info.name }}</p>
+ <p>鐢佃〃绫诲瀷锛歿{ info.type }}</p>
+ <p>鍏宠仈鎴块棿锛歿{ info.roomNames }}</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>
+ </el-tab-pane>
+ <el-tab-pane label="寮�鎴�" name="open">
+ <account-recharge-panel
+ :info="info"
+ :latest="latest"
+ :form="form"
+ :is-operating="isOperating"
+ mode="open"
+ @read="readMeter"
+ @confirm="doOperate('openAccount', '纭寮�鎴峰悧锛�')"
+ />
+ </el-tab-pane>
+ <el-tab-pane label="鍏呭��" name="recharge">
+ <account-recharge-panel
+ :info="info"
+ :latest="latest"
+ :form="form"
+ :is-operating="isOperating"
+ :purchase-count="purchaseCount"
+ mode="recharge"
+ @read="readMeter"
+ @confirm="doOperate('recharge', '纭鍏呭�煎悧锛�')"
+ />
+ </el-tab-pane>
+ </el-tabs>
+ </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import { getRemoteInfo, operate } from '@/api/business/ywelectrical'
+import AccountRechargePanel from './AccountRechargePanel'
+
+export default {
+ name: 'YwElectricalRemote',
+ components: { GlobalWindow, AccountRechargePanel },
+ data () {
+ return {
+ visible: false,
+ activeTab: 'basic',
+ electricalId: null,
+ info: {},
+ latest: null,
+ purchaseCount: '0',
+ form: { money: 0, remark: '' },
+ isOperating: false
+ }
+ },
+ methods: {
+ open (row, tab) {
+ this.electricalId = row.id
+ this.activeTab = tab || 'basic'
+ this.form = { money: 0, remark: '' }
+ this.visible = true
+ this.loadInfo()
+ },
+ loadInfo () {
+ getRemoteInfo(this.electricalId).then(res => {
+ this.info = res.electrical || {}
+ this.latest = res.latestData
+ this.purchaseCount = res.purchaseCount || '0'
+ }).catch(e => this.$tip.apiFailed(e))
+ },
+ readMeter () {
+ this.submitOperate('readMeter', true)
+ },
+ doOperate (action, msg) {
+ this.$dialog.actionConfirm(msg, '鎿嶄綔纭')
+ .then(() => this.submitOperate(action, false))
+ .catch(() => {})
+ },
+ submitOperate (action, silent) {
+ this.isOperating = true
+ operate({
+ electricalId: this.electricalId,
+ action,
+ money: this.form.money,
+ remark: this.form.remark
+ })
+ .then(res => {
+ this.$tip.apiSuccess(res || (silent ? '鎶勮〃璇锋眰宸叉彁浜�' : '鎻愪氦鎴愬姛锛岃鍦ㄣ�愭棩甯哥敤鐢电鐞�-鍏呭�艰褰曘�戜腑鏌ョ湅鍏呭�肩粨鏋�'))
+ this.loadInfo()
+ this.$emit('success')
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.isOperating = false })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.info-block { margin-bottom: 16px; line-height: 28px; color: #303133; }
+.btn-row .el-button { margin: 0 8px 8px 0; }
+</style>
diff --git a/admin/src/views/business/ywelectrical.vue b/admin/src/views/business/ywelectrical.vue
index 96658fb..6e4aa45 100644
--- a/admin/src/views/business/ywelectrical.vue
+++ b/admin/src/views/business/ywelectrical.vue
@@ -32,11 +32,26 @@
<el-table-column prop="name" label="鐢佃〃鍚嶇О" fixed min-width="140" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="address" label="鐢佃〃鍦板潃" min-width="130" align="center" show-overflow-tooltip></el-table-column>
<el-table-column prop="accountId" label="寮�鎴峰彿" min-width="100" align="center" show-overflow-tooltip></el-table-column>
+ <el-table-column label="寮�鎴风姸鎬�" min-width="100" align="center">
+ <template slot-scope="{ row }">
+ <span :class="row.accountStatus === 1 ? 'green' : 'red'">{{ formatAccountStatus(row.accountStatus) }}</span>
+ </template>
+ </el-table-column>
<el-table-column prop="roomNames" label="缁戝畾鎴块棿" min-width="160" align="center" show-overflow-tooltip></el-table-column>
+ <el-table-column prop="paramName" label="鐢佃〃鍙傛暟鍚�" min-width="140" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.paramName || '-' }}</template>
+ </el-table-column>
+ <el-table-column label="鐢佃〃鍊嶇巼" min-width="100" align="center">
+ <template slot-scope="{ row }">{{ formatRate(row.rate) }}</template>
+ </el-table-column>
<el-table-column prop="balanceBattery" label="绱鐢ㄧ數閲�" min-width="120" align="center">
<template slot-scope="{ row }">{{ formatBattery(row.balanceBattery) }}</template>
</el-table-column>
- <el-table-column prop="balance" label="璐︽埛浣欓" min-width="100" align="center"></el-table-column>
+ <el-table-column label="璐︽埛浣欓" min-width="110" align="center">
+ <template slot-scope="{ row }">
+ <span :class="{ red: isBalanceLow(row.balance) }">{{ formatBalance(row.balance) }}</span>
+ </template>
+ </el-table-column>
<el-table-column prop="createDate" label="鍒涘缓鏃堕棿" min-width="160" align="center"></el-table-column>
<el-table-column label="鍦ㄧ嚎鐘舵��" min-width="90" align="center">
<template slot-scope="{ row }">
@@ -50,9 +65,16 @@
<span v-else>-</span>
</template>
</el-table-column>
- <el-table-column label="棰勮鎯呭喌" min-width="160" align="center" show-overflow-tooltip>
+ <el-table-column label="棰勮鎯呭喌" min-width="200" align="center">
<template slot-scope="{ row }">
- <span class="red">{{ row.warnTypeName || row.warnType || '-' }}</span>
+ <template v-if="warnTypeLabels(row).length">
+ <span
+ v-for="(label, index) in warnTypeLabels(row)"
+ :key="index"
+ class="warn-tag"
+ >{{ label }}</span>
+ </template>
+ <span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="鎿嶄綔" align="center" min-width="220" fixed="right">
@@ -135,6 +157,32 @@
if (!val) return '-'
return String(val).indexOf('kwh') >= 0 || String(val).indexOf('kWh') >= 0 ? val : val + 'kwh'
},
+ formatRate (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ const n = Number(val)
+ return isNaN(n) ? val : n.toFixed(2)
+ },
+ formatBalance (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ const n = Number(val)
+ if (isNaN(n)) return val
+ return `${n.toFixed(2)}鍏僠
+ },
+ isBalanceLow (val) {
+ if (val === null || val === undefined || val === '') return false
+ const n = Number(val)
+ return !isNaN(n) && n <= 0
+ },
+ warnTypeLabels (row) {
+ const text = row.warnTypeName || row.warnType || ''
+ if (!text) return []
+ return text.split(',').map(item => item.trim()).filter(Boolean)
+ },
+ formatAccountStatus (val) {
+ if (val === 1 || val === '1') return '宸插紑鎴�'
+ if (val === 0 || val === '0') return '鏈紑鎴�'
+ return val == null || val === '' ? '-' : '鏈紑鎴�'
+ },
handleSync () {
this.$dialog.actionConfirm('纭浠庝笁鏂瑰钩鍙板悓姝ュ叏閮ㄧ數琛ㄦ暟鎹悧锛�', '鍚屾鐢佃〃')
.then(() => {
@@ -162,4 +210,15 @@
<style scoped>
.green { color: #67c23a; }
.red { color: #f56c6c; }
+.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;
+}
</style>
diff --git a/admin/src/views/business/ywelectricalactions.vue b/admin/src/views/business/ywelectricalactions.vue
new file mode 100644
index 0000000..d1ec309
--- /dev/null
+++ b/admin/src/views/business/ywelectricalactions.vue
@@ -0,0 +1,176 @@
+<template>
+ <TableLayout :permissions="['business:ywelectricalactions:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="鎿嶄綔绫诲瀷" prop="actionType">
+ <el-select v-model="searchForm.actionType" clearable placeholder="鍏ㄩ儴" style="min-width: 160px">
+ <el-option
+ v-for="item in actionTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎿嶄綔鏃堕棿" prop="operateTimeRange">
+ <el-date-picker
+ v-model="searchForm.operateTimeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ range-separator="-"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ style="width: 360px"
+ />
+ </el-form-item>
+ <section>
+ <el-button type="primary" icon="el-icon-search" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" @click="reset">閲嶇疆</el-button>
+ </section>
+ </el-form>
+ <template v-slot:table-wrap>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+ <el-table-column prop="id" label="ID" min-width="80" align="center" />
+ <el-table-column prop="electricalName" label="鐢佃〃鍚嶇О" min-width="140" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.electricalName || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="electricalAddress" label="鐢佃〃鍦板潃" min-width="140" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.electricalAddress || '-' }}</template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔绫诲瀷" min-width="120" align="center">
+ <template slot-scope="{ row }">{{ formatActionType(row.actionType) }}</template>
+ </el-table-column>
+ <el-table-column prop="oprId" label="鎿嶄綔ID" min-width="180" align="center" show-overflow-tooltip />
+ <el-table-column label="鐘舵��" min-width="90" 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="160" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.resultMsg || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="createDate" label="鎿嶄綔鏃堕棿" min-width="160" align="center" />
+ <el-table-column label="璇锋眰鎶ユ枃" min-width="100" align="center">
+ <template slot-scope="{ row }">
+ <el-button type="text" :disabled="!row.requestBody" @click="openJson('璇锋眰鎶ユ枃', row.requestBody)">鏌ョ湅</el-button>
+ </template>
+ </el-table-column>
+ <el-table-column label="鍝嶅簲鎶ユ枃" min-width="100" align="center">
+ <template slot-scope="{ row }">
+ <el-button type="text" :disabled="!row.responseBody" @click="openJson('鍝嶅簲鎶ユ枃', row.responseBody)">鏌ョ湅</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ @size-change="handleSizeChange"
+ @current-change="handlePageChange"
+ :pagination="tableData.pagination"
+ />
+ </template>
+ <OperaInterfaceLogWindow ref="jsonWindow" />
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import OperaInterfaceLogWindow from '@/components/business/OperaInterfaceLogWindow'
+import * as actionsApi from '@/api/business/ywelectricalactions'
+
+const ACTION_TYPE_MAP = {
+ 1: '棰勪粯璐规竻闆�',
+ 2: '鍚庝粯璐规竻闆�',
+ 3: '杩滅▼閿�鎴�',
+ 4: '鎷夐椄',
+ 5: '鍚堥椄',
+ 6: '寮�鎴�',
+ 7: '鍏呭��',
+ 8: '鎶勮〃'
+}
+
+export default {
+ name: 'YwElectricalActions',
+ extends: BaseTable,
+ components: { TableLayout, Pagination, OperaInterfaceLogWindow },
+ data () {
+ return {
+ searchForm: {
+ actionType: null,
+ operateTimeRange: []
+ },
+ actionTypeOptions: Object.keys(ACTION_TYPE_MAP).map(key => ({
+ value: Number(key),
+ label: ACTION_TYPE_MAP[key]
+ }))
+ }
+ },
+ created () {
+ this.api = actionsApi
+ this.module = '鐢佃〃杩滅▼鎿嶄綔璁板綍'
+ this.configData['field.id'] = 'id'
+ this.configData['field.main'] = 'id'
+ this.search()
+ },
+ methods: {
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.actionType !== null && this.searchForm.actionType !== '' && this.searchForm.actionType !== undefined) {
+ model.actionType = this.searchForm.actionType
+ }
+ if (this.searchForm.operateTimeRange && this.searchForm.operateTimeRange.length === 2) {
+ model.operateTimeBegin = this.searchForm.operateTimeRange[0]
+ model.operateTimeEnd = this.searchForm.operateTimeRange[1]
+ }
+ return model
+ },
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.isWorking.search = true
+ actionsApi.fetchList({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel(),
+ sorts: this.tableData.sorts
+ })
+ .then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ })
+ .catch(() => {})
+ .finally(() => { this.isWorking.search = false })
+ },
+ reset () {
+ this.searchForm = { actionType: null, operateTimeRange: [] }
+ if (this.$refs.searchForm) {
+ this.$refs.searchForm.resetFields()
+ }
+ this.search()
+ },
+ 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 })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.orange { color: #e6a23c; }
+</style>
diff --git a/admin/src/views/business/ywelectricalcharge.vue b/admin/src/views/business/ywelectricalcharge.vue
new file mode 100644
index 0000000..7eac02a
--- /dev/null
+++ b/admin/src/views/business/ywelectricalcharge.vue
@@ -0,0 +1,114 @@
+<template>
+ <TableLayout :permissions="['business:ywelectricalcharge:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="鐢佃〃淇℃伅" prop="meterKeyword">
+ <el-input v-model="searchForm.meterKeyword" placeholder="琛ㄥ悕绉�/琛ㄥ湴鍧�" clearable @keypress.enter.native="search"/>
+ </el-form-item>
+ <el-form-item label="鍏呭�肩姸鎬�" prop="status">
+ <el-select v-model="searchForm.status" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+ <el-option label="鍏呭�间腑" :value="0"/>
+ <el-option label="鍏呭�兼垚鍔�" :value="1"/>
+ <el-option label="鍏呭�煎け璐�" :value="2"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="浠诲姟ID" prop="oprId">
+ <el-input v-model="searchForm.oprId" placeholder="opr_id" clearable/>
+ </el-form-item>
+ <section>
+ <el-button type="primary" icon="el-icon-search" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" @click="reset">閲嶇疆</el-button>
+ </section>
+ </el-form>
+ <template v-slot:table-wrap>
+ <ul class="toolbar">
+ <li>
+ <el-button @click="exportExcel" :loading="isWorking.export" v-permissions="['business:ywelectricalcharge:exportExcel']">瀵煎嚭</el-button>
+ </li>
+ </ul>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+ <el-table-column prop="name" label="鐢佃〃鍚嶇О" min-width="130" align="center" show-overflow-tooltip/>
+ <el-table-column prop="address" label="琛ㄥ湴鍧�" min-width="130" align="center" show-overflow-tooltip/>
+ <el-table-column prop="roomNames" label="缁戝畾鎴块棿" min-width="150" align="center" show-overflow-tooltip/>
+ <el-table-column prop="money" label="鍏呭�奸噾棰�(鍏�)" min-width="110" align="center"/>
+ <el-table-column prop="banlance" label="鍏呭�煎墠浣欓" min-width="110" align="center"/>
+ <el-table-column label="鐘舵��" min-width="100" align="center">
+ <template slot-scope="{ row }">
+ <span v-if="row.status === 0">鍏呭�间腑</span>
+ <span v-else-if="row.status === 1" class="green">鍏呭�兼垚鍔�</span>
+ <span v-else-if="row.status === 2" class="red">鍏呭�煎け璐�</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="oprId" label="浠诲姟ID" min-width="200" align="center" show-overflow-tooltip/>
+ <el-table-column prop="remark" label="澶囨敞" min-width="120" align="center" show-overflow-tooltip/>
+ <el-table-column prop="statusInfo" label="鐘舵�佽鏄�" min-width="140" align="center" show-overflow-tooltip/>
+ <el-table-column prop="createDate" label="鎻愪氦鏃堕棿" min-width="160" align="center"/>
+ <el-table-column prop="statusTime" label="鐘舵�佹洿鏂版椂闂�" min-width="160" align="center"/>
+ </el-table>
+ <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination"/>
+ </template>
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import * as chargeApi from '@/api/business/ywelectricalcharge'
+
+export default {
+ name: 'YwElectricalCharge',
+ extends: BaseTable,
+ components: { TableLayout, Pagination },
+ data () {
+ return {
+ searchForm: {
+ meterKeyword: '',
+ status: '',
+ oprId: ''
+ }
+ }
+ },
+ created () {
+ this.api = chargeApi
+ this.module = '鐢佃〃鍏呭�艰褰�'
+ this.configData['field.id'] = 'id'
+ this.configData['field.main'] = 'name'
+ this.search()
+ },
+ methods: {
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.isWorking.search = true
+ chargeApi.fetchList({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel(),
+ sorts: this.tableData.sorts
+ })
+ .then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ })
+ .catch(() => {})
+ .finally(() => { this.isWorking.search = false })
+ },
+ buildSearchModel () {
+ const model = { type: 0 }
+ if (this.searchForm.meterKeyword) model.meterKeyword = this.searchForm.meterKeyword
+ if (this.searchForm.status !== '' && this.searchForm.status !== null) model.status = this.searchForm.status
+ if (this.searchForm.oprId) model.oprId = this.searchForm.oprId
+ return model
+ },
+ reset () {
+ this.searchForm = { meterKeyword: '', status: '', oprId: '' }
+ this.search()
+ }
+ }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/ywelectricaldata.vue b/admin/src/views/business/ywelectricaldata.vue
new file mode 100644
index 0000000..7dc1ab5
--- /dev/null
+++ b/admin/src/views/business/ywelectricaldata.vue
@@ -0,0 +1,238 @@
+<template>
+ <TableLayout :permissions="['business:ywelectricaldata:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="鐢佃〃淇℃伅" prop="meterKeyword">
+ <el-input
+ v-model="searchForm.meterKeyword"
+ placeholder="璇疯緭鍏ョ數琛ㄥ悕绉�/琛ㄥ彿"
+ clearable
+ @keypress.enter.native="search"
+ />
+ </el-form-item>
+ <el-form-item label="閫夋嫨鎴块棿" prop="roomId">
+ <el-select v-model="searchForm.roomId" clearable filterable placeholder="鍏ㄩ儴" style="min-width: 200px">
+ <el-option v-for="item in roomOptions" :key="item.id" :label="item.label" :value="item.id"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎶勮〃鏃堕棿" prop="readTimeRange">
+ <el-date-picker
+ v-model="searchForm.readTimeRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ range-separator="-"
+ start-placeholder="寮�濮嬫椂闂�"
+ end-placeholder="缁撴潫鏃堕棿"
+ style="width: 360px"
+ />
+ </el-form-item>
+ <section>
+ <el-button type="primary" icon="el-icon-search" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" @click="reset">閲嶇疆</el-button>
+ </section>
+ </el-form>
+ <template v-slot:table-wrap>
+ <ul class="toolbar">
+ <li>
+ <el-button
+ type="primary"
+ @click="handleReadNow"
+ :loading="isReading"
+ v-permissions="['business:ywelectricaldata:sync']"
+ >绔嬪嵆鎶勮〃</el-button>
+ </li>
+ <li>
+ <el-button
+ @click="exportExcel"
+ :loading="isWorking.export"
+ v-permissions="['business:ywelectricaldata:exportExcel']"
+ >瀵煎嚭</el-button>
+ </li>
+ </ul>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+ <el-table-column type="selection" width="55"/>
+ <el-table-column prop="address" label="鐢佃〃鍦板潃" min-width="140" align="center" show-overflow-tooltip/>
+ <el-table-column label="鐢佃〃鍚嶇О" min-width="140" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.electricalName || row.name || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="roomNames" label="缁戝畾鎴块棿" min-width="150" align="center" show-overflow-tooltip/>
+ <el-table-column label="缁撶畻鏂瑰紡" min-width="100" align="center">
+ <template slot-scope="{ row }">{{ formatJsfs(row.jsfs) }}</template>
+ </el-table-column>
+ <el-table-column label="缁勫悎鏈夊姛鎬荤數閲�" min-width="140" align="center">
+ <template slot-scope="{ row }">{{ formatEnergy(row.zhygzdl) }}</template>
+ </el-table-column>
+ <el-table-column prop="ye" label="璐︽埛浣欓" min-width="100" align="center"/>
+ <el-table-column prop="countnum" label="璐數娆℃暟" min-width="90" align="center"/>
+ <el-table-column label="鎶勮〃鏃堕棿" min-width="160" align="center">
+ <template slot-scope="{ row }">
+ <span :title="formatDateTime(readTimeValue(row))">{{ formatRelativeTime(readTimeValue(row)) }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" min-width="90" align="center">
+ <template>
+ <span class="red">宸茶Е鍙�</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination
+ @size-change="handleSizeChange"
+ @current-change="handlePageChange"
+ :pagination="tableData.pagination"
+ />
+ </template>
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import * as dataApi from '@/api/business/ywelectricaldata'
+import { tree } from '@/api/project/ywProject'
+import dayjs from 'dayjs'
+import relativeTime from 'dayjs/plugin/relativeTime'
+import 'dayjs/locale/zh-cn'
+
+dayjs.extend(relativeTime)
+dayjs.locale('zh-cn')
+
+export default {
+ name: 'YwElectricalData',
+ extends: BaseTable,
+ components: { TableLayout, Pagination },
+ data () {
+ return {
+ searchForm: {
+ meterKeyword: '',
+ roomId: null,
+ readTimeRange: []
+ },
+ roomOptions: [],
+ isReading: false
+ }
+ },
+ created () {
+ this.api = dataApi
+ this.module = '鎶勮〃璁板綍'
+ this.configData['field.id'] = 'id'
+ this.configData['field.main'] = 'address'
+ this.loadRoomOptions()
+ this.search()
+ },
+ methods: {
+ loadRoomOptions () {
+ tree({}).then(res => {
+ this.roomOptions = this.flattenRooms(res || [])
+ }).catch(() => {})
+ },
+ flattenRooms (nodes, prefix) {
+ if (!nodes || !nodes.length) return []
+ let list = []
+ nodes.forEach(node => {
+ const path = prefix ? prefix + '/' + node.name : node.name
+ if (node.lv === 3) {
+ list.push({ id: node.id, label: path })
+ }
+ if (node.projectDataVOList && node.projectDataVOList.length) {
+ list = list.concat(this.flattenRooms(node.projectDataVOList, path))
+ }
+ })
+ return list
+ },
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.meterKeyword) {
+ model.meterKeyword = this.searchForm.meterKeyword
+ }
+ if (this.searchForm.roomId != null && this.searchForm.roomId !== '') {
+ model.roomId = this.searchForm.roomId
+ }
+ if (this.searchForm.readTimeRange && this.searchForm.readTimeRange.length === 2) {
+ model.readTimeBegin = this.searchForm.readTimeRange[0]
+ model.readTimeEnd = this.searchForm.readTimeRange[1]
+ }
+ return model
+ },
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.isWorking.search = true
+ dataApi.fetchList({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel(),
+ sorts: this.tableData.sorts
+ })
+ .then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ })
+ .catch(() => {})
+ .finally(() => { this.isWorking.search = false })
+ },
+ reset () {
+ this.searchForm = {
+ meterKeyword: '',
+ roomId: null,
+ readTimeRange: []
+ }
+ if (this.$refs.searchForm) {
+ this.$refs.searchForm.resetFields()
+ }
+ this.search()
+ },
+ handleReadNow () {
+ this.$dialog.actionConfirm('纭绔嬪嵆浠庣涓夋柟骞冲彴鍚屾鎶勮〃鏁版嵁鍚楋紵', '鎿嶄綔纭鎻愰啋')
+ .then(() => {
+ this.isReading = true
+ dataApi.syncAll({})
+ .then(res => {
+ this.$tip.apiSuccess(res || '鎶勮〃鍚屾鎴愬姛')
+ this.search()
+ })
+ .catch(e => {
+ this.$tip.apiFailed(e)
+ })
+ .finally(() => {
+ this.isReading = false
+ })
+ })
+ .catch(() => {})
+ },
+ formatJsfs (val) {
+ if (val == null || val === '') return '-'
+ const text = String(val).trim().toLowerCase()
+ if (text === 'true' || text === '1') return '棰勪粯璐�'
+ if (text === 'false' || text === '0') return '鍚庝粯璐�'
+ return val
+ },
+ formatEnergy (val) {
+ if (val == null || val === '') return '-'
+ const text = String(val)
+ if (text.toLowerCase().indexOf('kwh') >= 0) return text
+ return text + 'kWh'
+ },
+ readTimeValue (row) {
+ return row.addTime || row.createDate
+ },
+ formatDateTime (value) {
+ if (!value) return '-'
+ const d = dayjs(value)
+ return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : value
+ },
+ formatRelativeTime (value) {
+ if (!value) return '-'
+ const d = dayjs(value)
+ if (!d.isValid()) return value
+ const diffHours = dayjs().diff(d, 'hour')
+ if (diffHours >= 24) {
+ return d.format('YYYY-MM-DD HH:mm:ss')
+ }
+ return d.fromNow()
+ }
+ }
+}
+</script>
+
+<style scoped>
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/ywelectricalparam.vue b/admin/src/views/business/ywelectricalparam.vue
new file mode 100644
index 0000000..aa2034f
--- /dev/null
+++ b/admin/src/views/business/ywelectricalparam.vue
@@ -0,0 +1,136 @@
+<template>
+ <TableLayout :permissions="['business:ywelectricalparam:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="閰嶇疆鍚嶇О" prop="nameKeyword">
+ <el-input v-model="searchForm.nameKeyword" placeholder="璇疯緭鍏ラ厤缃悕绉�" clearable @keypress.enter.native="search"/>
+ </el-form-item>
+ <section>
+ <el-button type="primary" icon="el-icon-search" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh" @click="reset">閲嶇疆</el-button>
+ </section>
+ </el-form>
+ <template v-slot:table-wrap>
+ <ul class="toolbar">
+ <li>
+ <el-button type="primary" icon="el-icon-plus" @click="$refs.editWindow.open('鏂板缓鐢佃〃鍙傛暟')" v-permissions="['business:ywelectricalparam:create']">鏂板缓</el-button>
+ </li>
+ <li>
+ <el-button @click="exportExcel" :loading="isWorking.export" v-permissions="['business:ywelectricalparam:exportExcel']">瀵煎嚭</el-button>
+ </li>
+ </ul>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55"/>
+ <el-table-column prop="name" label="閰嶇疆鍚嶇О" min-width="180" align="center" show-overflow-tooltip/>
+ <el-table-column label="鐢典环" min-width="120" align="center">
+ <template slot-scope="{ row }">{{ formatPrice(row.price) }}</template>
+ </el-table-column>
+ <el-table-column label="閫忔敮閲戦" min-width="100" align="center">
+ <template slot-scope="{ row }">{{ formatYuan(row.tzMoney) }}</template>
+ </el-table-column>
+ <el-table-column label="涓�绾ф姤璀﹂噾棰�" min-width="120" align="center">
+ <template slot-scope="{ row }">{{ formatYuan(row.yjbjMoney) }}</template>
+ </el-table-column>
+ <el-table-column label="浜岀骇鎶ヨ閲戦" min-width="120" align="center">
+ <template slot-scope="{ row }">{{ formatYuan(row.ejbjMoney) }}</template>
+ </el-table-column>
+ <el-table-column label="璐熻嵎闄愬埗鍔熺巼" min-width="120" align="center">
+ <template slot-scope="{ row }">{{ formatPower(row.limitFhgl) }}</template>
+ </el-table-column>
+ <el-table-column label="瓒呰礋鑽锋椂闂�" min-width="110" align="center">
+ <template slot-scope="{ row }">{{ formatMinutes(row.limitFhTime) }}</template>
+ </el-table-column>
+ <el-table-column prop="editDate" label="鏇存柊鏃堕棿" min-width="160" align="center"/>
+ <el-table-column label="鎿嶄綔" align="center" min-width="140" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button type="text" @click="$refs.editWindow.open('缂栬緫鐢佃〃鍙傛暟', row)" v-permissions="['business:ywelectricalparam:update']">缂栬緫</el-button>
+ <el-button type="text" class="red" @click="deleteById(row)" v-permissions="['business:ywelectricalparam:delete']">鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination"/>
+ </template>
+ <YwElectricalParamEdit ref="editWindow" @success="handlePageChange"/>
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import YwElectricalParamEdit from './components/YwElectricalParamEdit'
+
+export default {
+ name: 'YwElectricalParam',
+ extends: BaseTable,
+ components: { TableLayout, Pagination, YwElectricalParamEdit },
+ data () {
+ return {
+ searchForm: {
+ nameKeyword: ''
+ }
+ }
+ },
+ created () {
+ this.config({
+ module: '鐢佃〃鍙傛暟璁剧疆',
+ api: '/business/ywelectricalparam',
+ 'field.id': 'id',
+ 'field.main': 'name'
+ })
+ this.search()
+ },
+ methods: {
+ formatYuan (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ const n = Number(val)
+ if (isNaN(n)) return val
+ return `${n.toFixed(2)}鍏僠
+ },
+ formatPrice (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ const n = Number(val)
+ if (isNaN(n)) return val
+ return `${n.toFixed(2)} 鍏�/KWH`
+ },
+ formatPower (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ const n = Number(val)
+ if (isNaN(n)) return val
+ return `${n.toFixed(2)}KW`
+ },
+ formatMinutes (val) {
+ if (val === null || val === undefined || val === '') return '-'
+ return `${val}鍒嗛挓`
+ },
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.nameKeyword) model.nameKeyword = this.searchForm.nameKeyword
+ return model
+ },
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.isWorking.search = true
+ this.api.fetchList({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel(),
+ sorts: this.tableData.sorts
+ })
+ .then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ })
+ .catch(() => {})
+ .finally(() => { this.isWorking.search = false })
+ },
+ reset () {
+ this.searchForm = { nameKeyword: '' }
+ this.search()
+ }
+ }
+}
+</script>
+
+<style scoped>
+.red { color: #f56c6c; }
+</style>
diff --git a/server/db/ELECTRICAL_INTEGRATION.md b/server/db/ELECTRICAL_INTEGRATION.md
new file mode 100644
index 0000000..d206bdf
--- /dev/null
+++ b/server/db/ELECTRICAL_INTEGRATION.md
@@ -0,0 +1,172 @@
+# 鏅鸿兘鐢佃〃 鈥� 鑱旇皟楠岀涓庡厖鍊艰褰曞鎺ヨ鏄�
+
+## 涓�銆佹暟鎹簱鑴氭湰鎵ц椤哄簭
+
+鍦ㄤ笟鍔″簱锛堝 `funingyunwei`锛変緷娆℃墽琛岋細
+
+| 椤哄簭 | 鑴氭湰 | 璇存槑 |
+|------|------|------|
+| 1 | `yw_electrical_actions.sql` | 寮傛鎿嶄綔璁板綍琛� |
+| 2 | `business.yw_electrical.permissions.sql` | 鍚� `business:ywelectrical:device` |
+| 3 | `business.yw_electrical_charge.permissions.sql` | 鍏呭�艰褰曟煡璇�/瀵煎嚭 |
+| 4 | `business.yw_electrical_param.permissions.sql` | 鐢佃〃鍙傛暟锛堣嫢鏈墽琛岃繃锛� |
+| 5 | `business.yw_electrical.menu.sql` | 鏅鸿兘鐢佃〃銆佸厖鍊艰褰曘�佸弬鏁拌缃彍鍗� + admin 鎺堟潈 |
+| 6 | `business.yw_electrical_warning.*` | 鎶ヨ璁板綍锛堣嫢鏈墽琛岃繃锛� |
+
+鎵ц鍚庤 **閲嶆柊鐧诲綍** 鎴栧埛鏂版潈闄愮紦瀛橈紝渚ф爮鎵嶄細鍑虹幇鏂拌彍鍗曘��
+
+---
+
+## 浜屻�佸紓姝ュ洖璋� `electricalNotify` 鑱旇皟楠岀
+
+### 2.1 鍥炶皟鍦板潃
+
+- **璺緞**锛歚POST {鏈嶅姟鏍瑰湴鍧�}/electronic/electricalNotify`
+- **Content-Type**锛歚application/x-www-form-urlencoded`锛堣〃鍗曞瓧娈碉級
+- **瀛楁**锛歚response_content`銆乣timestamp`銆乣sign`
+- **閰嶇疆**锛歚ElectronicConstant.notify_url` 蹇呴』涓庝笁鏂瑰钩鍙板悗鍙伴厤缃殑 **瀹屾暣鍏綉 URL** 涓�鑷达紙鍚矾寰勶級銆�
+
+绀轰緥锛�
+
+```text
+https://your-domain.com/electronic/electricalNotify
+```
+
+### 2.2 楠岀绠楁硶锛堜笌鍑虹珯 `ElectronicToolUtil.getSign` 涓�鑷达級
+
+1. 鍙栧弬涓庣鍚嶅瓧娈碉紝缁勬垚 `Map<String,String>`锛�**鎺ㄨ崘**锛夛細
+ - `auth_code` = `ElectronicConstant.auth_code`
+ - `response_content` = 鍘熷 JSON 瀛楃涓诧紙涓� POST 鍙傛暟瀹屽叏涓�鑷达紝涓嶈浜屾鏍煎紡鍖栵級
+ - `timestamp` = 骞冲彴 POST 鐨� timestamp 瀛楃涓�
+2. 灏� Map 鐨� **key 鎸夊瓧鍏稿簭鎺掑簭**锛屼緷娆℃嫾鎺ュ悇 key 瀵瑰簲鐨� **value**锛堝彧鎷� value锛屼笉鍚� key 鍚嶏級銆�
+3. 鍦ㄦ嫾鎺ョ粨鏋滄湯灏捐拷鍔� `ElectronicConstant.nonce`銆�
+4. 瀵规渶缁堝瓧绗︿覆鍋� **UTF-8 MD5**锛屽緱鍒� 32 浣嶅皬鍐欏崄鍏繘鍒讹紙姣旇緝鏃跺拷鐣ュぇ灏忓啓锛夈��
+
+鑻ヤ笂杩伴獙绛惧け璐ワ紝浠g爜浼� **鍐嶅皾璇�** 浠呬娇鐢� `response_content + timestamp` 涓や釜瀛楁锛堜笉鍚� `auth_code`锛夛紝浠ュ吋瀹归儴鍒嗗钩鍙扮増鏈��
+
+### 2.3 鏈湴鑷祴 sign锛圝ava锛�
+
+```java
+Map<String, String> data = new HashMap<>();
+data.put("auth_code", ElectronicConstant.auth_code);
+data.put("response_content", responseContent); // 涓� POST 涓�鑷�
+data.put("timestamp", timestamp);
+String sign = ElectronicToolUtil.getSign(data);
+```
+
+### 2.4 鍝嶅簲绾﹀畾
+
+| 鍦烘櫙 | HTTP 鐘舵�� | 鍝嶅簲浣� | 骞冲彴琛屼负 |
+|------|-----------|--------|----------|
+| 楠岀閫氳繃涓斿鐞嗗畬鎴� | 200 | `SUCCESS` | 鍋滄閲嶈瘯 |
+| 楠岀澶辫触 | 400 | `FAIL` | 鎸夌瓥鐣ラ噸璇� |
+| 闈� 200 鎴� `FAIL` | - | - | 闂撮殧閲嶈瘯鐩磋嚦鎴愬姛鎴栬揪涓婇檺 |
+
+### 2.5 `response_content` 鍗曟潯缁撴瀯
+
+```json
+{
+ "opr_id": "涓庢彁浜ゅ紓姝ヤ换鍔℃椂涓�鑷�",
+ "status": "SUCCESS",
+ "cid": "閲囬泦鍣ㄥ彿",
+ "address": "琛ㄥ湴鍧�",
+ "error_msg": "澶辫触鏃剁殑閿欒鐮佹垨璇存槑"
+}
+```
+
+**status 涓庢湰鍦� `yw_electrical_actions.status` 鏄犲皠锛�**
+
+| 骞冲彴 status | actions.status | 璇存槑 |
+|-------------|----------------|------|
+| ACCEPTED / QUEUE / PROCESSING | 0 | 澶勭悊涓� |
+| SUCCESS / DELIVERED | 1 | 鎴愬姛 |
+| FAIL / TIMEOUT / NOTSUPPORT / CANCELED / RESPONSE_TIMEOUT / RESPONSE_FAIL / NOTFOUND | 2 | 澶辫触 |
+
+### 2.6 鑱旇皟妫�鏌ユ竻鍗�
+
+- [ ] `dmvisit_admin` 宸插惎鍔紝Shiro 宸叉斁琛� `/electronic/electricalNotify`
+- [ ] 鍏綉/NAT 鑳借闂洖璋� URL锛堝唴缃戦渶绔彛鏄犲皠鎴栫┛閫忥級
+- [ ] `auth_code`銆乣nonce` 涓庝笁鏂瑰悗鍙颁竴鑷�
+- [ ] 鎻愪氦杩滅▼鎿嶄綔鍚� `yw_electrical_actions` 鏈� `opr_id` 涓� `status=0`
+- [ ] 妯℃嫙 POST 鍥炶皟鍚� `status` 鏇存柊锛屾媺闂�/鍚堥椄/寮�鎴�/鍏呭�煎壇浣滅敤姝g‘
+- [ ] `yw_electrical_log` 鏈� `type=1` 鐨勬帹閫佽褰�
+
+### 2.7 甯歌闂
+
+**楠岀涓�鐩村け璐�**
+
+- 鏍稿 `response_content` 鏄惁涓庡钩鍙板彂閫佸瓧鑺傜骇涓�鑷达紙URL 缂栫爜銆佺┖鏍笺�佽浆涔夛級銆�
+- 纭 timestamp 涓哄瓧绗︿覆鑰岄潪甯﹀皬鏁般��
+- 鐢ㄥ钩鍙版枃妗g‘璁ゆ槸鍚﹀寘鍚� `auth_code` 鍙備笌绛惧悕銆�
+
+**鍥炶皟鎴愬姛浣� actions 涓嶆洿鏂�**
+
+- 鏌� `opr_id` 鏄惁涓� `yw_electrical_actions.opr_id` 瀹屽叏涓�鑷达紙鎻愪氦鏃舵湰鍦扮敓鎴愮殑 UUID锛夈��
+
+---
+
+## 涓夈�佸厖鍊艰褰曢〉瀵规帴
+
+### 3.1 鏁版嵁娴�
+
+```text
+鐢ㄦ埛銆岀‘璁ゅ厖鍊笺��
+ 鈫� operate(recharge) 鈫� 涓夋柟 recharger锛堝紓姝ワ級
+ 鈫� 鍐欏叆 yw_electrical_charge锛坰tatus=0 鍏呭�间腑锛宱pr_id=浠诲姟ID锛�
+ 鈫� 鍐欏叆 yw_electrical_actions + yw_electrical_log
+
+骞冲彴鍥炶皟 electricalNotify锛坰tatus=SUCCESS锛�
+ 鈫� 鏇存柊 yw_electrical_actions.status=1
+ 鈫� 鎸� opr_id 鏇存柊 yw_electrical_charge.status=1锛宻tatusTime/statusInfo
+ 鈫� 鍒锋柊 yw_electrical 浣欓锛坮efreshBalanceFromData锛�
+
+鍥炶皟 FAIL
+ 鈫� actions.status=2锛宑harge.status=2
+```
+
+### 3.2 琛ㄥ瓧娈靛搴�
+
+| yw_electrical_charge | 鏉ユ簮 |
+|----------------------|------|
+| obj_id | 鐢佃〃 id |
+| address / name / c_id | 鐢佃〃妗f |
+| money | 鍏呭�奸噾棰� |
+| remark | 鍏呭�煎娉� |
+| opr_id | 寮傛浠诲姟 opr_id |
+| status | 0鍏呭�间腑 / 1鎴愬姛 / 2澶辫触 |
+| banlance | 鍏呭�煎墠璐︽埛浣欓锛堢數琛� balance锛� |
+| room_names | 缁戝畾鎴块棿灞曠ず鍚� |
+
+### 3.3 鍓嶇椤甸潰
+
+- **鑿滃崟璺緞**锛歚/business/ywelectricalcharge`
+- **椤甸潰**锛歚admin/src/views/business/ywelectricalcharge.vue`
+- **鎺ュ彛**锛歚/visitsAdmin/cloudService/business/ywElectricalCharge/page`
+- **鏉冮檺**锛歚business:ywelectricalcharge:query`
+
+鐢ㄦ埛鍦ㄦ櫤鑳界數琛ㄨ繙绋嬫帶鍒舵彁浜ゅ厖鍊煎悗锛屾彁绀恒�岃鍒板厖鍊艰褰曟煡鐪嬬粨鏋溿�嶏紝鍗冲湪鍒楄〃鎸夎〃鍚�/鍦板潃銆乣opr_id`銆佺姸鎬佺瓫閫夋煡鐪嬨��
+
+### 3.4 涓庛�屽晢鎴峰厖鍊笺�嶅尯鍒�
+
+渚ф爮銆屽晢鎴峰厖鍊� / 鍏呭�艰褰曘�嶈嫢涓� `yw_top_up_log` 绛変笟鍔★紝涓� **鐢佃〃鍏呭�艰褰�**锛坄yw_electrical_charge`锛変负涓嶅悓鏁版嵁婧愶紱鏃ュ父鐢ㄧ數涓嬨�屽厖鍊艰褰曘�嶈彍鍗曟寚鍚戠數琛ㄤ笓鐢ㄩ〉闈€��
+
+---
+
+## 鍥涖�佺浉鍏抽厤缃」姹囨��
+
+| 閰嶇疆椤� | 浣嶇疆 | 璇存槑 |
+|--------|------|------|
+| auth_code | ElectronicConstant | 涓夋柟鎺堟潈鐮� |
+| nonce | ElectronicConstant | 绛惧悕闅忔満涓� |
+| notify_url | ElectronicConstant | 寮傛閫氱煡瀹屾暣 URL |
+| api_url / api2_url | ElectronicConstant | 涓夋柟 API 鏍瑰湴鍧� |
+
+---
+
+## 浜斻�佸畾鏃朵换鍔★紙鍙�� HTTP 瑙﹀彂锛�
+
+| 浠诲姟 | Cron | HTTP锛坅dmin_timer锛� |
+|------|------|---------------------|
+| 閲囬泦鍣ㄧ姸鎬� | `0 */5 * * * ?` | GET `/timer/yw/getElectricalStatus` |
+| 鎵归噺鎶勮〃 | `30 0 * * * ?` | GET `/timer/yw/syncElectricalMeterData` |
+| 鏃ュ織娓呯悊 | `0 30 2 * * ?` | GET `/timer/yw/cleanElectricalLog` |
diff --git a/server/db/business.yw_electrical.menu.sql b/server/db/business.yw_electrical.menu.sql
new file mode 100644
index 0000000..a02e5d6
--- /dev/null
+++ b/server/db/business.yw_electrical.menu.sql
@@ -0,0 +1,79 @@
+-- 鏃ュ父鐢ㄧ數绠$悊锛氭櫤鑳界數琛ㄣ�佸厖鍊艰褰曘�佺數琛ㄥ弬鏁拌缃紙闇�鍦� dev 搴撴墽琛岋級
+-- 鐖惰彍鍗曟寜銆屾棩甯哥敤鐢点�嶅尮閰嶏紝鑻ュ悕绉颁笉鍚岃鏀� LIKE 鏉′欢
+
+-- 鏅鸿兘鐢佃〃
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鏅鸿兘鐢佃〃', '/business/ywelectrical', '鏅鸿兘鐢佃〃绠$悊', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectrical')
+LIMIT 1;
+
+-- 鍏呭�艰褰曪紙鐢佃〃锛�
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鍏呭�艰褰�', '/business/ywelectricalcharge', '鐢佃〃鍏呭�艰褰�', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricalcharge')
+LIMIT 1;
+
+-- 鐢佃〃鍙傛暟璁剧疆
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鐢佃〃鍙傛暟璁剧疆', '/business/ywelectricalparam', '鐢佃〃鍙傛暟妗f', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricalparam')
+LIMIT 1;
+
+-- 鎶勮〃璁板綍
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鎶勮〃璁板綍', '/business/ywelectricaldata', '鐢佃〃鎶勮〃璁板綍', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricaldata')
+LIMIT 1;
+
+-- admin 瑙掕壊锛氳彍鍗�
+INSERT INTO `SYSTEM_ROLE_MENU` (`ROLE_ID`, `MENU_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, menu.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_MENU` menu ON menu.`PATH` IN ('/business/ywelectrical', '/business/ywelectricalcharge', '/business/ywelectricalparam', '/business/ywelectricaldata') AND menu.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_MENU` rm
+ WHERE rm.`ROLE_ID` = r.`ID` AND rm.`MENU_ID` = menu.`ID` AND rm.`DELETED` = 0
+ );
+
+-- admin 瑙掕壊锛氭櫤鑳界數琛ㄦ寜閽潈闄�
+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` IN (
+ 'business:ywelectrical:query',
+ 'business:ywelectrical:update',
+ 'business:ywelectrical:device',
+ 'business:ywelectrical:exportExcel',
+ 'business:ywelectricalcharge:query',
+ 'business:ywelectricalcharge:exportExcel',
+ 'business:ywelectricalparam:query',
+ 'business:ywelectricalparam:create',
+ 'business:ywelectricalparam:update',
+ 'business:ywelectricalparam:delete',
+ 'business:ywelectricalparam:exportExcel',
+ 'business:ywelectricaldata:query',
+ 'business:ywelectricaldata:exportExcel',
+ 'business:ywelectricaldata:sync'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ 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_electrical_actions.menu.sql b/server/db/business.yw_electrical_actions.menu.sql
new file mode 100644
index 0000000..58793c5
--- /dev/null
+++ b/server/db/business.yw_electrical_actions.menu.sql
@@ -0,0 +1,30 @@
+-- 鑿滃崟锛氭棩甯哥敤鐢电鐞� -> 鎿嶄綔璁板綍锛堥渶鍦� dev 搴撴墽琛岋級
+
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鎿嶄綔璁板綍', '/business/ywelectricalactions', '鐢佃〃杩滅▼鎿嶄綔璁板綍', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricalactions')
+LIMIT 1;
+
+INSERT INTO `SYSTEM_ROLE_MENU` (`ROLE_ID`, `MENU_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, menu.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_MENU` menu ON menu.`PATH` = '/business/ywelectricalactions' AND menu.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_MENU` rm
+ WHERE rm.`ROLE_ID` = r.`ID` AND rm.`MENU_ID` = menu.`ID` AND rm.`DELETED` = 0
+ );
+
+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:ywelectricalactions:query' AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ 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_electrical_actions.permissions.sql b/server/db/business.yw_electrical_actions.permissions.sql
new file mode 100644
index 0000000..0c1995c
--- /dev/null
+++ b/server/db/business.yw_electrical_actions.permissions.sql
@@ -0,0 +1 @@
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywelectricalactions:query', '鏌ヨ鐢佃〃杩滅▼鎿嶄綔璁板綍', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
diff --git a/server/db/business.yw_electrical_charge.permissions.sql b/server/db/business.yw_electrical_charge.permissions.sql
new file mode 100644
index 0000000..2c01ffa
--- /dev/null
+++ b/server/db/business.yw_electrical_charge.permissions.sql
@@ -0,0 +1,2 @@
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywelectricalcharge: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:ywelectricalcharge:exportExcel', '瀵煎嚭鐢佃〃鍏呭�艰褰�(Excel)', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
diff --git a/server/db/business.yw_electrical_data.menu.sql b/server/db/business.yw_electrical_data.menu.sql
new file mode 100644
index 0000000..94dac14
--- /dev/null
+++ b/server/db/business.yw_electrical_data.menu.sql
@@ -0,0 +1,34 @@
+-- 鎶勮〃璁板綍鑿滃崟涓� admin 鎺堟潈锛堥渶鍦� dev 搴撴墽琛岋紝鐖惰彍鍗曚负銆屾棩甯哥敤鐢电鐞嗐�嶏級
+
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鎶勮〃璁板綍', '/business/ywelectricaldata', '鐢佃〃鎶勮〃璁板綍', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricaldata')
+LIMIT 1;
+
+INSERT INTO `SYSTEM_ROLE_MENU` (`ROLE_ID`, `MENU_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, menu.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_MENU` menu ON menu.`PATH` = '/business/ywelectricaldata' AND menu.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_MENU` rm
+ WHERE rm.`ROLE_ID` = r.`ID` AND rm.`MENU_ID` = menu.`ID` AND rm.`DELETED` = 0
+ );
+
+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` IN (
+ 'business:ywelectricaldata:query',
+ 'business:ywelectricaldata:exportExcel',
+ 'business:ywelectricaldata:sync'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ 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_electrical_data.permissions.sql b/server/db/business.yw_electrical_data.permissions.sql
new file mode 100644
index 0000000..92ebce2
--- /dev/null
+++ b/server/db/business.yw_electrical_data.permissions.sql
@@ -0,0 +1,3 @@
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywelectricaldata: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:ywelectricaldata: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:ywelectricaldata:sync', '绔嬪嵆鎶勮〃', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
diff --git a/server/db/business.yw_electrical_param.menu.sql b/server/db/business.yw_electrical_param.menu.sql
new file mode 100644
index 0000000..211c947
--- /dev/null
+++ b/server/db/business.yw_electrical_param.menu.sql
@@ -0,0 +1,36 @@
+-- 鑿滃崟锛氭棩甯哥敤鐢电鐞� -> 鐢佃〃鍙傛暟璁剧疆
+
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT m.`ID`, '鐢佃〃鍙傛暟璁剧疆', '/business/ywelectricalparam', '鐢佃〃鍙傛暟妗f缁存姢', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = m.`ID` AND sm.`DELETED` = 0), 0) + 1,
+ 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` m
+WHERE m.`DELETED` = 0 AND m.`NAME` LIKE '%鏃ュ父鐢ㄧ數%'
+ AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywelectricalparam')
+LIMIT 1;
+
+INSERT INTO `SYSTEM_ROLE_MENU` (`ROLE_ID`, `MENU_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, menu.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_MENU` menu ON menu.`PATH` = '/business/ywelectricalparam' AND menu.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_MENU` rm
+ WHERE rm.`ROLE_ID` = r.`ID` AND rm.`MENU_ID` = menu.`ID` AND rm.`DELETED` = 0
+ );
+
+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` IN (
+ 'business:ywelectricalparam:query',
+ 'business:ywelectricalparam:create',
+ 'business:ywelectricalparam:update',
+ 'business:ywelectricalparam:delete',
+ 'business:ywelectricalparam:exportExcel'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` = '绠$悊鍛�')
+ 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_electrical_param.permissions.sql b/server/db/business.yw_electrical_param.permissions.sql
new file mode 100644
index 0000000..482f32b
--- /dev/null
+++ b/server/db/business.yw_electrical_param.permissions.sql
@@ -0,0 +1,5 @@
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`) VALUES ('business:ywelectricalparam:create', '鏂板缓鐢佃〃鍙傛暟', '', 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:ywelectricalparam:delete', '鍒犻櫎鐢佃〃鍙傛暟', '', 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:ywelectricalparam: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:ywelectricalparam: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:ywelectricalparam:exportExcel', '瀵煎嚭鐢佃〃鍙傛暟(Excel)', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0);
diff --git a/server/db/electrical_param.dict.sql b/server/db/electrical_param.dict.sql
new file mode 100644
index 0000000..afb7e77
--- /dev/null
+++ b/server/db/electrical_param.dict.sql
@@ -0,0 +1,49 @@
+-- 鏅鸿兘鐢佃〃绗笁鏂瑰钩鍙板弬鏁帮紙SYSTEM_DICT / SYSTEM_DICT_DATA锛�
+-- 瀛楀吀绫诲瀷锛欵LECTRICAL_PARAM
+-- 瀛楀吀鏁版嵁锛歭abel=鍙傛暟閿紝code=鍙傛暟鍊�
+
+INSERT INTO `SYSTEM_DICT` (`CODE`, `NAME`, `REMARK`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'ELECTRICAL_PARAM', '鐢佃〃涓夋柟骞冲彴鍙傛暟', '鏅鸿兘鐢佃〃绗笁鏂� API锛歛uth_code銆乶once銆乶otify_url銆乤pi_url銆乤pi_url2', 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT` WHERE `CODE` = 'ELECTRICAL_PARAM' AND `DELETED` = 0
+);
+
+INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT d.`ID`, '1f29d378fc6792d5d2b735877993ffb7', 'auth_code', '涓夋柟鎺堟潈鐮�', 1, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'ELECTRICAL_PARAM' AND d.`DELETED` = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'auth_code' AND x.`DELETED` = 0
+ );
+
+INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT d.`ID`, 'lvyOY7fcun719WkxF8ToVYatStgt', 'nonce', '绛惧悕闅忔満涓�', 2, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'ELECTRICAL_PARAM' AND d.`DELETED` = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'nonce' AND x.`DELETED` = 0
+ );
+
+INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT d.`ID`, 'http://115.221.15.8:8055/electronic/electricalNotify', 'notify_url', '寮傛鍥炶皟瀹屾暣 URL', 3, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'ELECTRICAL_PARAM' AND d.`DELETED` = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'notify_url' AND x.`DELETED` = 0
+ );
+
+INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT d.`ID`, 'http://api1.tqdianbiao.com', 'api_url', 'Api v1 鏍瑰湴鍧�', 4, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'ELECTRICAL_PARAM' AND d.`DELETED` = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'api_url' AND x.`DELETED` = 0
+ );
+
+INSERT INTO `SYSTEM_DICT_DATA` (`DICT_ID`, `CODE`, `LABEL`, `REMARK`, `SORT`, `DISABLED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT d.`ID`, 'http://api2.tqdianbiao.com', 'api_url2', 'Api v2 鏍瑰湴鍧�', 5, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'ELECTRICAL_PARAM' AND d.`DELETED` = 0
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'api_url2' AND x.`DELETED` = 0
+ );
diff --git a/server/db/yw_electrical.electrical_param_id.sql b/server/db/yw_electrical.electrical_param_id.sql
new file mode 100644
index 0000000..6e00652
--- /dev/null
+++ b/server/db/yw_electrical.electrical_param_id.sql
@@ -0,0 +1,3 @@
+-- 鐢佃〃琛ㄦ柊澧炴湰鍦板弬鏁版。妗堝叧鑱斿瓧娈碉紙鍏宠仈 yw_electrical_param.id锛�
+ALTER TABLE `yw_electrical`
+ ADD COLUMN `electrical_param_id` int NULL DEFAULT NULL COMMENT '鐢佃〃鍙傛暟ID锛堝叧鑱� yw_electrical_param.id锛�' AFTER `param_id`;
diff --git a/server/db/yw_electrical_actions.sql b/server/db/yw_electrical_actions.sql
new file mode 100644
index 0000000..2735fb9
--- /dev/null
+++ b/server/db/yw_electrical_actions.sql
@@ -0,0 +1,21 @@
+-- 鐢佃〃杩滅▼鎿嶄綔璁板綍锛堝紓姝ヤ换鍔¤窡韪級
+CREATE TABLE IF NOT EXISTS `yw_electrical_actions` (
+ `id` int NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+ `creator` int DEFAULT NULL COMMENT '鍒涘缓浜�',
+ `create_date` datetime DEFAULT NULL COMMENT '鍒涘缓鏃堕棿',
+ `editor` int DEFAULT NULL COMMENT '鏇存柊浜�',
+ `edit_date` datetime DEFAULT NULL COMMENT '鏇存柊鏃堕棿',
+ `isdeleted` int DEFAULT 0 COMMENT '0鍚� 1鏄�',
+ `remark` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+ `electrical_id` int NOT NULL COMMENT '鐢佃〃ID yw_electrical.id',
+ `action_type` int NOT NULL COMMENT '1棰勪粯璐规竻闆� 2鍚庝粯璐规竻闆� 3杩滅▼閿�鎴� 4鎷夐椄 5鍚堥椄 6寮�鎴� 7鍏呭�� 8鎶勮〃',
+ `opr_id` varchar(64) DEFAULT NULL COMMENT '绗笁鏂规搷浣淚D',
+ `request_body` text COMMENT '璇锋眰鎶ユ枃',
+ `response_body` text COMMENT '鍝嶅簲鎶ユ枃',
+ `status` int DEFAULT 0 COMMENT '0澶勭悊涓� 1鎴愬姛 2澶辫触',
+ `result_msg` varchar(500) DEFAULT NULL COMMENT '鎵ц缁撴灉璇存槑',
+ PRIMARY KEY (`id`),
+ KEY `idx_electrical_id` (`electrical_id`),
+ KEY `idx_opr_id` (`opr_id`),
+ KEY `idx_create_date` (`create_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鐢佃〃杩滅▼鎿嶄綔璁板綍';
diff --git a/server/system_service/src/main/java/com/doumee/core/utils/Constants.java b/server/system_service/src/main/java/com/doumee/core/utils/Constants.java
index 179b273..8fe6c89 100644
--- a/server/system_service/src/main/java/com/doumee/core/utils/Constants.java
+++ b/server/system_service/src/main/java/com/doumee/core/utils/Constants.java
@@ -162,6 +162,13 @@
public static final String WMS_OUTBOUND_PLATFROM_URL ="WMS_OUTBOUND_PLATFROM_URL" ;
public static final String TSM_PARAM ="TSM_PARAM" ;
public static final String TMS_ORDER_LIST_URL ="TMS_ORDER_LIST_URL" ;
+ /** 鏅鸿兘鐢佃〃绗笁鏂瑰钩鍙板弬鏁帮紙SYSTEM_DICT.code锛� */
+ public static final String ELECTRICAL_PARAM = "ELECTRICAL_PARAM";
+ public static final String ELECTRICAL_AUTH_CODE = "auth_code";
+ public static final String ELECTRICAL_NONCE = "nonce";
+ public static final String ELECTRICAL_NOTIFY_URL = "notify_url";
+ public static final String ELECTRICAL_API_URL = "api_url";
+ public static final String ELECTRICAL_API2_URL = "api_url2";
public static final String TMS_ORDER_DETAIL_URL ="TMS_ORDER_DETAIL_URL" ;
public static final String TMS_LOCK_STATUS_URL ="TMS_LOCK_STATUS_URL" ;
public static final String TMS_INTERFACE_URL_PREFIX ="TMS_INTERFACE_URL_PREFIX" ;
diff --git a/server/system_service/src/main/java/com/doumee/core/utils/FtpUtil.java b/server/system_service/src/main/java/com/doumee/core/utils/FtpUtil.java
index 9c009bb..696381c 100644
--- a/server/system_service/src/main/java/com/doumee/core/utils/FtpUtil.java
+++ b/server/system_service/src/main/java/com/doumee/core/utils/FtpUtil.java
@@ -18,7 +18,6 @@
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.xpath.operations.Bool;
-import sun.misc.BASE64Encoder;
/** */
/**
diff --git a/server/system_service/src/main/java/com/doumee/core/utils/ImageBase64Util.java b/server/system_service/src/main/java/com/doumee/core/utils/ImageBase64Util.java
index 4595df5..c899b3c 100644
--- a/server/system_service/src/main/java/com/doumee/core/utils/ImageBase64Util.java
+++ b/server/system_service/src/main/java/com/doumee/core/utils/ImageBase64Util.java
@@ -1,8 +1,6 @@
package com.doumee.core.utils;
import org.apache.commons.codec.binary.Base64;
-import sun.misc.BASE64Decoder;
-import sun.misc.BASE64Encoder;
import java.io.*;
import java.net.HttpURLConnection;
@@ -42,10 +40,8 @@
//
if (imgStr == null) // 鍥惧儚鏁版嵁涓虹┖
return false;
- BASE64Decoder decoder = new BASE64Decoder();
try {
- // Base64瑙g爜
- byte[] b = decoder.decodeBuffer(imgStr);
+ byte[] b = Base64.decodeBase64(imgStr);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {// 璋冩暣寮傚父鏁版嵁
b[i] += 256;
diff --git a/server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java b/server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java
index 3a31274..b9de78e 100644
--- a/server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java
+++ b/server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java
@@ -89,6 +89,12 @@
@ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺鐞嗗悎鍚岃处鍗曠紪鐮�")
@GetMapping("/timer/yw/ywDealContractBillCodeTimer")
ApiResponse ywDealContractBillCodeTimer();
+ @ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃舵煡璇㈡櫤鎱х數琛ㄨ澶囩姸鎬�")
+ @GetMapping("/timer/yw/getElectricalStatus")
+ ApiResponse getElectricalStatus();
+ @ApiOperation("銆愰槣瀹佽繍缁淬�戞瘡灏忔椂杩涜涓�娆℃妱琛ㄤ换鍔�")
+ @GetMapping("/timer/yw/syncElectricalMeterData")
+ ApiResponse syncElectricalMeterData();
@ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺鐞嗗悎鍚岃繃鏈�")
@GetMapping("/timer/yw/ywDealContractTimeOutTimer")
diff --git a/server/visits/admin_timer/src/main/java/com/doumee/job/ElectricalScheduleJob.java b/server/visits/admin_timer/src/main/java/com/doumee/job/ElectricalScheduleJob.java
new file mode 100644
index 0000000..42dbca7
--- /dev/null
+++ b/server/visits/admin_timer/src/main/java/com/doumee/job/ElectricalScheduleJob.java
@@ -0,0 +1,48 @@
+package com.doumee.job;
+
+import com.doumee.service.business.YwElectricalBizService;
+import com.doumee.service.business.YwElectricalService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class ElectricalScheduleJob {
+
+ @Autowired
+ private YwElectricalService ywElectricalService;
+ @Autowired
+ private YwElectricalBizService ywElectricalBizService;
+
+ /** 姣�5鍒嗛挓鍚屾閲囬泦鍣ㄥ湪绾跨姸鎬� */
+ @Scheduled(cron = "0 */5 * * * ?")
+ public void syncElectricalStatus() {
+ try {
+ ywElectricalService.getElectricalStatus();
+ } catch (Exception e) {
+ log.error("syncElectricalStatus failed", e);
+ }
+ }
+
+ /** 姣忓皬鏃�0鍒�30绉掓壒閲忔妱琛� */
+ @Scheduled(cron = "30 0 * * * ?")
+ public void syncMeterData() {
+ try {
+ ywElectricalBizService.syncMeterDataScheduled();
+ } catch (Exception e) {
+ log.error("syncMeterData failed", e);
+ }
+ }
+
+ /** 姣忔棩娓呯悊3涓湀鍓嶇殑鎺ュ彛鏃ュ織 */
+ @Scheduled(cron = "0 30 2 * * ?")
+ public void cleanElectricalLog() {
+ try {
+ ywElectricalBizService.cleanLogBeforeThreeMonths();
+ } catch (Exception e) {
+ log.error("cleanElectricalLog failed", e);
+ }
+ }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalActionsCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalActionsCloudController.java
new file mode 100644
index 0000000..d2b16b8
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalActionsCloudController.java
@@ -0,0 +1,37 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.model.YwElectricalActions;
+import com.doumee.service.business.YwElectricalActionsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 鐢佃〃杩滅▼鎿嶄綔璁板綍
+ */
+@Api(tags = "yw_electrical_actions鎺ュ彛")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywElectricalActions")
+public class YwElectricalActionsCloudController extends BaseController {
+
+ @Autowired
+ private YwElectricalActionsService ywElectricalActionsService;
+
+ @ApiOperation("鍒嗛〉鏌ヨ")
+ @PostMapping("/page")
+ @CloudRequiredPermission("business:ywelectricalactions:query")
+ public ApiResponse<PageData<YwElectricalActions>> findPage(@RequestBody PageWrap<YwElectricalActions> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ if (pageWrap.getModel() != null) {
+ pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+ }
+ return ApiResponse.success(ywElectricalActionsService.findPage(pageWrap));
+ }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalDataCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalDataCloudController.java
index 5c06865..babb5eb 100644
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalDataCloudController.java
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwElectricalDataCloudController.java
@@ -80,7 +80,7 @@
@CloudRequiredPermission("business:ywelectricaldata:exportExcel")
public void exportExcel (@RequestBody PageWrap<YwElectricalData> pageWrap, HttpServletResponse response, @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
- ExcelExporter.build(YwElectricalData.class).export(ywElectricalDataService.findPage(pageWrap).getRecords(), "鐢佃〃鎶勮〃鏁版嵁", response);
+ ExcelExporter.build(YwElectricalData.class).export(ywElectricalDataService.findPage(pageWrap).getRecords(), "鎶勮〃璁板綍", response);
}
@ApiOperation("鏍规嵁ID鏌ヨ")
@@ -89,4 +89,12 @@
public ApiResponse findById(@PathVariable Integer id, @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
return ApiResponse.success(ywElectricalDataService.findById(id));
}
+
+ @PreventRepeat
+ @ApiOperation("绔嬪嵆鎶勮〃锛堝悓姝ョ涓夋柟鎶勮〃鏁版嵁锛�")
+ @PostMapping("/syncAll")
+ @CloudRequiredPermission("business:ywelectricaldata:sync")
+ public ApiResponse<String> syncAll(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywElectricalDataService.syncFromPlatform());
+ }
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/ElectronicToolUtil.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/ElectronicToolUtil.java
index 9e14c73..065155a 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/ElectronicToolUtil.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/ElectronicToolUtil.java
@@ -155,8 +155,8 @@
}
public static void main(String[] args) {
QueryDataRequest param = new QueryDataRequest();
- param.setStart_time("2026-05-19 10:00::00");
- param.setEnd_time("2026-05-19 12:24:25");
+ param.setStart_time("2026-05-25 10:00::00");
+ param.setEnd_time("2026-05-25 14:24:25");
param.setLimit(1);
param.setFunctionids("253");
param.setOffset(0);
@@ -178,10 +178,7 @@
try {
String request_content = JSON.toJSONString(param);
String r = requestAsync(url, request_content);
- TypeReference typeReference =
- new TypeReference< ElectronicBaseResponse<List<MeterDealResponse>> >(){};
- ElectronicBaseResponse result = JSONObject.parseObject(r, typeReference.getType());
- return result;
+ return parseAsyncMeterResponse(r);
}catch (Exception e){
log.error("鐢佃〃==============寮�鎴�",e);
}
@@ -199,10 +196,7 @@
try {
String request_content = JSON.toJSONString(param);
String r = requestAsync(url, request_content);
- TypeReference typeReference =
- new TypeReference< ElectronicBaseResponse<List<MeterDealResponse>> >(){};
- ElectronicBaseResponse result = JSONObject.parseObject(r, typeReference.getType());
- return result;
+ return parseAsyncMeterResponse(r);
}catch (Exception e){
log.error("鐢佃〃==============寮�鎴�",e);
}
@@ -220,10 +214,7 @@
try {
String request_content = JSON.toJSONString(param);
String r = requestAsync(url, request_content);
- TypeReference typeReference =
- new TypeReference< ElectronicBaseResponse<List<MeterDealResponse>> >(){};
- ElectronicBaseResponse result = JSONObject.parseObject(r, typeReference.getType());
- return result;
+ return parseAsyncMeterResponse(r);
}catch (Exception e){
log.error("鐢佃〃==============寮�鎴�",e);
}
@@ -241,17 +232,14 @@
try {
String request_content = JSON.toJSONString(param);
String r = requestAsync(url, request_content);
- TypeReference typeReference =
- new TypeReference< ElectronicBaseResponse<List<MeterDealResponse>> >(){};
- ElectronicBaseResponse result = JSONObject.parseObject(r, typeReference.getType());
- return result;
+ return parseAsyncMeterResponse(r);
}catch (Exception e){
log.error("鐢佃〃==============鎷夊悎闂�",e);
}
return null;
}
/**
- * 鐢佃〃_鎷夊悎闂�
+ * 鐢佃〃_绔嬪嵆鎶勮〃
*/
public static ElectronicBaseResponse eleRead(List<EleReadRequest> param) {
if(param ==null || param.size()==0){
@@ -262,12 +250,9 @@
try {
String request_content = JSON.toJSONString(param);
String r = requestAsync(url, request_content);
- TypeReference typeReference =
- new TypeReference< ElectronicBaseResponse<List<MeterDealResponse>> >(){};
- ElectronicBaseResponse result = JSONObject.parseObject(r, typeReference.getType());
- return result;
+ return parseAsyncMeterResponse(r);
}catch (Exception e){
- log.error("鐢佃〃==============鎷夊悎闂�",e);
+ log.error("鐢佃〃==============绔嬪嵆鎶勮〃",e);
}
return null;
}
@@ -363,6 +348,14 @@
}
return null;
}
+
+ /**
+ * 鏌ヨ鍘嗗彶鏁版嵁鎺ュ彛
+ */
+ public static ElectronicDataResponse queryDataRequest() {
+ return queryDataRequest(null);
+ }
+
/**
* 鏌ヨ鍘嗗彶鏁版嵁鎺ュ彛
*/
@@ -474,6 +467,58 @@
}
// 鎵撳嵃鍝嶅簲鍐呭
+ /**
+ * 瑙f瀽 Api_v2 寮傛鐢佃〃鎿嶄綔鍝嶅簲銆�
+ * 骞冲彴 response_content 鍙兘鏄� JSON 鏁扮粍锛屼篃鍙兘鏄瓧绗︿覆褰㈠紡鐨� JSON 鏁扮粍銆�
+ */
+ public static ElectronicBaseResponse parseAsyncMeterResponse(String r) {
+ if (StringUtils.isBlank(r)) {
+ return null;
+ }
+ try {
+ JSONObject jsonObject = JSON.parseObject(r);
+ if (jsonObject == null) {
+ return null;
+ }
+ ElectronicBaseResponse<List<MeterDealResponse>> result = new ElectronicBaseResponse<>();
+ result.setStatus(jsonObject.getString("status"));
+ result.setTimestamp(jsonObject.getString("timestamp"));
+ result.setError_msg(jsonObject.getString("error_msg"));
+ result.setSign(jsonObject.getString("sign"));
+ result.setResponse_content(parseResponseContentList(jsonObject.get("response_content"), MeterDealResponse.class));
+ return result;
+ } catch (Exception e) {
+ log.error("parse async meter response failed, raw={}", r, e);
+ return null;
+ }
+ }
+
+ private static <T> List<T> parseResponseContentList(Object content, Class<T> clazz) {
+ if (content == null) {
+ return null;
+ }
+ if (content instanceof JSONArray) {
+ return ((JSONArray) content).toJavaList(clazz);
+ }
+ if (content instanceof JSONObject) {
+ T one = ((JSONObject) content).toJavaObject(clazz);
+ return one != null ? Collections.singletonList(one) : null;
+ }
+ String text = String.valueOf(content);
+ if (StringUtils.isBlank(text) || "null".equalsIgnoreCase(text.trim())) {
+ return null;
+ }
+ text = text.trim();
+ if (text.startsWith("[")) {
+ return JSON.parseArray(text, clazz);
+ }
+ if (text.startsWith("{")) {
+ T one = JSON.parseObject(text, clazz);
+ return one != null ? Collections.singletonList(one) : null;
+ }
+ return null;
+ }
+
public static void printResponse(String response) {
JSONObject jsonObject = JSON.parseObject(response);
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicConstant.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicConstant.java
index f9bd2c5..3b3603c 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicConstant.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicConstant.java
@@ -6,15 +6,24 @@
import java.util.List;
public class ElectronicConstant {
- // 鎺堟潈鐮� 鐧诲綍 鍚庡彴鑾峰彇锛� 鑱旂郴浣犵殑鐢叉柟鎴栬�呮槸閿�鍞�
- public static String auth_code = "1f29d378fc6792d5d2b735877993ffb7";
- // 闅忔満瀛楃涓� 鍚庡彴鑾峰彇
- public static String nonce = "lvyOY7fcun719WkxF8ToVYatStgt";
+
+ public static final String DEFAULT_AUTH_CODE = "1f29d378fc6792d5d2b735877993ffb7";
+ public static final String DEFAULT_NONCE = "lvyOY7fcun719WkxF8ToVYatStgt";
+ public static final String DEFAULT_NOTIFY_URL = "http://115.221.15.8:8055/electronic/electricalNotify";
+ public static final String DEFAULT_API_URL = "http://api1.tqdianbiao.com";
+ public static final String DEFAULT_API2_URL = "http://api2.tqdianbiao.com";
+
+ /** 鎺堟潈鐮侊紝鐢辨暟鎹瓧鍏� ELECTRICAL_PARAM 鍔犺浇 */
+ public static String auth_code = DEFAULT_AUTH_CODE;
+ /** 绛惧悕闅忔満涓诧紝鐢辨暟鎹瓧鍏� ELECTRICAL_PARAM 鍔犺浇 */
+ public static String nonce = DEFAULT_NONCE;
public static String debug_meter = "000066660942";
- // 寮傛閫氱煡鍦板潃锛堟湇鍔″鏋滈儴缃插湪鍐呯綉锛屽湪鍏綉鏃犳硶鐩存帴璁块棶鍒帮紝闇�瑕佸湪璺敱鍣ㄤ笂閰嶇疆绔彛鏄犲皠锛屾垨鑰呴厤缃唴缃戠┛閫忓伐鍏锋潵瀹炵幇璁块棶锛� 姝ゅ浠呬负娴嬭瘯 绀轰緥
- public static String notify_url = "http://115.221.15.8:8055/electronic/electricalNotify";
- public static String api_url = "http://api1.tqdianbiao.com";
- public static String api2_url = "http://api2.tqdianbiao.com";
+ /** 寮傛閫氱煡鍦板潃锛岀敱鏁版嵁瀛楀吀 ELECTRICAL_PARAM 鍔犺浇 */
+ public static String notify_url = DEFAULT_NOTIFY_URL;
+ /** Api v1 鏍瑰湴鍧�锛岀敱鏁版嵁瀛楀吀 ELECTRICAL_PARAM 鍔犺浇 */
+ public static String api_url = DEFAULT_API_URL;
+ /** Api v2 鏍瑰湴鍧�锛岀敱鏁版嵁瀛楀吀 ELECTRICAL_PARAM 鍔犺浇 */
+ public static String api2_url = DEFAULT_API2_URL;
/**
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicNotifyStatus.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicNotifyStatus.java
new file mode 100644
index 0000000..68164fa
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/ElectronicNotifyStatus.java
@@ -0,0 +1,55 @@
+package com.doumee.core.device.model;
+
+public enum ElectronicNotifyStatus {
+
+ NOTFOUND("NOTFOUND", "鏈壘鍒�"),
+ ACCEPTED("ACCEPTED", "宸叉帴鍙椾换鍔�"),
+ QUEUE("QUEUE", "闃熷垪涓�"),
+ PROCESSING("PROCESSING", "鎵ц涓�"),
+ DELIVERED("DELIVERED", "宸叉墽琛�"),
+ SUCCESS("SUCCESS", "鎴愬姛"),
+ NOTSUPPORT("NOTSUPPORT", "璁惧涓嶆敮鎸�"),
+ FAIL("FAIL", "澶辫触"),
+ TIMEOUT("TIMEOUT", "浠诲姟瓒呮椂"),
+ CANCELED("CANCELED", "鍙栨秷"),
+ RESPONSE_TIMEOUT("RESPONSE_TIMEOUT", "璁惧鍝嶅簲瓒呮椂"),
+ RESPONSE_FAIL("RESPONSE_FAIL", "璁惧鍝嶅簲澶辫触"),
+ UNKOWN("UNKOWN", "鏈煡");
+
+ private final String code;
+ private final String label;
+
+ ElectronicNotifyStatus(String code, String label) {
+ this.code = code;
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public static ElectronicNotifyStatus fromCode(String code) {
+ if (code == null) {
+ return UNKOWN;
+ }
+ for (ElectronicNotifyStatus s : values()) {
+ if (s.code.equalsIgnoreCase(code.trim())) {
+ return s;
+ }
+ }
+ return UNKOWN;
+ }
+
+ public boolean isTerminalSuccess() {
+ return this == SUCCESS || this == DELIVERED;
+ }
+
+ public boolean isTerminalFail() {
+ return this == FAIL || this == TIMEOUT || this == NOTSUPPORT || this == CANCELED
+ || this == RESPONSE_TIMEOUT || this == RESPONSE_FAIL || this == NOTFOUND;
+ }
+
+ public boolean isInProgress() {
+ return this == ACCEPTED || this == QUEUE || this == PROCESSING;
+ }
+}
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
new file mode 100644
index 0000000..7876274
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleControlApiRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.device.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鎷夊悎闂歌姹傦紙鍦ㄥ紑鎴疯姹傜粨鏋勫熀纭�涓婂鍔� type锛�
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class EleControlApiRequest extends OpenAccountRequest {
+
+ @ApiModelProperty("10鎷夐椄 11鍚堥椄")
+ private int type;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleReadRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleReadRequest.java
index b98fdca..e5602da 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleReadRequest.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/device/model/request/EleReadRequest.java
@@ -25,7 +25,7 @@
private int time_out;
@ApiModelProperty(value = "濡傛灉浼爐rue ,鍒欑郴缁熸帴鍙椾换鍔℃椂璁惧涓嶅湪绾跨洿鎺ヨ繑鍥炲け璐�")
private String must_online;
- @ApiModelProperty(value = "鎿嶄綔绫诲瀷ID")
+ @ApiModelProperty(value = "鎿嶄綔绫诲瀷ID锛堟妱鐢佃〃鏁版嵁涓� 3锛屽嬁涓� queryData functionids=253 娣锋穯锛�")
private int type;
@ApiModelProperty(value = "閲嶈瘯娆℃暟锛�0-3锛� ,榛樿1娆�")
private int retry_times;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwElectricalActionsMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwElectricalActionsMapper.java
new file mode 100644
index 0000000..2a3f4ea
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwElectricalActionsMapper.java
@@ -0,0 +1,9 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwElectricalActions;
+import com.github.yulichang.base.MPJBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface YwElectricalActionsMapper extends MPJBaseMapper<YwElectricalActions> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalEditDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalEditDTO.java
new file mode 100644
index 0000000..c133e64
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalEditDTO.java
@@ -0,0 +1,14 @@
+package com.doumee.dao.business.dto;
+
+import com.doumee.dao.business.model.YwElectrical;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class YwElectricalEditDTO extends YwElectrical {
+
+ private List<Integer> roomIds;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalOperateDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalOperateDTO.java
new file mode 100644
index 0000000..739535a
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwElectricalOperateDTO.java
@@ -0,0 +1,15 @@
+package com.doumee.dao.business.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class YwElectricalOperateDTO {
+
+ private Integer electricalId;
+ /** resetPrepay resetPostpay trip close openAccount recharge readMeter */
+ private String action;
+ private BigDecimal money;
+ private String remark;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectrical.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectrical.java
index 45621e9..f64d116 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectrical.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectrical.java
@@ -134,11 +134,16 @@
@ApiModelProperty("鍏宠仈鍙傛暟妗fID priceid鍜宲aram_id 姣忓潡琛ㄥ彧浼氱敤鍏朵腑涓�绉�")
@ExcelColumn(name="鍏宠仈鍙傛暟妗fID priceid鍜宲aram_id 姣忓潡琛ㄥ彧浼氱敤鍏朵腑涓�绉�",index=36 ,width=10)
private String paramId;
+ @ApiModelProperty("鐢佃〃鍙傛暟ID锛堝叧鑱� yw_electrical_param.id锛�")
+ private Integer electricalParamId;
@ApiModelProperty("缁戝畾鎴块棿鍚嶇О锛堝叧鑱攜w_electrical_room銆亂w_room锛�")
@TableField(exist = false)
private String roomNames;
@TableField(exist = false)
+ private String paramName;
+
+ @TableField(exist = false)
private String warnTypeName;
@TableField(exist = false)
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
new file mode 100644
index 0000000..f6ba30f
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalActions.java
@@ -0,0 +1,57 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.doumee.core.model.LoginUserModel;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@TableName("yw_electrical_actions")
+@ApiModel("鐢佃〃杩滅▼鎿嶄綔璁板綍")
+public class YwElectricalActions extends LoginUserModel {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+ private Integer creator;
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date createDate;
+ private Integer editor;
+ private Date editDate;
+ private Integer isdeleted;
+ private String remark;
+ @ApiModelProperty("鐢佃〃ID")
+ private Integer electricalId;
+ @ApiModelProperty("鎿嶄綔绫诲瀷 1棰勪粯璐规竻闆� 2鍚庝粯璐规竻闆� 3杩滅▼閿�鎴� 4鎷夐椄 5鍚堥椄 6寮�鎴� 7鍏呭�� 8鎶勮〃")
+ private Integer actionType;
+ private String oprId;
+ private String requestBody;
+ private String responseBody;
+ @ApiModelProperty("0澶勭悊涓� 1鎴愬姛 2澶辫触")
+ private Integer status;
+ private String resultMsg;
+
+ @TableField(exist = false)
+ @ApiModelProperty("鐢佃〃鍚嶇О")
+ private String electricalName;
+
+ @TableField(exist = false)
+ @ApiModelProperty("鐢佃〃鍦板潃")
+ private String electricalAddress;
+
+ @TableField(exist = false)
+ @ApiModelProperty("鎿嶄綔寮�濮嬫椂闂�")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date operateTimeBegin;
+
+ @TableField(exist = false)
+ @ApiModelProperty("鎿嶄綔缁撴潫鏃堕棿")
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ private Date operateTimeEnd;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalData.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalData.java
index c4606ac..0b21d7b 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalData.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalData.java
@@ -185,4 +185,29 @@
@ApiModelProperty("鍊嶇巼")
@ExcelColumn(name="鍊嶇巼",index=54 ,width=10)
private BigDecimal radio;
+
+ @TableField(exist = false)
+ private String meterKeyword;
+
+ @TableField(exist = false)
+ private Integer roomId;
+
+ @TableField(exist = false)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date readTimeBegin;
+
+ @TableField(exist = false)
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+ private Date readTimeEnd;
+
+ @TableField(exist = false)
+ private Integer electricalId;
+
+ @TableField(exist = false)
+ @ExcelColumn(name = "鐢佃〃鍚嶇О", index = 55, width = 20)
+ private String electricalName;
+
+ @TableField(exist = false)
+ @ExcelColumn(name = "缁戝畾鎴块棿", index = 56, width = 24)
+ private String roomNames;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalActionsService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalActionsService.java
new file mode 100644
index 0000000..4882fb9
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalActionsService.java
@@ -0,0 +1,13 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.YwElectricalActions;
+
+/**
+ * 鐢佃〃杩滅▼鎿嶄綔璁板綍 Service
+ */
+public interface YwElectricalActionsService {
+
+ PageData<YwElectricalActions> findPage(PageWrap<YwElectricalActions> pageWrap);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalBizService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalBizService.java
new file mode 100644
index 0000000..c2cf405
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalBizService.java
@@ -0,0 +1,34 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.dao.business.dto.YwElectricalEditDTO;
+import com.doumee.dao.business.dto.YwElectricalOperateDTO;
+import com.doumee.dao.business.model.YwElectrical;
+import com.doumee.dao.business.model.YwElectricalData;
+
+import java.util.List;
+import java.util.Map;
+
+public interface YwElectricalBizService {
+
+ YwElectricalEditDTO getDetail(Integer id);
+
+ void updateDetail(YwElectricalEditDTO dto, LoginUserInfo user);
+
+ Map<String, Object> getRemoteInfo(Integer electricalId);
+
+ String operate(YwElectricalOperateDTO dto, LoginUserInfo user);
+
+ boolean handleElectricalNotify(String responseContent, String timestamp, String sign);
+
+ void syncMeterDataScheduled();
+
+ /** 鎵嬪姩浠庣涓夋柟骞冲彴鎷夊彇鎶勮〃鏁版嵁鍏ュ簱 */
+ String syncMeterDataFromPlatform();
+
+ void cleanLogBeforeThreeMonths();
+
+ void enrichList(List<YwElectrical> list);
+
+ void enrichDataList(List<YwElectricalData> list);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalDataService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalDataService.java
index 96dcc0e..73a7488 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalDataService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwElectricalDataService.java
@@ -108,4 +108,9 @@
* @return long
*/
long count(YwElectricalData model);
+
+ /**
+ * 浠庣涓夋柟骞冲彴鍚屾鎶勮〃鏁版嵁
+ */
+ String syncFromPlatform();
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ElectronicConfigService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ElectronicConfigService.java
new file mode 100644
index 0000000..c6bc85e
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ElectronicConfigService.java
@@ -0,0 +1,52 @@
+package com.doumee.service.business.impl;
+
+import com.doumee.biz.system.SystemDictDataBiz;
+import com.doumee.core.device.model.ElectronicConstant;
+import com.doumee.core.utils.Constants;
+import com.doumee.dao.system.model.SystemDictData;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+/**
+ * 浠庢暟鎹瓧鍏� ELECTRICAL_PARAM 鍔犺浇鏅鸿兘鐢佃〃绗笁鏂瑰钩鍙拌繛鎺ュ弬鏁般��
+ */
+@Slf4j
+@Service
+public class ElectronicConfigService {
+
+ @Autowired
+ private SystemDictDataBiz systemDictDataBiz;
+
+ @PostConstruct
+ public void init() {
+ refreshFromDict();
+ }
+
+ /** 閲嶆柊浠庢暟鎹瓧鍏稿姞杞介厤缃紙淇敼瀛楀吀鍚庨噸鍚湇鍔$敓鏁堬紝鎴栦富鍔ㄨ皟鐢ㄥ埛鏂帮級 */
+ public void refreshFromDict() {
+ ElectronicConstant.auth_code = read(Constants.ELECTRICAL_AUTH_CODE, ElectronicConstant.DEFAULT_AUTH_CODE);
+ ElectronicConstant.nonce = read(Constants.ELECTRICAL_NONCE, ElectronicConstant.DEFAULT_NONCE);
+ ElectronicConstant.notify_url = read(Constants.ELECTRICAL_NOTIFY_URL, ElectronicConstant.DEFAULT_NOTIFY_URL);
+ ElectronicConstant.api_url = read(Constants.ELECTRICAL_API_URL, ElectronicConstant.DEFAULT_API_URL);
+ ElectronicConstant.api2_url = read(Constants.ELECTRICAL_API2_URL, ElectronicConstant.DEFAULT_API2_URL);
+ log.info("electrical config loaded from dict {}, api_url={}, api2_url={}, notify_url={}",
+ Constants.ELECTRICAL_PARAM, ElectronicConstant.api_url, ElectronicConstant.api2_url, ElectronicConstant.notify_url);
+ }
+
+ private String read(String label, String defaultValue) {
+ try {
+ SystemDictData data = systemDictDataBiz.queryByCode(Constants.ELECTRICAL_PARAM, label);
+ if (data != null && StringUtils.isNotBlank(data.getCode())) {
+ return data.getCode().trim();
+ }
+ log.warn("electrical config [{}] empty, use default", label);
+ } catch (Exception e) {
+ log.warn("electrical config [{}] load failed, use default", label, e);
+ }
+ return defaultValue;
+ }
+}
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
new file mode 100644
index 0000000..999a070
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalActionsServiceImpl.java
@@ -0,0 +1,53 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.Constants;
+import com.doumee.core.utils.Utils;
+import com.doumee.dao.business.YwElectricalActionsMapper;
+import com.doumee.dao.business.model.YwElectrical;
+import com.doumee.dao.business.model.YwElectricalActions;
+import com.doumee.service.business.YwElectricalActionsService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 鐢佃〃杩滅▼鎿嶄綔璁板綍 Service 瀹炵幇
+ */
+@Service
+public class YwElectricalActionsServiceImpl implements YwElectricalActionsService {
+
+ @Autowired
+ private YwElectricalActionsMapper ywElectricalActionsMapper;
+
+ @Override
+ public PageData<YwElectricalActions> findPage(PageWrap<YwElectricalActions> pageWrap) {
+ IPage<YwElectricalActions> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ YwElectricalActions model = pageWrap.getModel() == null ? new YwElectricalActions() : pageWrap.getModel();
+ Utils.MP.blankToNull(model);
+
+ MPJLambdaWrapper<YwElectricalActions> queryWrapper = new MPJLambdaWrapper<>();
+ queryWrapper.selectAll(YwElectricalActions.class)
+ .selectAs(YwElectrical::getName, YwElectricalActions::getElectricalName)
+ .selectAs(YwElectrical::getAddress, YwElectricalActions::getElectricalAddress)
+ .leftJoin(YwElectrical.class, YwElectrical::getId, YwElectricalActions::getElectricalId)
+ .eq(YwElectricalActions::getIsdeleted, Constants.ZERO);
+
+ if (model.getActionType() != null) {
+ queryWrapper.eq(YwElectricalActions::getActionType, model.getActionType());
+ }
+ if (model.getOperateTimeBegin() != null) {
+ queryWrapper.ge(YwElectricalActions::getCreateDate, Utils.Date.getStart(model.getOperateTimeBegin()));
+ }
+ if (model.getOperateTimeEnd() != null) {
+ queryWrapper.le(YwElectricalActions::getCreateDate, Utils.Date.getEnd(model.getOperateTimeEnd()));
+ }
+
+ queryWrapper.orderByDesc(YwElectricalActions::getId);
+ IPage<YwElectricalActions> result = ywElectricalActionsMapper.selectJoinPage(page, YwElectricalActions.class, queryWrapper);
+ return PageData.from(result);
+ }
+}
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
new file mode 100644
index 0000000..c873faf
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalBizServiceImpl.java
@@ -0,0 +1,939 @@
+package com.doumee.service.business.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.device.ElectronicToolUtil;
+import com.doumee.core.device.model.ElectronicConstant;
+import com.doumee.core.device.model.ElectronicNotifyStatus;
+import com.doumee.core.device.model.request.*;
+import com.doumee.core.device.model.response.ElectronicBaseResponse;
+import com.doumee.core.device.model.response.ElectronicDataResponse;
+import com.doumee.core.device.model.response.QueryDataInfoResponse;
+import com.doumee.core.device.model.response.QueryDataV2Response;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.utils.Constants;
+import com.doumee.core.utils.DateUtil;
+import com.doumee.dao.business.*;
+import com.doumee.dao.business.dto.YwElectricalEditDTO;
+import com.doumee.dao.business.dto.YwElectricalOperateDTO;
+import com.doumee.dao.business.model.*;
+import com.doumee.service.business.YwElectricalBizService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class YwElectricalBizServiceImpl implements YwElectricalBizService {
+
+ private static final Logger log = LoggerFactory.getLogger(YwElectricalBizServiceImpl.class);
+
+ public static final int ACTION_RESET_PREPAY = 1;
+ public static final int ACTION_RESET_POSTPAY = 2;
+ public static final int ACTION_REMOTE_CLOSE = 3;
+ public static final int ACTION_TRIP = 4;
+ public static final int ACTION_CLOSE = 5;
+ public static final int ACTION_OPEN = 6;
+ public static final int ACTION_RECHARGE = 7;
+ public static final int ACTION_READ = 8;
+
+ /** ele_read 鎶勭數琛ㄦ暟鎹搷浣滅被鍨嬶紙涓� queryData functionids=253 涓嶅悓锛� */
+ private static final int ELE_READ_TYPE_METER = 3;
+ /** queryData 鐢佃〃鐘舵�佽鎯呭姛鑳� ID */
+ private static final String QUERY_DATA_FUNCTION_METER_STATUS = "253";
+
+ @Autowired
+ private YwElectricalMapper ywElectricalMapper;
+ @Autowired
+ private YwElectricalRoomMapper ywElectricalRoomMapper;
+ @Autowired
+ private YwElectricalActionsMapper ywElectricalActionsMapper;
+ @Autowired
+ private YwElectricalLogMapper ywElectricalLogMapper;
+ @Autowired
+ private YwElectricalDataMapper ywElectricalDataMapper;
+ @Autowired
+ private YwElectricalChargeMapper ywElectricalChargeMapper;
+
+ @Override
+ public YwElectricalEditDTO getDetail(Integer id) {
+ YwElectrical e = ywElectricalMapper.selectById(id);
+ if (e == null || Objects.equals(e.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY);
+ }
+ YwElectricalEditDTO dto = new YwElectricalEditDTO();
+ BeanUtils.copyProperties(e, dto);
+ List<YwElectricalRoom> rooms = ywElectricalRoomMapper.selectList(new QueryWrapper<YwElectricalRoom>().lambda()
+ .eq(YwElectricalRoom::getIsdeleted, Constants.ZERO)
+ .eq(YwElectricalRoom::getType, Constants.ZERO)
+ .eq(YwElectricalRoom::getObjId, id));
+ dto.setRoomIds(rooms.stream().map(YwElectricalRoom::getRoomId).filter(Objects::nonNull).collect(Collectors.toList()));
+ fillRoomNames(Collections.singletonList(dto));
+ return dto;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateDetail(YwElectricalEditDTO dto, LoginUserInfo user) {
+ if (dto == null || dto.getId() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ ywElectricalMapper.update(null, new UpdateWrapper<YwElectrical>().lambda()
+ .eq(YwElectrical::getId, dto.getId())
+ .set(YwElectrical::getRate, dto.getRate() != null ? dto.getRate() : BigDecimal.ONE)
+ .set(YwElectrical::getElectricalParamId, dto.getElectricalParamId())
+ .set(YwElectrical::getEdirot, user.getId())
+ .set(YwElectrical::getEditDate, new Date()));
+ saveRooms(dto.getId(), dto.getRoomIds(), user);
+ }
+
+ private void saveRooms(Integer electricalId, List<Integer> roomIds, LoginUserInfo user) {
+ ywElectricalRoomMapper.update(null, new UpdateWrapper<YwElectricalRoom>().lambda()
+ .set(YwElectricalRoom::getIsdeleted, Constants.ONE)
+ .set(YwElectricalRoom::getEditDate, new Date())
+ .set(YwElectricalRoom::getEditor, user.getId())
+ .eq(YwElectricalRoom::getObjId, electricalId)
+ .eq(YwElectricalRoom::getType, Constants.ZERO));
+ if (CollectionUtils.isEmpty(roomIds)) {
+ return;
+ }
+ int sort = 0;
+ for (Integer roomId : roomIds) {
+ if (roomId == null) continue;
+ YwElectricalRoom r = new YwElectricalRoom();
+ r.setCreator(user.getId());
+ r.setCreateDate(new Date());
+ r.setEditor(user.getId());
+ r.setEditDate(new Date());
+ r.setIsdeleted(Constants.ZERO);
+ r.setType(Constants.ZERO);
+ r.setObjId(electricalId);
+ r.setRoomId(roomId);
+ r.setSortnum(++sort);
+ ywElectricalRoomMapper.insert(r);
+ }
+ }
+
+ @Override
+ public Map<String, Object> getRemoteInfo(Integer electricalId) {
+ YwElectrical e = requireElectrical(electricalId);
+ fillRoomNames(Collections.singletonList(e));
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("electrical", e);
+ YwElectricalData latest = findLatestData(e.getId(), e.getAddress());
+ map.put("latestData", latest);
+ map.put("purchaseCount", latest != null && latest.getCountnum() != null ? latest.getCountnum() : "0");
+ return map;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String operate(YwElectricalOperateDTO dto, LoginUserInfo user) {
+ YwElectrical e = requireElectrical(dto.getElectricalId());
+ String action = dto.getAction();
+ if (StringUtils.isBlank(action)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ switch (action) {
+ case "resetPrepay":
+ return doSecurityReset(e, "default", ACTION_RESET_PREPAY, user);
+ case "resetPostpay":
+ return doSecurityReset(e, "noprepay", ACTION_RESET_POSTPAY, user);
+ case "trip":
+ return doEleControl(e, 10, ACTION_TRIP, user);
+ case "close":
+ return doEleControl(e, 11, ACTION_CLOSE, user);
+ case "openAccount":
+ return doOpenAccount(e, dto, user);
+ case "recharge":
+ return doRecharge(e, dto, user);
+ case "readMeter":
+ return doReadMeter(e, user);
+ default:
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ }
+
+ private String doSecurityReset(YwElectrical e, String paymentMode, int actionType, LoginUserInfo user) {
+ String oprId = newOprId();
+ SecurityResetRequest req = new SecurityResetRequest();
+ req.setOpr_id(oprId);
+ req.setCid(e.getCollectorId());
+ req.setAddress(e.getAddress());
+ req.setTime_out(86400);
+ req.setMust_online("false");
+ req.setRetry_times(1);
+ SecurityResetParamRequest p = new SecurityResetParamRequest();
+ p.setPaymentmode(paymentMode);
+ p.setAccount_id(StringUtils.defaultIfBlank(e.getAccountId(), "1"));
+ req.setParams(p);
+ String reqJson = JSON.toJSONString(Collections.singletonList(req));
+ ElectronicBaseResponse resp = ElectronicToolUtil.eleSecurityReset(Collections.singletonList(req));
+ return finishAsync(e, actionType, oprId, "/Api_v2/ele_security/reset", reqJson, resp, user);
+ }
+
+ private String doEleControl(YwElectrical e, int type, int actionType, LoginUserInfo user) {
+ String oprId = newOprId();
+ EleControlApiRequest req = new EleControlApiRequest();
+ req.setOpr_id(oprId);
+ req.setCid(e.getCollectorId());
+ req.setAddress(e.getAddress());
+ req.setTime_out(86400);
+ req.setMust_online("false");
+ req.setRetry_times(1);
+ req.setType(type);
+ String reqJson = JSON.toJSONString(Collections.singletonList(req));
+ 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);
+ }
+
+ private String doOpenAccount(YwElectrical e, YwElectricalOperateDTO dto, LoginUserInfo user) {
+ String oprId = newOprId();
+ OpenAccountRequest req = buildOpenAccountRequest(e, oprId, dto.getMoney(), dto.getRemark());
+ String reqJson = JSON.toJSONString(Collections.singletonList(req));
+ ElectronicBaseResponse resp = ElectronicToolUtil.openAcount(Collections.singletonList(req));
+ return finishAsync(e, ACTION_OPEN, oprId, "/Api_v2/ele_security/open_acount", reqJson, resp, user);
+ }
+
+ private String doRecharge(YwElectrical e, YwElectricalOperateDTO dto, LoginUserInfo user) {
+ if (!Objects.equals(e.getAccountStatus(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鐢佃〃鏈紑鎴凤紝璇峰厛寮�鎴�");
+ }
+ String oprId = newOprId();
+ OpenAccountRequest req = buildOpenAccountRequest(e, oprId, dto.getMoney(), dto.getRemark());
+ String reqJson = JSON.toJSONString(Collections.singletonList(req));
+ ElectronicBaseResponse resp = ElectronicToolUtil.recharger(Collections.singletonList(req));
+ String msg = finishAsync(e, ACTION_RECHARGE, oprId, "/Api_v2/ele_security/recharge", reqJson, resp, user);
+ if (isSuccess(resp)) {
+ saveChargeRecord(e, oprId, dto, user);
+ }
+ return msg;
+ }
+
+ private String doReadMeter(YwElectrical e, LoginUserInfo user) {
+ String oprId = newOprId();
+ EleReadRequest req = new EleReadRequest();
+ req.setOpr_id(oprId);
+ req.setCid(e.getCollectorId());
+ req.setAddress(e.getAddress());
+ req.setTime_out(86400);
+ req.setMust_online("false");
+ req.setType(ELE_READ_TYPE_METER);
+ req.setRetry_times(1);
+ String reqJson = JSON.toJSONString(Collections.singletonList(req));
+ ElectronicBaseResponse resp = ElectronicToolUtil.eleRead(Collections.singletonList(req));
+ String msg = finishAsync(e, ACTION_READ, oprId, "/Api_v2/ele_read", reqJson, resp, user);
+ if (!isSuccess(resp)) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), msg);
+ }
+ boolean synced = syncMeterDataForElectrical(e);
+ refreshBalanceFromData(e);
+ return synced ? "鎶勮〃鎴愬姛锛岀敤閲忎綑棰濆凡鏇存柊" : "鎶勮〃璇锋眰宸叉彁浜わ紝鏆傛棤鏂版妱琛ㄦ暟鎹紝璇风◢鍚庡埛鏂版煡鐪�";
+ }
+
+ private OpenAccountRequest buildOpenAccountRequest(YwElectrical e, String oprId, BigDecimal money, String remark) {
+ OpenAccountRequest req = new OpenAccountRequest();
+ req.setOpr_id(oprId);
+ req.setCid(e.getCollectorId());
+ req.setAddress(e.getAddress());
+ req.setTime_out(86400);
+ req.setMust_online("false");
+ req.setRetry_times(1);
+ OpenAccountParamRequest p = new OpenAccountParamRequest();
+ p.setMoney(money != null ? money.toPlainString() : "0");
+ p.setAccount_id(StringUtils.defaultIfBlank(e.getAccountId(), "1"));
+ p.setCount("1");
+ p.setRate(e.getRate() != null ? e.getRate().toPlainString() : "1");
+ req.setParams(p);
+ return req;
+ }
+
+ private String finishAsync(YwElectrical e, int actionType, String oprId, String apiName, String reqJson,
+ ElectronicBaseResponse resp, LoginUserInfo user) {
+ saveLog(e, apiName, reqJson, resp, user);
+ YwElectricalActions act = new YwElectricalActions();
+ act.setCreator(user.getId());
+ act.setCreateDate(new Date());
+ act.setEditor(user.getId());
+ act.setEditDate(new Date());
+ act.setIsdeleted(Constants.ZERO);
+ act.setElectricalId(e.getId());
+ act.setActionType(actionType);
+ act.setOprId(oprId);
+ act.setRequestBody(reqJson);
+ act.setResponseBody(resp != null ? JSON.toJSONString(resp) : null);
+ if (isSuccess(resp)) {
+ act.setStatus(Constants.ZERO);
+ act.setResultMsg("宸叉彁浜わ紝绛夊緟骞冲彴鎵ц");
+ } else {
+ act.setStatus(Constants.TWO);
+ act.setResultMsg(resp != null ? resp.getError_msg() : "璋冪敤澶辫触");
+ }
+ ywElectricalActionsMapper.insert(act);
+ return isSuccess(resp) ? "鎻愪氦鎴愬姛锛岃绋嶅悗鍦ㄦ搷浣滆褰曟垨鍏呭�艰褰曚腑鏌ョ湅缁撴灉" : act.getResultMsg();
+ }
+
+ private void saveLog(YwElectrical e, String apiName, String reqJson, ElectronicBaseResponse resp, LoginUserInfo user) {
+ YwElectricalLog logRow = new YwElectricalLog();
+ logRow.setCreator(user.getId());
+ logRow.setCreateDate(new Date());
+ logRow.setEditor(user.getId());
+ logRow.setEditDate(new Date());
+ logRow.setIsdeleted(Constants.ZERO);
+ logRow.setDeviceType(Constants.ZERO);
+ logRow.setType(Constants.ZERO);
+ logRow.setName(apiName);
+ logRow.setUrl(apiName);
+ logRow.setRequest(reqJson);
+ logRow.setRepose(resp != null ? JSON.toJSONString(resp) : null);
+ logRow.setSuccess(isSuccess(resp) ? Constants.ZERO : Constants.ONE);
+ logRow.setObjId(String.valueOf(e.getId()));
+ ywElectricalLogMapper.insert(logRow);
+ }
+
+ private boolean isSuccess(ElectronicBaseResponse resp) {
+ return resp != null && "SUCCESS".equalsIgnoreCase(resp.getStatus());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public boolean handleElectricalNotify(String responseContent, String timestamp, String sign) {
+ if (!ElectronicToolUtil.verifyNotifySign(responseContent, timestamp, sign)) {
+ log.warn("electricalNotify sign check failed, timestamp={}", timestamp);
+ return false;
+ }
+ JSONArray items;
+ try {
+ items = JSON.parseArray(responseContent);
+ } catch (Exception e) {
+ log.warn("electricalNotify invalid response_content", e);
+ return false;
+ }
+ if (items == null || items.isEmpty()) {
+ return true;
+ }
+ for (int i = 0; i < items.size(); i++) {
+ applyNotifyItem(items.getJSONObject(i));
+ }
+ return true;
+ }
+
+ private void applyNotifyItem(JSONObject item) {
+ if (item == null) return;
+ String oprId = item.getString("opr_id");
+ if (StringUtils.isBlank(oprId)) return;
+ YwElectricalActions act = ywElectricalActionsMapper.selectOne(new QueryWrapper<YwElectricalActions>().lambda()
+ .eq(YwElectricalActions::getOprId, oprId)
+ .eq(YwElectricalActions::getIsdeleted, Constants.ZERO)
+ .orderByDesc(YwElectricalActions::getCreateDate)
+ .last("limit 1"));
+ if (act == null) {
+ log.info("electricalNotify no action for opr_id={}", oprId);
+ return;
+ }
+ ElectronicNotifyStatus ns = ElectronicNotifyStatus.fromCode(item.getString("status"));
+ String errMsg = item.get("error_msg") != null ? String.valueOf(item.get("error_msg")) : null;
+ String resultMsg = ns.getLabel() + (StringUtils.isNotBlank(errMsg) ? "锛�" + errMsg : "");
+
+ YwElectricalActions upd = new YwElectricalActions();
+ upd.setId(act.getId());
+ upd.setEditDate(new Date());
+ upd.setResponseBody(item.toJSONString());
+ upd.setResultMsg(resultMsg);
+ if (ns.isTerminalSuccess()) {
+ upd.setStatus(Constants.ONE);
+ } else if (ns.isTerminalFail()) {
+ upd.setStatus(Constants.TWO);
+ } else if (ns.isInProgress()) {
+ upd.setStatus(Constants.ZERO);
+ }
+ ywElectricalActionsMapper.updateById(upd);
+
+ YwElectrical e = ywElectricalMapper.selectById(act.getElectricalId());
+ if (e == null) return;
+ saveNotifyLog(e, item, ns);
+ if (ns.isTerminalSuccess()) {
+ applyNotifySideEffect(e, act.getActionType());
+ if (Objects.equals(act.getActionType(), ACTION_RECHARGE)) {
+ updateChargeByNotify(oprId, Constants.ONE, resultMsg);
+ }
+ } else if (ns.isTerminalFail() && Objects.equals(act.getActionType(), ACTION_RECHARGE)) {
+ updateChargeByNotify(oprId, Constants.TWO, resultMsg);
+ }
+ }
+
+ private void saveChargeRecord(YwElectrical e, String oprId, YwElectricalOperateDTO dto, LoginUserInfo user) {
+ fillRoomNames(Collections.singletonList(e));
+ YwElectricalCharge charge = new YwElectricalCharge();
+ charge.setCreator(user.getId());
+ charge.setCreateDate(new Date());
+ charge.setEditor(user.getId());
+ charge.setEditDate(new Date());
+ charge.setIsdeleted(Constants.ZERO);
+ charge.setType(Constants.ZERO);
+ charge.setObjId(e.getId());
+ charge.setAddress(e.getAddress());
+ charge.setName(e.getName());
+ charge.setCId(e.getCollectorId());
+ charge.setMoney(dto.getMoney() != null ? dto.getMoney() : BigDecimal.ZERO);
+ charge.setRemark(dto.getRemark());
+ charge.setOprId(oprId);
+ charge.setStatus(Constants.ZERO);
+ charge.setStatusTime(new Date());
+ charge.setStatusInfo("鍏呭�间腑");
+ charge.setBanlance(e.getBalance());
+ charge.setRoomNames(e.getRoomNames());
+ if (StringUtils.isNotBlank(e.getParamId())) {
+ try {
+ charge.setParamId(Integer.parseInt(e.getParamId()));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ ywElectricalChargeMapper.insert(charge);
+ }
+
+ private void updateChargeByNotify(String oprId, int status, String statusInfo) {
+ ywElectricalChargeMapper.update(null, new UpdateWrapper<YwElectricalCharge>().lambda()
+ .set(YwElectricalCharge::getStatus, status)
+ .set(YwElectricalCharge::getStatusTime, new Date())
+ .set(YwElectricalCharge::getStatusInfo, statusInfo)
+ .set(YwElectricalCharge::getEditDate, new Date())
+ .eq(YwElectricalCharge::getOprId, oprId)
+ .eq(YwElectricalCharge::getIsdeleted, Constants.ZERO));
+ }
+
+ private void saveNotifyLog(YwElectrical e, JSONObject item, ElectronicNotifyStatus ns) {
+ YwElectricalLog logRow = new YwElectricalLog();
+ logRow.setCreateDate(new Date());
+ logRow.setEditDate(new Date());
+ logRow.setIsdeleted(Constants.ZERO);
+ logRow.setDeviceType(Constants.ZERO);
+ logRow.setType(Constants.ONE);
+ logRow.setName("electricalNotify");
+ logRow.setUrl("/electronic/electricalNotify");
+ logRow.setRequest(null);
+ logRow.setRepose(item.toJSONString());
+ logRow.setSuccess(ns.isTerminalFail() ? Constants.ONE : Constants.ZERO);
+ logRow.setObjId(String.valueOf(e.getId()));
+ ywElectricalLogMapper.insert(logRow);
+ }
+
+ private void applyNotifySideEffect(YwElectrical e, Integer actionType) {
+ if (actionType == null) return;
+ UpdateWrapper<YwElectrical> uw = new UpdateWrapper<>();
+ uw.lambda().eq(YwElectrical::getId, e.getId()).set(YwElectrical::getEditDate, new Date());
+ switch (actionType) {
+ case ACTION_TRIP:
+ uw.lambda().set(YwElectrical::getRelayStatus, "0");
+ ywElectricalMapper.update(null, uw);
+ break;
+ case ACTION_CLOSE:
+ uw.lambda().set(YwElectrical::getRelayStatus, "1");
+ ywElectricalMapper.update(null, uw);
+ break;
+ case ACTION_OPEN:
+ uw.lambda().set(YwElectrical::getAccountStatus, Constants.ONE)
+ .set(YwElectrical::getLastOpenDate, new Date());
+ ywElectricalMapper.update(null, uw);
+ break;
+ case ACTION_RECHARGE:
+ case ACTION_READ:
+ syncMeterDataForElectrical(e);
+ refreshBalanceFromData(e);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void refreshBalanceFromData(YwElectrical e) {
+ YwElectricalData data = findLatestData(e.getId(), e.getAddress());
+ if (data == null) return;
+ UpdateWrapper<YwElectrical> uw = new UpdateWrapper<>();
+ uw.lambda().eq(YwElectrical::getId, e.getId());
+ if (StringUtils.isNotBlank(data.getZhygzdl())) {
+ uw.lambda().set(YwElectrical::getBalanceBattery, data.getZhygzdl());
+ }
+ if (StringUtils.isNotBlank(data.getYe())) {
+ try {
+ uw.lambda().set(YwElectrical::getBalance, new BigDecimal(data.getYe()));
+ } catch (Exception ignored) {
+ }
+ }
+ uw.lambda().set(YwElectrical::getBalanceTime, new Date());
+ uw.lambda().set(YwElectrical::getEditDate, new Date());
+ ywElectricalMapper.update(null, uw);
+ }
+
+ private YwElectricalData findLatestData(Integer electricalId, String address) {
+ QueryWrapper<YwElectricalData> q = new QueryWrapper<>();
+ q.lambda().eq(YwElectricalData::getIsdeleted, Constants.ZERO)
+ .and(w -> w.eq(YwElectricalData::getDeviceId, String.valueOf(electricalId))
+ .or().eq(StringUtils.isNotBlank(address), YwElectricalData::getAddress, address))
+ .orderByDesc(YwElectricalData::getCreateDate).last("limit 1");
+ return ywElectricalDataMapper.selectOne(q);
+ }
+
+ @Override
+ public void syncMeterDataScheduled() {
+ try {
+ syncMeterDataInternal();
+ } catch (Exception e) {
+ log.warn("syncMeterDataScheduled failed", e);
+ }
+ }
+
+ @Override
+ public String syncMeterDataFromPlatform() {
+ MeterDataSyncStats stats = syncMeterDataInternal();
+ return "鎶勮〃鍚屾瀹屾垚锛氭柊澧炪��" + stats.addCount + "銆戞潯锛岃烦杩囬噸澶嶃��" + stats.skipCount + "銆戞潯";
+ }
+
+ private static class MeterDataSyncStats {
+ private int addCount;
+ private int skipCount;
+ }
+
+ private MeterDataSyncStats syncMeterDataInternal() {
+ MeterDataSyncStats stats = new MeterDataSyncStats();
+ String startTime = resolveSyncStartTime();
+ QueryDataRequest param = buildQueryDataRequest(startTime, DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ log.info("sync meter data, start_time={}, end_time={}", startTime, param.getEnd_time());
+ List<QueryDataInfoResponse> list = fetchQueryDataList(param);
+ if (CollectionUtils.isEmpty(list)) {
+ return stats;
+ }
+ for (QueryDataInfoResponse item : list) {
+ if (item == null || StringUtils.isBlank(item.getAddress())) {
+ continue;
+ }
+ YwElectrical meter = ywElectricalMapper.selectOne(new QueryWrapper<YwElectrical>().lambda()
+ .eq(YwElectrical::getIsdeleted, Constants.ZERO)
+ .eq(YwElectrical::getAddress, item.getAddress())
+ .last("limit 1"));
+ if (saveDataRow(item, meter != null ? meter.getId() : null)) {
+ stats.addCount++;
+ if (meter != null) {
+ refreshBalanceFromData(meter);
+ }
+ } else {
+ stats.skipCount++;
+ }
+ }
+ return stats;
+ }
+
+ /** 鍗曡〃鎶勮〃鍚庝粠绗笁鏂规媺鍙栨渶鏂版暟鎹叆搴擄紝杩斿洖鏄惁鏈夋柊璁板綍 */
+ private boolean syncMeterDataForElectrical(YwElectrical e) {
+ if (e == null || StringUtils.isBlank(e.getAddress())) {
+ return false;
+ }
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.HOUR_OF_DAY, -24);
+ QueryDataRequest param = buildQueryDataRequest(
+ DateUtil.formatDate(cal.getTime(), "yyyy-MM-dd HH:mm:ss"),
+ DateUtil.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss"));
+ List<QueryDataInfoResponse> list;
+ try {
+ list = fetchQueryDataList(param);
+ } catch (BusinessException ex) {
+ log.warn("sync meter data for electricalId={} failed: {}", e.getId(), ex.getMessage());
+ return false;
+ }
+ if (CollectionUtils.isEmpty(list)) {
+ return false;
+ }
+ String address = e.getAddress().trim();
+ boolean updated = false;
+ for (QueryDataInfoResponse item : list) {
+ if (item == null || StringUtils.isBlank(item.getAddress())) {
+ continue;
+ }
+ if (!address.equalsIgnoreCase(item.getAddress().trim())) {
+ continue;
+ }
+ if (saveDataRow(item, e.getId())) {
+ updated = true;
+ }
+ }
+ return updated;
+ }
+
+ private QueryDataRequest buildQueryDataRequest(String startTime, String endTime) {
+ QueryDataRequest param = new QueryDataRequest();
+ param.setType("json");
+ param.setFunctionids(QUERY_DATA_FUNCTION_METER_STATUS);
+ param.setStart_time(startTime);
+ param.setEnd_time(endTime);
+ param.setOffset(0);
+ param.setLimit(500);
+ return param;
+ }
+
+ private List<QueryDataInfoResponse> fetchQueryDataList(QueryDataRequest param) {
+ ElectronicDataResponse response = ElectronicToolUtil.queryDataRequest(param);
+ if (!ElectronicToolUtil.isDataApiSuccess(response)) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),
+ ElectronicToolUtil.dataApiErrorMessage(response, "鎶勮〃鏁版嵁鍚屾澶辫触"));
+ }
+ return parseQueryDataList(response);
+ }
+
+ private List<QueryDataInfoResponse> parseQueryDataList(ElectronicDataResponse response) {
+ if (response == null || response.getData() == null) {
+ return Collections.emptyList();
+ }
+ String json = JSON.toJSONString(response.getData());
+ if (StringUtils.isBlank(json) || "null".equals(json)) {
+ return Collections.emptyList();
+ }
+ try {
+ String trimmed = json.trim();
+ if (trimmed.startsWith("[")) {
+ return JSON.parseArray(trimmed, QueryDataInfoResponse.class);
+ }
+ if (trimmed.startsWith("{")) {
+ QueryDataInfoResponse one = JSON.parseObject(trimmed, QueryDataInfoResponse.class);
+ return one != null ? Collections.singletonList(one) : Collections.emptyList();
+ }
+ } catch (Exception e) {
+ log.warn("parse queryDataRequest failed", e);
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "鎶勮〃鏁版嵁瑙f瀽澶辫触");
+ }
+ return Collections.emptyList();
+ }
+
+ /** 鍙栨湰鍦板凡鍚屾鐨勬渶澶ф妱琛ㄦ椂闂翠綔涓轰笅娆� start_time锛涙棤璁板綍鏃堕粯璁よ繎24灏忔椂 */
+ private String resolveSyncStartTime() {
+ YwElectricalData latest = ywElectricalDataMapper.selectOne(new QueryWrapper<YwElectricalData>().lambda()
+ .eq(YwElectricalData::getIsdeleted, Constants.ZERO)
+ .isNotNull(YwElectricalData::getAddTime)
+ .ne(YwElectricalData::getAddTime, "")
+ .orderByDesc(YwElectricalData::getAddTime)
+ .last("limit 1"));
+ if (latest != null && StringUtils.isNotBlank(latest.getAddTime())) {
+ String addTime = latest.getAddTime().trim();
+ if (addTime.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
+ return addTime;
+ }
+ log.warn("invalid addTime in yw_electrical_data, fallback to 24h: {}", addTime);
+ }
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.HOUR_OF_DAY, -24);
+ return DateUtil.formatDate(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
+ }
+
+ private boolean existsByDataId(String dataId) {
+ if (StringUtils.isBlank(dataId)) {
+ return false;
+ }
+ return ywElectricalDataMapper.selectCount(new QueryWrapper<YwElectricalData>().lambda()
+ .eq(YwElectricalData::getIsdeleted, Constants.ZERO)
+ .eq(YwElectricalData::getDataId, dataId.trim())) > 0;
+ }
+
+ /** 淇濆瓨鎶勮〃璁板綍锛沝ataId 宸插瓨鍦ㄥ垯璺宠繃锛岃繑鍥� false */
+ private boolean saveDataRow(QueryDataInfoResponse item, Integer electricalId) {
+ String dataId = item.getId();
+ if (StringUtils.isNotBlank(dataId) && existsByDataId(dataId)) {
+ return false;
+ }
+ YwElectricalData row = new YwElectricalData();
+ row.setCreateDate(new Date());
+ row.setEditDate(new Date());
+ row.setIsdeleted(Constants.ZERO);
+ row.setDeviceId(electricalId != null ? String.valueOf(electricalId) : null);
+ applyItemToRow(row, item);
+ ywElectricalDataMapper.insert(row);
+ return true;
+ }
+
+ /** 绗笁鏂� item / data_v2 瀛楁鍚嶄笌鏈湴瀹炰綋宸紓鏄犲皠 */
+ private static final Map<String, String> SYNC_FIELD_ALIASES;
+ private static final Set<String> ITEM_SKIP_KEYS;
+
+ static {
+ Map<String, String> aliases = new HashMap<>();
+ aliases.put("id", "dataId");
+ aliases.put("add_time", "addTime");
+ aliases.put("count", "countnum");
+ SYNC_FIELD_ALIASES = Collections.unmodifiableMap(aliases);
+ ITEM_SKIP_KEYS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("data_v2", "idnum")));
+ }
+
+ /** 瑙f瀽 data 鏁扮粍涓殑 item 灞炴�ф槧灏勫埌 yw_electrical_data */
+ private void applyItemToRow(YwElectricalData row, QueryDataInfoResponse item) {
+ JSONObject itemJson = (JSONObject) JSON.toJSON(item);
+ for (String key : itemJson.keySet()) {
+ if (ITEM_SKIP_KEYS.contains(key)) {
+ continue;
+ }
+ Object val = itemJson.get(key);
+ if (val == null) {
+ continue;
+ }
+ String fieldName = SYNC_FIELD_ALIASES.getOrDefault(key, key);
+ setElectricalDataField(row, fieldName, val);
+ }
+ JSONObject dataV2 = extractDataV2Json(item, itemJson);
+ if (dataV2 != null && !dataV2.isEmpty()) {
+ applyDataV2Json(row, dataV2);
+ }
+ applyDspFallback(row, item.getDsp());
+ }
+
+ private JSONObject extractDataV2Json(QueryDataInfoResponse item, JSONObject itemJson) {
+ if (!CollectionUtils.isEmpty(item.getData_v2())) {
+ return mergeDataV2Json(item.getData_v2());
+ }
+ Object dataV2 = itemJson.get("data_v2");
+ if (dataV2 == null) {
+ return null;
+ }
+ if (dataV2 instanceof JSONObject) {
+ return (JSONObject) dataV2;
+ }
+ if (dataV2 instanceof JSONArray) {
+ JSONArray arr = (JSONArray) dataV2;
+ JSONObject merged = new JSONObject();
+ for (int i = 0; i < arr.size(); i++) {
+ Object element = arr.get(i);
+ if (element instanceof JSONObject) {
+ mergeJsonObject(merged, (JSONObject) element);
+ } else if (element != null) {
+ QueryDataV2Response part = arr.getObject(i, QueryDataV2Response.class);
+ if (part != null) {
+ mergeJsonObject(merged, (JSONObject) JSON.toJSON(part));
+ }
+ }
+ }
+ return merged;
+ }
+ return null;
+ }
+
+ private void mergeJsonObject(JSONObject target, JSONObject source) {
+ for (String key : source.keySet()) {
+ Object val = source.get(key);
+ if (val != null) {
+ target.put(key, val);
+ }
+ }
+ }
+
+ private void applyDspFallback(YwElectricalData row, String dsp) {
+ if (StringUtils.isNotBlank(row.getZhygzdl()) || StringUtils.isBlank(dsp)) {
+ return;
+ }
+ if (dsp.contains("kWh") || dsp.contains("kwh")) {
+ String num = dsp.replaceAll("[^0-9.]", " ").trim().split("\\s+")[0];
+ if (StringUtils.isNotBlank(num)) {
+ row.setZhygzdl(num);
+ }
+ }
+ }
+
+ /** 鍚堝苟 data_v2 鍒楄〃涓悇鏉¤褰曠殑灞炴�э紙鍚庡�艰鐩栧墠鍊硷級 */
+ private JSONObject mergeDataV2Json(List<QueryDataV2Response> dataV2List) {
+ JSONObject merged = new JSONObject();
+ for (QueryDataV2Response part : dataV2List) {
+ if (part == null) {
+ continue;
+ }
+ JSONObject obj = (JSONObject) JSON.toJSON(part);
+ for (String key : obj.keySet()) {
+ Object val = obj.get(key);
+ if (val != null) {
+ merged.put(key, val);
+ }
+ }
+ }
+ return merged;
+ }
+
+ /** 鎸夊睘鎬у悕灏� data_v2 瑙f瀽鍐欏叆 yw_electrical_data 鍚屽悕瀛楁锛堣鐩� item 灞傚悓鍚嶅瓧娈碉級 */
+ private void applyDataV2Json(YwElectricalData row, JSONObject dataV2) {
+ if (dataV2 == null || dataV2.isEmpty()) {
+ return;
+ }
+ for (String key : dataV2.keySet()) {
+ Object val = dataV2.get(key);
+ if (val == null) {
+ continue;
+ }
+ String fieldName = SYNC_FIELD_ALIASES.getOrDefault(key, key);
+ setElectricalDataField(row, fieldName, val);
+ }
+ }
+
+ private void setElectricalDataField(YwElectricalData row, String fieldName, Object val) {
+ if (val == null || StringUtils.isBlank(fieldName)) {
+ return;
+ }
+ try {
+ Field field = YwElectricalData.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Class<?> type = field.getType();
+ if (String.class.equals(type)) {
+ String strVal = convertFieldString(val);
+ if (StringUtils.isNotBlank(strVal)) {
+ field.set(row, strVal);
+ }
+ } else if (Integer.class.equals(type) || int.class.equals(type)) {
+ if (val instanceof Number) {
+ field.set(row, ((Number) val).intValue());
+ } else {
+ String text = String.valueOf(val).trim();
+ if (StringUtils.isNotBlank(text)) {
+ field.set(row, Integer.parseInt(text));
+ }
+ }
+ } else if (BigDecimal.class.equals(type)) {
+ String text = String.valueOf(val).trim();
+ if (StringUtils.isNotBlank(text)) {
+ field.set(row, new BigDecimal(text));
+ }
+ }
+ } catch (NoSuchFieldException | IllegalAccessException | NumberFormatException ignored) {
+ // 蹇界暐绗笁鏂规墿灞曞瓧娈垫垨鏃犳硶杞崲鐨勫��
+ }
+ }
+
+ private String convertFieldString(Object val) {
+ if (val instanceof Boolean) {
+ return String.valueOf(val);
+ }
+ if (val instanceof JSONArray || val instanceof JSONObject || val instanceof Collection) {
+ return JSON.toJSONString(val);
+ }
+ return String.valueOf(val).trim();
+ }
+
+ @Override
+ public void cleanLogBeforeThreeMonths() {
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.MONTH, -3);
+ ywElectricalLogMapper.delete(new QueryWrapper<YwElectricalLog>().lambda()
+ .lt(YwElectricalLog::getCreateDate, cal.getTime()));
+ }
+
+ @Override
+ public void enrichList(List<YwElectrical> list) {
+ fillRoomNames(list);
+ if (CollectionUtils.isEmpty(list)) return;
+ for (YwElectrical row : list) {
+ if (StringUtils.isNotBlank(row.getWarnType())) {
+ row.setWarnTypeName(resolveWarnTypeNames(row.getWarnType()));
+ }
+ }
+ }
+
+ private String resolveWarnTypeNames(String warnType) {
+ String[] codes = warnType.split(",");
+ List<String> names = new ArrayList<>();
+ for (String codeStr : codes) {
+ if (StringUtils.isBlank(codeStr)) {
+ continue;
+ }
+ String trimmed = codeStr.trim();
+ try {
+ int code = Integer.parseInt(trimmed);
+ ElectronicConstant.warningDefId def = ElectronicConstant.warningDefId.getByKey(code);
+ names.add(def != null ? def.getName() : trimmed);
+ } catch (NumberFormatException ex) {
+ names.add(trimmed);
+ }
+ }
+ return names.isEmpty() ? warnType : String.join(",", names);
+ }
+
+ @Override
+ public void enrichDataList(List<YwElectricalData> list) {
+ if (CollectionUtils.isEmpty(list)) {
+ return;
+ }
+ List<YwElectrical> temp = new ArrayList<>();
+ for (YwElectricalData row : list) {
+ if (StringUtils.isBlank(row.getElectricalName()) && StringUtils.isNotBlank(row.getName())) {
+ row.setElectricalName(row.getName());
+ }
+ if (row.getElectricalId() == null) {
+ continue;
+ }
+ YwElectrical e = new YwElectrical();
+ e.setId(row.getElectricalId());
+ temp.add(e);
+ }
+ fillRoomNames(temp);
+ Map<Integer, String> roomMap = temp.stream()
+ .filter(e -> StringUtils.isNotBlank(e.getRoomNames()))
+ .collect(Collectors.toMap(YwElectrical::getId, YwElectrical::getRoomNames, (a, b) -> a));
+ for (YwElectricalData row : list) {
+ if (row.getElectricalId() != null) {
+ row.setRoomNames(roomMap.get(row.getElectricalId()));
+ }
+ }
+ }
+
+ private void fillRoomNames(List<? extends YwElectrical> list) {
+ if (CollectionUtils.isEmpty(list)) return;
+ List<Integer> ids = list.stream().map(YwElectrical::getId).filter(Objects::nonNull).collect(Collectors.toList());
+ if (ids.isEmpty()) return;
+ MPJLambdaWrapper<YwElectricalRoom> w = new MPJLambdaWrapper<>();
+ w.selectAll(YwElectricalRoom.class)
+ .selectAs(YwRoom::getRoomNum, YwElectricalRoom::getRoomName)
+ .selectAs(YwBuilding::getName, YwElectricalRoom::getBuildingName)
+ .selectAs(YwFloor::getName, YwElectricalRoom::getFloorName)
+ .leftJoin(YwRoom.class, YwRoom::getId, YwElectricalRoom::getRoomId)
+ .leftJoin(YwFloor.class, YwFloor::getId, YwRoom::getFloor)
+ .leftJoin(YwBuilding.class, YwBuilding::getId, YwRoom::getBuildingId)
+ .eq(YwElectricalRoom::getIsdeleted, Constants.ZERO)
+ .eq(YwElectricalRoom::getType, Constants.ZERO)
+ .in(YwElectricalRoom::getObjId, ids);
+ List<YwElectricalRoom> rooms = ywElectricalRoomMapper.selectJoinList(YwElectricalRoom.class, w);
+ Map<Integer, List<YwElectricalRoom>> grouped = rooms.stream().collect(Collectors.groupingBy(YwElectricalRoom::getObjId));
+ for (YwElectrical row : list) {
+ List<YwElectricalRoom> rs = grouped.get(row.getId());
+ if (CollectionUtils.isEmpty(rs)) continue;
+ row.setRoomNames(rs.stream().map(this::formatRoomPath).filter(StringUtils::isNotBlank)
+ .collect(Collectors.joining("銆�")));
+ }
+ }
+
+ private String formatRoomPath(YwElectricalRoom r) {
+ List<String> parts = new ArrayList<>();
+ if (StringUtils.isNotBlank(r.getBuildingName())) parts.add(r.getBuildingName());
+ if (StringUtils.isNotBlank(r.getFloorName())) parts.add(r.getFloorName());
+ if (StringUtils.isNotBlank(r.getRoomName())) parts.add(r.getRoomName());
+ return String.join("/", parts);
+ }
+
+ private YwElectrical requireElectrical(Integer id) {
+ YwElectrical e = ywElectricalMapper.selectById(id);
+ if (e == null || Objects.equals(e.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY);
+ }
+ return e;
+ }
+
+ private String newOprId() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalDataServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalDataServiceImpl.java
index 69d376e..b05ad19 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalDataServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalDataServiceImpl.java
@@ -1,32 +1,38 @@
package com.doumee.service.business.impl;
-import com.doumee.core.model.PageData;
-import com.doumee.core.model.PageWrap;
-import com.doumee.dao.business.model.YwElectricalData;
-import com.doumee.core.utils.Utils;
-import com.doumee.dao.business.YwElectricalDataMapper;
-import com.doumee.service.business.YwElectricalDataService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.core.utils.Constants;
+import com.doumee.core.utils.Utils;
+import com.doumee.dao.business.YwElectricalDataMapper;
+import com.doumee.dao.business.model.YwElectrical;
+import com.doumee.dao.business.model.YwElectricalData;
+import com.doumee.dao.business.model.YwElectricalRoom;
+import com.doumee.service.business.YwElectricalBizService;
+import com.doumee.service.business.YwElectricalDataService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
-import com.github.yulichang.wrapper.MPJLambdaWrapper;
-import com.doumee.core.model.LoginUserInfo;
+
import java.util.List;
/**
* 鐢佃〃鎶勮〃鏁版嵁Service瀹炵幇
- * @author doumee
- * @date 2026-05-20 14:59:07
*/
@Service
public class YwElectricalDataServiceImpl implements YwElectricalDataService {
@Autowired
private YwElectricalDataMapper ywElectricalDataMapper;
+ @Autowired
+ private YwElectricalBizService ywElectricalBizService;
@Override
public Integer create(YwElectricalData ywElectricalData) {
@@ -38,34 +44,37 @@
public void deleteById(Integer id) {
ywElectricalDataMapper.deleteById(id);
}
- @Override
- public void deleteByIdInBatch(List<Integer> ids ) {
+
+ @Override
+ public void deleteByIdInBatch(List<Integer> ids) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
- for(Integer id :ids){
+ for (Integer id : ids) {
deleteById(id);
}
}
+
@Override
public void deleteById(Integer id, LoginUserInfo user) {
ywElectricalDataMapper.deleteById(id);
}
- @Override
+
+ @Override
public void deleteByIdInBatch(List<Integer> ids, LoginUserInfo user) {
if (CollectionUtils.isEmpty(ids)) {
return;
}
- for(Integer id :ids){
- deleteById(id,user);
+ for (Integer id : ids) {
+ deleteById(id, user);
}
}
+
@Override
public void delete(YwElectricalData ywElectricalData) {
UpdateWrapper<YwElectricalData> deleteWrapper = new UpdateWrapper<>(ywElectricalData);
ywElectricalDataMapper.delete(deleteWrapper);
}
-
@Override
public void updateById(YwElectricalData ywElectricalData) {
@@ -77,7 +86,7 @@
if (CollectionUtils.isEmpty(ywElectricalDatas)) {
return;
}
- for (YwElectricalData ywElectricalData: ywElectricalDatas) {
+ for (YwElectricalData ywElectricalData : ywElectricalDatas) {
this.updateById(ywElectricalData);
}
}
@@ -98,79 +107,58 @@
QueryWrapper<YwElectricalData> wrapper = new QueryWrapper<>(ywElectricalData);
return ywElectricalDataMapper.selectList(wrapper);
}
-
+
@Override
public PageData<YwElectricalData> findPage(PageWrap<YwElectricalData> pageWrap) {
IPage<YwElectricalData> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ YwElectricalData model = pageWrap.getModel() == null ? new YwElectricalData() : pageWrap.getModel();
+ Utils.MP.blankToNull(model);
+
MPJLambdaWrapper<YwElectricalData> queryWrapper = new MPJLambdaWrapper<>();
- Utils.MP.blankToNull(pageWrap.getModel());
- queryWrapper.eq(pageWrap.getModel().getId() != null,YwElectricalData::getId, pageWrap.getModel().getId());
- queryWrapper.eq(pageWrap.getModel().getCreator() != null,YwElectricalData::getCreator, pageWrap.getModel().getCreator());
- if (pageWrap.getModel().getCreateDate() != null) {
- queryWrapper.ge(YwElectricalData::getCreateDate, Utils.Date.getStart(pageWrap.getModel().getCreateDate()));
- queryWrapper.le(YwElectricalData::getCreateDate, Utils.Date.getEnd(pageWrap.getModel().getCreateDate()));
+ queryWrapper.selectAll(YwElectricalData.class)
+ .selectAs(YwElectrical::getName, YwElectricalData::getElectricalName)
+ .selectAs(YwElectrical::getId, YwElectricalData::getElectricalId)
+ .leftJoin(YwElectrical.class, on -> on
+ .eq(YwElectrical::getAddress, YwElectricalData::getAddress)
+ .eq(YwElectrical::getIsdeleted, Constants.ZERO))
+ .eq(YwElectricalData::getIsdeleted, Constants.ZERO);
+
+ if (StringUtils.isNotBlank(model.getMeterKeyword())) {
+ String kw = model.getMeterKeyword().trim();
+ queryWrapper.and(w -> w.like(YwElectricalData::getAddress, kw)
+ .or().like(YwElectricalData::getName, kw)
+ .or().like(YwElectrical::getName, kw));
}
- queryWrapper.eq(pageWrap.getModel().getEditor() != null,YwElectricalData::getEditor, pageWrap.getModel().getEditor());
- if (pageWrap.getModel().getEditDate() != null) {
- queryWrapper.ge(YwElectricalData::getEditDate, Utils.Date.getStart(pageWrap.getModel().getEditDate()));
- queryWrapper.le(YwElectricalData::getEditDate, Utils.Date.getEnd(pageWrap.getModel().getEditDate()));
+ if (model.getRoomId() != null) {
+ queryWrapper.innerJoin(YwElectricalRoom.class, on -> on
+ .eq(YwElectricalRoom::getObjId, YwElectrical::getId)
+ .eq(YwElectricalRoom::getType, Constants.ZERO)
+ .eq(YwElectricalRoom::getIsdeleted, Constants.ZERO))
+ .eq(YwElectricalRoom::getRoomId, model.getRoomId());
}
- queryWrapper.eq(pageWrap.getModel().getIsdeleted() != null,YwElectricalData::getIsdeleted, pageWrap.getModel().getIsdeleted());
- queryWrapper.eq(pageWrap.getModel().getRemark() != null,YwElectricalData::getRemark, pageWrap.getModel().getRemark());
- queryWrapper.eq(pageWrap.getModel().getDeviceId() != null,YwElectricalData::getDeviceId, pageWrap.getModel().getDeviceId());
- queryWrapper.eq(pageWrap.getModel().getAddTime() != null,YwElectricalData::getAddTime, pageWrap.getModel().getAddTime());
- queryWrapper.eq(pageWrap.getModel().getJsfs() != null,YwElectricalData::getJsfs, pageWrap.getModel().getJsfs());
- queryWrapper.eq(pageWrap.getModel().getFls() != null,YwElectricalData::getFls, pageWrap.getModel().getFls());
- queryWrapper.eq(pageWrap.getModel().getXs() != null,YwElectricalData::getXs, pageWrap.getModel().getXs());
- queryWrapper.eq(pageWrap.getModel().getZyje() != null,YwElectricalData::getZyje, pageWrap.getModel().getZyje());
- queryWrapper.eq(pageWrap.getModel().getYe() != null,YwElectricalData::getYe, pageWrap.getModel().getYe());
- queryWrapper.eq(pageWrap.getModel().getCountnum() != null,YwElectricalData::getCountnum, pageWrap.getModel().getCountnum());
- queryWrapper.eq(pageWrap.getModel().getDqdj() != null,YwElectricalData::getDqdj, pageWrap.getModel().getDqdj());
- queryWrapper.eq(pageWrap.getModel().getZhygzdl() != null,YwElectricalData::getZhygzdl, pageWrap.getModel().getZhygzdl());
- queryWrapper.eq(pageWrap.getModel().getZqyl() != null,YwElectricalData::getZqyl, pageWrap.getModel().getZqyl());
- queryWrapper.eq(pageWrap.getModel().getAxdl() != null,YwElectricalData::getAxdl, pageWrap.getModel().getAxdl());
- queryWrapper.eq(pageWrap.getModel().getBxdl() != null,YwElectricalData::getBxdl, pageWrap.getModel().getBxdl());
- queryWrapper.eq(pageWrap.getModel().getCxdl() != null,YwElectricalData::getCxdl, pageWrap.getModel().getCxdl());
- queryWrapper.eq(pageWrap.getModel().getAxdy() != null,YwElectricalData::getAxdy, pageWrap.getModel().getAxdy());
- queryWrapper.eq(pageWrap.getModel().getBxdy() != null,YwElectricalData::getBxdy, pageWrap.getModel().getBxdy());
- queryWrapper.eq(pageWrap.getModel().getCxdy() != null,YwElectricalData::getCxdy, pageWrap.getModel().getCxdy());
- queryWrapper.eq(pageWrap.getModel().getZyggl() != null,YwElectricalData::getZyggl, pageWrap.getModel().getZyggl());
- queryWrapper.eq(pageWrap.getModel().getAxyggl() != null,YwElectricalData::getAxyggl, pageWrap.getModel().getAxyggl());
- queryWrapper.eq(pageWrap.getModel().getBxyggl() != null,YwElectricalData::getBxyggl, pageWrap.getModel().getBxyggl());
- queryWrapper.eq(pageWrap.getModel().getCxyggl() != null,YwElectricalData::getCxyggl, pageWrap.getModel().getCxyggl());
- queryWrapper.eq(pageWrap.getModel().getZwggl() != null,YwElectricalData::getZwggl, pageWrap.getModel().getZwggl());
- queryWrapper.eq(pageWrap.getModel().getAxwggl() != null,YwElectricalData::getAxwggl, pageWrap.getModel().getAxwggl());
- queryWrapper.eq(pageWrap.getModel().getBxwggl() != null,YwElectricalData::getBxwggl, pageWrap.getModel().getBxwggl());
- queryWrapper.eq(pageWrap.getModel().getCxwggl() != null,YwElectricalData::getCxwggl, pageWrap.getModel().getCxwggl());
- queryWrapper.eq(pageWrap.getModel().getZszgl() != null,YwElectricalData::getZszgl, pageWrap.getModel().getZszgl());
- queryWrapper.eq(pageWrap.getModel().getAxszgl() != null,YwElectricalData::getAxszgl, pageWrap.getModel().getAxszgl());
- queryWrapper.eq(pageWrap.getModel().getBxszgl() != null,YwElectricalData::getBxszgl, pageWrap.getModel().getBxszgl());
- queryWrapper.eq(pageWrap.getModel().getCxszgl() != null,YwElectricalData::getCxszgl, pageWrap.getModel().getCxszgl());
- queryWrapper.eq(pageWrap.getModel().getZglys() != null,YwElectricalData::getZglys, pageWrap.getModel().getZglys());
- queryWrapper.eq(pageWrap.getModel().getAxglys() != null,YwElectricalData::getAxglys, pageWrap.getModel().getAxglys());
- queryWrapper.eq(pageWrap.getModel().getBxglys() != null,YwElectricalData::getBxglys, pageWrap.getModel().getBxglys());
- queryWrapper.eq(pageWrap.getModel().getPl() != null,YwElectricalData::getPl, pageWrap.getModel().getPl());
- queryWrapper.eq(pageWrap.getModel().getWd() != null,YwElectricalData::getWd, pageWrap.getModel().getWd());
- queryWrapper.eq(pageWrap.getModel().getDdyy() != null,YwElectricalData::getDdyy, pageWrap.getModel().getDdyy());
- queryWrapper.eq(pageWrap.getModel().getDbzt() != null,YwElectricalData::getDbzt, pageWrap.getModel().getDbzt());
- queryWrapper.eq(pageWrap.getModel().getDataId() != null,YwElectricalData::getDataId, pageWrap.getModel().getDataId());
- queryWrapper.eq(pageWrap.getModel().getConsume() != null,YwElectricalData::getConsume, pageWrap.getModel().getConsume());
- queryWrapper.eq(pageWrap.getModel().getCid() != null,YwElectricalData::getCid, pageWrap.getModel().getCid());
- queryWrapper.eq(pageWrap.getModel().getMid() != null,YwElectricalData::getMid, pageWrap.getModel().getMid());
- queryWrapper.eq(pageWrap.getModel().getAddress() != null,YwElectricalData::getAddress, pageWrap.getModel().getAddress());
- queryWrapper.eq(pageWrap.getModel().getUid() != null,YwElectricalData::getUid, pageWrap.getModel().getUid());
- queryWrapper.eq(pageWrap.getModel().getName() != null,YwElectricalData::getName, pageWrap.getModel().getName());
- queryWrapper.eq(pageWrap.getModel().getFid() != null,YwElectricalData::getFid, pageWrap.getModel().getFid());
- queryWrapper.eq(pageWrap.getModel().getData() != null,YwElectricalData::getData, pageWrap.getModel().getData());
- queryWrapper.eq(pageWrap.getModel().getDsp() != null,YwElectricalData::getDsp, pageWrap.getModel().getDsp());
- queryWrapper.eq(pageWrap.getModel().getRadio() != null,YwElectricalData::getRadio, pageWrap.getModel().getRadio());
- queryWrapper.orderByDesc(YwElectricalData::getId);
- IPage<YwElectricalData> result = ywElectricalDataMapper.selectJoinPage(page, YwElectricalData.class,queryWrapper);
- return PageData.from(result);
+ if (model.getReadTimeBegin() != null) {
+ queryWrapper.ge(YwElectricalData::getCreateDate, Utils.Date.getStart(model.getReadTimeBegin()));
+ }
+ if (model.getReadTimeEnd() != null) {
+ queryWrapper.le(YwElectricalData::getCreateDate, Utils.Date.getEnd(model.getReadTimeEnd()));
+ }
+
+ queryWrapper.orderByDesc(YwElectricalData::getCreateDate)
+ .orderByDesc(YwElectricalData::getId);
+ IPage<YwElectricalData> result = ywElectricalDataMapper.selectJoinPage(page, YwElectricalData.class, queryWrapper);
+ PageData<YwElectricalData> pageData = PageData.from(result);
+ ywElectricalBizService.enrichDataList(pageData.getRecords());
+ return pageData;
}
+
@Override
public long count(YwElectricalData ywElectricalData) {
QueryWrapper<YwElectricalData> wrapper = new QueryWrapper<>(ywElectricalData);
return ywElectricalDataMapper.selectCount(wrapper);
}
+
+ @Override
+ public String syncFromPlatform() {
+ return ywElectricalBizService.syncMeterDataFromPlatform();
+ }
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalServiceImpl.java
index 9bd17e6..911f9ce 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwElectricalServiceImpl.java
@@ -20,6 +20,7 @@
import com.doumee.dao.business.model.DeviceData;
import com.doumee.dao.business.model.YwDevice;
import com.doumee.dao.business.model.YwElectrical;
+import com.doumee.dao.business.model.YwElectricalParam;
import com.doumee.core.utils.Utils;
import com.doumee.dao.business.YwElectricalMapper;
import com.doumee.service.business.YwElectricalBizService;
@@ -130,7 +131,12 @@
IPage<YwElectrical> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
MPJLambdaWrapper<YwElectrical> queryWrapper = new MPJLambdaWrapper<>();
Utils.MP.blankToNull(pageWrap.getModel());
- queryWrapper.eq(YwElectrical::getIsdeleted, Constants.ZERO);
+ queryWrapper.selectAll(YwElectrical.class)
+ .selectAs(YwElectricalParam::getName, YwElectrical::getParamName)
+ .leftJoin(YwElectricalParam.class, on -> on
+ .eq(YwElectricalParam::getId, YwElectrical::getElectricalParamId)
+ .eq(YwElectricalParam::getIsdeleted, Constants.ZERO))
+ .eq(YwElectrical::getIsdeleted, Constants.ZERO);
if (StringUtils.isNotBlank(pageWrap.getModel().getMeterKeyword())) {
String kw = pageWrap.getModel().getMeterKeyword().trim();
queryWrapper.and(w -> w.like(YwElectrical::getName, kw).or().like(YwElectrical::getAddress, kw));
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 d499d3b..8a06b32 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
@@ -35,9 +35,11 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -98,6 +100,7 @@
}
Map<String, YwElectrical> addressMap = buildElectricalAddressMap();
Map<String, YwElectrical> didMap = buildElectricalDidMap();
+ Set<Integer> affectedElectricalIds = new LinkedHashSet<>();
Date now = new Date();
int addCount = 0;
int updateCount = 0;
@@ -129,6 +132,9 @@
entity.setMsg(item.getMsg());
entity.setEditDate(now);
resolveElectricalId(entity, addressMap, didMap);
+ if (entity.getElectricalId() != null) {
+ affectedElectricalIds.add(entity.getElectricalId());
+ }
if (isNew) {
ywElectricalWarningMapper.insert(entity);
addCount++;
@@ -137,7 +143,8 @@
updateCount++;
}
}
- return "鍚屾瀹屾垚锛氭柊澧炪��" + addCount + "銆戞潯锛屾洿鏂般��" + updateCount + "銆戞潯";
+ int electricalUpdateCount = updateElectricalWarnTypes(affectedElectricalIds, now);
+ return "鍚屾瀹屾垚锛氭柊澧炪��" + addCount + "銆戞潯锛屾洿鏂般��" + updateCount + "銆戞潯锛屽洖鍐欑數琛ㄩ璀︺��" + electricalUpdateCount + "銆戝彴";
} finally {
Constants.DEALING_ELECTRICAL_WARNING_SYNC = false;
}
@@ -285,6 +292,37 @@
}
}
+ /** 鎸夌數琛ㄦ眹鎬诲叏閮ㄦ姤璀︾被鍨嬶紝閫楀彿鍒嗛殧鍥炲啓 warn_type */
+ private int updateElectricalWarnTypes(Set<Integer> affectedElectricalIds, Date editDate) {
+ if (CollectionUtils.isEmpty(affectedElectricalIds)) {
+ return 0;
+ }
+ for (Integer electricalId : affectedElectricalIds) {
+ refreshElectricalWarnType(electricalId, editDate);
+ }
+ return affectedElectricalIds.size();
+ }
+
+ private void refreshElectricalWarnType(Integer electricalId, Date editDate) {
+ List<YwElectricalWarning> warnings = ywElectricalWarningMapper.selectList(new QueryWrapper<YwElectricalWarning>().lambda()
+ .eq(YwElectricalWarning::getElectricalId, electricalId)
+ .eq(YwElectricalWarning::getIsdeleted, Constants.ZERO));
+ LinkedHashSet<Integer> defIds = new LinkedHashSet<>();
+ if (!CollectionUtils.isEmpty(warnings)) {
+ for (YwElectricalWarning warning : warnings) {
+ if (warning.getWarningDefId() != null) {
+ defIds.add(warning.getWarningDefId());
+ }
+ }
+ }
+ String warnType = defIds.stream().map(String::valueOf).collect(Collectors.joining(","));
+ YwElectrical upd = new YwElectrical();
+ upd.setId(electricalId);
+ upd.setWarnType(StringUtils.isBlank(warnType) ? null : warnType);
+ upd.setEditDate(editDate);
+ ywElectricalMapper.updateById(upd);
+ }
+
private YwElectricalWarning findExisting(String deviceAddress, Integer warningDefId, Date startTime) {
return ywElectricalWarningMapper.selectOne(new QueryWrapper<YwElectricalWarning>().lambda()
.eq(YwElectricalWarning::getDeviceAddress, deviceAddress)
--
Gitblit v1.9.3