From 074bcb8394fab66ce531c219e1e7de7c142ff2d5 Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期五, 29 五月 2026 11:07:10 +0800
Subject: [PATCH] 新增智能电表、空调管理

---
 admin/src/views/business/components/YwElectricalRemote.vue |  239 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 232 insertions(+), 7 deletions(-)

diff --git a/admin/src/views/business/components/YwElectricalRemote.vue b/admin/src/views/business/components/YwElectricalRemote.vue
index 6261d0e..571c9e1 100644
--- a/admin/src/views/business/components/YwElectricalRemote.vue
+++ b/admin/src/views/business/components/YwElectricalRemote.vue
@@ -1,6 +1,6 @@
 <template>
   <GlobalWindow title="鐢佃〃杩滅▼鎺у埗" :visible.sync="visible" width="780px" :show-confirm="false">
-    <el-tabs v-model="activeTab">
+    <el-tabs v-model="activeTab" @tab-click="onTabClick">
       <el-tab-pane label="鍩烘湰鎿嶄綔" name="basic">
         <div class="info-block">
           <p>閲囬泦鍣ㄥ彿锛歿{ info.collectorId }} <span :class="info.online === 1 ? 'green' : 'red'">{{ info.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span></p>
@@ -9,11 +9,23 @@
           <p>鐢佃〃绫诲瀷锛歿{ info.type }}</p>
           <p>鍏宠仈鎴块棿锛歿{ info.roomNames }}</p>
         </div>
+        <div class="meter-data-block" v-loading="infoLoading">
+          <div class="meter-data-block__title">鏈�鏂版妱琛ㄦ暟鎹�</div>
+          <p>褰撳墠鎬荤數閲忥細{{ totalPower }}</p>
+          <p>鐢甸噺鍚屾鏃堕棿锛歿{ latest && latest.addTime ? latest.addTime : '-' }}</p>
+          <p>褰撳墠鍓╀綑閲戦(鍏�)锛歿{ latest && latest.ye != null ? latest.ye : (info.balance != null ? info.balance : '0.0000') }}</p>
+          <p>浣欓鍚屾鏃堕棿锛歿{ balanceSyncTime }}</p>
+        </div>
         <div class="btn-row">
           <el-button type="primary" :loading="isOperating" @click="doOperate('resetPrepay', '纭娓呴浂骞跺垏鎹㈠埌棰勪粯璐规ā寮忓悧锛�')">娓呴浂骞跺垏鎹㈠埌棰勪粯璐规ā寮�</el-button>
           <el-button type="primary" :loading="isOperating" @click="doOperate('resetPostpay', '纭娓呴浂骞跺垏鎹㈠埌鍚庝粯璐规ā寮忓悧锛�')">娓呴浂骞跺垏鎹㈠埌鍚庝粯璐规ā寮�</el-button>
-          <el-button type="primary" :loading="isOperating" @click="doOperate('trip', '纭鎷夐椄鍚楋紵')">鎷夐椄</el-button>
-          <el-button type="primary" :loading="isOperating" @click="doOperate('close', '纭鍚堥椄鍚楋紵')">鍚堥椄</el-button>
+        </div>
+        <div class="btn-row btn-row--relay">
+          <span class="btn-row__label">鎷夊悎闂告搷浣�</span>
+          <el-button type="danger" :loading="isOperating" @click="doRelayOperate('trip')">鎷夐椄</el-button>
+          <el-button type="primary" :loading="isOperating" @click="doRelayOperate('close')">鍚堥椄</el-button>
+          <el-button type="warning" :loading="isOperating" @click="doRelayOperate('powerProtect')">淇濈數</el-button>
+          <el-button type="danger" plain :loading="isOperating" @click="doRelayOperate('powerProtectRelease')">瑙i櫎淇濈數</el-button>
         </div>
       </el-tab-pane>
       <el-tab-pane label="寮�鎴�" name="open">
@@ -39,18 +51,86 @@
           @confirm="doOperate('recharge', '纭鍏呭�煎悧锛�')"
         />
       </el-tab-pane>
+      <el-tab-pane label="鎺у埗璁板綍" name="records">
+        <el-table v-loading="recordsLoading" :data="recordsList" stripe size="small" class="records-table">
+          <el-table-column label="鎿嶄綔绫诲瀷" min-width="100" align="center">
+            <template slot-scope="{ row }">{{ formatActionType(row.actionType) }}</template>
+          </el-table-column>
+          <el-table-column prop="oprId" label="鎿嶄綔ID" min-width="150" align="center" show-overflow-tooltip />
+          <el-table-column label="鐘舵��" min-width="80" align="center">
+            <template slot-scope="{ row }">
+              <span :class="statusClass(row.status)">{{ formatStatus(row.status) }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="resultMsg" label="鎵ц缁撴灉" min-width="140" align="center" show-overflow-tooltip>
+            <template slot-scope="{ row }">{{ row.resultMsg || '-' }}</template>
+          </el-table-column>
+          <el-table-column prop="createDate" label="鎿嶄綔鏃堕棿" min-width="150" align="center" />
+          <el-table-column label="鎶ユ枃" min-width="120" align="center">
+            <template slot-scope="{ row }">
+              <el-button type="text" :disabled="!row.requestBody" @click="openJson('璇锋眰鎶ユ枃', row.requestBody)">璇锋眰</el-button>
+              <el-button type="text" :disabled="!row.responseBody" @click="openJson('鍝嶅簲鎶ユ枃', row.responseBody)">鍝嶅簲</el-button>
+            </template>
+          </el-table-column>
+          <el-table-column label="鎿嶄綔" min-width="100" align="center" fixed="right">
+            <template slot-scope="{ row }">
+              <el-button
+                v-if="row.status === 0 && row.oprId"
+                type="text"
+                :loading="queryLoadingId === row.id"
+                v-permissions="['business:ywelectricalactions:queryResult']"
+                @click="handleQueryResult(row)"
+              >鏌ヨ缁撴灉</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination
+          small
+          @size-change="handleRecordsSizeChange"
+          @current-change="loadActionRecords"
+          :pagination="recordsPagination"
+        />
+      </el-tab-pane>
     </el-tabs>
+    <OperaInterfaceLogWindow ref="jsonWindow" />
+    <template slot="footer">
+      <el-button type="primary" icon="el-icon-refresh" :loading="isRefreshing" @click="refreshMeterData">鍒锋柊鐢佃〃鏁版嵁</el-button>
+      <el-button @click="visible = false">杩斿洖</el-button>
+    </template>
   </GlobalWindow>
 </template>
 
 <script>
 import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import OperaInterfaceLogWindow from '@/components/business/OperaInterfaceLogWindow'
 import { getRemoteInfo, operate } from '@/api/business/ywelectrical'
+import * as actionsApi from '@/api/business/ywelectricalactions'
 import AccountRechargePanel from './AccountRechargePanel'
+
+const RELAY_ACTIONS = {
+  trip: { label: '鎷夐椄', message: '纭瀵硅鐢佃〃鎵ц鎷夐椄鎿嶄綔鍚楋紵' },
+  close: { label: '鍚堥椄', message: '纭瀵硅鐢佃〃鎵ц鍚堥椄鎿嶄綔鍚楋紵' },
+  powerProtect: { label: '淇濈數', message: '纭瀵硅鐢佃〃鎵ц淇濈數鎿嶄綔鍚楋紵' },
+  powerProtectRelease: { label: '瑙i櫎淇濈數', message: '纭瀵硅鐢佃〃鎵ц瑙i櫎淇濈數鎿嶄綔鍚楋紵' }
+}
+
+const ACTION_TYPE_MAP = {
+  1: '棰勪粯璐规竻闆�',
+  2: '鍚庝粯璐规竻闆�',
+  3: '杩滅▼閿�鎴�',
+  4: '鎷夐椄',
+  5: '鍚堥椄',
+  6: '寮�鎴�',
+  7: '鍏呭��',
+  8: '鎶勮〃',
+  9: '淇濈數',
+  10: '瑙i櫎淇濈數'
+}
 
 export default {
   name: 'YwElectricalRemote',
-  components: { GlobalWindow, AccountRechargePanel },
+  components: { GlobalWindow, AccountRechargePanel, Pagination, OperaInterfaceLogWindow },
   data () {
     return {
       visible: false,
@@ -60,7 +140,23 @@
       latest: null,
       purchaseCount: '0',
       form: { money: 0, remark: '' },
-      isOperating: false
+      isOperating: false,
+      isRefreshing: false,
+      infoLoading: false,
+      recordsLoading: false,
+      recordsList: [],
+      recordsPagination: { pageIndex: 1, pageSize: 10, total: 0 },
+      queryLoadingId: null
+    }
+  },
+  computed: {
+    totalPower () {
+      if (!this.latest) return '0.00kWh'
+      const v = this.latest.zhygzdl || '0'
+      return String(v).toLowerCase().indexOf('kwh') >= 0 ? v : v + 'kWh'
+    },
+    balanceSyncTime () {
+      return (this.latest && this.latest.addTime) ? this.latest.addTime : (this.info.balanceTime || '-')
     }
   },
   methods: {
@@ -68,21 +164,86 @@
       this.electricalId = row.id
       this.activeTab = tab || 'basic'
       this.form = { money: 0, remark: '' }
+      this.recordsPagination.pageIndex = 1
+      this.recordsList = []
       this.visible = true
       this.loadInfo()
+      if (this.activeTab === 'records') {
+        this.loadActionRecords(1)
+      }
+    },
+    onTabClick (tab) {
+      if (tab.name === 'records' && this.recordsList.length === 0) {
+        this.loadActionRecords(1)
+      }
     },
     loadInfo () {
+      if (!this.electricalId) return
+      this.infoLoading = true
       getRemoteInfo(this.electricalId).then(res => {
         this.info = res.electrical || {}
         this.latest = res.latestData
         this.purchaseCount = res.purchaseCount || '0'
-      }).catch(e => this.$tip.apiFailed(e))
+      }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.infoLoading = false })
+    },
+    refreshMeterData () {
+      if (!this.electricalId) return
+      this.isRefreshing = true
+      operate({
+        electricalId: this.electricalId,
+        action: 'readMeter'
+      })
+        .then(res => {
+          this.$tip.apiSuccess(res || '鎶勮〃璇锋眰宸叉彁浜わ紝姝e湪鍒锋柊鏁版嵁')
+          this.loadInfo()
+          this.$emit('success')
+          if (this.activeTab === 'records') {
+            this.loadActionRecords(this.recordsPagination.pageIndex)
+          }
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => { this.isRefreshing = false })
+    },
+    loadActionRecords (page) {
+      if (!this.electricalId) return
+      if (page) {
+        this.recordsPagination.pageIndex = page
+      }
+      this.recordsLoading = true
+      actionsApi.fetchList({
+        page: this.recordsPagination.pageIndex,
+        capacity: this.recordsPagination.pageSize,
+        model: { electricalId: this.electricalId }
+      })
+        .then(data => {
+          this.recordsList = data.records || []
+          this.recordsPagination.total = data.total || 0
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => { this.recordsLoading = false })
+    },
+    handleRecordsSizeChange (size) {
+      this.recordsPagination.pageSize = size
+      this.loadActionRecords(1)
     },
     readMeter () {
       this.submitOperate('readMeter', true)
     },
     doOperate (action, msg) {
       this.$dialog.actionConfirm(msg, '鎿嶄綔纭')
+        .then(() => this.submitOperate(action, false))
+        .catch(() => {})
+    },
+    relayMeterTip () {
+      const name = this.info.name || '-'
+      const address = this.info.address || '-'
+      return `琛ㄥ悕绉帮細${name}锛岃〃鍦板潃锛�${address}`
+    },
+    doRelayOperate (action) {
+      const cfg = RELAY_ACTIONS[action]
+      if (!cfg) return
+      const meterTip = this.relayMeterTip()
+      this.$dialog.actionConfirm(`${cfg.message}锛�${meterTip}锛塦, '鎿嶄綔纭')
         .then(() => this.submitOperate(action, false))
         .catch(() => {})
     },
@@ -98,15 +259,79 @@
           this.$tip.apiSuccess(res || (silent ? '鎶勮〃璇锋眰宸叉彁浜�' : '鎻愪氦鎴愬姛锛岃鍦ㄣ�愭棩甯哥敤鐢电鐞�-鍏呭�艰褰曘�戜腑鏌ョ湅鍏呭�肩粨鏋�'))
           this.loadInfo()
           this.$emit('success')
+          if (this.activeTab === 'records') {
+            this.loadActionRecords(this.recordsPagination.pageIndex)
+          }
         })
         .catch(e => this.$tip.apiFailed(e))
         .finally(() => { this.isOperating = false })
+    },
+    formatActionType (val) {
+      return ACTION_TYPE_MAP[val] || val || '-'
+    },
+    formatStatus (val) {
+      if (val === 0 || val === '0') return '澶勭悊涓�'
+      if (val === 1 || val === '1') return '鎴愬姛'
+      if (val === 2 || val === '2') return '澶辫触'
+      return val == null || val === '' ? '-' : String(val)
+    },
+    statusClass (val) {
+      if (val === 1 || val === '1') return 'green'
+      if (val === 2 || val === '2') return 'red'
+      if (val === 0 || val === '0') return 'orange'
+      return ''
+    },
+    openJson (title, content) {
+      if (!content) return
+      this.$refs.jsonWindow.open(title, { content })
+    },
+    handleQueryResult (row) {
+      this.queryLoadingId = row.id
+      actionsApi.queryResult(row.id)
+        .then(msg => {
+          this.$tip.success(msg || '鏌ヨ瀹屾垚')
+          this.loadActionRecords(this.recordsPagination.pageIndex)
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => { this.queryLoadingId = null })
     }
   }
 }
 </script>
 
 <style scoped>
-.info-block { margin-bottom: 16px; line-height: 28px; color: #303133; }
+.info-block { margin-bottom: 12px; line-height: 28px; color: #303133; }
+.meter-data-block {
+  margin-bottom: 16px;
+  padding: 12px 14px;
+  background: #fafbfe;
+  border: 1px solid #eef2f8;
+  border-radius: 8px;
+  line-height: 26px;
+  color: #303133;
+}
+.meter-data-block__title {
+  font-weight: 600;
+  font-size: 14px;
+  margin-bottom: 6px;
+  color: #222;
+}
+.meter-data-block p { margin: 0; }
 .btn-row .el-button { margin: 0 8px 8px 0; }
+.btn-row--relay {
+  margin-top: 4px;
+  padding-top: 12px;
+  border-top: 1px dashed #ebeef5;
+}
+.btn-row__label {
+  display: block;
+  width: 100%;
+  margin-bottom: 8px;
+  font-size: 13px;
+  color: #909399;
+}
+.records-table { width: 100%; margin-top: 4px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.orange { color: #e6a23c; }
 </style>

--
Gitblit v1.9.3