From f4d592f3626f94117d8a4eb22176a28290931980 Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期二, 26 五月 2026 18:51:54 +0800
Subject: [PATCH] 新增智能电表、空调管理

---
 server/db/conditioner_param.dict.sql                                                                                |   32 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java                         |  553 ++++
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerGatewayCloudController.java           |   55 
 admin/src/views/business/components/YwConditionerLockWindow.vue                                                     |  121 
 server/db/yw_conditioner_module.sql                                                                                 |  165 +
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/RoomInfoResponse.java         |   14 
 admin/src/views/business/ywconditioneractions.vue                                                                   |  195 +
 admin/src/views/system/menu.vue                                                                                     |    1 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGatewayLog.java              |   46 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerConfigService.java          |   49 
 admin/src/views/business/ywconditionerreport.vue                                                                    |  632 ++++
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerReportService.java             |   20 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerBilling.java                 |   48 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LogQueryRequest.java           |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/RoomManageRequest.java         |   22 
 server/db/business.yw_conditioner.menu.root.sql                                                                     |   68 
 admin/src/views/business/components/YwConditionerHistoryWindow.vue                                                  |  102 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerBillingMapper.java                 |    7 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UserManageRequest.java         |   31 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerServiceImpl.java          |  127 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlItem.java        |   25 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGateway.java                 |   45 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerToolTestUtil.java                 |   57 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/MeterDbInfoResponse.java      |   24 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayLogMapper.java              |    7 
 server/system_service/src/main/java/com/doumee/core/utils/Constants.java                                            |    5 
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerReportCloudController.java            |   60 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerService.java                   |   19 
 admin/src/api/business/ywconditioneractions.js                                                                      |   11 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManageRequest.java          |   34 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceStatusResponse.java     |   40 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerMeter.java                   |   58 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerActionsService.java            |   10 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AreaManageRequest.java         |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/CompanyGsManageRequest.java    |   40 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerOperateDTO.java                |   20 
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerActionsCloudController.java           |   49 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ChangeDlSjXsRequest.java       |   24 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgWithAreaRequest.java         |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/FloorInfoResponse.java        |   13 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/ConditionerBizService.java                  |   49 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerMeterService.java              |   17 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MoonDlQueryRequest.java        |   16 
 admin/src/directives/v-permissions.js                                                                               |    6 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerUsage.java                   |   39 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerGatewayService.java            |   15 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerBizServiceImpl.java         | 1031 +++++++
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerBillingCloudController.java           |   43 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingWithAreaRequest.java     |   16 
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerMeterCloudController.java             |   59 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DlSjXsResponse.java           |   22 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgManageRequest.java           |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GsWithAreaRequest.java         |   16 
 server/db/business.yw_conditioner.module.permissions.sql                                                            |   88 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayMapper.java                 |    7 
 server/db/docs/conditioner_api_index.md                                                                             |  113 
 admin/src/api/business/ywconditionerbilling.js                                                                      |   11 
 admin/src/views/business/ywconditionermeter.vue                                                                     |  169 +
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockManyControlItem.java    |   25 
 admin/src/api/business/ywconditioner.js                                                                             |   35 
 admin/src/views/business/ywconditionerbilling.vue                                                                   |  127 
 admin/src/api/business/ywconditionergateway.js                                                                      |   15 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceArchiveResponse.java    |   22 
 admin/src/api/business/ywconditionermeter.js                                                                        |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/CompanyGsInfoResponse.java    |   23 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerPageRequest.java    |   16 
 server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java                      |   33 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UserInfoResponse.java         |   23 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerBillingService.java            |   12 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerGatewayServiceImpl.java   |   66 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/BuildingManageRequest.java     |   22 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/BuildingInfoResponse.java     |   14 
 server/db/quartz_job.module.column.sql                                                                              |    4 
 admin/src/views/business/components/YwConditionerCard.vue                                                           |  293 ++
 server/visits/admin_timer/src/main/java/com/doumee/TimerApplication.java                                            |    2 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UnitInfoResponse.java         |   14 
 server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java                                      |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LoginRequest.java              |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DbDaySumQueryRequest.java      |   16 
 server/db/docs/pdf_extract.txt                                                                                      |  771 +++++
 admin/src/views/business/ywconditioner.vue                                                                          |  342 ++
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingManageRequest.java       |   46 
 server/visits/admin_timer/src/main/java/com/doumee/api/YwTimerController.java                                       |   24 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlRequest.java     |   24 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/GatewayInfoResponse.java      |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerReportQueryDTO.java            |   29 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerActions.java                 |   57 
 server/db/docs/智精灵API接口文档26-01.pdf                                                                                  |    0 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/FloorManageRequest.java        |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MeterDbManageRequest.java      |   42 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/TimingInfoResponse.java       |   20 
 server/db/business.yw_conditioner.admin_role_grant.sql                                                              |   60 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerReportServiceImpl.java    |  155 +
 admin/src/views/business/ywconditionergateway.vue                                                                   |  139 +
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerLockDTO.java                   |   22 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GetDevOneRequest.java          |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UnitManageRequest.java         |   22 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/ConditionerConstant.java               |   57 
 admin/src/views/business/components/YwConditionerGatewayLogWindow.vue                                               |   91 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerBillingServiceImpl.java   |   45 
 server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerCloudController.java                  |   65 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerSessionRequest.java |   39 
 admin/src/components/base/BasePage.vue                                                                              |    3 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerActionsServiceImpl.java   |   48 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DayDlQueryRequest.java         |   22 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevControlRequest.java         |   28 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/AreaInfoResponse.java         |   13 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportPageDTO.java        |   16 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockControlRequest.java     |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerActionsMapper.java                 |    7 
 server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerMeterServiceImpl.java     |   58 
 admin/src/views/business/components/YwElectricalEdit.vue                                                            |    2 
 server/db/quartz_job.yw_timer.sql                                                                                   |   40 
 /dev/null                                                                                                           |   48 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/ConditionerBaseResponse.java  |   28 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AddMoneyRequest.java           |   16 
 server/db/ELECTRICAL_INTEGRATION.md                                                                                 |   18 
 admin/src/api/business/ywconditionerreport.js                                                                       |   19 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportVO.java             |   27 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerMeterMapper.java                   |    7 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditioner.java                        |   54 
 server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerUsageMapper.java                   |    7 
 server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/LoginDataResponse.java        |   25 
 123 files changed, 7,968 insertions(+), 67 deletions(-)

diff --git a/admin/src/api/business/ywconditioner.js b/admin/src/api/business/ywconditioner.js
new file mode 100644
index 0000000..6375934
--- /dev/null
+++ b/admin/src/api/business/ywconditioner.js
@@ -0,0 +1,35 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditioner'
+
+export function fetchCardPage (data) {
+  return request.post(base + '/cardPage', data, { trim: true })
+}
+
+export function fetchList (data) {
+  return request.post(base + '/page', data, { trim: true })
+}
+
+export function syncAll (data) {
+  return request.post(base + '/syncAll', data || {})
+}
+
+export function syncDevicesAndStatus (data) {
+  return request.post(base + '/syncDevicesAndStatus', data || {})
+}
+
+export function operate (data) {
+  return request.post(base + '/operate', data)
+}
+
+export function lock (data) {
+  return request.post(base + '/lock', data)
+}
+
+export function historyPage (data) {
+  return request.post(base + '/historyPage', data, { trim: true })
+}
+
+export function gatewayOptions () {
+  return request.get(base + '/gatewayOptions')
+}
diff --git a/admin/src/api/business/ywconditioneractions.js b/admin/src/api/business/ywconditioneractions.js
new file mode 100644
index 0000000..a9d2ce5
--- /dev/null
+++ b/admin/src/api/business/ywconditioneractions.js
@@ -0,0 +1,11 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditionerActions'
+
+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/ywconditionerbilling.js b/admin/src/api/business/ywconditionerbilling.js
new file mode 100644
index 0000000..36f14b9
--- /dev/null
+++ b/admin/src/api/business/ywconditionerbilling.js
@@ -0,0 +1,11 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditionerBilling'
+
+export function fetchList (data) {
+  return request.post(base + '/page', data, { trim: true })
+}
+
+export function syncAll (data) {
+  return request.post(base + '/syncAll', data || {})
+}
diff --git a/admin/src/api/business/ywconditionergateway.js b/admin/src/api/business/ywconditionergateway.js
new file mode 100644
index 0000000..545f76e
--- /dev/null
+++ b/admin/src/api/business/ywconditionergateway.js
@@ -0,0 +1,15 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditionerGateway'
+
+export function fetchList (data) {
+  return request.post(base + '/page', data, { trim: true })
+}
+
+export function syncAll (data) {
+  return request.post(base + '/syncAll', data || {})
+}
+
+export function gatewayLogPage (data) {
+  return request.post(base + '/gatewayLogPage', data, { trim: true })
+}
diff --git a/admin/src/api/business/ywconditionermeter.js b/admin/src/api/business/ywconditionermeter.js
new file mode 100644
index 0000000..da35cca
--- /dev/null
+++ b/admin/src/api/business/ywconditionermeter.js
@@ -0,0 +1,19 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditionerMeter'
+
+export function fetchList (data) {
+  return request.post(base + '/page', data, { trim: true })
+}
+
+export function syncAll (data) {
+  return request.post(base + '/syncAll', data || {})
+}
+
+export function queryEnergy (id) {
+  return request.post(base + '/queryEnergy/' + id)
+}
+
+export function queryPower (id) {
+  return request.post(base + '/queryPower/' + id)
+}
diff --git a/admin/src/api/business/ywconditionerreport.js b/admin/src/api/business/ywconditionerreport.js
new file mode 100644
index 0000000..1c286eb
--- /dev/null
+++ b/admin/src/api/business/ywconditionerreport.js
@@ -0,0 +1,19 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywConditionerReport'
+
+export function fetchPage (data) {
+  return request.post(base + '/page', data, { trim: true })
+}
+
+export function syncUsage (data) {
+  return request.post(base + '/syncUsage', data || {})
+}
+
+export function merchantOptions () {
+  return request.get(base + '/merchantOptions')
+}
+
+export function exportExcel (data) {
+  return request.post(base + '/exportExcel', data, { trim: true, download: true })
+}
diff --git a/admin/src/components/base/BasePage.vue b/admin/src/components/base/BasePage.vue
index 38cf164..a0f0bf7 100644
--- a/admin/src/components/base/BasePage.vue
+++ b/admin/src/components/base/BasePage.vue
@@ -39,6 +39,9 @@
       if (permissions == null) {
         return true
       }
+      if (this.isAdmin) {
+        return true
+      }
       if (this.userInfo == null) {
         return false
       }
diff --git a/admin/src/directives/v-permissions.js b/admin/src/directives/v-permissions.js
index c96efda..5d2027d 100644
--- a/admin/src/directives/v-permissions.js
+++ b/admin/src/directives/v-permissions.js
@@ -14,8 +14,12 @@
     if (!(configPermissions instanceof Array)) {
       throw new Error('v-permissions鐨勫�煎繀椤讳负涓�涓暟缁�')
     }
+    // 瓒呯骇绠$悊鍛橈紙瑙掕壊 code=admin锛夋嫢鏈夊叏閮ㄦ寜閽潈闄�
+    if (userInfo.roles && userInfo.roles.findIndex(code => code === 'admin') > -1) {
+      return
+    }
     // 楠岃瘉鏉冮檺
-    if (configPermissions.findIndex(code => userInfo.permissions.findIndex(p => p === code) > -1) === -1) {
+    if (!userInfo.permissions || configPermissions.findIndex(code => userInfo.permissions.findIndex(p => p === code) > -1) === -1) {
       el.parentNode && el.parentNode.removeChild(el)
     }
   }
diff --git a/admin/src/views/business/components/YwConditionerCard.vue b/admin/src/views/business/components/YwConditionerCard.vue
new file mode 100644
index 0000000..e2aee2d
--- /dev/null
+++ b/admin/src/views/business/components/YwConditionerCard.vue
@@ -0,0 +1,293 @@
+<template>
+  <div class="cond-card" :class="{ offline: !isOnline }">
+    <div class="cond-card__head">
+      <span class="cond-card__name" :title="displayName">{{ displayName }}</span>
+      <i class="cond-card__online" :class="isOnline ? 'el-icon-success online' : 'el-icon-error offline'" :title="device.online || '绂荤嚎'"></i>
+    </div>
+    <div class="cond-card__uptime" v-if="device.uptime">杩愯 {{ device.uptime }}</div>
+    <div class="cond-card__temp">
+      <div class="temp-set">
+        <el-button type="text" class="temp-btn" icon="el-icon-arrow-down" @click="$emit('set-temp', device, -1)" v-permissions="['business:ywconditioner:operate']" />
+        <span class="temp-num">{{ formatTemp(device.tempSet) }}</span>
+        <el-button type="text" class="temp-btn" icon="el-icon-arrow-up" @click="$emit('set-temp', device, 1)" v-permissions="['business:ywconditioner:operate']" />
+        <span class="temp-label">銆愯瀹氭俯搴︺��</span>
+      </div>
+      <div class="temp-indoor">
+        <span class="temp-num sm">{{ formatTemp(device.temp) }}</span>
+        <span class="temp-label">銆愬鍐呮俯搴︺��</span>
+      </div>
+    </div>
+    <div class="cond-card__modes">
+      <span class="cond-card__row-label">妯″紡锛�</span>
+      <span
+        v-for="item in modeOptions"
+        :key="'m' + item.value"
+        class="mode-icon"
+        :class="{ active: device.mode === item.value, 'accent-red': item.accentRed && device.mode === item.value }"
+        :title="item.label"
+        @click="$emit('set-mode', device, item.value)"
+        v-permissions="['business:ywconditioner:operate']"
+      >{{ item.icon }}</span>
+    </div>
+    <div class="cond-card__fans">
+      <span class="cond-card__row-label">椋庨�燂細</span>
+      <span
+        v-for="item in fanOptions"
+        :key="'f' + item.value"
+        class="fan-icon"
+        :class="{ active: currentFan === item.value, 'accent-red': item.accentRed && currentFan === item.value }"
+        :title="item.label"
+        @click="$emit('set-fan', device, item.value)"
+        v-permissions="['business:ywconditioner:operate']"
+      >{{ item.short }}</span>
+    </div>
+    <div class="cond-card__pwr">
+      <span class="pwr-dot" :class="{ on: device.pwr === 1 }"></span>
+      {{ device.pwr === 1 ? '杩愯涓�' : '宸插叧鏈�' }}
+      <span v-if="device.ktLock === 1" class="lock-tag">閿佸畾</span>
+    </div>
+    <div class="cond-card__actions">
+      <el-button size="mini" plain @click="$emit('history', device)">鍘嗗彶</el-button>
+      <el-button
+        v-if="isDeviceLocked"
+        size="mini"
+        type="danger"
+        plain
+        :loading="lockLoading"
+        @click="$emit('unlock', device)"
+        v-permissions="['business:ywconditioner:operate']"
+      >瑙i攣</el-button>
+      <el-button
+        v-else
+        size="mini"
+        plain
+        :loading="lockLoading"
+        @click="$emit('lock', device)"
+        v-permissions="['business:ywconditioner:operate']"
+      >閿佸畾</el-button>
+      <el-button
+        size="mini"
+        :type="device.pwr === 1 ? 'danger' : 'primary'"
+        :loading="powerLoading"
+        @click="$emit('toggle-power', device)"
+        v-permissions="['business:ywconditioner:operate']"
+      >{{ device.pwr === 1 ? '鍏虫満' : '寮�鏈�' }}</el-button>
+    </div>
+    <div class="cond-card__meta" v-if="device.wgMac">
+      <span v-if="device.wgMac" class="wg-block">
+        <span class="wg-mac" :title="device.wgMac">{{ device.wgMac }}</span>
+        <span v-if="device.wgBz" class="wg-bz" :title="device.wgBz">{{ device.wgBz }}</span>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+const MODE_OPTIONS = [
+  { value: 1, label: '鍒剁儹', icon: '鈽�', accentRed: true },
+  { value: 2, label: '鍒跺喎', icon: '鉂�' },
+  { value: 3, label: '閫侀', icon: '椋�' },
+  { value: 4, label: '闄ゆ箍', icon: '婀�' }
+]
+
+const FAN_OPTIONS = [
+  { value: 1, label: '浣庨��', short: 'L' },
+  { value: 2, label: '涓��', short: 'M' },
+  { value: 3, label: '楂橀��', short: 'H' },
+  { value: 4, label: '鑷姩', short: 'A', accentRed: true }
+]
+
+export default {
+  name: 'YwConditionerCard',
+  props: {
+    device: {
+      type: Object,
+      required: true
+    },
+    powerLoading: {
+      type: Boolean,
+      default: false
+    },
+    lockLoading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      modeOptions: MODE_OPTIONS,
+      fanOptions: FAN_OPTIONS
+    }
+  },
+  computed: {
+    isOnline () {
+      const o = this.device.online
+      if (o === '鍦ㄧ嚎' || o === '1' || o === 1) return true
+      if (o === 88 || o === 66 || o === '88' || o === '66') return true
+      return false
+    },
+    displayName () {
+      const parts = [this.device.floorName, this.device.roomName, this.device.name]
+        .filter(p => p != null && String(p).trim() !== '')
+      return parts.length ? parts.join('/') : '-'
+    },
+    currentFan () {
+      return this.device.fanSet != null ? this.device.fanSet : this.device.fan
+    },
+    isDeviceLocked () {
+      return this.device.ktLock === 1
+    }
+  },
+  methods: {
+    formatTemp (val) {
+      if (val === null || val === undefined || val === '') return '--'
+      const n = Number(val)
+      if (isNaN(n)) return val
+      const celsius = n > 100 ? n / 10 : n
+      return celsius.toFixed(1) + '鈩�'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.cond-card {
+  background: linear-gradient(145deg, #2c3e50 0%, #1a252f 100%);
+  border-radius: 10px;
+  padding: 14px 16px;
+  color: #e8ecf0;
+  min-height: 220px;
+  display: flex;
+  flex-direction: column;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  transition: opacity 0.2s;
+}
+.cond-card.offline { opacity: 0.75; }
+.cond-card__head {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 6px;
+}
+.cond-card__name {
+  font-weight: 600;
+  font-size: 15px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  flex: 1;
+}
+.cond-card__online.online { color: #67c23a; font-size: 18px; }
+.cond-card__online.offline { color: #909399; font-size: 18px; }
+.cond-card__uptime {
+  font-size: 12px;
+  color: #a8b4c0;
+  margin-bottom: 10px;
+}
+.cond-card__temp {
+  display: flex;
+  align-items: flex-end;
+  gap: 20px;
+  margin-bottom: 12px;
+}
+.temp-set .temp-num { font-size: 28px; font-weight: 300; line-height: 1; }
+.temp-indoor .temp-num.sm { font-size: 20px; color: #b0bec5; }
+.temp-label { display: block; font-size: 11px; color: #90a4ae; margin-top: 4px; }
+.cond-card__modes,
+.cond-card__fans {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 8px;
+}
+.cond-card__row-label {
+  font-size: 12px;
+  color: #90a4ae;
+  flex-shrink: 0;
+  min-width: 36px;
+}
+.mode-icon,
+.fan-icon {
+  width: 28px;
+  height: 28px;
+  border-radius: 6px;
+  background: rgba(255, 255, 255, 0.08);
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+  color: #78909c;
+  cursor: pointer;
+}
+.mode-icon.active,
+.fan-icon.active {
+  background: rgba(64, 158, 255, 0.35);
+  color: #fff;
+}
+.mode-icon.active.accent-red,
+.fan-icon.active.accent-red {
+  background: rgba(245, 108, 108, 0.5);
+  color: #ffecec;
+  box-shadow: 0 0 0 1px rgba(245, 108, 108, 0.55);
+}
+.temp-btn {
+  color: #b0bec5;
+  padding: 0 4px;
+}
+.cond-card__pwr {
+  font-size: 13px;
+  margin-bottom: 12px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+.pwr-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #909399;
+}
+.pwr-dot.on { background: #67c23a; }
+.lock-tag {
+  font-size: 11px;
+  color: #e6a23c;
+  border: 1px solid #e6a23c;
+  border-radius: 3px;
+  padding: 0 4px;
+}
+.cond-card__actions {
+  display: flex;
+  gap: 6px;
+  flex-wrap: wrap;
+  margin-top: auto;
+}
+.cond-card__actions .el-button { flex: 1; min-width: 56px; }
+.cond-card__meta {
+  margin-top: 8px;
+  font-size: 11px;
+  color: #78909c;
+  display: flex;
+  justify-content: flex-end;
+  gap: 8px;
+}
+.wg-block {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  max-width: 100%;
+  text-align: right;
+  gap: 2px;
+}
+.wg-mac,
+.wg-bz {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 100%;
+}
+.wg-bz {
+  color: #90a4ae;
+  font-size: 10px;
+  line-height: 1.2;
+}
+</style>
diff --git a/admin/src/views/business/components/YwConditionerGatewayLogWindow.vue b/admin/src/views/business/components/YwConditionerGatewayLogWindow.vue
new file mode 100644
index 0000000..b90f242
--- /dev/null
+++ b/admin/src/views/business/components/YwConditionerGatewayLogWindow.vue
@@ -0,0 +1,91 @@
+<template>
+  <el-dialog
+    :title="'涓婁笅绾胯褰� - ' + (gateway.wgMac || '')"
+    :visible.sync="visible"
+    width="720px"
+    append-to-body
+    @open="load"
+  >
+    <el-table v-loading="loading" :data="list" stripe size="small" max-height="400">
+      <el-table-column prop="wgMac" label="缃戝叧MAC" min-width="140" align="center" show-overflow-tooltip />
+      <el-table-column prop="oldStatus" label="鍘熺姸鎬�" min-width="90" align="center" />
+      <el-table-column prop="newStatus" label="鏂扮姸鎬�" min-width="90" align="center">
+        <template slot-scope="{ row }">
+          <span :class="row.newStatus === '鍦ㄧ嚎' ? 'green' : 'red'">{{ row.newStatus || '-' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="logTime" label="璁板綍鏃堕棿" min-width="160" align="center" />
+      <el-table-column prop="source" label="鏉ユ簮" min-width="90" align="center" />
+    </el-table>
+    <pagination
+      class="mt10"
+      @size-change="onSizeChange"
+      @current-change="onPageChange"
+      :pagination="pagination"
+    />
+  </el-dialog>
+</template>
+
+<script>
+import Pagination from '@/components/common/Pagination'
+import { gatewayLogPage } from '@/api/business/ywconditionergateway'
+
+export default {
+  name: 'YwConditionerGatewayLogWindow',
+  components: { Pagination },
+  data () {
+    return {
+      visible: false,
+      loading: false,
+      gateway: {},
+      list: [],
+      pagination: {
+        pageIndex: 1,
+        pageSize: 10,
+        total: 0
+      }
+    }
+  },
+  methods: {
+    open (row) {
+      this.gateway = { ...row }
+      this.pagination.pageIndex = 1
+      this.visible = true
+    },
+    load () {
+      if (!this.gateway.id && !this.gateway.wgMac) return
+      this.loading = true
+      const model = {}
+      if (this.gateway.id) model.gatewayId = this.gateway.id
+      if (this.gateway.wgMac) model.wgMac = this.gateway.wgMac
+      gatewayLogPage({
+        page: this.pagination.pageIndex,
+        capacity: this.pagination.pageSize,
+        model,
+        sorts: []
+      })
+        .then(data => {
+          this.list = data.records || []
+          this.pagination.total = data.total || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.loading = false })
+    },
+    onPageChange (page) {
+      this.pagination.pageIndex = page || 1
+      this.load()
+    },
+    onSizeChange (size) {
+      this.pagination.pageSize = size
+      this.pagination.pageIndex = 1
+      this.load()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.mt10 { margin-top: 10px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/components/YwConditionerHistoryWindow.vue b/admin/src/views/business/components/YwConditionerHistoryWindow.vue
new file mode 100644
index 0000000..bf7e2f7
--- /dev/null
+++ b/admin/src/views/business/components/YwConditionerHistoryWindow.vue
@@ -0,0 +1,102 @@
+<template>
+  <GlobalWindow title="鎺у埗鍘嗗彶" :visible.sync="visible" width="900px" :show-confirm="false">
+    <p class="device-info" v-if="device.id">{{ device.name || device.code }} 路 {{ device.wgMac }}</p>
+    <el-table v-loading="loading" :data="list" stripe size="small" max-height="420">
+      <el-table-column prop="id" label="ID" width="70" align="center" />
+      <el-table-column label="鎿嶄綔绫诲瀷" min-width="100" align="center">
+        <template slot-scope="{ row }">{{ formatActionType(row.actionType) }}</template>
+      </el-table-column>
+      <el-table-column prop="actionContent" label="鎿嶄綔鎻忚堪" min-width="180" show-overflow-tooltip align="center" />
+      <el-table-column label="缁撴灉" width="80" align="center">
+        <template slot-scope="{ row }">
+          <span :class="row.resultStatus === 1 ? 'green' : 'red'">{{ row.resultStatus === 1 ? '鎴愬姛' : '澶辫触' }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="resultMsg" label="璇存槑" min-width="120" show-overflow-tooltip align="center" />
+      <el-table-column prop="createDate" label="鏃堕棿" min-width="150" align="center" />
+    </el-table>
+    <pagination
+      class="mt10"
+      @size-change="onSizeChange"
+      @current-change="onPageChange"
+      :pagination="pagination"
+    />
+  </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import { historyPage } from '@/api/business/ywconditioner'
+
+const ACTION_TYPE_MAP = {
+  1: '寮�鍏�',
+  2: '妯″紡',
+  3: '椋庨��',
+  4: '娓╁害',
+  5: '閿佸畾',
+  6: '鏌ョ數閲�',
+  7: '鏌ュ姛鐜�'
+}
+
+export default {
+  name: 'YwConditionerHistoryWindow',
+  components: { GlobalWindow, Pagination },
+  data () {
+    return {
+      visible: false,
+      loading: false,
+      device: {},
+      list: [],
+      pagination: {
+        pageIndex: 1,
+        pageSize: 10,
+        total: 0
+      }
+    }
+  },
+  methods: {
+    open (row) {
+      this.device = { ...row }
+      this.pagination.pageIndex = 1
+      this.visible = true
+      this.load()
+    },
+    load () {
+      if (!this.device.id) return
+      this.loading = true
+      historyPage({
+        page: this.pagination.pageIndex,
+        capacity: this.pagination.pageSize,
+        model: { conditionerId: this.device.id },
+        sorts: []
+      })
+        .then(data => {
+          this.list = data.records || []
+          this.pagination.total = data.total || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.loading = false })
+    },
+    onPageChange (page) {
+      this.pagination.pageIndex = page || 1
+      this.load()
+    },
+    onSizeChange (size) {
+      this.pagination.pageSize = size
+      this.pagination.pageIndex = 1
+      this.load()
+    },
+    formatActionType (val) {
+      return ACTION_TYPE_MAP[val] || val || '-'
+    }
+  }
+}
+</script>
+
+<style scoped>
+.device-info { margin-bottom: 12px; color: #606266; }
+.mt10 { margin-top: 10px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/components/YwConditionerLockWindow.vue b/admin/src/views/business/components/YwConditionerLockWindow.vue
new file mode 100644
index 0000000..283ea8a
--- /dev/null
+++ b/admin/src/views/business/components/YwConditionerLockWindow.vue
@@ -0,0 +1,121 @@
+<template>
+  <GlobalWindow title="璁惧閿佸畾璁剧疆" :visible.sync="visible" width="520px" @confirm="save" :confirm-working="isSaving">
+    <div class="lock-form" v-if="device.id">
+      <p class="device-info">{{ device.name || device.code }} 路 {{ device.wgMac }}</p>
+      <div class="lock-section">
+        <div class="lock-label">寮�鍏崇姸鎬�</div>
+        <el-radio-group v-model="form.pwr" size="small">
+          <el-radio-button :label="-1">涓嶉檺</el-radio-button>
+          <el-radio-button :label="0">鍏�</el-radio-button>
+          <el-radio-button :label="1">寮�</el-radio-button>
+        </el-radio-group>
+      </div>
+      <div class="lock-section">
+        <div class="lock-label">妯″紡閿佸畾</div>
+        <el-radio-group v-model="form.mode" size="small">
+          <el-radio-button :label="-1">涓嶉檺</el-radio-button>
+          <el-radio-button v-for="item in modeOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button>
+        </el-radio-group>
+      </div>
+      <div class="lock-section">
+        <div class="lock-label">椋庨�熼攣瀹�</div>
+        <el-radio-group v-model="form.fan" size="small">
+          <el-radio-button :label="-1">涓嶉檺</el-radio-button>
+          <el-radio-button v-for="item in fanOptions" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button>
+        </el-radio-group>
+      </div>
+      <div class="lock-section">
+        <div class="lock-label">娓╁害鑼冨洿 (掳C)</div>
+        <div class="temp-range">
+          <el-input-number v-model="form.minTemp" :min="-1" :max="35" :step="1" size="small" controls-position="right" />
+          <span class="range-sep">~</span>
+          <el-input-number v-model="form.maxTemp" :min="-1" :max="35" :step="1" size="small" controls-position="right" />
+        </div>
+        <p class="hint">璁句负 -1 琛ㄧず涓嶉檺鍒讹紙淇濆瓨鏃舵湭濉垯浼� -1锛�</p>
+      </div>
+    </div>
+  </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import { lock } from '@/api/business/ywconditioner'
+
+const MODE_OPTIONS = [
+  { value: 0, label: '鑷姩' },
+  { value: 1, label: '鍒跺喎' },
+  { value: 2, label: '鍒剁儹' },
+  { value: 3, label: '閫侀' },
+  { value: 4, label: '闄ゆ箍' }
+]
+
+const FAN_OPTIONS = [
+  { value: 0, label: '鑷姩' },
+  { value: 1, label: '浣庨��' },
+  { value: 2, label: '涓��' },
+  { value: 3, label: '楂橀��' }
+]
+
+export default {
+  name: 'YwConditionerLockWindow',
+  components: { GlobalWindow },
+  data () {
+    return {
+      visible: false,
+      isSaving: false,
+      device: {},
+      form: {
+        pwr: -1,
+        mode: -1,
+        fan: -1,
+        minTemp: 17,
+        maxTemp: 32
+      },
+      modeOptions: MODE_OPTIONS,
+      fanOptions: FAN_OPTIONS
+    }
+  },
+  methods: {
+    open (row) {
+      this.device = { ...row }
+      this.form = {
+        pwr: 1,
+        mode: 2,
+        fan: 0,
+        minTemp: 17,
+        maxTemp: 32
+      }
+      this.visible = true
+    },
+    save () {
+      this.isSaving = true
+      lock({
+        id: this.device.id,
+        lockPwr: 1,
+        pwr: this.form.pwr,
+        mode: this.form.mode,
+        fan: this.form.fan,
+        minTemp: this.form.minTemp != null ? this.form.minTemp : -1,
+        maxTemp: this.form.maxTemp != null ? this.form.maxTemp : -1,
+        source: 'admin'
+      })
+        .then(res => {
+          this.$tip.apiSuccess(res || '閿佸畾鎴愬姛')
+          this.visible = false
+          this.$emit('success')
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => { this.isSaving = false })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.device-info { margin-bottom: 16px; color: #606266; }
+.lock-section { margin-bottom: 18px; }
+.lock-label { font-size: 13px; color: #303133; margin-bottom: 8px; font-weight: 500; }
+.temp-range { display: flex; align-items: center; gap: 8px; }
+.range-sep { color: #909399; }
+.hint { font-size: 12px; color: #909399; margin-top: 6px; }
+</style>
diff --git a/admin/src/views/business/components/YwElectricalEdit.vue b/admin/src/views/business/components/YwElectricalEdit.vue
index 8522326..a354342 100644
--- a/admin/src/views/business/components/YwElectricalEdit.vue
+++ b/admin/src/views/business/components/YwElectricalEdit.vue
@@ -1,5 +1,5 @@
 <template>
-  <GlobalWindow title="缂栬緫鐢佃〃" :visible.sync="visible" width="920px" :confirm-working="isWorking" @confirm="confirm">
+  <GlobalWindow title="缂栬緫鐢佃〃" :visible.sync="visible" width="720px" :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>
diff --git a/admin/src/views/business/ywconditioner.vue b/admin/src/views/business/ywconditioner.vue
new file mode 100644
index 0000000..598a89e
--- /dev/null
+++ b/admin/src/views/business/ywconditioner.vue
@@ -0,0 +1,342 @@
+<template>
+  <TableLayout :permissions="['business:ywconditioner:query']">
+    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+      <el-form-item label="璁惧淇℃伅" prop="devKeyword">
+        <el-input v-model="searchForm.devKeyword" placeholder="鍚嶇О/缂栧彿/鎴块棿" clearable @keypress.enter.native="search" />
+      </el-form-item>
+      <el-form-item label="鍦ㄧ嚎鐘舵��" prop="onlineFilter">
+        <el-select v-model="searchForm.onlineFilter" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+          <el-option label="鍦ㄧ嚎" value="鍦ㄧ嚎" />
+          <el-option label="绂荤嚎" value="绂荤嚎" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="寮�鍏崇姸鎬�" prop="pwrFilter">
+        <el-select v-model="searchForm.pwrFilter" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+          <el-option label="寮�鏈�" :value="1" />
+          <el-option label="鍏虫満" :value="0" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="缃戝叧MAC" prop="wgMacFilter">
+        <el-select v-model="searchForm.wgMacFilter" clearable filterable placeholder="鍏ㄩ儴" style="min-width: 180px">
+          <el-option v-for="item in gatewayOptions" :key="item.wgMac" :label="item.wgMac" :value="item.wgMac" />
+        </el-select>
+      </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"
+            :loading="isSyncingAll"
+            v-permissions="['business:ywconditioner:sync']"
+            @click="handleSyncAll"
+          >涓�閿悓姝ョ┖璋冨鑱旀満</el-button>
+        </li>
+        <li>
+          <el-button
+            type="primary"
+            :loading="isSyncingDevices"
+            v-permissions="['business:ywconditioner:sync']"
+            @click="handleSyncDevices"
+          >鍚屾璁惧鍜岀姸鎬�</el-button>
+        </li>
+      </ul>
+      <div v-loading="isWorking.search" class="card-grid-wrap">
+        <div v-if="!tableData.list.length && !isWorking.search" class="empty-tip">鏆傛棤璁惧鏁版嵁锛岃鍏堝悓姝ュ唴鏈�</div>
+        <div class="card-grid">
+          <YwConditionerCard
+            v-for="item in tableData.list"
+            :key="item.id"
+            :device="item"
+            :power-loading="powerLoadingId === item.id"
+            :lock-loading="lockLoadingId === item.id"
+            @history="openHistory"
+            @lock="openLock"
+            @unlock="unlockDevice"
+            @toggle-power="togglePower"
+            @set-mode="setMode"
+            @set-fan="setFan"
+            @set-temp="setTemp"
+          />
+        </div>
+      </div>
+      <pagination
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+        :pagination="paginationConfig"
+      />
+    </template>
+    <YwConditionerLockWindow ref="lockWindow" @success="refreshList" />
+    <YwConditionerHistoryWindow ref="historyWindow" />
+  </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import * as conditionerApi from '@/api/business/ywconditioner'
+import YwConditionerCard from './components/YwConditionerCard'
+import YwConditionerLockWindow from './components/YwConditionerLockWindow'
+import YwConditionerHistoryWindow from './components/YwConditionerHistoryWindow'
+
+const ADJUST_OPERATE_INTERVAL_MS = 2000
+const ADJUST_ACTION_TYPES = [2, 3, 4]
+const OFFLINE_TIP = '璁惧绂荤嚎浜�,璇锋鏌ュ搴旇澶囩綉缁�'
+
+export default {
+  name: 'YwConditioner',
+  extends: BaseTable,
+  components: {
+    TableLayout,
+    Pagination,
+    YwConditionerCard,
+    YwConditionerLockWindow,
+    YwConditionerHistoryWindow
+  },
+  data () {
+    return {
+      searchForm: {
+        devKeyword: '',
+        onlineFilter: '',
+        pwrFilter: null,
+        wgMacFilter: ''
+      },
+      gatewayOptions: [],
+      isSyncingAll: false,
+      isSyncingDevices: false,
+      powerLoadingId: null,
+      lockLoadingId: null,
+      deviceAdjustOperateAt: {}
+    }
+  },
+  computed: {
+    paginationConfig () {
+      return {
+        ...this.tableData.pagination,
+        pageSizes: [15, 30, 45, 60]
+      }
+    }
+  },
+  created () {
+    this.api = conditionerApi
+    this.module = '绌鸿皟鍐呮満'
+    this.configData['field.id'] = 'id'
+    this.configData['field.main'] = 'name'
+    this.tableData.pagination.pageSize = 15
+    this.loadGatewayOptions()
+    this.search()
+  },
+  methods: {
+    loadGatewayOptions () {
+      conditionerApi.gatewayOptions()
+        .then(list => { this.gatewayOptions = list || [] })
+        .catch(() => {})
+    },
+    buildSearchModel () {
+      const model = {}
+      if (this.searchForm.devKeyword) model.devKeyword = this.searchForm.devKeyword
+      if (this.searchForm.onlineFilter) model.onlineFilter = this.searchForm.onlineFilter
+      if (this.searchForm.pwrFilter !== null && this.searchForm.pwrFilter !== '') {
+        model.pwrFilter = this.searchForm.pwrFilter
+      }
+      if (this.searchForm.wgMacFilter) model.wgMacFilter = this.searchForm.wgMacFilter
+      return model
+    },
+    handlePageChange (pageIndex) {
+      const page = Number(pageIndex)
+      if (page > 0) {
+        this.tableData.pagination.pageIndex = page
+      }
+      this.isWorking.search = true
+      conditionerApi.fetchCardPage({
+        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 || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.isWorking.search = false })
+    },
+    refreshList () {
+      this.handlePageChange(this.tableData.pagination.pageIndex)
+    },
+    patchDeviceInList (id, patch) {
+      const idx = this.tableData.list.findIndex(item => item.id === id)
+      if (idx === -1) return
+      this.$set(this.tableData.list, idx, { ...this.tableData.list[idx], ...patch })
+    },
+    isDeviceOnline (row) {
+      const o = row && row.online
+      if (o === '鍦ㄧ嚎' || o === '1' || o === 1) return true
+      if (o === 88 || o === 66 || o === '88' || o === '66') return true
+      if (typeof o === 'string' && o.toLowerCase() === 'online') return true
+      return false
+    },
+    ensureDeviceOnline (row) {
+      if (!this.isDeviceOnline(row)) {
+        this.$tip.warning(OFFLINE_TIP)
+        return false
+      }
+      return true
+    },
+    reserveAdjustOperate (deviceId) {
+      const last = this.deviceAdjustOperateAt[deviceId]
+      const now = Date.now()
+      if (last && now - last < ADJUST_OPERATE_INTERVAL_MS) {
+        const waitSec = ((ADJUST_OPERATE_INTERVAL_MS - (now - last)) / 1000).toFixed(1)
+        this.$tip.warning(`鎿嶄綔杩囦簬棰戠箒锛岃 ${waitSec} 绉掑悗鍐嶈瘯`)
+        return false
+      }
+      this.$set(this.deviceAdjustOperateAt, deviceId, now)
+      return true
+    },
+    reset () {
+      this.searchForm = { devKeyword: '', onlineFilter: '', pwrFilter: null, wgMacFilter: '' }
+      this.search()
+    },
+    handleSyncAll () {
+      this.$dialog.actionConfirm('纭鍚屾椂鍏ㄩ噺鍚屾鏇存柊 缃戝叧銆佺數琛ㄣ�佽璐圭郴鏁般�佸唴鏈烘暟鎹俊鎭悧锛�', '涓�閿悓姝ョ┖璋冨鑱旀満')
+        .then(() => {
+          this.isSyncingAll = true
+          conditionerApi.syncAll()
+            .then(res => {
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+              this.loadGatewayOptions()
+              this.search()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isSyncingAll = false })
+        })
+        .catch(() => {})
+    },
+    handleSyncDevices () {
+      this.$dialog.actionConfirm('纭浠庢櫤绮剧伒骞冲彴鍚屾璁惧淇℃伅鍜屾渶鏂拌澶囩姸鎬佸悧锛�', '鍚屾璁惧鍜岀姸鎬�')
+        .then(() => {
+          this.isSyncingDevices = true
+          conditionerApi.syncDevicesAndStatus()
+            .then(res => {
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+              this.search()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isSyncingDevices = false })
+        })
+        .catch(() => {})
+    },
+    openHistory (row) {
+      this.$refs.historyWindow.open(row)
+    },
+    openLock (row) {
+      if (!this.ensureDeviceOnline(row)) return
+      this.$refs.lockWindow.open(row)
+    },
+    unlockDevice (row) {
+      if (!this.ensureDeviceOnline(row)) return
+      this.$dialog.actionConfirm('纭瑙i攣璇ヨ澶囩殑閿佸畾璁剧疆鍚楋紵', '璁惧瑙i攣')
+        .then(() => {
+          this.lockLoadingId = row.id
+          conditionerApi.lock({
+            id: row.id,
+            lockPwr: 0,
+            pwr: -1,
+            mode: -1,
+            fan: -1,
+            minTemp: -1,
+            maxTemp: -1,
+            source: 'admin'
+          })
+            .then(res => {
+              this.$tip.apiSuccess(res || '瑙i攣鎴愬姛')
+              this.patchDeviceInList(row.id, { ktLock: 0, lockPwr: 0 })
+              this.refreshList()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.lockLoadingId = null })
+        })
+        .catch(() => {})
+    },
+    togglePower (row) {
+      if (!this.ensureDeviceOnline(row)) return
+      const next = row.pwr === 1 ? 0 : 1
+      const msg = next === 1 ? '纭寮�鏈哄悧锛�' : '纭鍏虫満鍚楋紵'
+      this.$dialog.actionConfirm(msg, '璁惧鎺у埗')
+        .then(() => {
+          this.powerLoadingId = row.id
+          conditionerApi.operate({
+            id: row.id,
+            actionType: 1,
+            setVal: next,
+            source: 'admin'
+          })
+            .then(res => {
+              this.$tip.apiSuccess(res || '鎿嶄綔鎴愬姛')
+              this.patchDeviceInList(row.id, { pwr: next })
+              this.refreshList()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.powerLoadingId = null })
+        })
+        .catch(() => {})
+    },
+    doOperate (row, actionType, setVal, label, patch) {
+      if (!this.ensureDeviceOnline(row)) return
+      if (ADJUST_ACTION_TYPES.includes(actionType) && !this.reserveAdjustOperate(row.id)) {
+        return
+      }
+      conditionerApi.operate({
+        id: row.id,
+        actionType,
+        setVal,
+        source: 'admin'
+      })
+        .then(res => {
+          this.$tip.apiSuccess(res || label + '鎴愬姛')
+          if (patch) {
+            this.patchDeviceInList(row.id, patch)
+          }
+        })
+        .catch(e => this.$tip.apiFailed(e))
+    },
+    setMode (row, mode) {
+      if (row.mode === mode) return
+      this.doOperate(row, 2, mode, '妯″紡璁剧疆', { mode })
+    },
+    setFan (row, fan) {
+      const cur = row.fanSet != null ? row.fanSet : row.fan
+      if (cur === fan) return
+      this.doOperate(row, 3, fan, '椋庨�熻缃�', { fanSet: fan, fan })
+    },
+    setTemp (row, delta) {
+      let cur = Number(row.tempSet)
+      if (isNaN(cur)) cur = 260
+      if (cur > 100) cur = cur / 10
+      const next = Math.max(16, Math.min(32, cur + delta))
+      this.doOperate(row, 4, next, '娓╁害璁剧疆', { tempSet: next * 10 })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.card-grid-wrap { min-height: 120px; }
+.card-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+  gap: 16px;
+  padding: 8px 0 16px;
+}
+.empty-tip {
+  text-align: center;
+  color: #909399;
+  padding: 48px 0;
+}
+</style>
diff --git a/admin/src/views/business/ywconditioneractions.vue b/admin/src/views/business/ywconditioneractions.vue
new file mode 100644
index 0000000..d6aad2a
--- /dev/null
+++ b/admin/src/views/business/ywconditioneractions.vue
@@ -0,0 +1,195 @@
+<template>
+  <TableLayout :permissions="['business:ywconditioneractions: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="devKeyword">
+        <el-input v-model="searchForm.devKeyword" placeholder="璁惧鍚嶇О" clearable @keypress.enter.native="search" />
+      </el-form-item>
+      <el-form-item label="缃戝叧MAC" prop="wgMac">
+        <el-input v-model="searchForm.wgMac" placeholder="缃戝叧MAC" clearable @keypress.enter.native="search" />
+      </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>
+      <ul class="toolbar">
+        <li>
+          <el-button
+            @click="exportExcel"
+            :loading="isWorking.export"
+            v-permissions="['business:ywconditioneractions:exportExcel']"
+          >瀵煎嚭</el-button>
+        </li>
+      </ul>
+      <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+        <el-table-column prop="id" label="ID" min-width="70" align="center" />
+        <el-table-column prop="devName" label="璁惧鍚嶇О" min-width="140" align="center" show-overflow-tooltip>
+          <template slot-scope="{ row }">{{ row.devName || '-' }}</template>
+        </el-table-column>
+        <el-table-column prop="wgMac" label="缃戝叧MAC" min-width="140" align="center" show-overflow-tooltip>
+          <template slot-scope="{ row }">{{ row.wgMac || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔绫诲瀷" min-width="100" align="center">
+          <template slot-scope="{ row }">{{ formatActionType(row.actionType) }}</template>
+        </el-table-column>
+        <el-table-column prop="actionContent" label="鎿嶄綔鎻忚堪" min-width="180" align="center" show-overflow-tooltip />
+        <el-table-column label="缁撴灉" min-width="80" align="center">
+          <template slot-scope="{ row }">
+            <span :class="row.resultStatus === 1 ? 'green' : 'red'">{{ formatResult(row.resultStatus) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="resultMsg" label="璇存槑" min-width="140" align="center" show-overflow-tooltip />
+        <el-table-column prop="createDate" label="鎿嶄綔鏃堕棿" min-width="160" align="center" />
+        <el-table-column label="璇锋眰鎶ユ枃" min-width="90" 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="90" 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/ywconditioneractions'
+
+const ACTION_TYPE_MAP = {
+  1: '寮�鍏�',
+  2: '妯″紡',
+  3: '椋庨��',
+  4: '娓╁害',
+  5: '閿佸畾',
+  6: '鏌ョ數閲�',
+  7: '鏌ュ姛鐜�'
+}
+
+export default {
+  name: 'YwConditionerActions',
+  extends: BaseTable,
+  components: { TableLayout, Pagination, OperaInterfaceLogWindow },
+  data () {
+    return {
+      searchForm: {
+        actionType: null,
+        devKeyword: '',
+        wgMac: '',
+        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.devKeyword) model.devKeyword = this.searchForm.devKeyword
+      if (this.searchForm.wgMac) model.wgMac = this.searchForm.wgMac
+      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 || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.isWorking.search = false })
+    },
+    reset () {
+      this.searchForm = { actionType: null, devKeyword: '', wgMac: '', operateTimeRange: [] }
+      if (this.$refs.searchForm) {
+        this.$refs.searchForm.resetFields()
+      }
+      this.search()
+    },
+    formatActionType (val) {
+      return ACTION_TYPE_MAP[val] || val || '-'
+    },
+    formatResult (val) {
+      if (val === 1 || val === '1') return '鎴愬姛'
+      if (val === 0 || val === '0') return '澶辫触'
+      return val == null ? '-' : String(val)
+    },
+    openJson (title, content) {
+      if (!content) return
+      this.$refs.jsonWindow.open(title, { content })
+    },
+    exportExcel () {
+      this.$dialog.exportConfirm('纭瀵煎嚭鍚楋紵')
+        .then(() => {
+          this.isWorking.export = true
+          actionsApi.exportExcel({
+            page: 1,
+            capacity: 1000000,
+            model: this.buildSearchModel(),
+            sorts: this.tableData.sorts
+          })
+            .then(response => { this.download(response) })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isWorking.export = false })
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/ywconditionerbilling.vue b/admin/src/views/business/ywconditionerbilling.vue
new file mode 100644
index 0000000..5c31ff6
--- /dev/null
+++ b/admin/src/views/business/ywconditionerbilling.vue
@@ -0,0 +1,127 @@
+<template>
+  <TableLayout :permissions="['business:ywconditionerbilling:query']">
+    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+      <el-form-item label="璁惧淇℃伅" prop="devKeyword">
+        <el-input v-model="searchForm.devKeyword" placeholder="璁惧鍚嶇О/ID" clearable @keypress.enter.native="search" />
+      </el-form-item>
+      <el-form-item label="缃戝叧MAC" prop="wgMac">
+        <el-input v-model="searchForm.wgMac" placeholder="缃戝叧MAC" 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"
+            :loading="isSyncing"
+            v-permissions="['business:ywconditionerbilling:sync']"
+            @click="handleSync"
+          >鍚屾璁¤垂绯绘暟</el-button>
+        </li>
+      </ul>
+      <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+        <el-table-column prop="platformDevId" label="骞冲彴璁惧ID" min-width="110" align="center" />
+        <el-table-column prop="devName" label="璁惧鍚嶇О" min-width="140" align="center" show-overflow-tooltip />
+        <el-table-column prop="wgMac" label="缃戝叧MAC" min-width="140" align="center" show-overflow-tooltip />
+        <el-table-column label="璁¤垂绫诲瀷" min-width="100" align="center">
+          <template slot-scope="{ row }">{{ formatKwType(row.kwType) }}</template>
+        </el-table-column>
+        <el-table-column prop="fanArg" label="椋庢満绯绘暟" min-width="100" align="center" />
+        <el-table-column prop="fanKw" label="椋庢満鍔熺巼" min-width="100" align="center" />
+        <el-table-column prop="higKw" label="楂樻。鍔熺巼" min-width="100" align="center" />
+        <el-table-column prop="midKw" label="涓。鍔熺巼" min-width="100" align="center" />
+        <el-table-column prop="lowKw" label="浣庢。鍔熺巼" min-width="100" align="center" />
+        <el-table-column prop="lastSyncDate" 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 billingApi from '@/api/business/ywconditionerbilling'
+
+const KW_TYPE_MAP = {
+  0: '鏃堕暱',
+  1: '鑳借��',
+  2: '鐢佃〃'
+}
+
+export default {
+  name: 'YwConditionerBilling',
+  extends: BaseTable,
+  components: { TableLayout, Pagination },
+  data () {
+    return {
+      searchForm: {
+        devKeyword: '',
+        wgMac: ''
+      },
+      isSyncing: false
+    }
+  },
+  created () {
+    this.api = billingApi
+    this.module = '绌鸿皟璁¤垂绯绘暟'
+    this.configData['field.id'] = 'id'
+    this.configData['field.main'] = 'devName'
+    this.search()
+  },
+  methods: {
+    buildSearchModel () {
+      const model = {}
+      if (this.searchForm.devKeyword) model.devKeyword = this.searchForm.devKeyword
+      if (this.searchForm.wgMac) model.wgMac = this.searchForm.wgMac
+      return model
+    },
+    handlePageChange (pageIndex) {
+      this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+      this.isWorking.search = true
+      billingApi.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 || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.isWorking.search = false })
+    },
+    reset () {
+      this.searchForm = { devKeyword: '', wgMac: '' }
+      this.search()
+    },
+    formatKwType (val) {
+      return KW_TYPE_MAP[val] != null ? KW_TYPE_MAP[val] : (val == null ? '-' : val)
+    },
+    handleSync () {
+      this.$dialog.actionConfirm('纭浠庢櫤绮剧伒骞冲彴鍚屾鍏ㄩ儴璁¤垂绯绘暟鍚楋紵', '鍚屾璁¤垂绯绘暟')
+        .then(() => {
+          this.isSyncing = true
+          billingApi.syncAll()
+            .then(res => {
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+              this.search()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isSyncing = false })
+        })
+        .catch(() => {})
+    }
+  }
+}
+</script>
diff --git a/admin/src/views/business/ywconditionergateway.vue b/admin/src/views/business/ywconditionergateway.vue
new file mode 100644
index 0000000..1877eee
--- /dev/null
+++ b/admin/src/views/business/ywconditionergateway.vue
@@ -0,0 +1,139 @@
+<template>
+  <TableLayout :permissions="['business:ywconditionergateway:query']">
+    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+      <el-form-item label="缃戝叧MAC" prop="wgMac">
+        <el-input v-model="searchForm.wgMac" placeholder="MAC鍦板潃" clearable @keypress.enter.native="search" />
+      </el-form-item>
+      <el-form-item label="鍦ㄧ嚎鐘舵��" prop="onlineStatus">
+        <el-select v-model="searchForm.onlineStatus" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+          <el-option label="鍦ㄧ嚎" value="鍦ㄧ嚎" />
+          <el-option label="绂荤嚎" value="绂荤嚎" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="鍏抽敭瀛�" prop="keyword">
+        <el-input v-model="searchForm.keyword" placeholder="MAC/澶囨敞" 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"
+            :loading="isSyncing"
+            v-permissions="['business:ywconditionergateway:sync']"
+            @click="handleSync"
+          >鍚屾缃戝叧</el-button>
+        </li>
+      </ul>
+      <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+        <el-table-column prop="wgMac" label="缃戝叧MAC" min-width="150" align="center" show-overflow-tooltip />
+        <el-table-column prop="wgBz" label="澶囨敞" min-width="140" align="center" show-overflow-tooltip>
+          <template slot-scope="{ row }">{{ row.wgBz || '-' }}</template>
+        </el-table-column>
+        <el-table-column label="鍦ㄧ嚎鐘舵��" min-width="100" align="center">
+          <template slot-scope="{ row }">
+            <span :class="row.onlineStatus === '鍦ㄧ嚎' ? 'green' : 'red'">{{ row.onlineStatus || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="lastSyncDate" label="鏈�鍚庡悓姝�" min-width="160" align="center" />
+        <el-table-column label="鎿嶄綔" min-width="120" align="center" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button type="text" @click="openLog(row)">涓婄嚎/绂荤嚎璁板綍</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        @size-change="handleSizeChange"
+        @current-change="handlePageChange"
+        :pagination="tableData.pagination"
+      />
+    </template>
+    <YwConditionerGatewayLogWindow ref="logWindow" />
+  </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import * as gatewayApi from '@/api/business/ywconditionergateway'
+import YwConditionerGatewayLogWindow from './components/YwConditionerGatewayLogWindow'
+
+export default {
+  name: 'YwConditionerGateway',
+  extends: BaseTable,
+  components: { TableLayout, Pagination, YwConditionerGatewayLogWindow },
+  data () {
+    return {
+      searchForm: {
+        wgMac: '',
+        onlineStatus: '',
+        keyword: ''
+      },
+      isSyncing: false
+    }
+  },
+  created () {
+    this.api = gatewayApi
+    this.module = '绌鸿皟缃戝叧'
+    this.configData['field.id'] = 'id'
+    this.configData['field.main'] = 'wgMac'
+    this.search()
+  },
+  methods: {
+    buildSearchModel () {
+      const model = {}
+      if (this.searchForm.wgMac) model.wgMac = this.searchForm.wgMac
+      if (this.searchForm.onlineStatus) model.onlineStatus = this.searchForm.onlineStatus
+      if (this.searchForm.keyword) model.keyword = this.searchForm.keyword
+      return model
+    },
+    handlePageChange (pageIndex) {
+      this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+      this.isWorking.search = true
+      gatewayApi.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 || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.isWorking.search = false })
+    },
+    reset () {
+      this.searchForm = { wgMac: '', onlineStatus: '', keyword: '' }
+      this.search()
+    },
+    handleSync () {
+      this.$dialog.actionConfirm('纭浠庢櫤绮剧伒骞冲彴鍚屾鍏ㄩ儴缃戝叧鍚楋紵', '鍚屾缃戝叧')
+        .then(() => {
+          this.isSyncing = true
+          gatewayApi.syncAll()
+            .then(res => {
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+              this.search()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isSyncing = false })
+        })
+        .catch(() => {})
+    },
+    openLog (row) {
+      this.$refs.logWindow.open(row)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/ywconditionermeter.vue b/admin/src/views/business/ywconditionermeter.vue
new file mode 100644
index 0000000..392aa24
--- /dev/null
+++ b/admin/src/views/business/ywconditionermeter.vue
@@ -0,0 +1,169 @@
+<template>
+  <TableLayout :permissions="['business:ywconditionermeter:query']">
+    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+      <el-form-item label="璁惧淇℃伅" prop="keyword">
+        <el-input v-model="searchForm.keyword" placeholder="鐢佃〃鍚嶇О/鍦板潃" clearable @keypress.enter.native="search" />
+      </el-form-item>
+      <el-form-item label="缃戝叧MAC" prop="wgMacFilter">
+        <el-input v-model="searchForm.wgMacFilter" placeholder="缃戝叧MAC" 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"
+            :loading="isSyncing"
+            v-permissions="['business:ywconditionermeter:sync']"
+            @click="handleSync"
+          >鍚屾鐢佃〃</el-button>
+        </li>
+      </ul>
+      <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+        <el-table-column prop="dbName" label="鐢佃〃鍚嶇О" min-width="130" align="center" show-overflow-tooltip />
+        <el-table-column prop="dbAdr" label="琛ㄥ湴鍧�" min-width="120" align="center" show-overflow-tooltip />
+        <el-table-column prop="wgMac" label="缃戝叧MAC" min-width="140" align="center" show-overflow-tooltip />
+        <el-table-column prop="dbBb" label="鍙樻瘮" min-width="80" align="center" />
+        <el-table-column prop="xyName" label="鍗忚" min-width="100" align="center" show-overflow-tooltip />
+        <el-table-column prop="standbyShare" label="寰呮満鍒嗘憡" min-width="100" align="center" show-overflow-tooltip />
+        <el-table-column prop="outdoorLoop" label="澶栨満鍥炶矾" min-width="90" align="center" />
+        <el-table-column label="鍔熺巼(kW)" min-width="100" align="center">
+          <template slot-scope="{ row }">{{ formatNum(row.powerKw) }}</template>
+        </el-table-column>
+        <el-table-column label="绱鐢甸噺" min-width="110" align="center">
+          <template slot-scope="{ row }">{{ formatNum(row.totalDl) }}</template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" min-width="160" align="center" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button
+              type="text"
+              :loading="operatingId === row.id && operateType === 'energy'"
+              v-permissions="['business:ywconditionermeter:operate']"
+              @click="queryEnergy(row)"
+            >鏌ョ數閲�</el-button>
+            <el-button
+              type="text"
+              :loading="operatingId === row.id && operateType === 'power'"
+              v-permissions="['business:ywconditionermeter:operate']"
+              @click="queryPower(row)"
+            >鏌ュ姛鐜�</el-button>
+          </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 meterApi from '@/api/business/ywconditionermeter'
+
+export default {
+  name: 'YwConditionerMeter',
+  extends: BaseTable,
+  components: { TableLayout, Pagination },
+  data () {
+    return {
+      searchForm: {
+        keyword: '',
+        wgMacFilter: ''
+      },
+      isSyncing: false,
+      operatingId: null,
+      operateType: ''
+    }
+  },
+  created () {
+    this.api = meterApi
+    this.module = '绌鸿皟鐢佃〃'
+    this.configData['field.id'] = 'id'
+    this.configData['field.main'] = 'dbName'
+    this.search()
+  },
+  methods: {
+    buildSearchModel () {
+      const model = {}
+      if (this.searchForm.keyword) model.keyword = this.searchForm.keyword
+      if (this.searchForm.wgMacFilter) model.wgMacFilter = this.searchForm.wgMacFilter
+      return model
+    },
+    handlePageChange (pageIndex) {
+      this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+      this.isWorking.search = true
+      meterApi.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 || 0
+        })
+        .catch(() => {})
+        .finally(() => { this.isWorking.search = false })
+    },
+    reset () {
+      this.searchForm = { keyword: '', wgMacFilter: '' }
+      this.search()
+    },
+    formatNum (val) {
+      if (val === null || val === undefined || val === '') return '-'
+      return val
+    },
+    handleSync () {
+      this.$dialog.actionConfirm('纭浠庢櫤绮剧伒骞冲彴鍚屾鍏ㄩ儴鐢佃〃鍚楋紵', '鍚屾鐢佃〃')
+        .then(() => {
+          this.isSyncing = true
+          meterApi.syncAll()
+            .then(res => {
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+              this.search()
+            })
+            .catch(e => this.$tip.apiFailed(e))
+            .finally(() => { this.isSyncing = false })
+        })
+        .catch(() => {})
+    },
+    queryEnergy (row) {
+      this.operatingId = row.id
+      this.operateType = 'energy'
+      meterApi.queryEnergy(row.id)
+        .then(res => {
+          this.$tip.apiSuccess(res || '鏌ョ數閲忚姹傚凡鎻愪氦')
+          this.handlePageChange()
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => {
+          this.operatingId = null
+          this.operateType = ''
+        })
+    },
+    queryPower (row) {
+      this.operatingId = row.id
+      this.operateType = 'power'
+      meterApi.queryPower(row.id)
+        .then(res => {
+          this.$tip.apiSuccess(res || '鏌ュ姛鐜囪姹傚凡鎻愪氦')
+          this.handlePageChange()
+        })
+        .catch(e => this.$tip.apiFailed(e))
+        .finally(() => {
+          this.operatingId = null
+          this.operateType = ''
+        })
+    }
+  }
+}
+</script>
diff --git a/admin/src/views/business/ywconditionerreport.vue b/admin/src/views/business/ywconditionerreport.vue
new file mode 100644
index 0000000..d01c8dc
--- /dev/null
+++ b/admin/src/views/business/ywconditionerreport.vue
@@ -0,0 +1,632 @@
+<template>
+
+  <TableLayout :permissions="['business:ywconditionerreport:query']">
+
+    <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+
+      <el-form-item label="鎶ヨ〃绫诲瀷" prop="reportType">
+        <el-select v-model="searchForm.reportType" style="min-width: 140px" @change="onReportTypeChange">
+          <el-option label="鏃ユ姤琛�" value="day" />
+          <el-option label="鏈堟姤琛�" value="month" />
+        </el-select>
+      </el-form-item>
+      <el-form-item v-if="searchForm.reportType === 'month'" label="鏈堜唤" prop="month">
+
+        <el-date-picker
+
+          v-model="searchForm.month"
+
+          type="month"
+
+          value-format="yyyy-MM"
+
+          placeholder="閫夋嫨鏈堜唤"
+
+          style="width: 160px"
+
+        />
+
+      </el-form-item>
+
+      <el-form-item v-if="searchForm.reportType === 'day'" label="鏃堕棿娈�" prop="dateRange">
+
+        <el-date-picker
+
+          v-model="searchForm.dateRange"
+
+          type="daterange"
+
+          value-format="yyyy-MM-dd"
+
+          range-separator="鑷�"
+
+          start-placeholder="寮�濮嬫棩鏈�"
+
+          end-placeholder="缁撴潫鏃ユ湡"
+
+          :picker-options="datePickerOptions"
+
+          style="width: 280px"
+
+          @change="onDateRangeChange"
+
+        />
+
+      </el-form-item>
+
+      <el-form-item label="鍟嗘埛" prop="gsId">
+
+        <el-select v-model="searchForm.gsId" clearable filterable placeholder="鍏ㄩ儴" style="min-width: 200px">
+
+          <el-option v-for="item in merchantOptions" :key="item.gsId" :label="item.gsName" :value="item.gsId" />
+
+        </el-select>
+
+      </el-form-item>
+
+      <el-form-item label="璁惧淇℃伅" prop="devKeyword">
+
+        <el-input v-model="searchForm.devKeyword" placeholder="璁惧鍚嶇О/ID" 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"
+
+            :loading="isSyncing"
+
+            v-permissions="['business:ywconditionerreport:sync']"
+
+            @click="handleSyncUsage"
+
+          >鍚屾鐢ㄩ噺</el-button>
+
+        </li>
+
+        <li>
+
+          <el-button
+
+            @click="handleExport"
+
+            :loading="isWorking.export"
+
+            v-permissions="['business:ywconditionerreport:exportExcel']"
+
+          >瀵煎嚭</el-button>
+
+        </li>
+
+      </ul>
+
+      <div class="report-scroll-wrap">
+
+        <el-table v-loading="isWorking.search" :data="tableData.list" stripe border size="small">
+
+          <el-table-column label="璁惧鍚嶇О" min-width="160" fixed align="center" show-overflow-tooltip>
+            <template slot-scope="{ row }">{{ formatDevName(row) }}</template>
+          </el-table-column>
+
+          <el-table-column prop="devId" label="璁惧ID" width="90" fixed align="center" />
+
+          <el-table-column label="鎬绘椂闀�(h)" min-width="100" align="center">
+            <template slot-scope="{ row }">{{ formatNum(row.totalTime) }}</template>
+          </el-table-column>
+          <el-table-column label="鎬荤數閲�(kWh)" min-width="110" align="center">
+            <template slot-scope="{ row }">{{ formatNum(row.totalDl) }}</template>
+          </el-table-column>
+          <el-table-column label="鎬荤數璐�(鍏�)" min-width="100" align="center">
+            <template slot-scope="{ row }">{{ formatNum(row.totalDf) }}</template>
+          </el-table-column>
+          <el-table-column
+            v-for="col in dateColumns"
+            :key="col"
+            :label="col"
+            align="center"
+          >
+            <el-table-column label="鐢甸噺(kWh)" min-width="88" align="center">
+              <template slot-scope="{ row }">{{ dailyVal(row, col, 'dl') }}</template>
+            </el-table-column>
+            <el-table-column label="鏃堕暱(h)" min-width="80" align="center">
+              <template slot-scope="{ row }">{{ dailyVal(row, col, 'time') }}</template>
+            </el-table-column>
+            <el-table-column label="鐢佃垂(鍏�)" min-width="88" align="center">
+              <template slot-scope="{ row }">{{ dailyVal(row, col, 'df') }}</template>
+            </el-table-column>
+          </el-table-column>
+
+        </el-table>
+
+      </div>
+
+      <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 reportApi from '@/api/business/ywconditionerreport'
+
+import dayjs from 'dayjs'
+
+
+
+function lastMonthStr () {
+
+  return dayjs().subtract(1, 'month').format('YYYY-MM')
+
+}
+
+
+
+function defaultDayRange () {
+
+  const end = dayjs().subtract(1, 'day')
+
+  const start = end.subtract(6, 'day')
+
+  return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
+
+}
+
+
+
+export default {
+
+  name: 'YwConditionerReport',
+
+  extends: BaseTable,
+
+  components: { TableLayout, Pagination },
+
+  data () {
+
+    return {
+
+      searchForm: {
+
+        reportType: 'day',
+
+        month: lastMonthStr(),
+
+        dateRange: defaultDayRange(),
+
+        gsId: null,
+
+        devKeyword: ''
+
+      },
+
+      dateColumns: [],
+
+      merchantOptions: [],
+
+      isSyncing: false,
+
+      rangePickAnchor: null,
+
+      datePickerOptions: {
+
+        onPick: ({ minDate, maxDate }) => {
+
+          this.rangePickAnchor = maxDate ? null : minDate
+
+        },
+
+        disabledDate: (time) => {
+
+          if (!this.rangePickAnchor) return false
+
+          const anchor = dayjs(this.rangePickAnchor)
+
+          const t = dayjs(time)
+
+          return t.isBefore(anchor.subtract(30, 'day'), 'day') || t.isAfter(anchor.add(30, 'day'), 'day')
+
+        }
+
+      }
+
+    }
+
+  },
+
+  created () {
+
+    this.api = reportApi
+
+    this.module = '绌鸿皟鐢ㄩ噺鎶ヨ〃'
+
+    this.configData['field.id'] = 'devId'
+
+    this.configData['field.main'] = 'devName'
+
+    this.loadMerchants()
+
+    this.search()
+
+  },
+
+  methods: {
+
+    loadMerchants () {
+
+      reportApi.merchantOptions()
+
+        .then(list => { this.merchantOptions = list || [] })
+
+        .catch(() => {})
+
+    },
+
+    onReportTypeChange (val) {
+      if (val === 'month') {
+        if (!this.searchForm.month) {
+          this.searchForm.month = lastMonthStr()
+        }
+      } else if (val === 'day') {
+
+        if (!this.searchForm.dateRange || this.searchForm.dateRange.length !== 2) {
+
+          this.searchForm.dateRange = defaultDayRange()
+
+        }
+
+      }
+
+    },
+
+    onDateRangeChange (val) {
+
+      this.rangePickAnchor = null
+
+      if (!val || val.length !== 2) return
+
+      const days = dayjs(val[1]).diff(dayjs(val[0]), 'day') + 1
+
+      if (days > 31) {
+
+        this.$tip.error('鏃堕棿娈垫渶澶氫笉瓒呰繃31澶�')
+
+        this.searchForm.dateRange = [
+
+          dayjs(val[1]).subtract(30, 'day').format('YYYY-MM-DD'),
+
+          val[1]
+
+        ]
+
+      }
+
+    },
+
+    validateDayRange () {
+
+      if (this.searchForm.reportType !== 'day') return null
+
+      const range = this.searchForm.dateRange
+
+      if (!range || range.length !== 2) {
+
+        return '璇烽�夋嫨鏃堕棿娈�'
+
+      }
+
+      const days = dayjs(range[1]).diff(dayjs(range[0]), 'day') + 1
+
+      if (days > 31) {
+
+        return '鏃堕棿娈垫渶澶氫笉瓒呰繃31澶�'
+
+      }
+
+      return null
+
+    },
+
+    search () {
+
+      const err = this.validateDayRange()
+
+      if (err) {
+
+        this.$tip.error(err)
+
+        return
+
+      }
+
+      this.handlePageChange(1)
+
+    },
+
+    buildQuery () {
+
+      const q = {
+
+        reportType: this.searchForm.reportType,
+
+        gsId: this.searchForm.gsId || undefined,
+
+        devKeyword: this.searchForm.devKeyword || undefined,
+
+        page: this.tableData.pagination.pageIndex,
+
+        capacity: this.tableData.pagination.pageSize
+
+      }
+
+      if (this.searchForm.reportType === 'day') {
+
+        const range = this.searchForm.dateRange || []
+
+        q.startTime = range[0]
+
+        q.endTime = range[1]
+
+      } else {
+
+        q.month = this.searchForm.month || lastMonthStr()
+
+      }
+
+      return q
+
+    },
+
+    handlePageChange (pageIndex) {
+
+      const err = this.validateDayRange()
+
+      if (err) {
+
+        this.$tip.error(err)
+
+        return
+
+      }
+
+      this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+
+      this.isWorking.search = true
+
+      reportApi.fetchPage(this.buildQuery())
+
+        .then(data => {
+
+          this.tableData.list = data.records || []
+
+          this.tableData.pagination.total = data.total || 0
+
+          this.dateColumns = data.dateColumns || []
+
+          if (data.page) this.tableData.pagination.pageIndex = data.page
+
+          if (data.capacity) this.tableData.pagination.pageSize = data.capacity
+
+        })
+
+        .catch(() => {})
+
+        .finally(() => { this.isWorking.search = false })
+
+    },
+
+    reset () {
+
+      this.searchForm = {
+
+        reportType: 'day',
+
+        month: lastMonthStr(),
+
+        dateRange: defaultDayRange(),
+
+        gsId: null,
+
+        devKeyword: ''
+
+      }
+
+      this.search()
+
+    },
+
+    columnToDateKey (col) {
+
+      if (this.searchForm.reportType === 'day') {
+
+        return col
+
+      }
+
+      const month = this.searchForm.month || lastMonthStr()
+
+      const parts = String(col).split('.')
+
+      if (parts.length !== 2) return null
+
+      const m = parseInt(parts[0], 10)
+
+      const d = parseInt(parts[1], 10)
+
+      if (isNaN(m) || isNaN(d)) return null
+
+      const year = parseInt(month.split('-')[0], 10)
+
+      const pad = n => (n < 10 ? '0' + n : '' + n)
+
+      return `${year}-${pad(m)}-${pad(d)}`
+
+    },
+
+    dailyVal (row, col, field) {
+
+      if (!row.daily) return '-'
+
+      const key = this.columnToDateKey(col)
+
+      if (!key || !row.daily[key]) return '-'
+
+      const v = row.daily[key][field]
+
+      return v != null && v !== '' ? v : '-'
+
+    },
+
+    formatNum (val) {
+
+      if (val === null || val === undefined || val === '') return '-'
+
+      return val
+
+    },
+
+    formatDevName (row) {
+      const parts = [row.floorName, row.roomName, row.devName]
+        .filter(p => p != null && String(p).trim() !== '')
+      return parts.length ? parts.join('/') : '-'
+    },
+
+    handleSyncUsage () {
+
+      const err = this.validateDayRange()
+
+      if (err) {
+
+        this.$tip.error(err)
+
+        return
+
+      }
+
+      const q = this.buildQuery()
+
+      const msg = q.reportType === 'day'
+
+        ? `纭鍚屾 ${q.startTime} 鑷� ${q.endTime} 鐢ㄩ噺鏁版嵁鍚楋紵`
+
+        : `纭鍚屾 ${q.month} 鐢ㄩ噺鏁版嵁鍚楋紵`
+
+      this.$dialog.actionConfirm(msg, '鍚屾鐢ㄩ噺')
+
+        .then(() => {
+
+          this.isSyncing = true
+
+          reportApi.syncUsage(q)
+
+            .then(res => {
+
+              this.$tip.apiSuccess(res || '鍚屾鎴愬姛')
+
+              this.search()
+
+            })
+
+            .catch(e => this.$tip.apiFailed(e))
+
+            .finally(() => { this.isSyncing = false })
+
+        })
+
+        .catch(() => {})
+
+    },
+
+    handleExport () {
+
+      const err = this.validateDayRange()
+
+      if (err) {
+
+        this.$tip.error(err)
+
+        return
+
+      }
+
+      this.$dialog.exportConfirm('纭瀵煎嚭鍚楋紵')
+
+        .then(() => {
+
+          this.isWorking.export = true
+
+          reportApi.exportExcel({
+
+            ...this.buildQuery(),
+
+            page: 1,
+
+            capacity: 1000000
+
+          })
+
+            .then(response => {
+
+              this.download(response)
+
+            })
+
+            .catch(e => this.$tip.apiFailed(e))
+
+            .finally(() => { this.isWorking.export = false })
+
+        })
+
+        .catch(() => {})
+
+    }
+
+  }
+
+}
+
+</script>
+
+
+
+<style scoped>
+.report-scroll-wrap {
+  width: 100%;
+  overflow-x: auto;
+}
+</style>
+
diff --git a/admin/src/views/system/menu.vue b/admin/src/views/system/menu.vue
index 27b05ad..dd130a9 100644
--- a/admin/src/views/system/menu.vue
+++ b/admin/src/views/system/menu.vue
@@ -15,7 +15,6 @@
         :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
         row-key="id"
         stripe
-        default-expand-all
         @selection-change="handleSelectionChange"
       >
         <el-table-column type="selection" width="55" fixed="left"></el-table-column>
diff --git a/server/db/ELECTRICAL_INTEGRATION.md b/server/db/ELECTRICAL_INTEGRATION.md
index d206bdf..7c85612 100644
--- a/server/db/ELECTRICAL_INTEGRATION.md
+++ b/server/db/ELECTRICAL_INTEGRATION.md
@@ -163,10 +163,16 @@
 
 ---
 
-## 浜斻�佸畾鏃朵换鍔★紙鍙�� HTTP 瑙﹀彂锛�
+## 浜斻�佸畾鏃朵换鍔★紙system_timer + quartz_job锛�
 
-| 浠诲姟 | Cron | HTTP锛坅dmin_timer锛� |
-|------|------|---------------------|
-| 閲囬泦鍣ㄧ姸鎬� | `0 */5 * * * ?` | GET `/timer/yw/getElectricalStatus` |
-| 鎵归噺鎶勮〃 | `30 0 * * * ?` | GET `/timer/yw/syncElectricalMeterData` |
-| 鏃ュ織娓呯悊 | `0 30 2 * * ?` | GET `/timer/yw/cleanElectricalLog` |
+鐢� `system_timer` 璇诲彇 `quartz_job` 琛紝閫氳繃 `visitServiceJob` Bean 鍙嶅皠璋冪敤 `VisitServiceFegin` 瀵瑰簲鏂规硶锛孒TTP 钀藉埌 `admin_timer` 鐨� `YwTimerController`銆�
+
+閰嶇疆鑴氭湰锛歚server/db/quartz_job.yw_timer.sql`锛堟墽琛屽悗闇�鍦ㄤ綔涓氳皟搴﹀钩鍙伴噸鍚�/鎭㈠浠诲姟浠ュ姞杞� Cron锛夈��
+
+| 浠诲姟 | Cron | module锛團eign 鏂规硶锛� | HTTP锛坅dmin_timer锛� |
+|------|------|----------------------|---------------------|
+| 閲囬泦鍣ㄧ姸鎬� | `0 */5 * * * ?` | `getElectricalStatus` | GET `/timer/yw/getElectricalStatus` |
+| 鎵归噺鎶勮〃 | `30 0 * * * ?` | `syncElectricalMeterData` | GET `/timer/yw/syncElectricalMeterData` |
+| 鏃ュ織娓呯悊 | `0 30 2 * * ?` | `cleanElectricalLog` | GET `/timer/yw/cleanElectricalLog` |
+| 绌鸿皟缃戝叧鐘舵�� | `0 */5 * * * ?` | `syncConditionerGatewayStatus` | GET `/timer/yw/syncConditionerGatewayStatus` |
+| 绌鸿皟鍐呮満鐘舵�� | `0 */10 * * * ?` | `syncConditionerIndoorUnits` | GET `/timer/yw/syncConditionerIndoorUnits` |
diff --git a/server/db/business.yw_conditioner.admin_role_grant.sql b/server/db/business.yw_conditioner.admin_role_grant.sql
new file mode 100644
index 0000000..0768656
--- /dev/null
+++ b/server/db/business.yw_conditioner.admin_role_grant.sql
@@ -0,0 +1,60 @@
+-- 绌鸿皟澶氳仈鏈猴細涓鸿秴绾х鐞嗗憳瑙掕壊琛ュ叏鏉冮檺锛堝彲閲嶅鎵ц锛�
+-- 鎵ц鍚庤閲嶆柊鐧诲綍浠ュ埛鏂� Redis 涓殑鏉冮檺缂撳瓨
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioner:sync', '鍚屾绌鸿皟鍐呮満', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioner:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioner:operate', '绌鸿皟鍐呮満鎺у埗', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioner:operate' AND `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:ywconditioner:query',
+    'business:ywconditioner:create',
+    'business:ywconditioner:update',
+    'business:ywconditioner:delete',
+    'business:ywconditioner:exportExcel',
+    'business:ywconditioner:sync',
+    'business:ywconditioner:operate',
+    'business:ywconditionergateway:query',
+    'business:ywconditionergateway:sync',
+    'business:ywconditionermeter:query',
+    'business:ywconditionermeter:sync',
+    'business:ywconditionermeter:operate',
+    'business:ywconditionerbilling:query',
+    'business:ywconditionerbilling:sync',
+    'business:ywconditioneractions:query',
+    'business:ywconditioneractions:exportExcel',
+    'business:ywconditionerreport:query',
+    'business:ywconditionerreport:sync',
+    'business:ywconditionerreport:exportExcel'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+  AND NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` rp
+    WHERE rp.`ROLE_ID` = r.`ID` AND rp.`PERMISSION_ID` = p.`ID` AND rp.`DELETED` = 0
+  );
+
+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.`DELETED` = 0 AND (
+    (menu.`NAME` = '绌鸿皟澶氳仈鏈�' AND (menu.`PATH` IS NULL OR menu.`PATH` = ''))
+    OR menu.`PATH` IN (
+        '/business/ywconditioner',
+        '/business/ywconditionergateway',
+        '/business/ywconditionermeter',
+        '/business/ywconditionerbilling',
+        '/business/ywconditioneractions',
+        '/business/ywconditionerreport'
+    )
+)
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+  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
+  );
diff --git a/server/db/business.yw_conditioner.menu.root.sql b/server/db/business.yw_conditioner.menu.root.sql
new file mode 100644
index 0000000..3b240d9
--- /dev/null
+++ b/server/db/business.yw_conditioner.menu.root.sql
@@ -0,0 +1,68 @@
+-- 绌鸿皟澶氳仈鏈猴細涓�绾ц彍鍗� + 6 瀛愯彍鍗� + 瓒呯骇绠$悊鍛樻巿鏉�
+
+-- 涓�绾х洰褰曪紙鏃� PATH 鎴栫┖ PATH锛屼粎浣滃垎缁勶級
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT 0, '绌鸿皟澶氳仈鏈�', '', '鏅虹簿鐏电┖璋冨鑱旀満绠$悊', NULL, 0,
+       IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = 0 AND sm.`DELETED` = 0), 0) + 1,
+       0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+WHERE NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`NAME` = '绌鸿皟澶氳仈鏈�' AND (x.`PATH` IS NULL OR x.`PATH` = '')
+);
+
+-- 瀛愯彍鍗�
+INSERT INTO `SYSTEM_MENU` (`PARENT_ID`, `NAME`, `PATH`, `REMARK`, `ICON`, `DISABLED`, `SORT`, `FIXED`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`, `PARAMS`)
+SELECT p.`ID`, '绌鸿皟鍐呮満', '/business/ywconditioner', '绌鸿皟鍐呮満鍗$墖鎺у埗', NULL, 0, 1, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditioner')
+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 p.`ID`, '缃戝叧绠$悊', '/business/ywconditionergateway', '缃戝叧鍚屾涓庝笂涓嬬嚎璁板綍', NULL, 0, 2, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditionergateway')
+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 p.`ID`, '鐢佃〃绠$悊', '/business/ywconditionermeter', '鐢佃〃鍚屾涓庢煡閲�', NULL, 0, 3, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditionermeter')
+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 p.`ID`, '璁¤垂绯绘暟', '/business/ywconditionerbilling', '璁¤垂绯绘暟鍚屾', NULL, 0, 4, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditionerbilling')
+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 p.`ID`, '璁惧鎺у埗璁板綍', '/business/ywconditioneractions', '绌鸿皟鎺у埗鎿嶄綔鍘嗗彶', NULL, 0, 5, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditioneractions')
+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 p.`ID`, '鐢ㄩ噺鏁版嵁鎶ヨ〃', '/business/ywconditionerreport', '璁惧鐢ㄩ噺閫忚鎶ヨ〃', NULL, 0, 6, 0, CURRENT_TIMESTAMP, NULL, 1, NULL, 0, NULL
+FROM `SYSTEM_MENU` p WHERE p.`DELETED` = 0 AND p.`NAME` = '绌鸿皟澶氳仈鏈�' AND (p.`PATH` IS NULL OR p.`PATH` = '')
+  AND NOT EXISTS (SELECT 1 FROM `SYSTEM_MENU` x WHERE x.`DELETED` = 0 AND x.`PATH` = '/business/ywconditionerreport')
+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.`DELETED` = 0 AND (
+    (menu.`NAME` = '绌鸿皟澶氳仈鏈�' AND (menu.`PATH` IS NULL OR menu.`PATH` = ''))
+    OR menu.`PATH` IN (
+        '/business/ywconditioner',
+        '/business/ywconditionergateway',
+        '/business/ywconditionermeter',
+        '/business/ywconditionerbilling',
+        '/business/ywconditioneractions',
+        '/business/ywconditionerreport'
+    )
+)
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+  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
+  );
diff --git a/server/db/business.yw_conditioner.module.permissions.sql b/server/db/business.yw_conditioner.module.permissions.sql
new file mode 100644
index 0000000..fa074f4
--- /dev/null
+++ b/server/db/business.yw_conditioner.module.permissions.sql
@@ -0,0 +1,88 @@
+-- 绌鸿皟澶氳仈鏈烘ā鍧楁潈闄愶紙闃查噸澶� INSERT锛�
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioner:sync', '鍚屾绌鸿皟鍐呮満', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioner:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioner:operate', '绌鸿皟鍐呮満鎺у埗', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioner:operate' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionergateway:query', '鏌ヨ缃戝叧', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionergateway:query' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionergateway:sync', '鍚屾缃戝叧', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionergateway:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionermeter:query', '鏌ヨ鐢佃〃', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionermeter:query' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionermeter:sync', '鍚屾鐢佃〃', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionermeter:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionermeter:operate', '鐢佃〃鏌ラ噺鏌ュ姛鐜�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionermeter:operate' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionerbilling:query', '鏌ヨ璁¤垂绯绘暟', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionerbilling:query' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionerbilling:sync', '鍚屾璁¤垂绯绘暟', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionerbilling:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioneractions:query', '鏌ヨ鎺у埗璁板綍', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioneractions:query' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditioneractions:exportExcel', '瀵煎嚭鎺у埗璁板綍', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditioneractions:exportExcel' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionerreport:query', '鏌ヨ鐢ㄩ噺鎶ヨ〃', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionerreport:query' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionerreport:sync', '鍚屾鐢ㄩ噺鏁版嵁', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionerreport:sync' AND `DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION`(`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywconditionerreport:exportExcel', '瀵煎嚭鐢ㄩ噺鎶ヨ〃', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` WHERE `CODE` = 'business:ywconditionerreport:exportExcel' AND `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:ywconditioner:query',
+    'business:ywconditioner:create',
+    'business:ywconditioner:update',
+    'business:ywconditioner:delete',
+    'business:ywconditioner:exportExcel',
+    'business:ywconditioner:sync',
+    'business:ywconditioner:operate',
+    'business:ywconditionergateway:query',
+    'business:ywconditionergateway:sync',
+    'business:ywconditionermeter:query',
+    'business:ywconditionermeter:sync',
+    'business:ywconditionermeter:operate',
+    'business:ywconditionerbilling:query',
+    'business:ywconditionerbilling:sync',
+    'business:ywconditioneractions:query',
+    'business:ywconditioneractions:exportExcel',
+    'business:ywconditionerreport:query',
+    'business:ywconditionerreport:sync',
+    'business:ywconditionerreport:exportExcel'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+  AND NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` rp
+    WHERE rp.`ROLE_ID` = r.`ID` AND rp.`PERMISSION_ID` = p.`ID` AND rp.`DELETED` = 0
+  );
diff --git a/server/db/conditioner_param.dict.sql b/server/db/conditioner_param.dict.sql
new file mode 100644
index 0000000..94f9fe9
--- /dev/null
+++ b/server/db/conditioner_param.dict.sql
@@ -0,0 +1,32 @@
+-- 鏅虹簿鐏电┖璋冨钩鍙板弬鏁帮紙SYSTEM_DICT / SYSTEM_DICT_DATA锛�
+-- 瀛楀吀绫诲瀷锛欳ONDITIONER_PARAM
+
+INSERT INTO `SYSTEM_DICT` (`CODE`, `NAME`, `REMARK`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'CONDITIONER_PARAM', '鏅虹簿鐏电┖璋冨钩鍙板弬鏁�', 'base_url銆乽sername銆乸assword', 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_DICT` WHERE `CODE` = 'CONDITIONER_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`, 'http://119.45.163.5:1125/zjl/API', 'base_url', 'API 鏍瑰湴鍧�锛堜笉鍚湯灏炬枩鏉狅級', 1, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'CONDITIONER_PARAM' AND d.`DELETED` = 0
+  AND NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'base_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`, 'admin', 'username', '鐧诲綍鐢ㄦ埛鍚�', 2, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'CONDITIONER_PARAM' AND d.`DELETED` = 0
+  AND NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'username' 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`, '12345678', 'password', '鐧诲綍瀵嗙爜', 3, 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+FROM `SYSTEM_DICT` d
+WHERE d.`CODE` = 'CONDITIONER_PARAM' AND d.`DELETED` = 0
+  AND NOT EXISTS (
+    SELECT 1 FROM `SYSTEM_DICT_DATA` x WHERE x.`DICT_ID` = d.`ID` AND x.`LABEL` = 'password' AND x.`DELETED` = 0
+  );
diff --git a/server/db/docs/conditioner_api_index.md b/server/db/docs/conditioner_api_index.md
new file mode 100644
index 0000000..c631b1a
--- /dev/null
+++ b/server/db/docs/conditioner_api_index.md
@@ -0,0 +1,113 @@
+# 鏅虹簿鐏� API 鎺ュ彛绱㈠紩锛堟潵婧愶細鏅虹簿鐏礎PI鎺ュ彛鏂囨。26-01.pdf锛�
+
+## 鍗忚璇存槑
+
+| 绫诲瀷 | 鏂瑰紡 | 璇存槑 |
+|------|------|------|
+| 璇诲彇 | GET | Query 鍙傛暟锛歚kt_token`銆乣kt_dwid`銆乣kt_sonid` 绛� |
+| 鎺у埗/鍐欏叆 | POST | Body 涓� JSON锛宍Content-Type: application/json` |
+| 鎴愬姛鏍囪瘑 | `code=200` | 澶辫触鏃� `code=500` 绛� |
+
+Base URL 绀轰緥锛歚http://119.45.163.5:1125/zjl/API/`锛堥厤缃」 `base_url`锛�
+
+鐧诲綍鍚庡叕鍏卞弬鏁帮細`kt_token`銆乣kt_dwid`锛堝叕鍙窱D锛夈�乣kt_sonid`锛堝瓙璐﹀彿锛岄粯璁� `0`锛�
+
+## 浜屻�佸熀纭�鎺у埗
+
+| Util 鏂规硶 | Path | Method | 璇存槑 |
+|-----------|------|--------|------|
+| `login` | `/login` | POST | 鐢ㄦ埛鐧诲綍锛岃幏鍙� token |
+| `getDevList` | `/getDevList` | GET | 鍏ㄩ儴璁惧鐘舵�� |
+| `getDevOne` | `/getDevOne` | GET | 鍗曞彴璁惧鐘舵�侊紙wg_mac, wg_qid锛� |
+| `devCtr` | `/devCtr` | POST | 鍗曡澶囨帶鍒� / 缁勫悎鎺у埗 / 鐢佃〃鏌ヨ |
+| `devManyCtr` | `/devManyCtr` | POST | 澶氳澶囨帶鍒� / 閿佸畾鎺у埗 |
+
+## 涓夈�佽澶囩鐞�
+
+| Util 鏂规硶 | Path | Method | 璇存槑 |
+|-----------|------|--------|------|
+| `getWg` | `/getWg` | GET | 鑾峰彇缃戝叧鍒楄〃锛堟枃妗f湭鍐� URL锛屾寜鍛藉悕绾﹀畾锛� |
+| `addWg` | `/addWg` | POST | 娣诲姞缃戝叧 |
+| `changeWg` | `/changeWg` | POST | 淇敼缃戝叧 |
+| `delWg` | `/delWg` | POST | 鍒犻櫎缃戝叧 |
+| `wgWithArea` | `/wgWithArea` | POST | 缃戝叧鍏宠仈鍖哄煙 |
+| `getDev` | `/getDev` | GET | 鑾峰彇璁惧妗f鍒楄〃 |
+| `addDev` | `/addDev` | POST | 娣诲姞璁惧 |
+| `changeDev` | `/changeDev` | POST | 淇敼璁惧 |
+| `delDev` | `/delDev` | POST | 鍒犻櫎璁惧 |
+
+## 鍥涖�佸尯鍩熺鐞�
+
+| Util 鏂规硶 | Path | Method |
+|-----------|------|--------|
+| `getRoom` | `/getRoom` | GET |
+| `addRoom` | `/addRoom` | POST |
+| `changeRoom` | `/changeRoom` | POST |
+| `delRoom` | `/delRoom` | POST |
+| `getFloor` | `/getFloor` | GET |
+| `addFloor` | `/addFloor` | POST |
+| `changeFloor` | `/changeFloor` | POST |
+| `delFloor` | `/delFloor` | POST |
+| `getUnit` | `/getUnit` | GET |
+| `addUnit` | `/addUnit` | POST |
+| `changeUnit` | `/changeUnit` | POST |
+| `delUnit` | `/delUnit` | POST |
+| `getBuilding` | `/getBuilding` | GET |
+| `addBuilding` | `/addBuilding` | POST |
+| `changeBuilding` | `/changeBuilding` | POST |
+| `delBuilding` | `/delBuilding` | POST |
+| `getArea` | `/getArea` | GET |
+| `addArea` | `/addArea` | POST |
+| `changeArea` | `/changeArea` | POST |
+| `delArea` | `/delArea` | POST |
+
+## 浜斻�佸畾鏃剁鐞�
+
+| Util 鏂规硶 | Path | Method |
+|-----------|------|--------|
+| `getTiming` | `/getTiming` | GET |
+| `addTiming` | `/addTiming` | POST |
+| `changeTiming` | `/changeTiming` | POST |
+| `delTiming` | `/delTiming` | POST |
+| `timingWithArea` | `/timingWithArea` | POST |
+
+## 鍏�佹暟鎹煡璇�
+
+| Util 鏂规硶 | Path | Method |
+|-----------|------|--------|
+| `getLogWg` | `/getLogWg` | GET |
+| `getLogDev` | `/getLogDev` | GET |
+
+## 涓冦�佽閲忕鐞�
+
+| Util 鏂规硶 | Path | Method |
+|-----------|------|--------|
+| `getDlSjXs` | `/getDlSjXs` | GET |
+| `changeDlSjXs` | `/changeDlSjXs` | POST |
+| `getDb` | `/glDb/getDb` | GET |
+| `addDb` | `/glDb/addDb` | POST |
+| `changeDb` | `/glDb/changeDb` | POST |
+| `delDb` | `/glDb/delDb` | POST |
+| `getDbDaySum` | `/getDbDaySum` | GET |
+| `getDayDl` | `/getDayDl` | GET |
+| `getMoonDl` | `/getMoonDl` | GET |
+| `getGs` | `/getGs` | GET |
+| `addGs` | `/addGs` | POST |
+| `changeGs` | `/changeGs` | POST |
+| `delGs` | `/delGs` | POST |
+| `gsWithArea` | `/gsWithArea` | POST |
+| `addMoney` | `/addMoney` | POST |
+| `cleanMoney` | `/cleanMoney` | POST |
+| `getCzLog` | `/getCzLog` | GET |
+
+## 鍏�佽处鍙风鐞�
+
+| Util 鏂规硶 | Path | Method |
+|-----------|------|--------|
+| `getUser` | `/getUser` | GET |
+| `addUser` | `/addUser` | POST |
+| `changeUser` | `/changeUser` | POST |
+| `changeUserPwd` | `/changeUserPwd` | POST |
+| `delUser` | `/delUser` | POST |
+
+> 鏍囨敞銆屾寜鍛藉悕绾﹀畾銆嶇殑鎺ュ彛鍦� PDF 涓湭缁欏嚭瀹屾暣 URL锛屽疄鐜颁笌鍚岀被鎺ュ彛淇濇寔涓�鑷达紱鑻ヨ仈璋� 404 璇蜂互骞冲彴瀹為檯璺緞涓哄噯銆�
diff --git a/server/db/docs/pdf_extract.txt b/server/db/docs/pdf_extract.txt
new file mode 100644
index 0000000..20a620e
--- /dev/null
+++ b/server/db/docs/pdf_extract.txt
@@ -0,0 +1,771 @@
+API鎺ュ彛鏂囨。
+涓�銆佸崗璁鏄�
+1銆佸崗璁被鍨嬭鏄�
+璇诲彇鎺ュ彛浣跨敤GET
+鎺у埗鎺ュ彛浣跨敤POST锛屽弬鏁癲ata:JSON
+浜屻�佸熀纭�鎺у埗
+1銆佺敤鎴风櫥褰�
+http://119.45.163.5:1125/zjl/API/login?
+{"username":"admin","password":"12345678"}
+
+2銆佽幏鍙栧叏閮ㄨ澶囩姸鎬�
+http://119.45.163.5:1125/zjl/API/getDevList?
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_token 鐧诲綍鍥炲弬
+kt_dwid 鍏徃ID 鐧诲綍鍥炲弬
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+dev_id 璁惧ID
+online 88鍜�66鍦ㄧ嚎锛屽叾浠栫绾� 璁惧鐘舵��
+xh 淇″彿寮哄害
+kt_lock 0鏈攣锛�1閿佸畾 閿佸畾鐘舵��
+pwr 0鍏抽棴锛�1寮�鍚� 寮�鍏�
+mode 1鍒剁儹锛�2鍒跺喎锛�3閫侀锛�4闄ゆ箍 妯″紡
+fan 1浣庨��2涓��3楂橀��4鑷姩 椋庨�熺姸鎬�
+fan_set 1浣庨��2涓��3楂橀��4鑷姩 璁惧畾椋庨��
+sf_pwr 0鍏抽棴锛�1寮�鍚� 姘撮榾鐘舵��
+temp 鏄剧ず鐨勮闄や互10 鐜娓╁害
+temp_set 鏄剧ず鐨勮闄や互10 璁惧畾娓╁害
+stop_logo 0鍋滄満锛�1鏈仠 鍋滄満鐘舵��
+sum_runtime 鎬昏繍琛屾椂闀�
+hig_runtime 楂橀�熻繍琛屾椂闀�
+mid_runtime 涓�熻繍琛屾椂闀�
+low_runtime 浣庨�熻繍琛屾椂闀�
+wg_id 缃戝叧id
+wg_mac 缃戝叧mac鍦板潃
+wg_qid 璁惧鍦板潃
+dev_name 璁惧鍚嶇О
+floor_id 妤煎眰id
+floor_name 妤煎眰鍚嶇О
+room_id 鎴块棿id
+room_name 鎴块棿鍚嶇О
+dev_type_id 璁惧绫诲瀷id
+dev_type_name 璁惧绫诲瀷鍚嶇О
+uptime 涓婃姤鏃堕棿
+2.1鑾峰彇鍗曞彴璁惧鐘舵��
+http://119.45.163.5:1125/zjl/API/getDevOne?kt_token=172588271126741&kt_dwid=0&kt_sonid=0&wg_
+mac=010000000001&wg_qid=1
+wg_mac骞冲彴娣诲姞鐨勫搴旂綉鍏砿ac鍦板潃
+wg_qid缃戝叧涓嬬殑璁惧鍦板潃锛堣澶囩鐞嗕腑鐨勮澶嘔D锛�
+3銆佸崟涓澶囨帶鍒�
+http://119.45.163.5:1125/zjl/API/devCtr?
+璇锋眰鏂瑰紡POST
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"wg_mac":"9abde73056c353e3",
+"wg_qid":"1",
+"pid":"sj",
+"set_type":"pwr",
+"set_val":0,
+"ctr_type":"set_one",
+"kt_sonid":"0"
+}
+http://119.45.163.5:1125/zjl/API/devCtr?
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_token 鐧诲綍鍥炲弬
+kt_dwid 鍏徃ID 鐧诲綍鍥炲弬
+pid 姘存満sj銆佸鑱旀満dlj锛堣幏鍙栫姸鎬佹椂杩斿洖锛� 璁惧绫诲瀷
+wg_mac 缃戝叧mac鍦板潃
+wg_qid 璁惧鍦板潃
+ctr_type set_one鍗曟潯鎺у埗 鎺у埗绫诲瀷
+set_typepwr寮�鍏筹紝mode妯″紡
+fan椋庨�燂紝temp娓╁害璁剧疆绫诲瀷
+set_val 0鍏抽棴锛�1寮�鍚� 寮�鍏宠缃��
+set_val 1鍒剁儹锛�2鍒跺喎锛�3閫侀锛�4闄ゆ箍 妯″紡璁剧疆鍊�
+set_val 1浣庨锛�2涓锛�3楂橀锛�4鑷姩 椋庨�熻缃��
+set_val 娓╁害鍊�*10 娓╁害璁剧疆鍊�
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+code 200鎴愬姛锛�500鎶ラ敊
+4銆佸涓澶囧悓鏃舵帶鍒�
+http://119.45.163.5:1125/zjl/API/devManyCtr?
+{
+"ctr_type":"set_many",
+"li_data":[
+{
+"wg_mac":"9abde73056c353e3",
+"wg_qid":"91f64e6991f64e69",
+"pid":"sj",
+"set_type":"pwr",
+"set_val":1
+},
+{
+"wg_mac":"9abde73056c353e3",
+"wg_qid":"931a964e931a964e",
+"pid":"sj",
+"set_type":"pwr",
+"set_val":1
+}
+],
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0"
+}
+5銆佸鍔熻兘缁勫悎鎺у埗
+http://119.45.163.5:1125/zjl/API/devCtr?
+瀛楁 瀛楁璇存槑 澶囨敞
+ctr_type set_one鍗曡澶囷紝set_many澶氳澶� 鎺у埗绫诲瀷
+pid 姘存満sj銆佸鑱旀満dlj锛堣幏鍙栫姸鎬佹椂杩斿洖锛� 璁惧绫诲瀷
+set_type many_ctr 璁剧疆绫诲瀷
+set_val {"pwr":1,"mode":1} 鍊煎拰鍗曞姛鑳戒竴鑷�
+6銆侀攣瀹氭帶鍒�
+http://119.45.163.5:1125/zjl/API/devManyCtr?
+瀛楁 瀛楁璇存槑 澶囨敞
+pid 姘存満sj銆佸鑱旀満dlj锛堣幏鍙栫姸鎬佹椂杩斿洖锛� 璁惧绫诲瀷
+set_type many_ctr 璁剧疆绫诲瀷
+set_val 璁剧疆鍊�
+lock_pwr 0瑙i攣锛�1閿佸畾 閿佸畾璁剧疆
+pwr 0閿佸畾鍏虫満锛�1閿佸畾寮�鏈猴紝-1涓嶉攣瀹� 寮�鍏抽攣瀹�
+mode 1閿佸畾鍒剁儹锛�2鍒跺喎锛�3閫侀锛�4闄ゆ箍锛�-1涓嶉攣 妯″紡閿佸畾
+fan 1閿佸畾浣庨锛�2涓锛�3楂橀锛�4鑷姩锛�-1涓嶉攣 椋庨�熼攣瀹�
+min_temp 娓╁害鍊�*10锛�-1涓嶉攣 娓╁害涓嬮檺
+max_temp 娓╁害鍊�*10锛�-1涓嶉攣 娓╁害涓婇檺
+涓夈�佽澶囩鐞�
+1銆佺綉鍏崇鐞�
+1锛夎幏鍙栫綉鍏�
+鍥炲弬锛�
+{
+"code":200,
+"message":"ok",
+"data":[{
+"wg_id":1,
+"wg_mac":"000000000416",
+"wg_bz":"",
+"wg_status":"鍦ㄧ嚎"锛�
+"area":{}锛�//鍖哄煙
+}]
+}
+2锛夋坊鍔犵綉鍏�
+3锛変慨鏀圭綉鍏�
+4锛夊垹闄ょ綉鍏�
+
+5锛夌綉鍏冲叧鑱斿尯鍩�
+http://119.45.163.5:1125/zjl/API/wgWithArea?
+璇锋眰鏂瑰紡锛歅OST
+{
+"id":1852,
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":1,
+"room_id":6786
+}
+2銆佽澶囩鐞�
+1锛夎幏鍙栬澶�
+鍥炲弬锛�
+{
+"code":200,
+"message":"ok",
+"data":[{
+"wg_mac":"000000000416",
+"floor_name":"1",
+"room_name":"22",
+"dev_type_name":"姘存満",
+"dev_id":1,
+"floor_id":1,
+"room_id":1,
+"wg_id":1,
+"wg_qid":3,
+"dev_name":"22-03",
+"dev_bz":"",
+"dev_type_id":1
+}]
+}
+瀛楁 瀛楁璇存槑 澶囨敞
+wg_id 缃戝叧id
+wg_mac 缃戝叧mac鍦板潃
+wg_qid 璁惧鍦板潃
+dev_id 璁惧id
+dev_name 璁惧鍚嶇О
+floor_id 妤煎眰id
+floor_name 妤煎眰鍚嶇О
+room_id 鎴块棿id
+room_name 鎴块棿鍚嶇О
+dev_type_id 璁惧绫诲瀷id
+dev_type_name璁惧绫诲瀷鍚嶇О
+dev_bz 璁惧澶囨敞
+2锛夋坊鍔犺澶�
+http://119.45.163.5:1125/zjl/API/addDev?
+瀛楁 瀛楁璇存槑 澶囨敞
+wg_id 缃戝叧id
+wg_qid 璁惧鍦板潃
+floor_id 妤煎眰id
+room_id 鎴块棿id
+dev_name 璁惧鍚嶇О
+dev_type_id 璁惧绫诲瀷id
+dev_bz 澶囨敞
+3锛変慨鏀硅澶�
+4锛夊垹闄よ澶�
+
+鍥涖�佸尯鍩熺鐞�
+1銆佹埧闂寸鐞�
+1锛夎幏鍙栨埧闂�
+鍥炲弬锛�
+{
+"code":200,
+"data":[{
+"room_id":49,
+"floor_id":1,
+"room_name":"111",
+"room_bz":""
+}],
+"message":""
+}
+瀛楁 瀛楁璇存槑 澶囨敞
+floor_id 妤煎眰id
+room_id 鎴块棿id
+room_name 鎴块棿鍚嶇О
+room_bz 鎴块棿澶囨敞
+2锛夋坊鍔犳埧闂�
+3锛変慨鏀规埧闂�
+
+4锛夊垹闄ゆ埧闂�
+2銆佹ゼ灞傜鐞�
+1锛夎幏鍙栨ゼ灞�
+http://119.45.163.5:1125/zjl/API/getFloor?kt_token=172588271126741
+&kt_dwid=0&kt_unit=1&kt_sonid=0&page=1&pageSize=100
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_token
+kt_dwid 鍏徃id
+kt_unit 鍗曞厓id
+kt_sonid 璐﹀彿id
+page 椤�
+pageSize 涓�椤垫潯鏁�
+鍥炲弬锛�
+{
+"code":200,
+"message":"ok",
+"data":[{
+"floor_id":1,
+"floor_name":"1",
+"floor_bz":""
+}]
+}
+瀛楁 瀛楁璇存槑 澶囨敞
+floor_id 妤煎眰id
+floor_name 妤煎眰鍚嶇О
+floor_bz 妤煎眰澶囨敞
+2锛夋坊鍔犳ゼ灞�
+http://119.45.163.5:1125/zjl/API/addFloor?
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_token
+kt_dwid 鍏徃id
+kt_unit 鍗曞厓id
+kt_sonid 璐﹀彿id
+floor_name 妤煎眰鍚嶇О
+floor_bz 妤煎眰澶囨敞
+3锛変慨鏀规ゼ灞�
+http://119.45.163.5:1125/zjl/API/changeFloor?
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_token
+kt_dwid 鍏徃id
+kt_unit 鍗曞厓id
+kt_sonid 璐﹀彿id
+floor_id 妤煎眰id
+floor_name 妤煎眰鍚嶇О
+floor_bz 妤煎眰澶囨敞
+4锛夊垹闄ゆゼ灞�
+3銆佸崟鍏冪鐞�
+1锛夎幏鍙栧崟鍏�
+http://119.45.163.5:1125/zjl/API/getUnit?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&kt_d
+wid=20&kt_sonid=0&page=1&pageSize=100000
+璇锋眰鏂瑰紡GET
+2锛夋坊鍔犲崟鍏�
+http://119.45.163.5:1125/zjl/API/addUnit?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"building_id":21,
+"unit_name":"1",
+"unit_bz":""
+}
+3锛変慨鏀瑰崟鍏�
+http://119.45.163.5:1125/zjl/API/changeUnit?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"unit_id":527,
+"unit_name":"12",
+"unit_bz":""
+}
+4锛夊垹闄ゅ崟鍏�
+http://119.45.163.5:1125/zjl/API/delUnit?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"unit_id":527
+}
+4銆佹ゼ鏍嬬鐞�
+1锛夎幏鍙栨ゼ鏍�
+http://119.45.163.5:1125/zjl/API/getBuilding?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&k
+t_dwid=20&kt_sonid=0&page=1&pageSize=100000
+璇锋眰鏂瑰紡GET
+2锛夋坊鍔犳ゼ鏍�
+http://119.45.163.5:1125/zjl/API/addBuilding?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"area_id":21,
+"building_name":"1F",
+"building_bz":""
+}
+3锛変慨鏀规ゼ鏍�
+http://119.45.163.5:1125/zjl/API/changeBuilding?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"building_id":335,
+"building_name":"2F",
+"building_bz":""
+}
+4锛夊垹闄ゆゼ鏍�
+http://119.45.163.5:1125/zjl/API/delBuilding?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"building_id":294
+}
+5銆佸皬鍖虹鐞�
+1锛夎幏鍙栧皬鍖�
+http://119.45.163.5:1125/zjl/API/getArea?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&kt_d
+wid=20&kt_sonid=0&page=1&pageSize=100000
+璇锋眰鏂瑰紡GET
+2锛夋坊鍔犲皬鍖�
+http://119.45.163.5:1125/zjl/API/addArea?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"area_name":"1area",
+"area_bz":""
+}
+3锛変慨鏀瑰皬鍖�
+http://119.45.163.5:1125/zjl/API/changeArea?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"area_id":298,
+"area_name":"2area",
+"area_bz":""
+}
+4锛夊垹闄ゅ皬鍖�
+http://119.45.163.5:1125/zjl/API/delArea?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"area_id":298
+}
+浜斻�佸畾鏃剁鐞�
+1銆佽幏鍙栧畾鏃�
+鍥炲弬锛�
+{
+"code":200,
+"message":"ok",
+"data":[{
+"id":9,
+"time_pwr":1,
+"time_week":"0,1,2,3,4,5,6,",
+"time_type":"lock",
+"uptime":"15:05:00",
+"li_devid":[1099],
+"d_scene":{
+"li_data":[{
+"set_type":"lock_many",
+"set_val":{
+"lock_pwr":1,
+"pwr":0,
+"mode":2,
+"fan":3,
+"min_temp":160,
+"max_temp":320
+},
+"wg_mac":"010000000001",
+"wg_qid":1
+}]
+},
+"time_bz":"瀹氭椂1"
+}]
+}
+瀛楁 瀛楁璇存槑 澶囨敞
+id 瀹氭椂id
+time_pwr 瀹氭椂寮�鍏�
+time_week 0鍛ㄤ竴锛�6鍛ㄦ棩
+time_type pwr寮�鍏筹紝lock閿佸畾锛宮any缁勫悎鎺у埗 瀹氭椂绫诲瀷
+uptime 瀹氭椂鏃堕棿
+li_devid 璁惧id闆嗗悎
+li_data 鍦烘櫙闆嗗悎
+set_type many_ctr鎺у埗锛宭ock_many閿佸畾 璁剧疆绫诲瀷
+set_val 鍜岃澶囨帶鍒朵竴鑷� 璁剧疆鍊�
+wg_mac 缃戝叧mac
+wg_qid 缃戝叧qid
+2銆佹坊鍔犲畾鏃�
+http://119.45.163.5:1125/zjl/API/addTiming?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"uptime":"18:00",
+"li_devid":[
+43868
+],
+"time_pwr":1,
+"time_type":"pwr",
+"d_scene":{
+"li_data":[
+{
+"set_type":"pwr",
+"set_val":0,
+"wg_mac":"000000001403",
+"wg_qid":"3",
+"pid":"sj"
+}
+]
+},
+"date_type":0,
+"time_date":"",
+"time_week":"0,1,2,3,4,",
+"time_bz":"瀹氭椂1",
+"sceneId":0,
+"sceneName":""
+}
+time_pwr瀹氭椂寮�鍏� 0鍏抽棴銆�1寮�鍚�
+time_type瀹氭椂绫诲瀷 寮�鍏硃wr銆佸満鏅痵cene銆侀攣瀹氭帶鍒秎ock
+缁勫悎鎺у埗many
+d_scene{li_data:[]}鎺у埗鍐呭 鍜屾壒閲忔帶鍒朵竴鑷�
+date_type鏃堕棿绫诲瀷 0鍛ㄦ湡鎺у埗銆�1鏃ユ湡鎺у埗
+time_week鍛ㄦ湡 0鍛ㄤ竴銆�6鍛ㄦ棩
+time_date瀹氭椂鏃ユ湡2026-01-07
+sceneId鍦烘櫙ID 鍜屽満鏅叧鑱旀墽琛�
+sceneName鍦烘櫙鍚嶇О 鍜屽満鏅叧鑱旀墽琛�
+3銆佷慨鏀瑰畾鏃�
+4銆佸垹闄ゅ畾鏃�
+6銆佸叧鑱斿尯鍩�
+http://119.45.163.5:1125/zjl/API/timingWithArea?
+{
+"id":593,
+"kt_dwid":"20",
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"room_id":6783
+}
+鍏�佹暟鎹煡璇�
+1銆佺綉鍏充笂绾�/绂荤嚎璁板綍
+2銆佽澶囨帶鍒惰褰�
+http://119.45.163.5:1125/zjl/API/getLogDev?kt_token=172594860456857&kt_dwid=20&kt_
+sonid=0&start_time=2025-07-21&end_time=2025-07-22&page=1&pageSize=10
+
+涓冦�佽閲忕鐞�
+1銆佽閲忕郴鏁�
+1锛夎幏鍙栬閲忕郴鏁�
+http://119.45.163.5:1125/zjl/API/getDlSjXs?kt_token=172588271126741&kt_dwid=0&kt_sonid=0&kt_uni
+t=1
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+dev_id 璁惧ID
+kt_dj 璁¤垂鐢典环
+kw_type 0鏃堕暱璁¤垂1鑳借�楄璐�2鐢佃〃璁¤垂 璁¤垂绫诲瀷
+hig_kw 楂樻。椋庨�熺郴鏁�
+mid_kw 涓。椋庨�熺郴鏁�
+low_kw 浣庢。椋庨�熺郴鏁�
+fan_arg 鐩樼绯绘暟
+fan_kw 椋庢満鑰楃數
+cold_kw 鍒跺喎鍗犳瘮 鏆傛湭鐢�
+heat_kw 鍒剁儹鍗犳瘮 鏆傛湭鐢�
+day_kw 澶栨満鏃ヨ�楃數 鏆傛湭鐢�
+2锛変慨鏀硅閲忕郴鏁�
+http://119.45.163.5:1125/zjl/API/changeDlSjXs?
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"dev_id":43868,
+"kt_sonid":"0",
+"kw_type":1,
+"fan_arg":1,
+"fan_kw":1,
+"hig_kw":1.3,
+"mid_kw":1.1,
+"low_kw":1
+}
+2銆佺數琛ㄧ鐞�
+1锛夎幏鍙栫數琛ㄥ垪琛�
+http://119.45.163.5:1125/zjl/API/glDb/getDb?kt_token=172588271126741&kt_dwid=0&kt_sonid=0&kt_
+unit=1&page=1&pageSize=100000
+绛涢�夊崟鍙扮數琛ㄥ姞&db_mac=990000011e01
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+db_id 鐢佃〃id
+wg_mac 鐢佃〃缃戝叧MAC
+wg_id 鐢佃〃缃戝叧id
+xy_name 鍗忚鍚嶇О
+xy_id 鍗忚id
+db_adr 鐢佃〃鍦板潃
+db_name 鐢佃〃鍚嶇О
+db_bb 鍙樻瘮
+db_rhd 澶栨満鏃ヨ�楃數
+li_dev [1099,1100,1102] 璁惧id鍒楄〃
+db_bz 澶囨敞
+db_data 鏈�鏂颁竴鏉℃暟鎹�
+db_uptime 鏈�鏂颁竴鏉℃暟鎹椂闂�
+2锛夋坊鍔犵數琛�
+http://119.45.163.5:1125/zjl/API/glDb/addDb?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_sonid":"0",
+"kt_unit":"1",
+"wg_id":147,
+"xy_id":2,
+"db_adr":"1",
+"db_name":"1",
+"db_bb":1,
+"db_rhd":0,
+"db_bz":"1",
+"li_dev":[1,3]
+}
+3锛変慨鏀圭數琛ㄤ俊鎭�
+http://119.45.163.5:1125/zjl/API/glDb/changeDb?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_sonid":"0",
+"kt_unit":"1",
+"db_id":1,
+"wg_id":147,
+"xy_id":2,
+"db_adr":"1",
+"db_name":"鐢佃〃1",
+"db_bb":30,
+"db_rhd":0,
+"db_bz":"",
+"li_dev":[
+1099,
+1100,
+1102
+]
+}
+4锛夊垹闄ょ數琛�
+http://119.45.163.5:1125/zjl/API/glDb/delDb?
+5锛夎Е鍙戠數琛ㄦ煡璇㈢數閲�
+http://119.45.163.5:1125/zjl/API/devCtr?
+璇锋眰绫诲瀷POST
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"wg_mac":"990000011e01",
+"wg_qid":"1",
+"ctr_type":"set_one",
+"set_type":"find_dn",
+"set_val":{
+"db_bb":1,
+"xy_name":"645鍗忚"
+}
+}
+6锛夎Е鍙戠數琛ㄦ煡璇㈠姛鐜�
+http://119.45.163.5:1125/zjl/API/devCtr?
+璇锋眰绫诲瀷POST
+{
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"kt_dwid":"20",
+"kt_sonid":"0",
+"wg_mac":"990000011e01",
+"wg_qid":"1",
+"ctr_type":"set_one",
+"set_type":"find_power",
+"set_val":{
+"db_bb":1,
+"xy_name":"645鍗忚"
+}
+}
+7锛夋煡璇㈢數琛ㄦ暟鎹�
+http://119.45.163.5:1125/zjl/API/getDbDaySum?kt_token=172588271126741&kt_dwid=0&kt_unit=1&kt_
+sonid=0&start_time=2024-12-03&end_time=2024-12-04
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+uptime 涓婃姤鏃堕棿
+wg_mac 鐢佃〃缃戝叧mac
+db_adr 鐢佃〃鍦板潃
+dl 鐢甸噺
+3銆佺數閲忔暟鎹�
+1锛夋棩鐢甸噺鎶ヨ〃
+http://119.45.163.5:1125/zjl/API/getDayDl?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&kt_dwid=20
+&kt_sonid=0&page=1&pageSize=1000000&start_time=2026-01-06&end_time=2026-01-07
+绛涢�夊叕鍙稿姞&gs_id=1
+绛涢�夎澶囧姞&dev_id=10
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+dev_id 璁惧ID
+uptime 璁¤垂鏃堕棿
+sum_df 褰撴棩鎬荤數璐�
+sum_dl 褰撴棩鎬荤數閲�
+sum_time 褰撴棩鎬昏繍琛屾椂闂�
+hig_time 褰撴棩楂樻。杩愯鏃堕棿
+mid_time 褰撴棩涓。杩愯鏃堕棿
+low_time 褰撴棩浣庢。杩愯鏃堕棿
+2锛夋湀鐢甸噺鎶ヨ〃
+http://119.45.163.5:1125/zjl/API/getMoonDl?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&k
+t_dwid=20&kt_sonid=0&page=1&pageSize=1000000&date=2025-12
+绛涢�夊叕鍙稿姞&gs_id=1
+4銆佽璐瑰叕鍙哥鐞�
+1锛夎幏鍙栧叕鍙稿垪琛�
+http://119.45.163.5:1125/zjl/API/getGs?kt_token=v14YwV2SKsq477W9sw4z5m3O667g1&kt_dwi
+d=20&kt_sonid=0
+绛涢�夊叕鍙稿姞&id=1
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+id 鍏徃id
+uptime 涓婃姤鏃堕棿
+is_pwr 鍏呭�煎紑鍏�
+is_rest_stop 澶滈棿涓嶅仠鏈�
+gs_name 鍏徃鍚嶇О
+left_money 鍓╀綑閲戦
+left_money_y 鏄ㄦ棩鍓╀綑閲戦
+is_stop 鏄惁鍋滄満
+li_dev 璁惧鍒楄〃
+d_dev 鍒嗘憡姣斾緥 鏆傛湭鍚敤
+gs_bz 鍏徃澶囨敞
+2锛夋坊鍔犲叕鍙�
+http://119.45.163.5:1125/zjl/API/addGs?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_unit":"1",
+"kt_sonid":"0",
+"is_pwr":1,
+"li_dev":[
+1,
+2
+],
+"d_dev":{
+"1":100,
+"2":100
+},
+"gs_name":"鍏徃1",
+"is_rest_stop":1,
+"gs_bz":""
+}
+3锛変慨鏀瑰叕鍙镐俊鎭�
+http://119.45.163.5:1125/zjl/API/changeGs?
+{
+"id":52,
+"is_pwr":1,
+"is_rest_stop":1,
+"gs_name":"鍏徃1",
+"left_money":0,
+"is_stop":0,
+"li_dev":[
+1,
+2
+],
+"d_dev":{
+"1":100,
+"2":100
+},
+"gs_bz":"",
+"is_pwr_xs":"寮�鍚�",
+"is_stop_xs":"鏈仠鏈�"
+}
+4锛夊垹闄ゅ叕鍙�
+http://119.45.163.5:1125/zjl/API/delGs?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_sonid":"0",
+"kt_unit":"1",
+"id":52
+}
+5锛夊叧鑱斿尯鍩�
+http://119.45.163.5:1125/zjl/API/gsWithArea?
+{
+"id":2431,
+"kt_dwid":"20",
+"kt_token":"v14YwV2SKsq477W9sw4z5m3O667g1",
+"room_id":6783
+}
+6锛夊厖鍊艰垂鐢�
+http://119.45.163.5:1125/zjl/API/addMoney?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_unit":"1",
+"kt_sonid":"0",
+"id":54,
+"cz_money":"100"
+}
+7锛夋竻绌洪噾棰�
+http://119.45.163.5:1125/zjl/API/cleanMoney?
+{
+"kt_token":"172588271126741",
+"kt_dwid":"0",
+"kt_unit":"1",
+"kt_sonid":"0",
+"id":54
+}
+8锛夋煡璇㈠厖鍊艰褰�
+http://119.45.163.5:1125/zjl/API/getCzLog?kt_token=172588271126741&kt_dwid=0&kt_unit=1&kt_soni
+d=0&start_time=2024-12-03&end_time=2024-12-05&page=1&pageSize=100000
+鍥炲弬锛�
+瀛楁 瀛楁璇存槑 澶囨敞
+gs_name 鍏徃鍚嶇О
+uptime 鍏呭�兼椂闂�
+cz_user 鍏呭�肩敤鎴�
+cz_tip 鍏呭�兼彁绀�
+cz_money 鍏呭�奸噾棰�
+left_money 鍓╀綑閲戦
+鍏�佽处鍙风鐞�
+1銆佽幏鍙栬处鍙�
+鍥炲弬锛�
+{
+"code":200,
+"message":"ok",
+"data":[{
+"user_id":1,
+"kt_token":"172588271126741",
+"kt_dwid":0,
+"username":"admin",
+"kt_qx":0,
+"kt_level":0,
+"kt_sonid":0,
+"kt_area":"0",
+"cz_pwr":0,
+"gs_name":"鏅虹簿鐏�",
+"kt_bz":"",
+"end_time":"2228-08-0808:08:08",
+"kt_unit":"1"
+}]
+}
+瀛楁 瀛楁璇存槑 澶囨敞
+user_id 鐢ㄦ埛id
+username 鐢ㄦ埛鍚嶇О
+kt_qx 鐢ㄦ埛鏉冮檺
+kt_level 0锛氭墍鏈夊姛鑳藉彲鎺� 鎺у埗鏉冮檺
+kt_area 0锛氳秴绾х鐞嗗憳 鏉冮檺绛夌骇
+kt_unit "0"锛氭墍鏈夊尯鍩燂紝鍜宬t_level鍏宠仈 鍙帶鍒跺尯鍩�
+2銆佹坊鍔犲瓙璐﹀彿
+http://119.45.163.5:1125/zjl/API/addUser?
+瀛楁 瀛楁璇存槑 澶囨敞
+kt_qx0鎵�鏈夊姛鑳藉彲鎺�
+1鍙兘鎺у埗锛屼笉鑳戒慨鏀硅澶囦俊鎭�
+2鍙兘鏌ョ湅锛屼笉鑳芥帶鍒朵慨鏀�
+kt_level0瓒呯骇绠$悊鍛�
+1璁惧绠$悊鍛�
+2鎴块棿绠$悊鍛�
+3妤煎眰绠$悊鍛�
+4鍗曞厓绠$悊鍛�
+5妤兼爧绠$悊鍛�
+6鍖哄煙绠$悊鍛�
+kt_area鏍规嵁kt_level鏀瑰彉
+濡俴t_level涓�1锛屽~鍏ラ�夋嫨鐨勮澶噄d
+瀛楃涓�"0,1,2,3,35,38,49,"
+涓�2鍒欏~鍏ユ埧闂寸殑id
+3銆佷慨鏀硅处鍙蜂俊鎭�
+4銆佷慨鏀硅处鍙峰瘑鐮�
+
+5锛夊垹闄よ处鍙�
+
diff --git "a/server/db/docs/\346\231\272\347\262\276\347\201\265API\346\216\245\345\217\243\346\226\207\346\241\24326-01.pdf" "b/server/db/docs/\346\231\272\347\262\276\347\201\265API\346\216\245\345\217\243\346\226\207\346\241\24326-01.pdf"
new file mode 100644
index 0000000..ab7e85d
--- /dev/null
+++ "b/server/db/docs/\346\231\272\347\262\276\347\201\265API\346\216\245\345\217\243\346\226\207\346\241\24326-01.pdf"
Binary files differ
diff --git a/server/db/quartz_job.module.column.sql b/server/db/quartz_job.module.column.sql
new file mode 100644
index 0000000..56c5af3
--- /dev/null
+++ b/server/db/quartz_job.module.column.sql
@@ -0,0 +1,4 @@
+-- quartz_job 澧炲姞 module 瀛楁锛圴isitServiceJobBiz 閫氳繃 module 鍙嶅皠璋冪敤 Feign 鏂规硶锛�
+-- 鑻ュ垪宸插瓨鍦紝鎵ц鎶ラ敊鍙拷鐣�
+
+ALTER TABLE `quartz_job` ADD COLUMN `module` varchar(200) DEFAULT NULL COMMENT 'Feign鏂规硶鍚�' AFTER `params`;
diff --git a/server/db/quartz_job.yw_timer.sql b/server/db/quartz_job.yw_timer.sql
new file mode 100644
index 0000000..449636b
--- /dev/null
+++ b/server/db/quartz_job.yw_timer.sql
@@ -0,0 +1,40 @@
+-- 闃滃畞杩愮淮瀹氭椂浠诲姟锛氱敱 system_timer 寰湇鍔¢�氳繃 quartz_job 璋冨害
+-- 鍓嶇疆锛氬厛鎵ц quartz_job.module.column.sql锛堣嫢灏氭棤 module 鍒楋級
+-- bean_name 鍥哄畾涓� visitServiceJob锛圴isitServiceJobBiz锛夛紝module 涓� VisitServiceFegin 鏂规硶鍚�
+-- state锛�1=杩愯 2=鏆傚仠锛涙柊澧炲悗璇峰湪銆屼綔涓氳皟搴︺�嶄腑鎭㈠浠诲姟鎴栭噸鍚� system_timer 浠ュ姞杞� Cron
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'getElectricalStatus', '0 */5 * * * ?', 1, '鏅鸿兘鐢佃〃-閲囬泦鍣ㄥ湪绾跨姸鎬�', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'getElectricalStatus' AND `bean_name` = 'visitServiceJob'
+);
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'syncElectricalMeterData', '30 0 * * * ?', 1, '鏅鸿兘鐢佃〃-姣忓皬鏃舵壒閲忔妱琛�', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'syncElectricalMeterData' AND `bean_name` = 'visitServiceJob'
+);
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'cleanElectricalLog', '0 30 2 * * ?', 1, '鏅鸿兘鐢佃〃-娓呯悊涓変釜鏈堝墠鎺ュ彛鏃ュ織', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'cleanElectricalLog' AND `bean_name` = 'visitServiceJob'
+);
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'syncConditionerGatewayStatus', '0 */5 * * * ?', 1, '绌鸿皟澶氳仈鏈�-缃戝叧鍦ㄧ嚎鐘舵�佸悓姝�', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'syncConditionerGatewayStatus' AND `bean_name` = 'visitServiceJob'
+);
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'syncConditionerIndoorUnits', '0 */10 * * * ?', 1, '绌鸿皟澶氳仈鏈�-鍐呮満杩愯鎬佸悓姝�', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'syncConditionerIndoorUnits' AND `bean_name` = 'visitServiceJob'
+);
+
+INSERT INTO `quartz_job` (`bean_name`, `params`, `module`, `cron_expres`, `state`, `remark`, `create_time`)
+SELECT 'visitServiceJob', '', 'syncConditionerUsagePreviousDay', '0 1 0 * * ?', 1, '绌鸿皟澶氳仈鏈�-鍚屾鍓嶄竴鏃ョ敤閲忔姤琛�', CURRENT_TIMESTAMP
+WHERE NOT EXISTS (
+    SELECT 1 FROM `quartz_job` WHERE `module` = 'syncConditionerUsagePreviousDay' AND `bean_name` = 'visitServiceJob'
+);
diff --git a/server/db/yw_conditioner_module.sql b/server/db/yw_conditioner_module.sql
new file mode 100644
index 0000000..759a21c
--- /dev/null
+++ b/server/db/yw_conditioner_module.sql
@@ -0,0 +1,165 @@
+-- 绌鸿皟澶氳仈鏈烘ā鍧楋細缃戝叧/鐢佃〃/璁¤垂/鎿嶄綔璁板綍/鐢ㄩ噺 + 鍐呮満琛ㄦ墿灞�
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_gateway` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `platform_wg_id` int DEFAULT NULL COMMENT '骞冲彴缃戝叧ID',
+  `wg_mac` varchar(64) NOT NULL COMMENT '缃戝叧MAC',
+  `wg_bz` varchar(500) DEFAULT NULL COMMENT '澶囨敞',
+  `online_status` varchar(20) DEFAULT NULL COMMENT '鍦ㄧ嚎/绂荤嚎',
+  `last_sync_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_wg_mac` (`wg_mac`),
+  KEY `idx_online_status` (`online_status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏅虹簿鐏电綉鍏抽暅鍍�';
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_gateway_log` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `gateway_id` int DEFAULT NULL,
+  `wg_mac` varchar(64) DEFAULT NULL,
+  `old_status` varchar(20) DEFAULT NULL,
+  `new_status` varchar(20) DEFAULT NULL,
+  `log_time` datetime DEFAULT NULL,
+  `source` varchar(32) DEFAULT NULL COMMENT 'manual/schedule',
+  PRIMARY KEY (`id`),
+  KEY `idx_gateway_id` (`gateway_id`),
+  KEY `idx_wg_mac` (`wg_mac`),
+  KEY `idx_log_time` (`log_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='缃戝叧涓婁笅绾胯褰�';
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_meter` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `platform_db_id` int DEFAULT NULL,
+  `db_name` varchar(128) DEFAULT NULL,
+  `db_adr` varchar(64) DEFAULT NULL,
+  `wg_mac` varchar(64) DEFAULT NULL,
+  `wg_id` int DEFAULT NULL,
+  `xy_id` int DEFAULT NULL,
+  `xy_name` varchar(128) DEFAULT NULL,
+  `db_bb` int DEFAULT NULL COMMENT '鍙樻瘮',
+  `standby_share` varchar(32) DEFAULT NULL COMMENT '寰呮満鍒嗘憡',
+  `outdoor_loop` int DEFAULT NULL COMMENT '澶栨満鍥炶矾鍙�',
+  `power_kw` decimal(12,4) DEFAULT NULL,
+  `total_dl` decimal(12,4) DEFAULT NULL,
+  `db_data` text,
+  `db_uptime` varchar(32) DEFAULT NULL,
+  `last_sync_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_platform_db_id` (`platform_db_id`),
+  KEY `idx_wg_mac` (`wg_mac`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏅虹簿鐏电數琛ㄩ暅鍍�';
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_billing` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `platform_dev_id` int DEFAULT NULL,
+  `dev_name` varchar(128) DEFAULT NULL,
+  `wg_mac` varchar(64) DEFAULT NULL,
+  `kw_type` int DEFAULT NULL COMMENT '0鏃堕暱1鑳借��2鐢佃〃',
+  `fan_arg` decimal(12,4) DEFAULT NULL,
+  `fan_kw` decimal(12,4) DEFAULT NULL,
+  `hig_kw` decimal(12,4) DEFAULT NULL,
+  `mid_kw` decimal(12,4) DEFAULT NULL,
+  `low_kw` decimal(12,4) DEFAULT NULL,
+  `kt_dj` decimal(12,4) DEFAULT NULL,
+  `last_sync_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_platform_dev_id` (`platform_dev_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='鏅虹簿鐏佃璐圭郴鏁伴暅鍍�';
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_actions` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `conditioner_id` int DEFAULT NULL,
+  `platform_dev_id` int DEFAULT NULL,
+  `dev_name` varchar(128) DEFAULT NULL,
+  `wg_mac` varchar(64) DEFAULT NULL,
+  `action_type` int NOT NULL COMMENT '1寮�鍏�2妯″紡3椋庨��4娓╁害5閿佸畾6鏌ョ數閲�7鏌ュ姛鐜�',
+  `action_content` varchar(500) DEFAULT NULL,
+  `result_status` int DEFAULT 1 COMMENT '0澶辫触1鎴愬姛',
+  `result_msg` varchar(500) DEFAULT NULL,
+  `source` varchar(64) DEFAULT NULL COMMENT '鎿嶄綔鏉ユ簮',
+  `request_body` text,
+  `response_body` text,
+  PRIMARY KEY (`id`),
+  KEY `idx_conditioner_id` (`conditioner_id`),
+  KEY `idx_platform_dev_id` (`platform_dev_id`),
+  KEY `idx_create_date` (`create_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='绌鸿皟璁惧鎺у埗璁板綍';
+
+CREATE TABLE IF NOT EXISTS `yw_conditioner_usage` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `creator` int DEFAULT NULL,
+  `create_date` datetime DEFAULT NULL,
+  `editor` int DEFAULT NULL,
+  `edit_date` datetime DEFAULT NULL,
+  `isdeleted` int DEFAULT 0,
+  `remark` varchar(500) DEFAULT NULL,
+  `platform_dev_id` int NOT NULL,
+  `dev_name` varchar(128) DEFAULT NULL,
+  `usage_date` date NOT NULL,
+  `sum_time` decimal(12,2) DEFAULT NULL,
+  `sum_dl` decimal(12,4) DEFAULT NULL,
+  `sum_df` decimal(12,4) DEFAULT NULL,
+  `gs_id` int DEFAULT NULL,
+  `sync_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_dev_date_gs` (`platform_dev_id`,`usage_date`,`gs_id`),
+  KEY `idx_usage_date` (`usage_date`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='璁惧鏃ョ敤閲忛暅鍍�';
+
+-- 鎵╁睍 yw_conditioner锛堥娆℃墽琛岋紱鍒楀凡瀛樺湪鏃朵細鎶ラ敊鍙拷鐣ワ級
+ALTER TABLE `yw_conditioner` ADD COLUMN `platform_dev_id` int DEFAULT NULL COMMENT '骞冲彴璁惧ID';
+ALTER TABLE `yw_conditioner` ADD COLUMN `wg_id` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `wg_mac` varchar(64) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `wg_qid` varchar(64) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `pid` varchar(16) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `online` varchar(20) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `pwr` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `mode` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `fan` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `fan_set` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `temp` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `temp_set` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `kt_lock` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `stop_logo` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `uptime` varchar(32) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `floor_id` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `floor_name` varchar(64) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `room_id` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `room_name` varchar(64) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `dev_type_id` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `dev_type_name` varchar(64) DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `lock_pwr` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `lock_mode` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `lock_fan` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `lock_min_temp` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `lock_max_temp` int DEFAULT NULL;
+ALTER TABLE `yw_conditioner` ADD COLUMN `last_sync_date` datetime DEFAULT NULL;
diff --git a/server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java b/server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java
index 428da3f..abd19e5 100644
--- a/server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java
+++ b/server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java
@@ -27,6 +27,9 @@
 
 public class LoginHandlerInterceptor implements HandlerInterceptor {
 
+    /** 涓庡墠绔� BasePage.adminCode 涓�鑷达紝瓒呯骇绠$悊鍛樿烦杩囨帴鍙f潈闄愮爜鏍¢獙 */
+    private static final String ADMIN_ROLE_CODE = "admin";
+
     private RedisTemplate<String,Object> stringRedisTemplate;
 
 
@@ -55,21 +58,23 @@
                     LoginUserInfo user =   checkLogin(token);
                     if (handlerMethod.hasMethodAnnotation(CloudRequiredPermission.class)) {
                         CloudRequiredPermission p = handlerMethod.getMethodAnnotation(CloudRequiredPermission.class);
-                        if(p.value()!=null && p.value().length>0){
+                        if (p.value() != null && p.value().length > 0 && !isAdminUser(user)) {
                             boolean hasPermission = false;
-                            for(String s :p.value()){
-                                if(user.getPermissions()!=null){
-                                    for(String t :user.getPermissions()){
-                                        if(StringUtils.equals(t,s)){
+                            for (String s : p.value()) {
+                                if (user.getPermissions() != null) {
+                                    for (String t : user.getPermissions()) {
+                                        if (StringUtils.equals(t, s)) {
                                             hasPermission = true;
                                             break;
                                         }
                                     }
                                 }
+                                if (hasPermission) {
+                                    break;
+                                }
                             }
-                            if(!hasPermission) {
-                                //娌℃湁鎿嶄綔鏉冮檺
-                                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),"娌℃湁璇ユ搷浣滄潈闄�");
+                            if (!hasPermission) {
+                                throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "娌℃湁璇ユ搷浣滄潈闄�");
                             }
                         }
                     }
@@ -118,6 +123,18 @@
         return body;
     }
 
+    private boolean isAdminUser(LoginUserInfo user) {
+        if (user == null || user.getRoles() == null) {
+            return false;
+        }
+        for (String role : user.getRoles()) {
+            if (StringUtils.equals(role, ADMIN_ROLE_CODE)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private LoginUserInfo checkLogin(String token) {
         if (token == null || token.isEmpty()) {
             throw new BusinessException(ResponseStatus.NO_LOGIN.getCode(),"鏈櫥褰�");
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 8fe6c89..b12f95f 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
@@ -169,6 +169,11 @@
     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";
+    /** 鏅虹簿鐏电┖璋冨钩鍙板弬鏁帮紙SYSTEM_DICT.code锛� */
+    public static final String CONDITIONER_PARAM = "CONDITIONER_PARAM";
+    public static final String CONDITIONER_BASE_URL = "base_url";
+    public static final String CONDITIONER_USERNAME = "username";
+    public static final String CONDITIONER_PASSWORD = "password";
     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_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java b/server/system_timer/src/main/java/com/doumee/jobs/fegin/VisitServiceFegin.java
index b9de78e..15fa368 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
@@ -96,6 +96,22 @@
     @GetMapping("/timer/yw/syncElectricalMeterData")
     ApiResponse syncElectricalMeterData();
 
+    @ApiOperation("銆愰槣瀹佽繍缁淬�戞竻鐞嗕笁涓湀鍓嶇數琛ㄦ帴鍙f棩蹇�")
+    @GetMapping("/timer/yw/cleanElectricalLog")
+    ApiResponse cleanElectricalLog();
+
+    @ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺悓姝ユ櫤绮剧伒缃戝叧鍦ㄧ嚎鐘舵��")
+    @GetMapping("/timer/yw/syncConditionerGatewayStatus")
+    ApiResponse syncConditionerGatewayStatus();
+
+    @ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺悓姝ユ櫤绮剧伒绌鸿皟鍐呮満杩愯鎬�")
+    @GetMapping("/timer/yw/syncConditionerIndoorUnits")
+    ApiResponse syncConditionerIndoorUnits();
+
+    @ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺悓姝ュ墠涓�鏃ョ┖璋冨鑱旀満鐢ㄩ噺")
+    @GetMapping("/timer/yw/syncConditionerUsagePreviousDay")
+    ApiResponse syncConditionerUsagePreviousDay();
+
     @ApiOperation("銆愰槣瀹佽繍缁淬�戝畾鏃跺鐞嗗悎鍚岃繃鏈�")
     @GetMapping("/timer/yw/ywDealContractTimeOutTimer")
     ApiResponse ywDealContractTimeOutTimer();
diff --git a/server/visits/admin_timer/src/main/java/com/doumee/TimerApplication.java b/server/visits/admin_timer/src/main/java/com/doumee/TimerApplication.java
index 21c86e7..8541170 100644
--- a/server/visits/admin_timer/src/main/java/com/doumee/TimerApplication.java
+++ b/server/visits/admin_timer/src/main/java/com/doumee/TimerApplication.java
@@ -7,7 +7,6 @@
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 import org.springframework.context.ApplicationContext;
 import org.springframework.scheduling.annotation.EnableAsync;
-import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * 鍚姩绫�
@@ -16,7 +15,6 @@
  */
 @Slf4j
 @EnableAsync
-@EnableScheduling
 @SpringBootApplication
 @MapperScan("com.doumee.dao.*")
 @EnableDiscoveryClient
diff --git a/server/visits/admin_timer/src/main/java/com/doumee/api/YwTimerController.java b/server/visits/admin_timer/src/main/java/com/doumee/api/YwTimerController.java
index c08f79e..ca761db 100644
--- a/server/visits/admin_timer/src/main/java/com/doumee/api/YwTimerController.java
+++ b/server/visits/admin_timer/src/main/java/com/doumee/api/YwTimerController.java
@@ -109,6 +109,30 @@
         return ApiResponse.success("鐢佃〃鎺ュ彛鏃ュ織娓呯悊鎴愬姛");
     }
 
+    @Autowired
+    private com.doumee.service.business.ConditionerBizService conditionerBizService;
+
+    @ApiOperation("瀹氭椂鍚屾鏅虹簿鐏电綉鍏冲湪绾跨姸鎬�")
+    @GetMapping("/syncConditionerGatewayStatus")
+    public ApiResponse syncConditionerGatewayStatus() {
+        conditionerBizService.syncGatewayStatus();
+        return ApiResponse.success("瀹氭椂鍚屾缃戝叧鐘舵�佹垚鍔�");
+    }
+
+    @ApiOperation("瀹氭椂鍚屾鏅虹簿鐏电┖璋冨唴鏈鸿繍琛屾��")
+    @GetMapping("/syncConditionerIndoorUnits")
+    public ApiResponse syncConditionerIndoorUnits() {
+        conditionerBizService.syncIndoorUnits();
+        return ApiResponse.success("瀹氭椂鍚屾绌鸿皟鍐呮満鐘舵�佹垚鍔�");
+    }
+
+    @ApiOperation("瀹氭椂鍚屾鍓嶄竴鏃ョ┖璋冨鑱旀満鐢ㄩ噺鎶ヨ〃")
+    @GetMapping("/syncConditionerUsagePreviousDay")
+    public ApiResponse syncConditionerUsagePreviousDay() {
+        String msg = conditionerBizService.syncUsagePreviousDay();
+        return ApiResponse.success(msg);
+    }
+
     @ApiOperation("瀹氭椂鏇存柊鎴挎簮绉熻祦鐘舵��")
     @GetMapping("/ywRoomStatusTimer")
     public ApiResponse ywRoomStatusTimer() {
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
deleted file mode 100644
index 42dbca7..0000000
--- a/server/visits/admin_timer/src/main/java/com/doumee/job/ElectricalScheduleJob.java
+++ /dev/null
@@ -1,48 +0,0 @@
-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/YwConditionerActionsCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerActionsCloudController.java
new file mode 100644
index 0000000..1954e5b
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerActionsCloudController.java
@@ -0,0 +1,49 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.annotation.excel.ExcelExporter;
+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.YwConditionerActions;
+import com.doumee.service.business.YwConditionerActionsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+
+@Api(tags = "绌鸿皟璁惧鎺у埗璁板綍")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywConditionerActions")
+public class YwConditionerActionsCloudController extends BaseController {
+
+    @Autowired
+    private YwConditionerActionsService actionsService;
+
+    @ApiOperation("鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @CloudRequiredPermission("business:ywconditioneractions:query")
+    public ApiResponse<PageData<YwConditionerActions>> findPage(@RequestBody PageWrap<YwConditionerActions> pageWrap,
+                                                                  @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(actionsService.findPage(pageWrap));
+    }
+
+    @ApiOperation("瀵煎嚭Excel")
+    @PostMapping("/exportExcel")
+    @CloudRequiredPermission("business:ywconditioneractions:exportExcel")
+    public void exportExcel(@RequestBody PageWrap<YwConditionerActions> pageWrap, HttpServletResponse response,
+                            @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        ExcelExporter.build(YwConditionerActions.class)
+                .export(actionsService.findPage(pageWrap).getRecords(), "绌鸿皟璁惧鎺у埗璁板綍", response);
+    }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerBillingCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerBillingCloudController.java
new file mode 100644
index 0000000..56da312
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerBillingCloudController.java
@@ -0,0 +1,43 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.annotation.pr.PreventRepeat;
+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.YwConditionerBilling;
+import com.doumee.service.business.YwConditionerBillingService;
+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 = "绌鸿皟璁¤垂绯绘暟")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywConditionerBilling")
+public class YwConditionerBillingCloudController extends BaseController {
+
+    @Autowired
+    private YwConditionerBillingService billingService;
+
+    @ApiOperation("鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @CloudRequiredPermission("business:ywconditionerbilling:query")
+    public ApiResponse<PageData<YwConditionerBilling>> findPage(@RequestBody PageWrap<YwConditionerBilling> pageWrap,
+                                                                @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(billingService.findPage(pageWrap));
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍚屾璁¤垂绯绘暟")
+    @PostMapping("/syncAll")
+    @CloudRequiredPermission("business:ywconditionerbilling:sync")
+    public ApiResponse<String> syncAll(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(billingService.syncAll());
+    }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerCloudController.java
index 56fc4e7..9f79a7a 100644
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerCloudController.java
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerCloudController.java
@@ -7,7 +7,10 @@
 import com.doumee.core.model.PageData;
 import com.doumee.core.model.PageWrap;
 import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.dto.YwConditionerLockDTO;
+import com.doumee.dao.business.dto.YwConditionerOperateDTO;
 import com.doumee.dao.business.model.YwConditioner;
+import com.doumee.dao.business.model.YwConditionerActions;
 import com.doumee.service.business.YwConditionerService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -18,6 +21,7 @@
 import javax.servlet.http.HttpServletResponse;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 绌鸿皟璁惧淇℃伅
@@ -93,4 +97,65 @@
         pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
         ExcelExporter.build(YwConditioner.class).export(ywConditionerService.findPage(pageWrap).getRecords(), "绌鸿皟璁惧淇℃伅", response);
     }
+
+    @ApiOperation("鍗$墖鍒嗛〉")
+    @PostMapping("/cardPage")
+    @CloudRequiredPermission("business:ywconditioner:query")
+    public ApiResponse<PageData<YwConditioner>> findCardPage(@RequestBody PageWrap<YwConditioner> pageWrap,
+                                                             @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(ywConditionerService.findCardPage(pageWrap));
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍏ㄩ噺鍚屾")
+    @PostMapping("/syncAll")
+    @CloudRequiredPermission("business:ywconditioner:sync")
+    public ApiResponse<String> syncAll(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(ywConditionerService.syncAll());
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍚屾璁惧涓庣姸鎬�")
+    @PostMapping("/syncDevicesAndStatus")
+    @CloudRequiredPermission("business:ywconditioner:sync")
+    public ApiResponse<String> syncDevicesAndStatus(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(ywConditionerService.syncDevicesAndStatus());
+    }
+
+    @ApiOperation("璁惧鎺у埗")
+    @PostMapping("/operate")
+    @CloudRequiredPermission("business:ywconditioner:operate")
+    public ApiResponse<String> operate(@RequestBody YwConditionerOperateDTO dto,
+                                       @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(ywConditionerService.operate(dto, this.getLoginUser(token)));
+    }
+
+    @ApiOperation("璁惧閿佸畾")
+    @PostMapping("/lock")
+    @CloudRequiredPermission("business:ywconditioner:operate")
+    public ApiResponse<String> lock(@RequestBody YwConditionerLockDTO dto,
+                                  @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(ywConditionerService.lock(dto, this.getLoginUser(token)));
+    }
+
+    @ApiOperation("鍗曡澶囨帶鍒跺巻鍙�")
+    @PostMapping("/historyPage")
+    @CloudRequiredPermission("business:ywconditioner:query")
+    public ApiResponse<PageData<YwConditionerActions>> historyPage(@RequestBody PageWrap<YwConditionerActions> pageWrap,
+                                                                   @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(ywConditionerService.historyPage(pageWrap));
+    }
+
+    @ApiOperation("缃戝叧涓嬫媺")
+    @GetMapping("/gatewayOptions")
+    @CloudRequiredPermission("business:ywconditioner:query")
+    public ApiResponse<List<Map<String, Object>>> gatewayOptions(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(ywConditionerService.gatewayOptions());
+    }
 }
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerGatewayCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerGatewayCloudController.java
new file mode 100644
index 0000000..71ca916
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerGatewayCloudController.java
@@ -0,0 +1,55 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.annotation.pr.PreventRepeat;
+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.YwConditionerGateway;
+import com.doumee.dao.business.model.YwConditionerGatewayLog;
+import com.doumee.service.business.YwConditionerGatewayService;
+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 = "绌鸿皟缃戝叧绠$悊")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywConditionerGateway")
+public class YwConditionerGatewayCloudController extends BaseController {
+
+    @Autowired
+    private YwConditionerGatewayService gatewayService;
+
+    @ApiOperation("鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @CloudRequiredPermission("business:ywconditionergateway:query")
+    public ApiResponse<PageData<YwConditionerGateway>> findPage(@RequestBody PageWrap<YwConditionerGateway> pageWrap,
+                                                                @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(gatewayService.findPage(pageWrap));
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍚屾缃戝叧")
+    @PostMapping("/syncAll")
+    @CloudRequiredPermission("business:ywconditionergateway:sync")
+    public ApiResponse<String> syncAll(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(gatewayService.syncAll());
+    }
+
+    @ApiOperation("缃戝叧涓婁笅绾胯褰�")
+    @PostMapping("/gatewayLogPage")
+    @CloudRequiredPermission("business:ywconditionergateway:query")
+    public ApiResponse<PageData<YwConditionerGatewayLog>> gatewayLogPage(@RequestBody PageWrap<YwConditionerGatewayLog> pageWrap,
+                                                                         @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(gatewayService.gatewayLogPage(pageWrap));
+    }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerMeterCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerMeterCloudController.java
new file mode 100644
index 0000000..95587a3
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerMeterCloudController.java
@@ -0,0 +1,59 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.annotation.pr.PreventRepeat;
+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.YwConditionerMeter;
+import com.doumee.service.business.YwConditionerMeterService;
+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 = "绌鸿皟鐢佃〃绠$悊")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywConditionerMeter")
+public class YwConditionerMeterCloudController extends BaseController {
+
+    @Autowired
+    private YwConditionerMeterService meterService;
+
+    @ApiOperation("鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @CloudRequiredPermission("business:ywconditionermeter:query")
+    public ApiResponse<PageData<YwConditionerMeter>> findPage(@RequestBody PageWrap<YwConditionerMeter> pageWrap,
+                                                              @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        if (pageWrap.getModel() != null) {
+            pageWrap.getModel().setLoginUserInfo(this.getLoginUser(token));
+        }
+        return ApiResponse.success(meterService.findPage(pageWrap));
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍚屾鐢佃〃")
+    @PostMapping("/syncAll")
+    @CloudRequiredPermission("business:ywconditionermeter:sync")
+    public ApiResponse<String> syncAll(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(meterService.syncAll());
+    }
+
+    @ApiOperation("鏌ョ數閲�")
+    @PostMapping("/queryEnergy/{id}")
+    @CloudRequiredPermission("business:ywconditionermeter:operate")
+    public ApiResponse<String> queryEnergy(@PathVariable Integer id,
+                                           @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(meterService.queryEnergy(id, this.getLoginUser(token)));
+    }
+
+    @ApiOperation("鏌ュ姛鐜�")
+    @PostMapping("/queryPower/{id}")
+    @CloudRequiredPermission("business:ywconditionermeter:operate")
+    public ApiResponse<String> queryPower(@PathVariable Integer id,
+                                          @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(meterService.queryPower(id, this.getLoginUser(token)));
+    }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerReportCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerReportCloudController.java
new file mode 100644
index 0000000..116939f
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwConditionerReportCloudController.java
@@ -0,0 +1,60 @@
+package com.doumee.cloud.admin;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.CloudRequiredPermission;
+import com.doumee.core.annotation.pr.PreventRepeat;
+import com.doumee.core.model.ApiResponse;
+import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.dto.YwConditionerReportQueryDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportPageDTO;
+import com.doumee.service.business.YwConditionerReportService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+@Api(tags = "绌鸿皟鐢ㄩ噺鏁版嵁鎶ヨ〃")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywConditionerReport")
+public class YwConditionerReportCloudController extends BaseController {
+
+    @Autowired
+    private YwConditionerReportService reportService;
+
+    @ApiOperation("閫忚鍒嗛〉鏌ヨ")
+    @PostMapping("/page")
+    @CloudRequiredPermission("business:ywconditionerreport:query")
+    public ApiResponse<YwConditionerUsageReportPageDTO> findPage(@RequestBody YwConditionerReportQueryDTO query,
+                                                                 @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(reportService.findPage(query));
+    }
+
+    @PreventRepeat
+    @ApiOperation("鍚屾鐢ㄩ噺")
+    @PostMapping("/syncUsage")
+    @CloudRequiredPermission("business:ywconditionerreport:sync")
+    public ApiResponse<String> syncUsage(@RequestBody YwConditionerReportQueryDTO query,
+                                         @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(reportService.syncUsage(query));
+    }
+
+    @ApiOperation("鍟嗘埛涓嬫媺")
+    @GetMapping("/merchantOptions")
+    @CloudRequiredPermission("business:ywconditionerreport:query")
+    public ApiResponse<List<Map<String, Object>>> merchantOptions(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        return ApiResponse.success(reportService.merchantOptions());
+    }
+
+    @ApiOperation("瀵煎嚭Excel")
+    @PostMapping("/exportExcel")
+    @CloudRequiredPermission("business:ywconditionerreport:exportExcel")
+    public void exportExcel(@RequestBody YwConditionerReportQueryDTO query,
+                            HttpServletResponse response,
+                            @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+        reportService.exportExcel(query, response);
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerToolTestUtil.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerToolTestUtil.java
new file mode 100644
index 0000000..57b413f
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerToolTestUtil.java
@@ -0,0 +1,57 @@
+package com.doumee.core.conditoner;
+
+import com.alibaba.fastjson.JSON;
+import com.doumee.core.conditoner.model.ConditionerConstant;
+import com.doumee.core.conditoner.model.request.ConditionerSessionRequest;
+import com.doumee.core.conditoner.model.request.GetDevOneRequest;
+import com.doumee.core.conditoner.model.response.ConditionerBaseResponse;
+import com.doumee.core.conditoner.model.response.DeviceStatusResponse;
+import com.doumee.core.conditoner.model.response.LoginDataResponse;
+
+import java.util.List;
+
+/**
+ * 鏅虹簿鐏垫帴鍙e啋鐑熸祴璇曪紙main 鏂规硶鑱旇皟锛夈��
+ * <p>
+ * 杩愯鍓嶈纭 {@link ConditionerConstant} 涓� base_url / username / password 宸查厤缃紝
+ * 鎴栧凡閫氳繃瀛楀吀 {@code CONDITIONER_PARAM} 鐢� {@link com.doumee.service.business.impl.ConditionerConfigService} 鍔犺浇銆�
+ * </p>
+ */
+public class ConditionerToolTestUtil {
+
+    public static void main(String[] args) {
+        System.out.println("=== 鏅虹簿鐏� login ===");
+        ConditionerBaseResponse<LoginDataResponse> loginResp = ConditionerUtil.login();
+        print(loginResp);
+
+        if (loginResp == null || !loginResp.isSuccess()) {
+            System.out.println("鐧诲綍澶辫触锛岀粓姝㈠悗缁祴璇�");
+            return;
+        }
+
+        System.out.println("session kt_token=" + ConditionerConstant.kt_token + ", kt_dwid=" + ConditionerConstant.kt_dwid);
+
+        System.out.println("\n=== getDevList ===");
+        ConditionerSessionRequest session = new ConditionerSessionRequest();
+        session.fillSessionDefaults();
+        ConditionerBaseResponse<List<DeviceStatusResponse>> devList = ConditionerUtil.getDevList(session);
+        print(devList);
+
+        if (devList != null && devList.getData() != null && !devList.getData().isEmpty()) {
+            DeviceStatusResponse first = devList.getData().get(0);
+            System.out.println("\n=== getDevOne (first device) ===");
+            GetDevOneRequest oneReq = new GetDevOneRequest();
+            oneReq.setWg_mac(first.getWg_mac());
+            oneReq.setWg_qid(first.getWg_qid());
+            oneReq.fillSessionDefaults();
+            print(ConditionerUtil.getDevOne(oneReq));
+        }
+
+        System.out.println("\n=== getUser ===");
+        print(ConditionerUtil.getUser(session));
+    }
+
+    private static void print(Object obj) {
+        System.out.println(JSON.toJSONString(obj, true));
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java
new file mode 100644
index 0000000..95dd398
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/ConditionerUtil.java
@@ -0,0 +1,553 @@
+package com.doumee.core.conditoner;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.doumee.core.conditoner.model.ConditionerConstant;
+import com.doumee.core.conditoner.model.request.*;
+import com.doumee.core.conditoner.model.response.*;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鏅虹簿鐏电┖璋冨钩鍙� HTTP 宸ュ叿绫汇��
+ * <p>
+ * 鍗忚锛欸ET 璇诲彇锛圦uery 鍙傛暟锛夛紱POST 鎺у埗/鍐欏叆锛圝SON Body锛夈��
+ * 鎴愬姛鏍囪瘑 {@code code=200}銆傜櫥褰曞悗鍏叡鍙傛暟 {@code kt_token}銆亄@code kt_dwid}銆亄@code kt_sonid}銆�
+ * </p>
+ */
+@Slf4j
+public class ConditionerUtil {
+
+    private static final int HTTP_RETRY = 3;
+
+    private ConditionerUtil() {
+    }
+
+    // ==================== 浜屻�佸熀纭�鎺у埗 ====================
+
+    public static ConditionerBaseResponse<LoginDataResponse> login() {
+        return login(null);
+    }
+
+    public static ConditionerBaseResponse<LoginDataResponse> login(LoginRequest req) {
+        LoginRequest r = req != null ? req : new LoginRequest();
+        JSONObject body = new JSONObject();
+        body.put("username", StringUtils.isNotBlank(r.getUsername()) ? r.getUsername() : ConditionerConstant.username);
+        body.put("password", StringUtils.isNotBlank(r.getPassword()) ? r.getPassword() : ConditionerConstant.password);
+        try {
+            String raw = doPostRaw("/login", body.toJSONString());
+            ConditionerBaseResponse<LoginDataResponse> resp = parseObjectResponse(raw, LoginDataResponse.class);
+            applyLoginSession(resp);
+            return resp;
+        } catch (Exception e) {
+            log.error("conditioner login failed", e);
+            return null;
+        }
+    }
+
+    public static ConditionerBaseResponse<List<DeviceStatusResponse>> getDevList(ConditionerSessionRequest req) {
+        return getList("/getDevList", req, DeviceStatusResponse.class);
+    }
+
+    public static ConditionerBaseResponse<DeviceStatusResponse> getDevOne(GetDevOneRequest req) {
+        if (req == null) {
+            return null;
+        }
+        req.fillSessionDefaults();
+        try {
+            String raw = doGetRaw("/getDevOne", toQueryMap(req));
+            return parseObjectResponse(raw, DeviceStatusResponse.class);
+        } catch (Exception e) {
+            log.error("conditioner getDevOne failed", e);
+            return null;
+        }
+    }
+
+    public static ConditionerBaseResponse<Object> devCtr(DevControlRequest req) {
+        return postJson("/devCtr", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> devManyCtr(DevManyControlRequest req) {
+        return postJson("/devManyCtr", req, Object.class);
+    }
+    public static ConditionerBaseResponse<Object> devLockManyCtr(DevLockControlRequest req) {
+        return postJson("/devManyCtr", req, Object.class);
+    }
+
+    // ==================== 涓夈�佽澶囩鐞� ====================
+
+    public static ConditionerBaseResponse<List<GatewayInfoResponse>> getWg(ConditionerSessionRequest req) {
+        return getList("/getWg", req, GatewayInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addWg(WgManageRequest req) {
+        return postJson("/addWg", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeWg(WgManageRequest req) {
+        return postJson("/changeWg", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delWg(WgManageRequest req) {
+        return postJson("/delWg", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> wgWithArea(WgWithAreaRequest req) {
+        return postJson("/wgWithArea", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<DeviceArchiveResponse>> getDev(ConditionerSessionRequest req) {
+        return getList("/getDev", req, DeviceArchiveResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addDev(DevManageRequest req) {
+        return postJson("/addDev", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeDev(DevManageRequest req) {
+        return postJson("/changeDev", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delDev(DevManageRequest req) {
+        return postJson("/delDev", req, Object.class);
+    }
+
+    // ==================== 鍥涖�佸尯鍩熺鐞� ====================
+
+    public static ConditionerBaseResponse<List<RoomInfoResponse>> getRoom(ConditionerSessionRequest req) {
+        return getList("/getRoom", req, RoomInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addRoom(RoomManageRequest req) {
+        return postJson("/addRoom", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeRoom(RoomManageRequest req) {
+        return postJson("/changeRoom", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delRoom(RoomManageRequest req) {
+        return postJson("/delRoom", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<FloorInfoResponse>> getFloor(ConditionerPageRequest req) {
+        return getList("/getFloor", req, FloorInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addFloor(FloorManageRequest req) {
+        return postJson("/addFloor", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeFloor(FloorManageRequest req) {
+        return postJson("/changeFloor", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delFloor(FloorManageRequest req) {
+        return postJson("/delFloor", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<UnitInfoResponse>> getUnit(ConditionerPageRequest req) {
+        return getList("/getUnit", req, UnitInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addUnit(UnitManageRequest req) {
+        return postJson("/addUnit", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeUnit(UnitManageRequest req) {
+        return postJson("/changeUnit", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delUnit(UnitManageRequest req) {
+        return postJson("/delUnit", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<BuildingInfoResponse>> getBuilding(ConditionerPageRequest req) {
+        return getList("/getBuilding", req, BuildingInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addBuilding(BuildingManageRequest req) {
+        return postJson("/addBuilding", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeBuilding(BuildingManageRequest req) {
+        return postJson("/changeBuilding", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delBuilding(BuildingManageRequest req) {
+        return postJson("/delBuilding", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<AreaInfoResponse>> getArea(ConditionerPageRequest req) {
+        return getList("/getArea", req, AreaInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addArea(AreaManageRequest req) {
+        return postJson("/addArea", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeArea(AreaManageRequest req) {
+        return postJson("/changeArea", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delArea(AreaManageRequest req) {
+        return postJson("/delArea", req, Object.class);
+    }
+
+    // ==================== 浜斻�佸畾鏃剁鐞� ====================
+
+    public static ConditionerBaseResponse<List<TimingInfoResponse>> getTiming(ConditionerSessionRequest req) {
+        return getList("/getTiming", req, TimingInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addTiming(TimingManageRequest req) {
+        return postJson("/addTiming", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeTiming(TimingManageRequest req) {
+        return postJson("/changeTiming", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delTiming(TimingManageRequest req) {
+        return postJson("/delTiming", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> timingWithArea(TimingWithAreaRequest req) {
+        return postJson("/timingWithArea", req, Object.class);
+    }
+
+    // ==================== 鍏�佹暟鎹煡璇� ====================
+
+    public static ConditionerBaseResponse<List<Object>> getLogWg(LogQueryRequest req) {
+        return getList("/getLogWg", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<Object>> getLogDev(LogQueryRequest req) {
+        return getList("/getLogDev", req, Object.class);
+    }
+
+    // ==================== 涓冦�佽閲忕鐞� ====================
+
+    public static ConditionerBaseResponse<List<DlSjXsResponse>> getDlSjXs(ConditionerSessionRequest req) {
+        return getList("/getDlSjXs", req, DlSjXsResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeDlSjXs(ChangeDlSjXsRequest req) {
+        return postJson("/changeDlSjXs", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<MeterDbInfoResponse>> getDb(MeterDbManageRequest req) {
+        return getList("/glDb/getDb", req, MeterDbInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addDb(MeterDbManageRequest req) {
+        return postJson("/glDb/addDb", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeDb(MeterDbManageRequest req) {
+        return postJson("/glDb/changeDb", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delDb(MeterDbManageRequest req) {
+        return postJson("/glDb/delDb", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<Object>> getDbDaySum(DbDaySumQueryRequest req) {
+        return getList("/getDbDaySum", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<Object>> getDayDl(DayDlQueryRequest req) {
+        return getList("/getDayDl", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<Object>> getMoonDl(MoonDlQueryRequest req) {
+        return getList("/getMoonDl", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<CompanyGsInfoResponse>> getGs(CompanyGsManageRequest req) {
+        return getList("/getGs", req, CompanyGsInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addGs(CompanyGsManageRequest req) {
+        return postJson("/addGs", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeGs(CompanyGsManageRequest req) {
+        return postJson("/changeGs", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delGs(CompanyGsManageRequest req) {
+        return postJson("/delGs", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> gsWithArea(GsWithAreaRequest req) {
+        return postJson("/gsWithArea", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addMoney(AddMoneyRequest req) {
+        return postJson("/addMoney", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> cleanMoney(CompanyGsManageRequest req) {
+        return postJson("/cleanMoney", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<List<Object>> getCzLog(LogQueryRequest req) {
+        return getList("/getCzLog", req, Object.class);
+    }
+
+    // ==================== 鍏�佽处鍙风鐞� ====================
+
+    public static ConditionerBaseResponse<List<UserInfoResponse>> getUser(ConditionerSessionRequest req) {
+        return getList("/getUser", req, UserInfoResponse.class);
+    }
+
+    public static ConditionerBaseResponse<Object> addUser(UserManageRequest req) {
+        return postJson("/addUser", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeUser(UserManageRequest req) {
+        return postJson("/changeUser", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> changeUserPwd(UserManageRequest req) {
+        return postJson("/changeUserPwd", req, Object.class);
+    }
+
+    public static ConditionerBaseResponse<Object> delUser(UserManageRequest req) {
+        return postJson("/delUser", req, Object.class);
+    }
+
+    // ==================== 浼氳瘽 ====================
+
+    public static void applyLoginSession(ConditionerBaseResponse<LoginDataResponse> resp) {
+        if (resp == null || !resp.isSuccess() || resp.getData() == null) {
+            return;
+        }
+        LoginDataResponse data = resp.getData();
+        if (StringUtils.isNotBlank(data.getKt_token())) {
+            ConditionerConstant.kt_token = data.getKt_token();
+        }
+        if (data.getKt_dwid() != null) {
+            ConditionerConstant.kt_dwid = String.valueOf(data.getKt_dwid());
+        }
+        if (data.getKt_sonid() != null) {
+            ConditionerConstant.kt_sonid = String.valueOf(data.getKt_sonid());
+        }
+    }
+
+    // ==================== HTTP / 瑙f瀽 ====================
+
+    private static <T> ConditionerBaseResponse<List<T>> getList(String path, ConditionerSessionRequest req, Class<T> itemClass) {
+        if (req != null) {
+            req.fillSessionDefaults();
+        } else {
+            req = new ConditionerSessionRequest();
+            req.fillSessionDefaults();
+        }
+        try {
+            String raw = doGetRaw(path, toQueryMap(req));
+            return parseListResponse(raw, itemClass);
+        } catch (Exception e) {
+            log.error("conditioner GET {} failed", path, e);
+            return null;
+        }
+    }
+
+    private static <T> ConditionerBaseResponse<T> postJson(String path, ConditionerSessionRequest req, Class<T> dataClass) {
+        if (req != null) {
+            req.fillSessionDefaults();
+        }
+        try {
+            String raw = doPostRaw(path, JSON.toJSONString(req));
+            return parseObjectResponse(raw, dataClass);
+        } catch (Exception e) {
+            log.error("conditioner POST {} failed", path, e);
+            return null;
+        }
+    }
+
+    private static String resolveUrl(String path) {
+        String base = StringUtils.defaultIfBlank(ConditionerConstant.base_url, ConditionerConstant.DEFAULT_BASE_URL);
+        if (base.endsWith("/")) {
+            base = base.substring(0, base.length() - 1);
+        }
+        if (!path.startsWith("/")) {
+            path = "/" + path;
+        }
+        return base + path;
+    }
+
+    private static String doGetRaw(String path, Map<String, String> params) throws Exception {
+        String url = buildGetUrl(path, params);
+        log.info("conditioner GET url={}", maskUrl(url));
+        HttpClient client = HttpClientBuilder.create().build();
+        HttpGet get = new HttpGet(url);
+        HttpResponse response = executeWithRetry(client, get);
+        String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+        log.info("conditioner GET response={}", abbreviate(resp));
+        return resp;
+    }
+
+    private static String doPostRaw(String path, String jsonBody) throws Exception {
+        String url = resolveUrl(path);
+        log.info("conditioner POST url={}, body={}", url, maskJson(jsonBody));
+        HttpClient client = HttpClientBuilder.create().build();
+        HttpPost post = new HttpPost(url);
+        post.setHeader("Content-Type", "application/json;charset=UTF-8");
+        post.setEntity(new StringEntity(jsonBody, StandardCharsets.UTF_8));
+        HttpResponse response = executeWithRetry(client, post);
+        String resp = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+        log.info("conditioner POST response={}", abbreviate(resp));
+        return resp;
+    }
+
+    private static HttpResponse executeWithRetry(HttpClient client, org.apache.http.client.methods.HttpUriRequest request) throws Exception {
+        HttpResponse execute = null;
+        int retry = HTTP_RETRY;
+        while (retry-- > 0) {
+            try {
+                execute = client.execute(request);
+                break;
+            } catch (Exception e) {
+                if (retry <= 0) {
+                    throw e;
+                }
+                Thread.sleep(5000);
+            }
+        }
+        if (execute == null) {
+            throw new Exception("conditioner http request failed");
+        }
+        return execute;
+    }
+
+    private static String buildGetUrl(String path, Map<String, String> params) throws Exception {
+        StringBuilder sb = new StringBuilder(resolveUrl(path));
+        if (params == null || params.isEmpty()) {
+            return sb.toString();
+        }
+        sb.append("?");
+        boolean first = true;
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            if (StringUtils.isBlank(entry.getValue())) {
+                continue;
+            }
+            if (!first) {
+                sb.append("&");
+            }
+            sb.append(urlEncode(entry.getKey())).append("=").append(urlEncode(entry.getValue()));
+            first = false;
+        }
+        return sb.toString();
+    }
+
+    private static Map<String, String> toQueryMap(Object obj) {
+        Map<String, String> map = new LinkedHashMap<>();
+        if (obj == null) {
+            return map;
+        }
+        JSONObject json = (JSONObject) JSON.toJSON(obj);
+        for (Map.Entry<String, Object> entry : json.entrySet()) {
+            Object val = entry.getValue();
+            if (val != null) {
+                map.put(entry.getKey(), String.valueOf(val));
+            }
+        }
+        return map;
+    }
+
+    private static <T> ConditionerBaseResponse<T> parseObjectResponse(String raw, Class<T> dataClass) {
+        if (StringUtils.isBlank(raw)) {
+            return null;
+        }
+        try {
+            JSONObject root = JSON.parseObject(raw);
+            ConditionerBaseResponse<T> resp = new ConditionerBaseResponse<>();
+            resp.setCode(root.getInteger("code"));
+            resp.setMessage(root.getString("message"));
+            Object data = root.get("data");
+            if (data != null && dataClass != null) {
+                if (data instanceof JSONObject) {
+                    resp.setData(((JSONObject) data).toJavaObject(dataClass));
+                } else if (data instanceof String) {
+                    String text = ((String) data).trim();
+                    if (text.startsWith("{")) {
+                        resp.setData(JSON.parseObject(text, dataClass));
+                    }
+                } else if (!(data instanceof JSONArray)) {
+                    resp.setData(JSON.parseObject(JSON.toJSONString(data), dataClass));
+                }
+            }
+            return resp;
+        } catch (Exception e) {
+            log.error("conditioner parse object response failed: {}", abbreviate(raw), e);
+            return null;
+        }
+    }
+
+    private static <T> ConditionerBaseResponse<List<T>> parseListResponse(String raw, Class<T> itemClass) {
+        if (StringUtils.isBlank(raw)) {
+            return null;
+        }
+        try {
+            JSONObject root = JSON.parseObject(raw);
+            ConditionerBaseResponse<List<T>> resp = new ConditionerBaseResponse<>();
+            resp.setCode(root.getInteger("code"));
+            resp.setMessage(root.getString("message"));
+            Object data = root.get("data");
+            if (data instanceof JSONArray) {
+                resp.setData(((JSONArray) data).toJavaList(itemClass));
+            } else if (data instanceof String) {
+                String text = ((String) data).trim();
+                if (text.startsWith("[")) {
+                    resp.setData(JSON.parseArray(text, itemClass));
+                }
+            }
+            return resp;
+        } catch (Exception e) {
+            log.error("conditioner parse list response failed: {}", abbreviate(raw), e);
+            return null;
+        }
+    }
+
+    private static String urlEncode(String value) throws Exception {
+        return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
+    }
+
+    private static String maskUrl(String url) {
+        if (url == null) {
+            return null;
+        }
+        return url.replaceAll("(kt_token=)[^&]+", "$1***");
+    }
+
+    private static String maskJson(String json) {
+        if (json == null) {
+            return null;
+        }
+        return json.replaceAll("(\\\"password\\\"\\s*:\\s*\\\")[^\\\"]+(\\\")", "$1***$2")
+                .replaceAll("(\\\"kt_token\\\"\\s*:\\s*\\\")[^\\\"]+(\\\")", "$1***$2");
+    }
+
+    private static String abbreviate(String text) {
+        if (text == null) {
+            return null;
+        }
+        return text.length() > 500 ? text.substring(0, 500) + "..." : text;
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/ConditionerConstant.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/ConditionerConstant.java
new file mode 100644
index 0000000..c7b6df4
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/ConditionerConstant.java
@@ -0,0 +1,57 @@
+package com.doumee.core.conditoner.model;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 鏅虹簿鐏靛钩鍙拌繛鎺ヤ笌浼氳瘽鍙傛暟锛堝彲鐢� {@link com.doumee.service.business.impl.ConditionerConfigService} 浠庡瓧鍏稿姞杞斤級
+ */
+public class ConditionerConstant {
+
+    public static final String DEFAULT_BASE_URL = "http://119.45.163.5:1125/zjl/API";
+    public static final String DEFAULT_USERNAME = "admin";
+    public static final String DEFAULT_PASSWORD = "12345678";
+    public static final String DEFAULT_KT_SONID = "0";
+
+    /** API 鏍瑰湴鍧�锛屽 http://119.45.163.5:1125/zjl/API */
+    public static String base_url = DEFAULT_BASE_URL;
+    public static String username = DEFAULT_USERNAME;
+    public static String password = DEFAULT_PASSWORD;
+
+    /** 鐧诲綍鍚庝細璇濓紙login 鎴愬姛鍚庡啓鍏ワ級 */
+    public static String kt_token;
+    public static String kt_dwid;
+    public static String kt_sonid = DEFAULT_KT_SONID;
+
+    private ConditionerConstant() {
+    }
+
+    /**
+     * getDevList / getDevOne 璁惧 online锛�88銆�66 琛ㄧず鍦ㄧ嚎锛屽叾瀹冧负绂荤嚎銆�
+     */
+    public static String normalizeDeviceOnline(Object online) {
+        if (online == null) {
+            return "绂荤嚎";
+        }
+        String s = String.valueOf(online).trim();
+        if (StringUtils.isBlank(s) || "null".equalsIgnoreCase(s)) {
+            return "绂荤嚎";
+        }
+        if ("88".equals(s) || "66".equals(s) || "1".equals(s)
+                || "鍦ㄧ嚎".equalsIgnoreCase(s) || "online".equalsIgnoreCase(s)) {
+            return "鍦ㄧ嚎";
+        }
+        return "绂荤嚎";
+    }
+
+    /** 缃戝叧 wg_status锛堥�氬父涓轰腑鏂囥�屽湪绾裤��/銆岀绾裤�嶏級 */
+    public static String normalizeGatewayOnline(String status) {
+        if (StringUtils.isBlank(status)) {
+            return "绂荤嚎";
+        }
+        String s = status.trim();
+        if ("1".equals(s) || "鍦ㄧ嚎".equalsIgnoreCase(s) || "online".equalsIgnoreCase(s)) {
+            return "鍦ㄧ嚎";
+        }
+        return "绂荤嚎";
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AddMoneyRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AddMoneyRequest.java
new file mode 100644
index 0000000..a651454
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AddMoneyRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AddMoneyRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鍏徃 ID")
+    private Integer id;
+
+    @ApiModelProperty("鍏呭�奸噾棰�")
+    private String cz_money;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AreaManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AreaManageRequest.java
new file mode 100644
index 0000000..f532869
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/AreaManageRequest.java
@@ -0,0 +1,19 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class AreaManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("灏忓尯 ID")
+    private Integer area_id;
+
+    @ApiModelProperty("灏忓尯鍚嶇О")
+    private String area_name;
+
+    @ApiModelProperty("澶囨敞")
+    private String area_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/BuildingManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/BuildingManageRequest.java
new file mode 100644
index 0000000..0385995
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/BuildingManageRequest.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class BuildingManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("妤兼爧 ID")
+    private Integer building_id;
+
+    @ApiModelProperty("灏忓尯 ID")
+    private Integer area_id;
+
+    @ApiModelProperty("妤兼爧鍚嶇О")
+    private String building_name;
+
+    @ApiModelProperty("澶囨敞")
+    private String building_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ChangeDlSjXsRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ChangeDlSjXsRequest.java
new file mode 100644
index 0000000..0f49f82
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ChangeDlSjXsRequest.java
@@ -0,0 +1,24 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ChangeDlSjXsRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("璁惧 ID")
+    private Integer dev_id;
+
+    @ApiModelProperty("0 鏃堕暱 1 鑳借�� 2 鐢佃〃")
+    private Integer kw_type;
+
+    private BigDecimal fan_arg;
+    private BigDecimal fan_kw;
+    private BigDecimal hig_kw;
+    private BigDecimal mid_kw;
+    private BigDecimal low_kw;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/CompanyGsManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/CompanyGsManageRequest.java
new file mode 100644
index 0000000..a215ed7
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/CompanyGsManageRequest.java
@@ -0,0 +1,40 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyGsManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鍏徃 ID")
+    private Integer id;
+
+    @ApiModelProperty("鍏徃鍚嶇О")
+    private String gs_name;
+
+    @ApiModelProperty("鍏呭�煎紑鍏�")
+    private Integer is_pwr;
+
+    @ApiModelProperty("澶滈棿涓嶅仠鏈�")
+    private Integer is_rest_stop;
+
+    @ApiModelProperty("鍓╀綑閲戦")
+    private Object left_money;
+
+    @ApiModelProperty("鏄惁鍋滄満")
+    private Integer is_stop;
+
+    @ApiModelProperty("璁惧鍒楄〃")
+    private List<Integer> li_dev;
+
+    @ApiModelProperty("鍒嗘憡姣斾緥")
+    private Map<String, Object> d_dev;
+
+    @ApiModelProperty("澶囨敞")
+    private String gs_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerPageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerPageRequest.java
new file mode 100644
index 0000000..d722cca
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerPageRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ConditionerPageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("椤电爜")
+    private Integer page;
+
+    @ApiModelProperty("姣忛〉鏉℃暟")
+    private Integer pageSize;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerSessionRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerSessionRequest.java
new file mode 100644
index 0000000..22ddda8
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/ConditionerSessionRequest.java
@@ -0,0 +1,39 @@
+package com.doumee.core.conditoner.model.request;
+
+import com.doumee.core.conditoner.model.ConditionerConstant;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+
+/**
+ * 鐧诲綍鍚庝細璇濆叕鍏卞弬鏁帮紙GET query / POST body锛�
+ */
+@Data
+public class ConditionerSessionRequest implements Serializable {
+
+    @ApiModelProperty("鐧诲綍 token")
+    private String kt_token;
+
+    @ApiModelProperty("鍏徃 ID")
+    private String kt_dwid;
+
+    @ApiModelProperty("瀛愯处鍙� ID锛岄粯璁� 0")
+    private String kt_sonid;
+
+    @ApiModelProperty("鍗曞厓 ID锛堥儴鍒嗘帴鍙i渶瑕侊級")
+    private String kt_unit;
+
+    public void fillSessionDefaults() {
+        if (StringUtils.isBlank(kt_token)) {
+            kt_token = ConditionerConstant.kt_token;
+        }
+        if (StringUtils.isBlank(kt_dwid)) {
+            kt_dwid = ConditionerConstant.kt_dwid;
+        }
+        if (StringUtils.isBlank(kt_sonid)) {
+            kt_sonid = ConditionerConstant.kt_sonid;
+        }
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DayDlQueryRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DayDlQueryRequest.java
new file mode 100644
index 0000000..9965fc5
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DayDlQueryRequest.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DayDlQueryRequest extends ConditionerPageRequest {
+
+    @ApiModelProperty("寮�濮嬫棩鏈�")
+    private String start_time;
+
+    @ApiModelProperty("缁撴潫鏃ユ湡")
+    private String end_time;
+
+    @ApiModelProperty("鍏徃 ID 绛涢��")
+    private Integer gs_id;
+
+    @ApiModelProperty("璁惧 ID 绛涢��")
+    private Integer dev_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DbDaySumQueryRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DbDaySumQueryRequest.java
new file mode 100644
index 0000000..8bcf297
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DbDaySumQueryRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DbDaySumQueryRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("寮�濮嬫棩鏈�")
+    private String start_time;
+
+    @ApiModelProperty("缁撴潫鏃ユ湡")
+    private String end_time;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevControlRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevControlRequest.java
new file mode 100644
index 0000000..e65a314
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevControlRequest.java
@@ -0,0 +1,28 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DevControlRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("姘存満 sj銆佸鑱旀満 dlj")
+    private String pid;
+
+    @ApiModelProperty("缃戝叧 MAC")
+    private String wg_mac;
+
+    @ApiModelProperty("璁惧鍦板潃")
+    private String wg_qid;
+
+    @ApiModelProperty("set_one 鍗曟潯鎺у埗")
+    private String ctr_type;
+
+    @ApiModelProperty("pwr/mode/fan/temp/many_ctr/find_dn/find_power 绛�")
+    private String set_type;
+
+    @ApiModelProperty("璁剧疆鍊硷紝鍙负鏁板瓧鎴� JSON 瀵硅薄")
+    private Object set_val;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockControlRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockControlRequest.java
new file mode 100644
index 0000000..2462452
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockControlRequest.java
@@ -0,0 +1,19 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DevLockControlRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("set_many 澶氳澶囨帶鍒�")
+    private String ctr_type;
+
+    @ApiModelProperty("璁惧鎺у埗椤瑰垪琛�")
+    private List<DevLockManyControlItem> li_data;
+
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockManyControlItem.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockManyControlItem.java
new file mode 100644
index 0000000..e2aa5a7
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevLockManyControlItem.java
@@ -0,0 +1,25 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class DevLockManyControlItem implements Serializable {
+
+    @ApiModelProperty("缃戝叧 MAC")
+    private String wg_mac;
+
+    @ApiModelProperty("璁惧鍦板潃")
+    private String wg_qid;
+
+    @ApiModelProperty("姘存満 sj銆佸鑱旀満 dlj")
+    private String pid;
+
+    @ApiModelProperty("lock_many")
+    private String set_type;
+
+    @ApiModelProperty("璁剧疆鍊�")
+    private Object set_val;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManageRequest.java
new file mode 100644
index 0000000..20a1c19
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManageRequest.java
@@ -0,0 +1,34 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DevManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("璁惧 ID")
+    private Integer dev_id;
+
+    @ApiModelProperty("缃戝叧 ID")
+    private Integer wg_id;
+
+    @ApiModelProperty("璁惧鍦板潃")
+    private String wg_qid;
+
+    @ApiModelProperty("妤煎眰 ID")
+    private Integer floor_id;
+
+    @ApiModelProperty("鎴块棿 ID")
+    private Integer room_id;
+
+    @ApiModelProperty("璁惧鍚嶇О")
+    private String dev_name;
+
+    @ApiModelProperty("璁惧绫诲瀷 ID")
+    private Integer dev_type_id;
+
+    @ApiModelProperty("澶囨敞")
+    private String dev_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlItem.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlItem.java
new file mode 100644
index 0000000..500a38d
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlItem.java
@@ -0,0 +1,25 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class DevManyControlItem implements Serializable {
+
+    @ApiModelProperty("缃戝叧 MAC")
+    private String wg_mac;
+
+    @ApiModelProperty("璁惧鍦板潃")
+    private String wg_qid;
+
+    @ApiModelProperty("姘存満 sj銆佸鑱旀満 dlj")
+    private String pid;
+
+    @ApiModelProperty("pwr/mode/fan/temp/many_ctr 绛�")
+    private String set_type;
+
+    @ApiModelProperty("璁剧疆鍊�")
+    private Object set_val;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlRequest.java
new file mode 100644
index 0000000..e6eea05
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/DevManyControlRequest.java
@@ -0,0 +1,24 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DevManyControlRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("set_many 澶氳澶囨帶鍒�")
+    private String ctr_type;
+
+    @ApiModelProperty("璁惧鎺у埗椤瑰垪琛�")
+    private List<DevManyControlItem> li_data;
+
+    @ApiModelProperty("閿佸畾绛夊鍔熻兘鎺у埗鏃剁殑璁剧疆绫诲瀷")
+    private String set_type;
+
+    @ApiModelProperty("閿佸畾绛夋帶鍒舵椂鐨勮缃��")
+    private Object set_val;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/FloorManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/FloorManageRequest.java
new file mode 100644
index 0000000..270e29c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/FloorManageRequest.java
@@ -0,0 +1,19 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FloorManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("妤煎眰 ID")
+    private Integer floor_id;
+
+    @ApiModelProperty("妤煎眰鍚嶇О")
+    private String floor_name;
+
+    @ApiModelProperty("澶囨敞")
+    private String floor_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GetDevOneRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GetDevOneRequest.java
new file mode 100644
index 0000000..c7dd6b3
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GetDevOneRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GetDevOneRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("缃戝叧 MAC")
+    private String wg_mac;
+
+    @ApiModelProperty("璁惧鍦板潃")
+    private String wg_qid;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GsWithAreaRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GsWithAreaRequest.java
new file mode 100644
index 0000000..388ba90
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/GsWithAreaRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class GsWithAreaRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鍏徃 ID")
+    private Integer id;
+
+    @ApiModelProperty("鎴块棿 ID")
+    private Integer room_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LogQueryRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LogQueryRequest.java
new file mode 100644
index 0000000..f9c6e57
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LogQueryRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class LogQueryRequest extends ConditionerPageRequest {
+
+    @ApiModelProperty("寮�濮嬫棩鏈� yyyy-MM-dd")
+    private String start_time;
+
+    @ApiModelProperty("缁撴潫鏃ユ湡 yyyy-MM-dd")
+    private String end_time;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LoginRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LoginRequest.java
new file mode 100644
index 0000000..1e97fc4
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/LoginRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class LoginRequest implements Serializable {
+
+    @ApiModelProperty("鐢ㄦ埛鍚嶏紝榛樿鍙栧瓧鍏搁厤缃�")
+    private String username;
+
+    @ApiModelProperty("瀵嗙爜锛岄粯璁ゅ彇瀛楀吀閰嶇疆")
+    private String password;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MeterDbManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MeterDbManageRequest.java
new file mode 100644
index 0000000..32c89f2
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MeterDbManageRequest.java
@@ -0,0 +1,42 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MeterDbManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鐢佃〃 ID")
+    private Integer db_id;
+
+    @ApiModelProperty("缃戝叧 ID")
+    private Integer wg_id;
+
+    @ApiModelProperty("鍗忚 ID")
+    private Integer xy_id;
+
+    @ApiModelProperty("鐢佃〃鍦板潃")
+    private String db_adr;
+
+    @ApiModelProperty("鐢佃〃鍚嶇О")
+    private String db_name;
+
+    @ApiModelProperty("鍙樻瘮")
+    private Integer db_bb;
+
+    @ApiModelProperty("澶栨満鏃ヨ�楃數")
+    private Integer db_rhd;
+
+    @ApiModelProperty("澶囨敞")
+    private String db_bz;
+
+    @ApiModelProperty("鍏宠仈璁惧 ID 鍒楄〃")
+    private List<Integer> li_dev;
+
+    @ApiModelProperty("绛涢�� MAC")
+    private String db_mac;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MoonDlQueryRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MoonDlQueryRequest.java
new file mode 100644
index 0000000..ab497d2
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/MoonDlQueryRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class MoonDlQueryRequest extends ConditionerPageRequest {
+
+    @ApiModelProperty("鏈堜唤 yyyy-MM")
+    private String date;
+
+    @ApiModelProperty("鍏徃 ID 绛涢��")
+    private Integer gs_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/RoomManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/RoomManageRequest.java
new file mode 100644
index 0000000..686e870
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/RoomManageRequest.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class RoomManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鎴块棿 ID")
+    private Integer room_id;
+
+    @ApiModelProperty("妤煎眰 ID")
+    private Integer floor_id;
+
+    @ApiModelProperty("鎴块棿鍚嶇О")
+    private String room_name;
+
+    @ApiModelProperty("澶囨敞")
+    private String room_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingManageRequest.java
new file mode 100644
index 0000000..8866d05
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingManageRequest.java
@@ -0,0 +1,46 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TimingManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("瀹氭椂 ID")
+    private Integer id;
+
+    @ApiModelProperty("瀹氭椂鏃堕棿 HH:mm")
+    private String uptime;
+
+    @ApiModelProperty("璁惧 ID 鍒楄〃")
+    private List<Integer> li_devid;
+
+    @ApiModelProperty("瀹氭椂寮�鍏� 0/1")
+    private Integer time_pwr;
+
+    @ApiModelProperty("pwr/lock/many/scene")
+    private String time_type;
+
+    @ApiModelProperty("鍦烘櫙鍐呭")
+    private Map<String, Object> d_scene;
+
+    @ApiModelProperty("0 鍛ㄦ湡 1 鏃ユ湡")
+    private Integer date_type;
+
+    @ApiModelProperty("鍛ㄦ湡 0-6")
+    private String time_week;
+
+    @ApiModelProperty("鏃ユ湡 yyyy-MM-dd")
+    private String time_date;
+
+    @ApiModelProperty("澶囨敞")
+    private String time_bz;
+
+    private Integer sceneId;
+    private String sceneName;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingWithAreaRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingWithAreaRequest.java
new file mode 100644
index 0000000..c87c990
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/TimingWithAreaRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TimingWithAreaRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("瀹氭椂 ID")
+    private Integer id;
+
+    @ApiModelProperty("鎴块棿 ID")
+    private Integer room_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UnitManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UnitManageRequest.java
new file mode 100644
index 0000000..eee23eb
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UnitManageRequest.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UnitManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鍗曞厓 ID")
+    private Integer unit_id;
+
+    @ApiModelProperty("妤兼爧 ID")
+    private Integer building_id;
+
+    @ApiModelProperty("鍗曞厓鍚嶇О")
+    private String unit_name;
+
+    @ApiModelProperty("澶囨敞")
+    private String unit_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UserManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UserManageRequest.java
new file mode 100644
index 0000000..715dedd
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/UserManageRequest.java
@@ -0,0 +1,31 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class UserManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("鐢ㄦ埛 ID")
+    private Integer user_id;
+
+    @ApiModelProperty("鐢ㄦ埛鍚�")
+    private String username;
+
+    @ApiModelProperty("瀵嗙爜")
+    private String password;
+
+    @ApiModelProperty("鏉冮檺")
+    private Integer kt_qx;
+
+    @ApiModelProperty("绛夌骇")
+    private Integer kt_level;
+
+    @ApiModelProperty("鍙帶鍒跺尯鍩�")
+    private String kt_area;
+
+    @ApiModelProperty("澶囨敞")
+    private String kt_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgManageRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgManageRequest.java
new file mode 100644
index 0000000..29e2c9d
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgManageRequest.java
@@ -0,0 +1,19 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WgManageRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("缃戝叧 ID")
+    private Integer wg_id;
+
+    @ApiModelProperty("缃戝叧 MAC")
+    private String wg_mac;
+
+    @ApiModelProperty("澶囨敞")
+    private String wg_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgWithAreaRequest.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgWithAreaRequest.java
new file mode 100644
index 0000000..d6144be
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/request/WgWithAreaRequest.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WgWithAreaRequest extends ConditionerSessionRequest {
+
+    @ApiModelProperty("缃戝叧 ID")
+    private Integer id;
+
+    @ApiModelProperty("鎴块棿 ID")
+    private Integer room_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/AreaInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/AreaInfoResponse.java
new file mode 100644
index 0000000..0a94ccc
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/AreaInfoResponse.java
@@ -0,0 +1,13 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class AreaInfoResponse implements Serializable {
+
+    private Integer area_id;
+    private String area_name;
+    private String area_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/BuildingInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/BuildingInfoResponse.java
new file mode 100644
index 0000000..5c9b599
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/BuildingInfoResponse.java
@@ -0,0 +1,14 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class BuildingInfoResponse implements Serializable {
+
+    private Integer building_id;
+    private Integer area_id;
+    private String building_name;
+    private String building_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/CompanyGsInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/CompanyGsInfoResponse.java
new file mode 100644
index 0000000..51daf01
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/CompanyGsInfoResponse.java
@@ -0,0 +1,23 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class CompanyGsInfoResponse implements Serializable {
+
+    private Integer id;
+    private String uptime;
+    private Integer is_pwr;
+    private Integer is_rest_stop;
+    private String gs_name;
+    private Object left_money;
+    private Object left_money_y;
+    private Integer is_stop;
+    private List<Integer> li_dev;
+    private Map<String, Object> d_dev;
+    private String gs_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/ConditionerBaseResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/ConditionerBaseResponse.java
new file mode 100644
index 0000000..0697ce5
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/ConditionerBaseResponse.java
@@ -0,0 +1,28 @@
+package com.doumee.core.conditoner.model.response;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 鏅虹簿鐏� API 缁熶竴鍝嶅簲锛歝ode=200 琛ㄧず鎴愬姛
+ */
+@ApiModel("鏅虹簿鐏靛搷搴斿璞�")
+@Data
+public class ConditionerBaseResponse<T> implements Serializable {
+
+    @ApiModelProperty("200 鎴愬姛锛�500 绛夎〃绀哄け璐�")
+    private Integer code;
+
+    @ApiModelProperty("鎻愮ず淇℃伅")
+    private String message;
+
+    @ApiModelProperty("涓氬姟鏁版嵁")
+    private T data;
+
+    public boolean isSuccess() {
+        return code != null && code == 200;
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceArchiveResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceArchiveResponse.java
new file mode 100644
index 0000000..eb5c2d9
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceArchiveResponse.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class DeviceArchiveResponse implements Serializable {
+
+    private String wg_mac;
+    private String floor_name;
+    private String room_name;
+    private String dev_type_name;
+    private Integer dev_id;
+    private Integer floor_id;
+    private Integer room_id;
+    private Integer wg_id;
+    private String wg_qid;
+    private String dev_name;
+    private String dev_bz;
+    private Integer dev_type_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceStatusResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceStatusResponse.java
new file mode 100644
index 0000000..afa4739
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DeviceStatusResponse.java
@@ -0,0 +1,40 @@
+package com.doumee.core.conditoner.model.response;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class DeviceStatusResponse implements Serializable {
+
+    private Integer dev_id;
+    private String online;
+    private String xh;
+    private Integer kt_lock;
+    private Integer pwr;
+    private Integer mode;
+    private Integer fan;
+    private Integer fan_set;
+    private Integer sf_pwr;
+    private Integer temp;
+    private Integer temp_set;
+    private Integer stop_logo;
+    private String sum_runtime;
+    private String hig_runtime;
+    private String mid_runtime;
+    private String low_runtime;
+    private Integer wg_id;
+    private String wg_mac;
+    private String wg_qid;
+    private String dev_name;
+    private Integer floor_id;
+    private String floor_name;
+    private Integer room_id;
+    private String room_name;
+    private Integer dev_type_id;
+    private String dev_type_name;
+    private String uptime;
+    @ApiModelProperty("姘存満 sj銆佸鑱旀満 dlj")
+    private String pid;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DlSjXsResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DlSjXsResponse.java
new file mode 100644
index 0000000..2a20b2c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/DlSjXsResponse.java
@@ -0,0 +1,22 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class DlSjXsResponse implements Serializable {
+
+    private Integer dev_id;
+    private BigDecimal kt_dj;
+    private Integer kw_type;
+    private BigDecimal hig_kw;
+    private BigDecimal mid_kw;
+    private BigDecimal low_kw;
+    private BigDecimal fan_arg;
+    private BigDecimal fan_kw;
+    private BigDecimal cold_kw;
+    private BigDecimal heat_kw;
+    private BigDecimal day_kw;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/FloorInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/FloorInfoResponse.java
new file mode 100644
index 0000000..fb15330
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/FloorInfoResponse.java
@@ -0,0 +1,13 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FloorInfoResponse implements Serializable {
+
+    private Integer floor_id;
+    private String floor_name;
+    private String floor_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/GatewayInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/GatewayInfoResponse.java
new file mode 100644
index 0000000..9d48314
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/GatewayInfoResponse.java
@@ -0,0 +1,16 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+@Data
+public class GatewayInfoResponse implements Serializable {
+
+    private Integer wg_id;
+    private String wg_mac;
+    private String wg_bz;
+    private String wg_status;
+    private Map<String, Object> area;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/LoginDataResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/LoginDataResponse.java
new file mode 100644
index 0000000..ab0787f
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/LoginDataResponse.java
@@ -0,0 +1,25 @@
+package com.doumee.core.conditoner.model.response;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class LoginDataResponse implements Serializable {
+
+    @ApiModelProperty("鐧诲綍 token")
+    private String kt_token;
+
+    @ApiModelProperty("鍏徃 ID")
+    private Object kt_dwid;
+
+    @ApiModelProperty("瀛愯处鍙� ID")
+    private Object kt_sonid;
+
+    @ApiModelProperty("鐢ㄦ埛鍚�")
+    private String username;
+
+    @ApiModelProperty("鐢ㄦ埛 ID")
+    private Integer user_id;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/MeterDbInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/MeterDbInfoResponse.java
new file mode 100644
index 0000000..4ecb48a
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/MeterDbInfoResponse.java
@@ -0,0 +1,24 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class MeterDbInfoResponse implements Serializable {
+
+    private Integer db_id;
+    private String wg_mac;
+    private Integer wg_id;
+    private String xy_name;
+    private Integer xy_id;
+    private String db_adr;
+    private String db_name;
+    private Integer db_bb;
+    private Integer db_rhd;
+    private List<Integer> li_dev;
+    private String db_bz;
+    private Object db_data;
+    private String db_uptime;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/RoomInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/RoomInfoResponse.java
new file mode 100644
index 0000000..2946218
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/RoomInfoResponse.java
@@ -0,0 +1,14 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class RoomInfoResponse implements Serializable {
+
+    private Integer room_id;
+    private Integer floor_id;
+    private String room_name;
+    private String room_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/TimingInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/TimingInfoResponse.java
new file mode 100644
index 0000000..e061142
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/TimingInfoResponse.java
@@ -0,0 +1,20 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class TimingInfoResponse implements Serializable {
+
+    private Integer id;
+    private Integer time_pwr;
+    private String time_week;
+    private String time_type;
+    private String uptime;
+    private List<Integer> li_devid;
+    private Map<String, Object> d_scene;
+    private String time_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UnitInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UnitInfoResponse.java
new file mode 100644
index 0000000..bfe7fbc
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UnitInfoResponse.java
@@ -0,0 +1,14 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class UnitInfoResponse implements Serializable {
+
+    private Integer unit_id;
+    private Integer building_id;
+    private String unit_name;
+    private String unit_bz;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UserInfoResponse.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UserInfoResponse.java
new file mode 100644
index 0000000..7bdbe83
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/conditoner/model/response/UserInfoResponse.java
@@ -0,0 +1,23 @@
+package com.doumee.core.conditoner.model.response;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class UserInfoResponse implements Serializable {
+
+    private Integer user_id;
+    private String kt_token;
+    private Integer kt_dwid;
+    private String username;
+    private Integer kt_qx;
+    private Integer kt_level;
+    private Integer kt_sonid;
+    private String kt_area;
+    private Integer cz_pwr;
+    private String gs_name;
+    private String kt_bz;
+    private String end_time;
+    private String kt_unit;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerActionsMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerActionsMapper.java
new file mode 100644
index 0000000..d80f148
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerActionsMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerActions;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerActionsMapper extends MPJBaseMapper<YwConditionerActions> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerBillingMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerBillingMapper.java
new file mode 100644
index 0000000..485eda7
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerBillingMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerBilling;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerBillingMapper extends MPJBaseMapper<YwConditionerBilling> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayLogMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayLogMapper.java
new file mode 100644
index 0000000..5642c3b
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayLogMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerGatewayLog;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerGatewayLogMapper extends MPJBaseMapper<YwConditionerGatewayLog> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayMapper.java
new file mode 100644
index 0000000..b1c24e2
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerGatewayMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerGateway;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerGatewayMapper extends MPJBaseMapper<YwConditionerGateway> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerMeterMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerMeterMapper.java
new file mode 100644
index 0000000..c1995a4
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerMeterMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerMeter;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerMeterMapper extends MPJBaseMapper<YwConditionerMeter> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerUsageMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerUsageMapper.java
new file mode 100644
index 0000000..f1543d8
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwConditionerUsageMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwConditionerUsage;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwConditionerUsageMapper extends MPJBaseMapper<YwConditionerUsage> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerLockDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerLockDTO.java
new file mode 100644
index 0000000..b8fd0bc
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerLockDTO.java
@@ -0,0 +1,22 @@
+package com.doumee.dao.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class YwConditionerLockDTO {
+
+    @ApiModelProperty("鍐呮満涓婚敭")
+    private Integer id;
+
+    @ApiModelProperty("鏄惁閿佸畾寮�鍏� -1涓嶉攣 0鍏� 1寮�")
+    private Integer lockPwr;
+    private Integer pwr;
+    private Integer mode;
+    private Integer fan;
+    private Integer minTemp;
+    private Integer maxTemp;
+
+    @ApiModelProperty("鎿嶄綔鏉ユ簮")
+    private String source;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerOperateDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerOperateDTO.java
new file mode 100644
index 0000000..e4a3a54
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerOperateDTO.java
@@ -0,0 +1,20 @@
+package com.doumee.dao.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class YwConditionerOperateDTO {
+
+    @ApiModelProperty("鍐呮満涓婚敭")
+    private Integer id;
+
+    @ApiModelProperty("1寮�鍏�2妯″紡3椋庨��4娓╁害")
+    private Integer actionType;
+
+    @ApiModelProperty("璁剧疆鍊�")
+    private Object setVal;
+
+    @ApiModelProperty("鎿嶄綔鏉ユ簮")
+    private String source;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerReportQueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerReportQueryDTO.java
new file mode 100644
index 0000000..189b85c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerReportQueryDTO.java
@@ -0,0 +1,29 @@
+package com.doumee.dao.business.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class YwConditionerReportQueryDTO {
+
+    @ApiModelProperty("鎶ヨ〃绫诲瀷锛歞ay/month")
+    private String reportType;
+
+    @ApiModelProperty("鏈堜唤 yyyy-MM")
+    private String month;
+
+    @ApiModelProperty("鏃ユ姤琛ㄥ紑濮嬫棩鏈� yyyy-MM-dd")
+    private String startTime;
+
+    @ApiModelProperty("鏃ユ姤琛ㄧ粨鏉熸棩鏈� yyyy-MM-dd")
+    private String endTime;
+
+    @ApiModelProperty("鍏徃ID")
+    private Integer gsId;
+
+    @ApiModelProperty("璁惧鍏抽敭瀛�")
+    private String devKeyword;
+
+    private Integer page;
+    private Integer capacity;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportPageDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportPageDTO.java
new file mode 100644
index 0000000..2ae90da
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportPageDTO.java
@@ -0,0 +1,16 @@
+package com.doumee.dao.business.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class YwConditionerUsageReportPageDTO {
+
+    private List<String> dateColumns = new ArrayList<>();
+    private List<YwConditionerUsageReportVO> records = new ArrayList<>();
+    private long total;
+    private int page;
+    private int capacity;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportVO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportVO.java
new file mode 100644
index 0000000..2662789
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwConditionerUsageReportVO.java
@@ -0,0 +1,27 @@
+package com.doumee.dao.business.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Data
+public class YwConditionerUsageReportVO {
+
+    private Integer devId;
+    private String devName;
+    private String floorName;
+    private String roomName;
+    private BigDecimal totalTime;
+    private BigDecimal totalDl;
+    private BigDecimal totalDf;
+    private Map<String, YwConditionerUsageDailyVO> daily = new LinkedHashMap<>();
+
+    @Data
+    public static class YwConditionerUsageDailyVO {
+        private BigDecimal time;
+        private BigDecimal dl;
+        private BigDecimal df;
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditioner.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditioner.java
index a36ddc0..21b9db0 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditioner.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditioner.java
@@ -5,6 +5,7 @@
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 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 lombok.Data;
@@ -81,4 +82,57 @@
     @ApiModelProperty(value = "绱鐢ㄧ數閲戦")
     @ExcelColumn(name = "绱鐢ㄧ數閲戦")
     private BigDecimal useAmount;
+
+    @ApiModelProperty("骞冲彴璁惧ID")
+    private Integer platformDevId;
+    private Integer wgId;
+    private String wgMac;
+
+    @TableField(exist = false)
+    @ApiModelProperty("缃戝叧澶囨敞锛堝叧鑱旂綉鍏宠〃锛�")
+    private String wgBz;
+
+    private String wgQid;
+    @ApiModelProperty("姘存満 sj銆佸鑱旀満 dlj")
+    private String pid;
+    @ApiModelProperty("鍦ㄧ嚎/绂荤嚎")
+    private String online;
+    private Integer pwr;
+    private Integer mode;
+    private Integer fan;
+    private Integer fanSet;
+    private Integer temp;
+    private Integer tempSet;
+    private Integer ktLock;
+    private Integer stopLogo;
+    private String uptime;
+    private Integer floorId;
+    private String floorName;
+    private Integer roomId;
+    private String roomName;
+    private Integer devTypeId;
+    private String devTypeName;
+    private Integer lockPwr;
+    private Integer lockMode;
+    private Integer lockFan;
+    private Integer lockMinTemp;
+    private Integer lockMaxTemp;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastSyncDate;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鍗$墖绛涢��-璁惧鍚嶇О/缂栧彿")
+    private String devKeyword;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鍗$墖绛涢��-鍦ㄧ嚎鐘舵��")
+    private String onlineFilter;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鍗$墖绛涢��-寮�鍏崇姸鎬侊細1寮�鏈�0鍏虫満")
+    private Integer pwrFilter;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鍗$墖绛涢��-缃戝叧MAC")
+    private String wgMacFilter;
 }
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerActions.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerActions.java
new file mode 100644
index 0000000..e57f2c5
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerActions.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
+@ApiModel("绌鸿皟璁惧鎺у埗璁板綍")
+@TableName("yw_conditioner_actions")
+public class YwConditionerActions 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;
+
+    private Integer conditionerId;
+    private Integer platformDevId;
+    private String devName;
+    private String wgMac;
+    @ApiModelProperty("1寮�鍏�2妯″紡3椋庨��4娓╁害5閿佸畾6鏌ョ數閲�7鏌ュ姛鐜�")
+    private Integer actionType;
+    private String actionContent;
+    @ApiModelProperty("0澶辫触1鎴愬姛")
+    private Integer resultStatus;
+    private String resultMsg;
+    private String source;
+    private String requestBody;
+    private String responseBody;
+
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date operateTimeBegin;
+
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date operateTimeEnd;
+
+    @TableField(exist = false)
+    private String devKeyword;
+
+    @TableField(exist = false)
+    private String onlineFilter;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerBilling.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerBilling.java
new file mode 100644
index 0000000..553bd41
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerBilling.java
@@ -0,0 +1,48 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ApiModel("鏅虹簿鐏佃璐圭郴鏁伴暅鍍�")
+@TableName("yw_conditioner_billing")
+public class YwConditionerBilling 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;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date editDate;
+    private Integer isdeleted;
+    private String remark;
+
+    private Integer platformDevId;
+    private String devName;
+    private String wgMac;
+    @ApiModelProperty("0鏃堕暱1鑳借��2鐢佃〃")
+    private Integer kwType;
+    private BigDecimal fanArg;
+    private BigDecimal fanKw;
+    private BigDecimal higKw;
+    private BigDecimal midKw;
+    private BigDecimal lowKw;
+    private BigDecimal ktDj;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastSyncDate;
+
+    @TableField(exist = false)
+    private String devKeyword;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGateway.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGateway.java
new file mode 100644
index 0000000..023dae9
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGateway.java
@@ -0,0 +1,45 @@
+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
+@ApiModel("鏅虹簿鐏电綉鍏抽暅鍍�")
+@TableName("yw_conditioner_gateway")
+public class YwConditionerGateway 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;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date editDate;
+    private Integer isdeleted;
+    private String remark;
+
+    @ApiModelProperty("骞冲彴缃戝叧ID")
+    private Integer platformWgId;
+    @ApiModelProperty("缃戝叧MAC")
+    private String wgMac;
+    @ApiModelProperty("澶囨敞")
+    private String wgBz;
+    @ApiModelProperty("鍦ㄧ嚎/绂荤嚎")
+    private String onlineStatus;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastSyncDate;
+
+    @TableField(exist = false)
+    @ApiModelProperty("鍏抽敭瀛�(MAC/澶囨敞)")
+    private String keyword;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGatewayLog.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGatewayLog.java
new file mode 100644
index 0000000..4c2271d
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerGatewayLog.java
@@ -0,0 +1,46 @@
+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
+@ApiModel("缃戝叧涓婁笅绾胯褰�")
+@TableName("yw_conditioner_gateway_log")
+public class YwConditionerGatewayLog 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;
+
+    private Integer gatewayId;
+    private String wgMac;
+    private String oldStatus;
+    private String newStatus;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date logTime;
+    @ApiModelProperty("manual/schedule")
+    private String source;
+
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date logTimeBegin;
+
+    @TableField(exist = false)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date logTimeEnd;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerMeter.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerMeter.java
new file mode 100644
index 0000000..2221bb7
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerMeter.java
@@ -0,0 +1,58 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ApiModel("鏅虹簿鐏电數琛ㄩ暅鍍�")
+@TableName("yw_conditioner_meter")
+public class YwConditionerMeter 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;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date editDate;
+    private Integer isdeleted;
+    private String remark;
+
+    private Integer platformDbId;
+    private String dbName;
+    private String dbAdr;
+    private String wgMac;
+    private Integer wgId;
+    private Integer xyId;
+    private String xyName;
+    @ApiModelProperty("鍙樻瘮")
+    private Integer dbBb;
+    @ApiModelProperty("寰呮満鍒嗘憡")
+    private String standbyShare;
+    @ApiModelProperty("澶栨満鍥炶矾鍙�")
+    private Integer outdoorLoop;
+    private BigDecimal powerKw;
+    private BigDecimal totalDl;
+    private String dbData;
+    private String dbUptime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastSyncDate;
+
+    @TableField(exist = false)
+    @ApiModelProperty("璁惧淇℃伅鍏抽敭瀛�")
+    private String keyword;
+
+    @TableField(exist = false)
+    private String wgMacFilter;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerUsage.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerUsage.java
new file mode 100644
index 0000000..2e31c2d
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwConditionerUsage.java
@@ -0,0 +1,39 @@
+package com.doumee.dao.business.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+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 lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ApiModel("璁惧鏃ョ敤閲忛暅鍍�")
+@TableName("yw_conditioner_usage")
+public class YwConditionerUsage 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;
+
+    private Integer platformDevId;
+    private String devName;
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    private Date usageDate;
+    private BigDecimal sumTime;
+    private BigDecimal sumDl;
+    private BigDecimal sumDf;
+    private Integer gsId;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date syncDate;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/ConditionerBizService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/ConditionerBizService.java
new file mode 100644
index 0000000..3ed7fe2
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/ConditionerBizService.java
@@ -0,0 +1,49 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.dao.business.dto.YwConditionerLockDTO;
+import com.doumee.dao.business.dto.YwConditionerOperateDTO;
+import com.doumee.dao.business.dto.YwConditionerReportQueryDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportPageDTO;
+import com.doumee.dao.business.model.YwConditioner;
+import com.doumee.dao.business.model.YwConditionerActions;
+import com.doumee.dao.business.model.YwConditionerMeter;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ConditionerBizService {
+
+    void ensureLogin();
+
+    String syncGateways(String source);
+
+    String syncGatewayStatus();
+
+    String syncMeters();
+
+    String syncBilling();
+
+    String syncIndoorUnits();
+
+    String syncUsage(YwConditionerReportQueryDTO query);
+
+    /** 瀹氭椂浠诲姟锛氬悓姝ュ墠涓�鏃ョ敤閲忔姤琛ㄦ暟鎹� */
+    String syncUsagePreviousDay();
+
+    String operate(YwConditionerOperateDTO dto, LoginUserInfo user);
+
+    String lock(YwConditionerLockDTO dto, LoginUserInfo user);
+
+    String queryMeterEnergy(Integer meterId, LoginUserInfo user);
+
+    String queryMeterPower(Integer meterId, LoginUserInfo user);
+
+    void saveAction(YwConditionerActions action, LoginUserInfo user);
+
+    YwConditionerUsageReportPageDTO queryUsageReport(YwConditionerReportQueryDTO query);
+
+    List<Map<String, Object>> gatewayOptions();
+
+    String syncAll();
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerActionsService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerActionsService.java
new file mode 100644
index 0000000..634e647
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerActionsService.java
@@ -0,0 +1,10 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.YwConditionerActions;
+
+public interface YwConditionerActionsService {
+
+    PageData<YwConditionerActions> findPage(PageWrap<YwConditionerActions> pageWrap);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerBillingService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerBillingService.java
new file mode 100644
index 0000000..8b1923d
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerBillingService.java
@@ -0,0 +1,12 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.YwConditionerBilling;
+
+public interface YwConditionerBillingService {
+
+    PageData<YwConditionerBilling> findPage(PageWrap<YwConditionerBilling> pageWrap);
+
+    String syncAll();
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerGatewayService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerGatewayService.java
new file mode 100644
index 0000000..1c2ebb3
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerGatewayService.java
@@ -0,0 +1,15 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.YwConditionerGateway;
+import com.doumee.dao.business.model.YwConditionerGatewayLog;
+
+public interface YwConditionerGatewayService {
+
+    PageData<YwConditionerGateway> findPage(PageWrap<YwConditionerGateway> pageWrap);
+
+    PageData<YwConditionerGatewayLog> gatewayLogPage(PageWrap<YwConditionerGatewayLog> pageWrap);
+
+    String syncAll();
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerMeterService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerMeterService.java
new file mode 100644
index 0000000..99b4f67
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerMeterService.java
@@ -0,0 +1,17 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.model.YwConditionerMeter;
+
+public interface YwConditionerMeterService {
+
+    PageData<YwConditionerMeter> findPage(PageWrap<YwConditionerMeter> pageWrap);
+
+    String syncAll();
+
+    String queryEnergy(Integer meterId, LoginUserInfo user);
+
+    String queryPower(Integer meterId, LoginUserInfo user);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerReportService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerReportService.java
new file mode 100644
index 0000000..ce739a1
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerReportService.java
@@ -0,0 +1,20 @@
+package com.doumee.service.business;
+
+import com.doumee.dao.business.dto.YwConditionerReportQueryDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportPageDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportVO;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+public interface YwConditionerReportService {
+
+    YwConditionerUsageReportPageDTO findPage(YwConditionerReportQueryDTO query);
+
+    String syncUsage(YwConditionerReportQueryDTO query);
+
+    List<Map<String, Object>> merchantOptions();
+
+    void exportExcel(YwConditionerReportQueryDTO query, HttpServletResponse response);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerService.java
index 0714f90..682e7e0 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwConditionerService.java
@@ -3,8 +3,13 @@
 import com.doumee.core.model.LoginUserInfo;
 import com.doumee.core.model.PageData;
 import com.doumee.core.model.PageWrap;
+import com.doumee.dao.business.dto.YwConditionerLockDTO;
+import com.doumee.dao.business.dto.YwConditionerOperateDTO;
 import com.doumee.dao.business.model.YwConditioner;
+import com.doumee.dao.business.model.YwConditionerActions;
+
 import java.util.List;
+import java.util.Map;
 
 /**
  * 绌鸿皟璁惧淇℃伅Service瀹氫箟
@@ -18,4 +23,18 @@
     void updateById(YwConditioner ywConditioner);
     YwConditioner findById(Integer id);
     PageData<YwConditioner> findPage(PageWrap<YwConditioner> pageWrap);
+
+    PageData<YwConditioner> findCardPage(PageWrap<YwConditioner> pageWrap);
+
+    String syncAll();
+
+    String syncDevicesAndStatus();
+
+    String operate(YwConditionerOperateDTO dto, LoginUserInfo user);
+
+    String lock(YwConditionerLockDTO dto, LoginUserInfo user);
+
+    PageData<YwConditionerActions> historyPage(PageWrap<YwConditionerActions> pageWrap);
+
+    List<Map<String, Object>> gatewayOptions();
 }
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerBizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerBizServiceImpl.java
new file mode 100644
index 0000000..5e2c44f
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerBizServiceImpl.java
@@ -0,0 +1,1031 @@
+package com.doumee.service.business.impl;
+
+import com.alibaba.fastjson.JSON;
+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.conditoner.ConditionerUtil;
+import com.doumee.core.conditoner.model.ConditionerConstant;
+import com.doumee.core.conditoner.model.request.*;
+import com.doumee.core.conditoner.model.response.*;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.*;
+import com.doumee.dao.business.dto.*;
+import com.doumee.dao.business.model.*;
+import com.doumee.service.business.ConditionerBizService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@Service
+public class ConditionerBizServiceImpl implements ConditionerBizService {
+
+    public static final int ACTION_PWR = 1;
+    public static final int ACTION_MODE = 2;
+    public static final int ACTION_FAN = 3;
+    public static final int ACTION_TEMP = 4;
+    public static final int ACTION_LOCK = 5;
+    public static final int ACTION_QUERY_DL = 6;
+    public static final int ACTION_QUERY_POWER = 7;
+
+    private static final String PID_DLJ = "dlj";
+    private static final String LOCK_SET_TYPE = "lock_many";
+    private static final long ADJUST_OPERATE_INTERVAL_MS = 2000L;
+    private static volatile boolean syncing = false;
+    /** 鍚屼竴鍐呮満锛氭俯搴�/椋庨��/妯″紡鎿嶄綔鏈�灏忛棿闅旓紙姣锛� */
+    private static final Map<Integer, Long> LAST_ADJUST_OPERATE_MS = new ConcurrentHashMap<>();
+
+    @Autowired
+    private YwConditionerGatewayMapper gatewayMapper;
+    @Autowired
+    private YwConditionerGatewayLogMapper gatewayLogMapper;
+    @Autowired
+    private YwConditionerMeterMapper meterMapper;
+    @Autowired
+    private YwConditionerBillingMapper billingMapper;
+    @Autowired
+    private YwConditionerMapper conditionerMapper;
+    @Autowired
+    private YwConditionerActionsMapper actionsMapper;
+    @Autowired
+    private YwConditionerUsageMapper usageMapper;
+
+    @Override
+    public void ensureLogin() {
+        if (StringUtils.isNotBlank(ConditionerConstant.kt_token)) {
+            return;
+        }
+        ConditionerBaseResponse<LoginDataResponse> resp = ConditionerUtil.login();
+        if (resp == null || !resp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),
+                    resp != null ? resp.getMessage() : "鏅虹簿鐏靛钩鍙扮櫥褰曞け璐�");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String syncGateways(String source) {
+        ensureLogin();
+        ConditionerSessionRequest req = new ConditionerSessionRequest();
+        ConditionerBaseResponse<List<GatewayInfoResponse>> resp = ConditionerUtil.getWg(req);
+        if (resp == null || !resp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鍚屾缃戝叧澶辫触"));
+        }
+        List<GatewayInfoResponse> list = resp.getData() != null ? resp.getData() : Collections.emptyList();
+        Date now = new Date();
+        int add = 0, upd = 0, logs = 0;
+        for (GatewayInfoResponse item : list) {
+            if (StringUtils.isBlank(item.getWg_mac())) {
+                continue;
+            }
+            String newStatus = ConditionerConstant.normalizeGatewayOnline(item.getWg_status());
+            YwConditionerGateway local = gatewayMapper.selectOne(new QueryWrapper<YwConditionerGateway>().lambda()
+                    .eq(YwConditionerGateway::getIsdeleted, Constants.ZERO)
+                    .eq(YwConditionerGateway::getWgMac, item.getWg_mac())
+                    .last(" limit 1 "));
+            if (local == null) {
+                local = new YwConditionerGateway();
+                local.setCreator(0);
+                local.setCreateDate(now);
+                local.setIsdeleted(Constants.ZERO);
+                local.setPlatformWgId(item.getWg_id());
+                local.setWgMac(item.getWg_mac());
+                local.setWgBz(item.getWg_bz());
+                local.setOnlineStatus(newStatus);
+                local.setLastSyncDate(now);
+                gatewayMapper.insert(local);
+                add++;
+            } else {
+                String oldStatus = local.getOnlineStatus();
+                local.setPlatformWgId(item.getWg_id());
+                local.setWgBz(item.getWg_bz());
+                local.setOnlineStatus(newStatus);
+                local.setLastSyncDate(now);
+                local.setEditDate(now);
+                gatewayMapper.updateById(local);
+                upd++;
+                if (!Objects.equals(oldStatus, newStatus)) {
+                    writeGatewayLog(local.getId(), item.getWg_mac(), oldStatus, newStatus, source, now);
+                    logs++;
+                }
+            }
+        }
+        return "鍚屾缃戝叧锛氭柊澧炪��" + add + "銆戞洿鏂般��" + upd + "銆戠姸鎬佸彉鏇淬��" + logs + "銆�";
+    }
+
+    @Override
+    public String syncGatewayStatus() {
+        return syncGateways("schedule");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String syncMeters() {
+        ensureLogin();
+        MeterDbManageRequest req = new MeterDbManageRequest();
+        ConditionerBaseResponse<List<MeterDbInfoResponse>> resp = ConditionerUtil.getDb(req);
+        if (resp == null || !resp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鍚屾鐢佃〃澶辫触"));
+        }
+        List<MeterDbInfoResponse> list = resp.getData() != null ? resp.getData() : Collections.emptyList();
+        Date now = new Date();
+        int add = 0, upd = 0;
+        for (MeterDbInfoResponse item : list) {
+            if (item.getDb_id() == null) {
+                continue;
+            }
+            YwConditionerMeter local = meterMapper.selectOne(new QueryWrapper<YwConditionerMeter>().lambda()
+                    .eq(YwConditionerMeter::getIsdeleted, Constants.ZERO)
+                    .eq(YwConditionerMeter::getPlatformDbId, item.getDb_id())
+                    .last(" limit 1 "));
+            if (local == null) {
+                local = new YwConditionerMeter();
+                local.setCreator(0);
+                local.setCreateDate(now);
+                local.setIsdeleted(Constants.ZERO);
+                add++;
+            } else {
+                upd++;
+            }
+            fillMeter(local, item, now);
+            if (local.getId() == null) {
+                meterMapper.insert(local);
+            } else {
+                meterMapper.updateById(local);
+            }
+        }
+        return "鍚屾鐢佃〃锛氭柊澧炪��" + add + "銆戞洿鏂般��" + upd + "銆�";
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String syncBilling() {
+        ensureLogin();
+        ConditionerSessionRequest req = new ConditionerSessionRequest();
+        ConditionerBaseResponse<List<DlSjXsResponse>> resp = ConditionerUtil.getDlSjXs(req);
+        if (resp == null || !resp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鍚屾璁¤垂绯绘暟澶辫触"));
+        }
+        List<DlSjXsResponse> list = resp.getData() != null ? resp.getData() : Collections.emptyList();
+        Date now = new Date();
+        int add = 0, upd = 0;
+        for (DlSjXsResponse item : list) {
+            if (item.getDev_id() == null) {
+                continue;
+            }
+            YwConditionerBilling local = billingMapper.selectOne(new QueryWrapper<YwConditionerBilling>().lambda()
+                    .eq(YwConditionerBilling::getIsdeleted, Constants.ZERO)
+                    .eq(YwConditionerBilling::getPlatformDevId, item.getDev_id())
+                    .last(" limit 1 "));
+            if (local == null) {
+                local = new YwConditionerBilling();
+                local.setCreator(0);
+                local.setCreateDate(now);
+                local.setIsdeleted(Constants.ZERO);
+                add++;
+            } else {
+                upd++;
+            }
+            local.setPlatformDevId(item.getDev_id());
+            local.setKwType(item.getKw_type());
+            local.setFanArg(item.getFan_arg());
+            local.setFanKw(item.getFan_kw());
+            local.setHigKw(item.getHig_kw());
+            local.setMidKw(item.getMid_kw());
+            local.setLowKw(item.getLow_kw());
+            local.setKtDj(item.getKt_dj());
+            local.setLastSyncDate(now);
+            local.setEditDate(now);
+            YwConditioner dev = findConditionerByPlatformDevId(item.getDev_id());
+            if (dev != null) {
+                local.setDevName(dev.getName());
+                local.setWgMac(dev.getWgMac());
+            }
+            if (local.getId() == null) {
+                billingMapper.insert(local);
+            } else {
+                billingMapper.updateById(local);
+            }
+        }
+        return "鍚屾璁¤垂绯绘暟锛氭柊澧炪��" + add + "銆戞洿鏂般��" + upd + "銆�";
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String syncIndoorUnits() {
+        ensureLogin();
+        ConditionerSessionRequest session = new ConditionerSessionRequest();
+        ConditionerBaseResponse<List<DeviceStatusResponse>> statusResp = ConditionerUtil.getDevList(session);
+        if (statusResp == null || !statusResp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(statusResp, "鍚屾鍐呮満鐘舵�佸け璐�"));
+        }
+        ConditionerBaseResponse<List<DeviceArchiveResponse>> archiveResp = ConditionerUtil.getDev(session);
+        Map<Integer, DeviceArchiveResponse> archiveMap = new HashMap<>();
+        if (archiveResp != null && archiveResp.getData() != null) {
+            for (DeviceArchiveResponse a : archiveResp.getData()) {
+                if (a.getDev_id() != null) {
+                    archiveMap.put(a.getDev_id(), a);
+                }
+            }
+        }
+        List<DeviceStatusResponse> list = statusResp.getData() != null ? statusResp.getData() : Collections.emptyList();
+        Date now = new Date();
+        int add = 0, upd = 0;
+        for (DeviceStatusResponse item : list) {
+            if (item.getDev_id() == null) {
+                continue;
+            }
+            YwConditioner local = findConditionerByPlatformDevId(item.getDev_id());
+            if (local == null) {
+                local = new YwConditioner();
+                local.setCreator(0);
+                local.setCreateDate(now);
+                local.setIsdeleted(Constants.ZERO);
+                local.setCode(String.valueOf(item.getDev_id()));
+                local.setPlatformDevId(item.getDev_id());
+                add++;
+            } else {
+                upd++;
+            }
+            fillConditionerFromStatus(local, item, archiveMap.get(item.getDev_id()), now);
+            if (local.getId() == null) {
+                conditionerMapper.insert(local);
+            } else {
+                conditionerMapper.updateById(local);
+            }
+        }
+        return "鍚屾璁惧涓庣姸鎬侊細鏂板銆�" + add + "銆戞洿鏂般��" + upd + "銆�";
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String syncUsage(YwConditionerReportQueryDTO query) {
+        YwConditionerReportQueryDTO q = query != null ? query : new YwConditionerReportQueryDTO();
+        ReportDateRange range = resolveReportDateRange(q);
+        ensureLogin();
+        DayDlQueryRequest req = new DayDlQueryRequest();
+        req.setStart_time(range.start.toString());
+        req.setEnd_time(range.end.toString());
+        Integer gsId = q.getGsId();
+        if (gsId != null) {
+            req.setGs_id(gsId);
+        }
+        req.setPage(1);
+        req.setPageSize(5000);
+        ConditionerBaseResponse<List<Object>> resp = ConditionerUtil.getDayDl(req);
+        if (resp == null || !resp.isSuccess()) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鍚屾鐢ㄩ噺澶辫触"));
+        }
+        List<Object> list = resp.getData() != null ? resp.getData() : Collections.emptyList();
+        Date now = new Date();
+        int upsert = 0;
+        for (Object row : list) {
+            JSONObject o = (JSONObject) JSON.toJSON(row);
+            Integer devId = o.getInteger("dev_id");
+            String uptime = o.getString("uptime");
+            if (devId == null || StringUtils.isBlank(uptime)) {
+                continue;
+            }
+            Date usageDate = parseDate(uptime);
+            if (usageDate == null) {
+                continue;
+            }
+            YwConditionerUsage usage = usageMapper.selectOne(new QueryWrapper<YwConditionerUsage>().lambda()
+                    .eq(YwConditionerUsage::getIsdeleted, Constants.ZERO)
+                    .eq(YwConditionerUsage::getPlatformDevId, devId)
+                    .eq(YwConditionerUsage::getUsageDate, usageDate)
+                    .eq(gsId != null, YwConditionerUsage::getGsId, gsId)
+                    .isNull(gsId == null, YwConditionerUsage::getGsId)
+                    .last(" limit 1 "));
+            if (usage == null) {
+                usage = new YwConditionerUsage();
+                usage.setCreator(0);
+                usage.setCreateDate(now);
+                usage.setIsdeleted(Constants.ZERO);
+                usage.setPlatformDevId(devId);
+                usage.setGsId(gsId);
+            }
+            usage.setDevName(o.getString("dev_name"));
+            usage.setUsageDate(usageDate);
+            usage.setSumTime(o.getBigDecimal("sum_time"));
+            usage.setSumDl(o.getBigDecimal("sum_dl"));
+            usage.setSumDf(o.getBigDecimal("sum_df"));
+            usage.setSyncDate(now);
+            usage.setEditDate(now);
+            if (usage.getId() == null) {
+                usageMapper.insert(usage);
+            } else {
+                usageMapper.updateById(usage);
+            }
+            upsert++;
+        }
+        return "鍚屾鐢ㄩ噺锛氬鐞嗐��" + upsert + "銆戞潯";
+    }
+
+    @Override
+    public String syncUsagePreviousDay() {
+        LocalDate yesterday = LocalDate.now().minusDays(1);
+        YwConditionerReportQueryDTO query = new YwConditionerReportQueryDTO();
+        query.setReportType("day");
+        query.setStartTime(yesterday.toString());
+        query.setEndTime(yesterday.toString());
+        return syncUsage(query);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String operate(YwConditionerOperateDTO dto, LoginUserInfo user) {
+        if (dto == null || dto.getId() == null || dto.getActionType() == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST);
+        }
+        YwConditioner dev = requireConditioner(dto.getId());
+        ensureDeviceOnline(dev);
+        ensureLogin();
+        String setType;
+        Object setVal = dto.getSetVal();
+        int actionType = dto.getActionType();
+        reserveAdjustOperateInterval(dev.getId(), actionType);
+        switch (actionType) {
+            case ACTION_PWR:
+                setType = "pwr";
+                break;
+            case ACTION_MODE:
+                setType = "mode";
+                break;
+            case ACTION_FAN:
+                setType = "fan";
+                break;
+            case ACTION_TEMP:
+                setType = "temp";
+                if (setVal != null) {
+                    try {
+                        setVal = Integer.parseInt(String.valueOf(setVal)) * 10;
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+                break;
+            default:
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "涓嶆敮鎸佺殑鎿嶄綔绫诲瀷");
+        }
+        DevControlRequest req = buildDevControl(dev, setType, setVal);
+        String reqJson = JSON.toJSONString(req);
+        ConditionerBaseResponse<Object> resp = ConditionerUtil.devCtr(req);
+        boolean ok = resp != null && resp.isSuccess();
+        String actionDesc = formatOperateDescription(actionType, dto.getSetVal());
+        saveActionRecord(dev, actionType, actionDesc, ok, resp, reqJson, dto.getSource(), user);
+        if (!ok) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鎺у埗澶辫触"));
+        }
+        // 娓╁害/椋庨��/妯″紡锛氭帶鍒舵垚鍔熷嵆鎸夎瀹氬�兼洿鏂版湰鍦帮紝涓嶅啀鎷夊彇涓夋柟璁惧鐘舵��
+        if (actionType == ACTION_PWR) {
+            refreshDevStatus(dev);
+        }
+        applyOperateResultToDevice(dev, actionType, dto.getSetVal());
+        dev.setEditDate(new Date());
+        dev.setLastSyncDate(new Date());
+        conditionerMapper.updateById(dev);
+        return "鎿嶄綔鎴愬姛";
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String lock(YwConditionerLockDTO dto, LoginUserInfo user) {
+        if (dto == null || dto.getId() == null) {
+            throw new BusinessException(ResponseStatus.BAD_REQUEST);
+        }
+        YwConditioner dev = requireConditioner(dto.getId());
+        ensureDeviceOnline(dev);
+        ensureLogin();
+        JSONObject lockVal = buildLockSetVal(dto);
+        DevLockControlRequest req = buildLockManyControlRequest(dev, lockVal);
+        String reqJson = JSON.toJSONString(req);
+        ConditionerBaseResponse<Object> resp = ConditionerUtil.devLockManyCtr(req);
+        boolean ok = resp != null && resp.isSuccess();
+        saveActionRecord(dev, ACTION_LOCK, lockVal.toJSONString(), ok, resp, reqJson, dto.getSource(), user);
+        if (!ok) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "閿佸畾澶辫触"));
+        }
+        applyLockResultToDevice(dev, dto);
+        refreshDevStatus(dev);
+        applyLockResultToDevice(dev, dto);
+        Date now = new Date();
+        dev.setEditDate(now);
+        dev.setLastSyncDate(now);
+        conditionerMapper.updateById(dev);
+        return Objects.equals(dto.getLockPwr(), 0) ? "瑙i攣鎴愬姛" : "閿佸畾鎴愬姛";
+    }
+
+    private JSONObject buildLockSetVal(YwConditionerLockDTO dto) {
+        JSONObject lockVal = new JSONObject();
+        lockVal.put("lock_pwr", dto.getLockPwr() != null ? dto.getLockPwr() : -1);
+        lockVal.put("pwr", dto.getPwr() != null ? dto.getPwr() : -1);
+        lockVal.put("mode", dto.getMode() != null ? dto.getMode() : -1);
+        lockVal.put("fan", dto.getFan() != null ? dto.getFan() : -1);
+        lockVal.put("min_temp", dto.getMinTemp() != null ? dto.getMinTemp() : -1);
+        lockVal.put("max_temp", dto.getMaxTemp() != null ? dto.getMaxTemp() : -1);
+        return lockVal;
+    }
+
+    private void applyLockResultToDevice(YwConditioner dev, YwConditionerLockDTO dto) {
+        dev.setLockPwr(dto.getLockPwr());
+        dev.setLockMode(dto.getMode());
+        dev.setLockFan(dto.getFan());
+        dev.setLockMinTemp(dto.getMinTemp());
+        dev.setLockMaxTemp(dto.getMaxTemp());
+        dev.setKtLock(hasActiveLock(dto) ? 1 : 0);
+    }
+
+    private DevLockControlRequest buildLockManyControlRequest(YwConditioner dev, JSONObject lockVal) {
+        DevLockControlRequest req = new DevLockControlRequest();
+        req.fillSessionDefaults();
+        req.setCtr_type("set_many");
+        DevLockManyControlItem item = new DevLockManyControlItem();
+        item.setWg_mac(dev.getWgMac());
+        item.setWg_qid(dev.getWgQid());
+        item.setPid(StringUtils.defaultIfBlank(dev.getPid(), PID_DLJ));
+        item.setSet_type(LOCK_SET_TYPE);
+        item.setSet_val(lockVal);
+        req.setLi_data(Collections.singletonList(item));
+        return req;
+    }
+
+    private boolean hasActiveLock(YwConditionerLockDTO dto) {
+        return Objects.equals(dto.getLockPwr(), 1)
+                || (dto.getMode() != null && dto.getMode() >= 0)
+                || (dto.getFan() != null && dto.getFan() >= 0)
+                || (dto.getMinTemp() != null && dto.getMinTemp() >= 0)
+                || (dto.getMaxTemp() != null && dto.getMaxTemp() >= 0);
+    }
+
+    /**
+     * 鍚屼竴鍙板唴鏈猴細璁惧畾娓╁害/椋庨��/妯″紡鎿嶄綔闂撮殧涓嶄綆浜� 2 绉�
+     */
+    private void reserveAdjustOperateInterval(Integer deviceId, int actionType) {
+        if (deviceId == null
+                || (actionType != ACTION_MODE && actionType != ACTION_FAN && actionType != ACTION_TEMP)) {
+            return;
+        }
+        long now = System.currentTimeMillis();
+        Long last = LAST_ADJUST_OPERATE_MS.get(deviceId);
+        if (last != null && now - last < ADJUST_OPERATE_INTERVAL_MS) {
+            long waitSec = (ADJUST_OPERATE_INTERVAL_MS - (now - last) + 999) / 1000;
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(),
+                    "鎿嶄綔杩囦簬棰戠箒锛岃" + waitSec + "绉掑悗鍐嶈瘯");
+        }
+        LAST_ADJUST_OPERATE_MS.put(deviceId, now);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String queryMeterEnergy(Integer meterId, LoginUserInfo user) {
+        return queryMeter(meterId, "find_dn", ACTION_QUERY_DL, user);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String queryMeterPower(Integer meterId, LoginUserInfo user) {
+        return queryMeter(meterId, "find_power", ACTION_QUERY_POWER, user);
+    }
+
+    private String queryMeter(Integer meterId, String setType, int actionType, LoginUserInfo user) {
+        YwConditionerMeter meter = meterMapper.selectById(meterId);
+        if (meter == null || Objects.equals(meter.getIsdeleted(), Constants.ONE)) {
+            throw new BusinessException(ResponseStatus.DATA_EMPTY);
+        }
+        ensureLogin();
+        DevControlRequest req = buildMeterQueryRequest(meter, setType);
+        String reqJson = JSON.toJSONString(req);
+        ConditionerBaseResponse<Object> resp = ConditionerUtil.devCtr(req);
+        boolean ok = resp != null && resp.isSuccess();
+        YwConditionerActions action = new YwConditionerActions();
+        action.setActionType(actionType);
+        action.setWgMac(meter.getWgMac());
+        action.setDevName(meter.getDbName());
+        action.setActionContent(setType);
+        action.setRequestBody(reqJson);
+        action.setResponseBody(resp != null ? JSON.toJSONString(resp) : null);
+        action.setResultStatus(ok ? Constants.ONE : Constants.ZERO);
+        action.setResultMsg(ok ? "鎴愬姛" : apiMsg(resp, "澶辫触"));
+        action.setSource("admin");
+        saveAction(action, user);
+        if (!ok) {
+            throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), apiMsg(resp, "鏌ヨ澶辫触"));
+        }
+        if (ACTION_QUERY_DL == actionType && resp.getData() != null) {
+            try {
+                BigDecimal dl = new BigDecimal(String.valueOf(resp.getData()));
+                meter.setTotalDl(dl);
+            } catch (Exception ignored) {
+            }
+        }
+        if (ACTION_QUERY_POWER == actionType && resp.getData() != null) {
+            try {
+                BigDecimal kw = new BigDecimal(String.valueOf(resp.getData()));
+                meter.setPowerKw(kw);
+            } catch (Exception ignored) {
+            }
+        }
+        meter.setLastSyncDate(new Date());
+        meterMapper.updateById(meter);
+        return "鏌ヨ鎴愬姛";
+    }
+
+    @Override
+    public void saveAction(YwConditionerActions action, LoginUserInfo user) {
+        Date now = new Date();
+        if (action.getCreateDate() == null) {
+            action.setCreateDate(now);
+        }
+        if (action.getCreator() == null && user != null) {
+            action.setCreator(user.getId());
+        }
+        action.setIsdeleted(Constants.ZERO);
+        actionsMapper.insert(action);
+    }
+
+    @Override
+    public YwConditionerUsageReportPageDTO queryUsageReport(YwConditionerReportQueryDTO query) {
+        YwConditionerReportQueryDTO q = query != null ? query : new YwConditionerReportQueryDTO();
+        ReportDateRange range = resolveReportDateRange(q);
+        LocalDate start = range.start;
+        LocalDate end = range.end;
+        List<String> dateColumns = new ArrayList<>();
+        DateTimeFormatter colFmt = range.dayMode
+                ? DateTimeFormatter.ofPattern("yyyy-MM-dd")
+                : DateTimeFormatter.ofPattern("M.d");
+        for (LocalDate d = start; !d.isAfter(end); d = d.plusDays(1)) {
+            dateColumns.add(d.format(colFmt));
+        }
+        QueryWrapper<YwConditionerUsage> wrapper = new QueryWrapper<>();
+        wrapper.lambda()
+                .eq(YwConditionerUsage::getIsdeleted, Constants.ZERO)
+                .ge(YwConditionerUsage::getUsageDate, java.sql.Date.valueOf(start))
+                .le(YwConditionerUsage::getUsageDate, java.sql.Date.valueOf(end));
+        if (q.getGsId() != null) {
+            wrapper.lambda().eq(YwConditionerUsage::getGsId, q.getGsId());
+        }
+        if (StringUtils.isNotBlank(q.getDevKeyword())) {
+            wrapper.lambda().and(w -> w.like(YwConditionerUsage::getDevName, q.getDevKeyword())
+                    .or().eq(YwConditionerUsage::getPlatformDevId, parseIntOrNull(q.getDevKeyword())));
+        }
+        List<YwConditionerUsage> rows = usageMapper.selectList(wrapper);
+        Map<Integer, YwConditionerUsageReportVO> grouped = new LinkedHashMap<>();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        for (YwConditionerUsage row : rows) {
+            YwConditionerUsageReportVO vo = grouped.computeIfAbsent(row.getPlatformDevId(), k -> {
+                YwConditionerUsageReportVO n = new YwConditionerUsageReportVO();
+                n.setDevId(row.getPlatformDevId());
+                n.setDevName(row.getDevName());
+                n.setTotalTime(BigDecimal.ZERO);
+                n.setTotalDl(BigDecimal.ZERO);
+                n.setTotalDf(BigDecimal.ZERO);
+                return n;
+            });
+            if (StringUtils.isBlank(vo.getDevName()) && StringUtils.isNotBlank(row.getDevName())) {
+                vo.setDevName(row.getDevName());
+            }
+            String dayKey = sdf.format(row.getUsageDate());
+            YwConditionerUsageReportVO.YwConditionerUsageDailyVO daily = new YwConditionerUsageReportVO.YwConditionerUsageDailyVO();
+            daily.setTime(row.getSumTime());
+            daily.setDl(row.getSumDl());
+            daily.setDf(row.getSumDf());
+            vo.getDaily().put(dayKey, daily);
+            vo.setTotalTime(add(vo.getTotalTime(), row.getSumTime()));
+            vo.setTotalDl(add(vo.getTotalDl(), row.getSumDl()));
+            vo.setTotalDf(add(vo.getTotalDf(), row.getSumDf()));
+        }
+        List<YwConditionerUsageReportVO> all = new ArrayList<>(grouped.values());
+        enrichReportDeviceInfo(all);
+        int page = q.getPage() != null && q.getPage() > 0 ? q.getPage() : 1;
+        int capacity = q.getCapacity() != null && q.getCapacity() > 0 ? q.getCapacity() : 20;
+        int from = (page - 1) * capacity;
+        int to = Math.min(from + capacity, all.size());
+        List<YwConditionerUsageReportVO> pageRecords = from >= all.size()
+                ? Collections.emptyList() : all.subList(from, to);
+
+        YwConditionerUsageReportPageDTO result = new YwConditionerUsageReportPageDTO();
+        result.setDateColumns(dateColumns);
+        result.setRecords(pageRecords);
+        result.setTotal(all.size());
+        result.setPage(page);
+        result.setCapacity(capacity);
+        return result;
+    }
+
+    @Override
+    public List<Map<String, Object>> gatewayOptions() {
+        List<YwConditionerGateway> list = gatewayMapper.selectList(new QueryWrapper<YwConditionerGateway>().lambda()
+                .eq(YwConditionerGateway::getIsdeleted, Constants.ZERO)
+                .orderByAsc(YwConditionerGateway::getWgMac));
+        return list.stream().map(g -> {
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("id", g.getId());
+            m.put("wgMac", g.getWgMac());
+            m.put("label", g.getWgMac() + (StringUtils.isNotBlank(g.getWgBz()) ? " (" + g.getWgBz() + ")" : ""));
+            return m;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public String syncAll() {
+        if (syncing) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鍚屾浠诲姟姝e湪鎵ц锛岃绋嶅悗");
+        }
+        syncing = true;
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(syncGateways("manual")).append("锛�");
+            sb.append(syncMeters()).append("锛�");
+            sb.append(syncBilling()).append("锛�");
+            sb.append(syncIndoorUnits());
+            return sb.toString();
+        } finally {
+            syncing = false;
+        }
+    }
+
+    private void refreshDevStatus(YwConditioner dev) {
+        GetDevOneRequest oneReq = new GetDevOneRequest();
+        oneReq.setWg_mac(dev.getWgMac());
+        oneReq.setWg_qid(dev.getWgQid());
+        oneReq.fillSessionDefaults();
+        ConditionerBaseResponse<DeviceStatusResponse> resp = ConditionerUtil.getDevOne(oneReq);
+        if (resp != null && resp.isSuccess() && resp.getData() != null) {
+            fillConditionerFromStatus(dev, resp.getData(), null, new Date());
+        }
+    }
+
+    private void applyOperateResultToDevice(YwConditioner dev, int actionType, Object setVal) {
+        if (setVal == null) {
+            return;
+        }
+        Integer val = parseIntOrNull(String.valueOf(setVal));
+        if (val == null) {
+            return;
+        }
+        switch (actionType) {
+            case ACTION_PWR:
+                dev.setPwr(val);
+                break;
+            case ACTION_MODE:
+                dev.setMode(val);
+                break;
+            case ACTION_FAN:
+                dev.setFanSet(val);
+                dev.setFan(val);
+                break;
+            case ACTION_TEMP:
+                dev.setTempSet(val <= 50 ? val * 10 : val);
+                break;
+            default:
+                break;
+        }
+    }
+
+    /** 鍐呮満鎺у埗鎿嶄綔鍙鎻忚堪锛屽啓鍏ユ帶鍒跺巻鍙� actionContent */
+    private String formatOperateDescription(int actionType, Object setVal) {
+        Integer val = setVal == null ? null : parseIntOrNull(String.valueOf(setVal));
+        switch (actionType) {
+            case ACTION_PWR:
+                if (val == null) {
+                    return "璁剧疆寮�鍏�";
+                }
+                return String.format("璁剧疆寮�鍏充负銆�%s銆�", val == 1 ? "寮�鏈�" : "鍏虫満");
+            case ACTION_MODE:
+                if (val == null) {
+                    return "璁剧疆妯″紡";
+                }
+                return String.format("璁剧疆妯″紡涓恒��%s銆�", modeLabel(val));
+            case ACTION_FAN:
+                if (val == null) {
+                    return "璁剧疆椋庨��";
+                }
+                return String.format("璁剧疆椋庨�熶负銆�%s銆�", fanLabel(val));
+            case ACTION_TEMP:
+                if (val == null) {
+                    return "璁剧疆娓╁害";
+                }
+                return String.format("璁剧疆娓╁害涓恒��%s銆�", formatTempDisplay(val));
+            default:
+                return setVal == null ? "璁惧鎺у埗" : String.valueOf(setVal);
+        }
+    }
+
+    private String modeLabel(int mode) {
+        switch (mode) {
+            case 1:
+                return "鍒剁儹";
+            case 2:
+                return "鍒跺喎";
+            case 3:
+                return "閫侀";
+            case 4:
+                return "闄ゆ箍";
+            default:
+                return String.valueOf(mode);
+        }
+    }
+
+    private String fanLabel(int fan) {
+        switch (fan) {
+            case 1:
+                return "浣庨��";
+            case 2:
+                return "涓��";
+            case 3:
+                return "楂橀��";
+            case 4:
+                return "鑷姩";
+            default:
+                return String.valueOf(fan);
+        }
+    }
+
+    private String formatTempDisplay(int val) {
+        double celsius = val > 100 ? val / 10.0 : val;
+        if (Math.abs(celsius - Math.rint(celsius)) < 0.05) {
+            return String.format("%.0f鈩�", celsius);
+        }
+        return String.format("%.1f鈩�", celsius);
+    }
+
+    private DevControlRequest buildMeterQueryRequest(YwConditionerMeter meter, String setType) {
+        if (StringUtils.isBlank(meter.getWgMac())) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鐢佃〃鏈粦瀹氱綉鍏筹紝璇峰厛鍚屾鐢佃〃");
+        }
+        if (StringUtils.isBlank(meter.getDbAdr())) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鐢佃〃鍦板潃涓虹┖锛岃鍏堝悓姝ョ數琛�");
+        }
+        if (StringUtils.isBlank(meter.getXyName())) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "鐢佃〃鍗忚涓虹┖锛岃鍏堝悓姝ョ數琛�");
+        }
+        DevControlRequest req = new DevControlRequest();
+        req.fillSessionDefaults();
+        req.setWg_mac(meter.getWgMac());
+        req.setWg_qid(meter.getDbAdr());
+        req.setCtr_type("set_one");
+        req.setSet_type(setType);
+        JSONObject setVal = new JSONObject();
+        setVal.put("db_bb", meter.getDbBb() != null ? meter.getDbBb() : 1);
+        setVal.put("xy_name", meter.getXyName());
+        req.setSet_val(setVal);
+        return req;
+    }
+
+    private DevControlRequest buildDevControl(YwConditioner dev, String setType, Object setVal) {
+        DevControlRequest req = new DevControlRequest();
+        req.fillSessionDefaults();
+        req.setPid(StringUtils.defaultIfBlank(dev.getPid(), PID_DLJ));
+        req.setWg_mac(dev.getWgMac());
+        req.setWg_qid(dev.getWgQid());
+        req.setCtr_type("set_one");
+        req.setSet_type(setType);
+        req.setSet_val(setVal);
+        return req;
+    }
+
+    private void saveActionRecord(YwConditioner dev, int actionType, String content, boolean ok,
+                                  ConditionerBaseResponse<?> resp, String reqJson, String source, LoginUserInfo user) {
+        YwConditionerActions action = new YwConditionerActions();
+        action.setConditionerId(dev.getId());
+        action.setPlatformDevId(dev.getPlatformDevId());
+        action.setDevName(dev.getName());
+        action.setWgMac(dev.getWgMac());
+        action.setActionType(actionType);
+        action.setActionContent(content);
+        action.setRequestBody(reqJson);
+        action.setResponseBody(resp != null ? JSON.toJSONString(resp) : null);
+        action.setResultStatus(ok ? Constants.ONE : Constants.ZERO);
+        action.setResultMsg(ok ? "鎴愬姛" : apiMsg(resp, "澶辫触"));
+        action.setSource(StringUtils.defaultIfBlank(source, "admin"));
+        saveAction(action, user);
+    }
+
+    private void writeGatewayLog(Integer gatewayId, String wgMac, String oldStatus, String newStatus,
+                                 String source, Date now) {
+        YwConditionerGatewayLog log = new YwConditionerGatewayLog();
+        log.setCreator(0);
+        log.setCreateDate(now);
+        log.setIsdeleted(Constants.ZERO);
+        log.setGatewayId(gatewayId);
+        log.setWgMac(wgMac);
+        log.setOldStatus(oldStatus);
+        log.setNewStatus(newStatus);
+        log.setLogTime(now);
+        log.setSource(StringUtils.defaultIfBlank(source, "manual"));
+        gatewayLogMapper.insert(log);
+    }
+
+    private void fillMeter(YwConditionerMeter local, MeterDbInfoResponse item, Date now) {
+        local.setPlatformDbId(item.getDb_id());
+        local.setDbName(item.getDb_name());
+        local.setDbAdr(item.getDb_adr());
+        local.setWgMac(item.getWg_mac());
+        local.setWgId(item.getWg_id());
+        local.setXyId(item.getXy_id());
+        local.setXyName(item.getXy_name());
+        local.setDbBb(item.getDb_bb());
+        local.setOutdoorLoop(item.getDb_rhd());
+        local.setDbUptime(item.getDb_uptime());
+        if (item.getDb_data() != null) {
+            local.setDbData(JSON.toJSONString(item.getDb_data()));
+        }
+        local.setLastSyncDate(now);
+        local.setEditDate(now);
+    }
+
+    private void fillConditionerFromStatus(YwConditioner local, DeviceStatusResponse item,
+                                           DeviceArchiveResponse archive, Date now) {
+        local.setPlatformDevId(item.getDev_id());
+        local.setWgId(item.getWg_id());
+        local.setWgMac(item.getWg_mac());
+        local.setWgQid(item.getWg_qid());
+        local.setPid(StringUtils.defaultIfBlank(item.getPid(), PID_DLJ));
+        local.setOnline(ConditionerConstant.normalizeDeviceOnline(item.getOnline()));
+        local.setPwr(item.getPwr());
+        local.setMode(item.getMode());
+        local.setFan(item.getFan());
+        local.setFanSet(item.getFan_set());
+        local.setTemp(item.getTemp());
+        local.setTempSet(item.getTemp_set());
+        local.setKtLock(item.getKt_lock());
+        local.setStopLogo(item.getStop_logo());
+        local.setUptime(item.getUptime());
+        local.setFloorId(item.getFloor_id());
+        local.setFloorName(item.getFloor_name());
+        local.setRoomId(item.getRoom_id());
+        local.setRoomName(item.getRoom_name());
+        local.setDevTypeId(item.getDev_type_id());
+        local.setDevTypeName(item.getDev_type_name());
+        if (StringUtils.isNotBlank(item.getDev_name())) {
+            local.setName(item.getDev_name());
+        }
+        if (archive != null) {
+            if (StringUtils.isNotBlank(archive.getDev_name())) {
+                local.setName(archive.getDev_name());
+            }
+            if (archive.getFloor_id() != null) {
+                local.setFloorId(archive.getFloor_id());
+            }
+            if (StringUtils.isNotBlank(archive.getFloor_name())) {
+                local.setFloorName(archive.getFloor_name());
+            }
+            if (archive.getRoom_id() != null) {
+                local.setRoomId(archive.getRoom_id());
+            }
+            if (StringUtils.isNotBlank(archive.getRoom_name())) {
+                local.setRoomName(archive.getRoom_name());
+            }
+        }
+        local.setLastSyncDate(now);
+        local.setEditDate(now);
+    }
+
+    private YwConditioner findConditionerByPlatformDevId(Integer platformDevId) {
+        return conditionerMapper.selectOne(new QueryWrapper<YwConditioner>().lambda()
+                .eq(YwConditioner::getIsdeleted, Constants.ZERO)
+                .eq(YwConditioner::getPlatformDevId, platformDevId)
+                .last(" limit 1 "));
+    }
+
+    private YwConditioner requireConditioner(Integer id) {
+        YwConditioner dev = conditionerMapper.selectById(id);
+        if (dev == null || Objects.equals(dev.getIsdeleted(), Constants.ONE)) {
+            throw new BusinessException(ResponseStatus.DATA_EMPTY);
+        }
+        if (StringUtils.isBlank(dev.getWgMac()) || StringUtils.isBlank(dev.getWgQid())) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "璁惧鏈粦瀹氬钩鍙扮綉鍏充俊鎭紝璇峰厛鍚屾鍐呮満");
+        }
+        return dev;
+    }
+
+    private void ensureDeviceOnline(YwConditioner dev) {
+        if (!"鍦ㄧ嚎".equals(ConditionerConstant.normalizeDeviceOnline(dev.getOnline()))) {
+            throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "璁惧绂荤嚎浜�,璇锋鏌ュ搴旇澶囩綉缁�");
+        }
+    }
+
+    private YearMonth parseMonth(String month) {
+        if (StringUtils.isBlank(month)) {
+            return YearMonth.now().minusMonths(1);
+        }
+        try {
+            return YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"));
+        } catch (Exception e) {
+            return YearMonth.now().minusMonths(1);
+        }
+    }
+
+    private void enrichReportDeviceInfo(List<YwConditionerUsageReportVO> records) {
+        if (records == null || records.isEmpty()) {
+            return;
+        }
+        Set<Integer> devIds = records.stream()
+                .map(YwConditionerUsageReportVO::getDevId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (devIds.isEmpty()) {
+            return;
+        }
+        List<YwConditioner> devs = conditionerMapper.selectList(new QueryWrapper<YwConditioner>().lambda()
+                .eq(YwConditioner::getIsdeleted, Constants.ZERO)
+                .in(YwConditioner::getPlatformDevId, devIds));
+        Map<Integer, YwConditioner> devMap = devs.stream()
+                .filter(d -> d.getPlatformDevId() != null)
+                .collect(Collectors.toMap(YwConditioner::getPlatformDevId, d -> d, (a, b) -> a));
+        for (YwConditionerUsageReportVO vo : records) {
+            YwConditioner dev = devMap.get(vo.getDevId());
+            if (dev == null) {
+                continue;
+            }
+            vo.setFloorName(dev.getFloorName());
+            vo.setRoomName(dev.getRoomName());
+            if (StringUtils.isNotBlank(dev.getName())) {
+                vo.setDevName(dev.getName());
+            }
+        }
+    }
+
+    private static class ReportDateRange {
+        LocalDate start;
+        LocalDate end;
+        boolean dayMode;
+    }
+
+    private ReportDateRange resolveReportDateRange(YwConditionerReportQueryDTO q) {
+        String reportType = StringUtils.defaultIfBlank(q.getReportType(), "month");
+        ReportDateRange range = new ReportDateRange();
+        if ("day".equalsIgnoreCase(reportType)) {
+            if (StringUtils.isBlank(q.getStartTime()) || StringUtils.isBlank(q.getEndTime())) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璇烽�夋嫨鏃堕棿娈�");
+            }
+            LocalDate start;
+            LocalDate end;
+            try {
+                start = LocalDate.parse(q.getStartTime());
+                end = LocalDate.parse(q.getEndTime());
+            } catch (Exception e) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏃ユ湡鏍煎紡涓嶆纭�");
+            }
+            if (end.isBefore(start)) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "缁撴潫鏃ユ湡涓嶈兘鏃╀簬寮�濮嬫棩鏈�");
+            }
+            long days = ChronoUnit.DAYS.between(start, end) + 1;
+            if (days > 31) {
+                throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鏃堕棿娈典笉鑳借秴杩�31澶�");
+            }
+            range.start = start;
+            range.end = end;
+            range.dayMode = true;
+            return range;
+        }
+        YearMonth ym = parseMonth(q.getMonth());
+        range.start = ym.atDay(1);
+        range.end = ym.atEndOfMonth();
+        range.dayMode = false;
+        return range;
+    }
+
+    private Date parseDate(String text) {
+        try {
+            if (text.length() > 10) {
+                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(text);
+            }
+            return new SimpleDateFormat("yyyy-MM-dd").parse(text);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private Integer parseIntOrNull(String text) {
+        try {
+            return Integer.parseInt(text.trim());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private BigDecimal add(BigDecimal a, BigDecimal b) {
+        if (a == null) {
+            a = BigDecimal.ZERO;
+        }
+        return b != null ? a.add(b) : a;
+    }
+
+    private String apiMsg(ConditionerBaseResponse<?> resp, String def) {
+        return resp != null && StringUtils.isNotBlank(resp.getMessage()) ? resp.getMessage() : def;
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerConfigService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerConfigService.java
new file mode 100644
index 0000000..5956a4b
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/ConditionerConfigService.java
@@ -0,0 +1,49 @@
+package com.doumee.service.business.impl;
+
+import com.doumee.biz.system.SystemDictDataBiz;
+import com.doumee.core.conditoner.model.ConditionerConstant;
+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;
+
+/**
+ * 浠庢暟鎹瓧鍏� CONDITIONER_PARAM 鍔犺浇鏅虹簿鐏靛钩鍙拌繛鎺ュ弬鏁般��
+ */
+@Slf4j
+@Service
+public class ConditionerConfigService {
+
+    @Autowired
+    private SystemDictDataBiz systemDictDataBiz;
+
+    @PostConstruct
+    public void init() {
+        refreshFromDict();
+    }
+
+    public void refreshFromDict() {
+        ConditionerConstant.base_url = read(Constants.CONDITIONER_BASE_URL, ConditionerConstant.DEFAULT_BASE_URL);
+        ConditionerConstant.username = read(Constants.CONDITIONER_USERNAME, ConditionerConstant.DEFAULT_USERNAME);
+        ConditionerConstant.password = read(Constants.CONDITIONER_PASSWORD, ConditionerConstant.DEFAULT_PASSWORD);
+        log.info("conditioner config loaded from dict {}, base_url={}",
+                Constants.CONDITIONER_PARAM, ConditionerConstant.base_url);
+    }
+
+    private String read(String label, String defaultValue) {
+        try {
+            SystemDictData data = systemDictDataBiz.queryByCode(Constants.CONDITIONER_PARAM, label);
+            if (data != null && StringUtils.isNotBlank(data.getCode())) {
+                return data.getCode().trim();
+            }
+            log.warn("conditioner config [{}] empty, use default", label);
+        } catch (Exception e) {
+            log.warn("conditioner config [{}] load failed, use default", label, e);
+        }
+        return defaultValue;
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerActionsServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerActionsServiceImpl.java
new file mode 100644
index 0000000..e5dba46
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerActionsServiceImpl.java
@@ -0,0 +1,48 @@
+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.YwConditionerActionsMapper;
+import com.doumee.dao.business.model.YwConditioner;
+import com.doumee.dao.business.model.YwConditionerActions;
+import com.doumee.service.business.YwConditionerActionsService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class YwConditionerActionsServiceImpl implements YwConditionerActionsService {
+
+    @Autowired
+    private YwConditionerActionsMapper actionsMapper;
+
+    @Override
+    public PageData<YwConditionerActions> findPage(PageWrap<YwConditionerActions> pageWrap) {
+        IPage<YwConditionerActions> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        YwConditionerActions model = pageWrap.getModel() == null ? new YwConditionerActions() : pageWrap.getModel();
+        Utils.MP.blankToNull(model);
+        MPJLambdaWrapper<YwConditionerActions> qw = new MPJLambdaWrapper<>();
+        qw.selectAll(YwConditionerActions.class)
+                .selectAs(YwConditioner::getOnline, YwConditionerActions::getOnlineFilter)
+                .leftJoin(YwConditioner.class, YwConditioner::getId, YwConditionerActions::getConditionerId)
+                .eq(YwConditionerActions::getIsdeleted, Constants.ZERO)
+                .eq(model.getConditionerId() != null, YwConditionerActions::getConditionerId, model.getConditionerId())
+                .eq(model.getActionType() != null, YwConditionerActions::getActionType, model.getActionType())
+                .eq(StringUtils.isNotBlank(model.getWgMac()), YwConditionerActions::getWgMac, model.getWgMac())
+                .eq(StringUtils.isNotBlank(model.getOnlineFilter()), YwConditioner::getOnline, model.getOnlineFilter())
+                .and(StringUtils.isNotBlank(model.getDevKeyword()), w -> w
+                        .like(YwConditionerActions::getDevName, model.getDevKeyword())
+                        .or().like(YwConditionerActions::getWgMac, model.getDevKeyword()))
+                .ge(model.getOperateTimeBegin() != null, YwConditionerActions::getCreateDate,
+                        model.getOperateTimeBegin() != null ? Utils.Date.getStart(model.getOperateTimeBegin()) : null)
+                .le(model.getOperateTimeEnd() != null, YwConditionerActions::getCreateDate,
+                        model.getOperateTimeEnd() != null ? Utils.Date.getEnd(model.getOperateTimeEnd()) : null)
+                .orderByDesc(YwConditionerActions::getId);
+        return PageData.from(actionsMapper.selectJoinPage(page, YwConditionerActions.class, qw));
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerBillingServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerBillingServiceImpl.java
new file mode 100644
index 0000000..171bab6
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerBillingServiceImpl.java
@@ -0,0 +1,45 @@
+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.YwConditionerBillingMapper;
+import com.doumee.dao.business.model.YwConditionerBilling;
+import com.doumee.service.business.ConditionerBizService;
+import com.doumee.service.business.YwConditionerBillingService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class YwConditionerBillingServiceImpl implements YwConditionerBillingService {
+
+    @Autowired
+    private YwConditionerBillingMapper billingMapper;
+    @Autowired
+    private ConditionerBizService conditionerBizService;
+
+    @Override
+    public PageData<YwConditionerBilling> findPage(PageWrap<YwConditionerBilling> pageWrap) {
+        IPage<YwConditionerBilling> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        YwConditionerBilling model = pageWrap.getModel() == null ? new YwConditionerBilling() : pageWrap.getModel();
+        Utils.MP.blankToNull(model);
+        MPJLambdaWrapper<YwConditionerBilling> qw = new MPJLambdaWrapper<>();
+        qw.selectAll(YwConditionerBilling.class)
+                .eq(YwConditionerBilling::getIsdeleted, Constants.ZERO)
+                .and(StringUtils.isNotBlank(model.getDevKeyword()), w -> w
+                        .like(YwConditionerBilling::getDevName, model.getDevKeyword())
+                        .or().eq(YwConditionerBilling::getPlatformDevId, model.getDevKeyword()))
+                .orderByDesc(YwConditionerBilling::getId);
+        return PageData.from(billingMapper.selectJoinPage(page, YwConditionerBilling.class, qw));
+    }
+
+    @Override
+    public String syncAll() {
+        return conditionerBizService.syncBilling();
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerGatewayServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerGatewayServiceImpl.java
new file mode 100644
index 0000000..c37588a
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerGatewayServiceImpl.java
@@ -0,0 +1,66 @@
+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.YwConditionerGatewayLogMapper;
+import com.doumee.dao.business.YwConditionerGatewayMapper;
+import com.doumee.dao.business.model.YwConditionerGateway;
+import com.doumee.dao.business.model.YwConditionerGatewayLog;
+import com.doumee.service.business.ConditionerBizService;
+import com.doumee.service.business.YwConditionerGatewayService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class YwConditionerGatewayServiceImpl implements YwConditionerGatewayService {
+
+    @Autowired
+    private YwConditionerGatewayMapper gatewayMapper;
+    @Autowired
+    private YwConditionerGatewayLogMapper gatewayLogMapper;
+    @Autowired
+    private ConditionerBizService conditionerBizService;
+
+    @Override
+    public PageData<YwConditionerGateway> findPage(PageWrap<YwConditionerGateway> pageWrap) {
+        IPage<YwConditionerGateway> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        YwConditionerGateway model = pageWrap.getModel() == null ? new YwConditionerGateway() : pageWrap.getModel();
+        Utils.MP.blankToNull(model);
+        MPJLambdaWrapper<YwConditionerGateway> qw = new MPJLambdaWrapper<>();
+        qw.selectAll(YwConditionerGateway.class)
+                .eq(YwConditionerGateway::getIsdeleted, Constants.ZERO)
+                .eq(StringUtils.isNotBlank(model.getOnlineStatus()), YwConditionerGateway::getOnlineStatus, model.getOnlineStatus())
+                .and(StringUtils.isNotBlank(model.getKeyword()), w -> w
+                        .like(YwConditionerGateway::getWgMac, model.getKeyword())
+                        .or().like(YwConditionerGateway::getWgBz, model.getKeyword()))
+                .orderByDesc(YwConditionerGateway::getId);
+        return PageData.from(gatewayMapper.selectJoinPage(page, YwConditionerGateway.class, qw));
+    }
+
+    @Override
+    public PageData<YwConditionerGatewayLog> gatewayLogPage(PageWrap<YwConditionerGatewayLog> pageWrap) {
+        IPage<YwConditionerGatewayLog> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        YwConditionerGatewayLog model = pageWrap.getModel() == null ? new YwConditionerGatewayLog() : pageWrap.getModel();
+        Utils.MP.blankToNull(model);
+        MPJLambdaWrapper<YwConditionerGatewayLog> qw = new MPJLambdaWrapper<>();
+        qw.selectAll(YwConditionerGatewayLog.class)
+                .eq(YwConditionerGatewayLog::getIsdeleted, Constants.ZERO)
+                .eq(model.getGatewayId() != null, YwConditionerGatewayLog::getGatewayId, model.getGatewayId())
+                .eq(StringUtils.isNotBlank(model.getWgMac()), YwConditionerGatewayLog::getWgMac, model.getWgMac())
+                .ge(model.getLogTimeBegin() != null, YwConditionerGatewayLog::getLogTime, model.getLogTimeBegin())
+                .le(model.getLogTimeEnd() != null, YwConditionerGatewayLog::getLogTime, model.getLogTimeEnd())
+                .orderByDesc(YwConditionerGatewayLog::getLogTime);
+        return PageData.from(gatewayLogMapper.selectJoinPage(page, YwConditionerGatewayLog.class, qw));
+    }
+
+    @Override
+    public String syncAll() {
+        return conditionerBizService.syncGateways("manual");
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerMeterServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerMeterServiceImpl.java
new file mode 100644
index 0000000..63c9270
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerMeterServiceImpl.java
@@ -0,0 +1,58 @@
+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.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.YwConditionerMeterMapper;
+import com.doumee.dao.business.model.YwConditionerMeter;
+import com.doumee.service.business.ConditionerBizService;
+import com.doumee.service.business.YwConditionerMeterService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class YwConditionerMeterServiceImpl implements YwConditionerMeterService {
+
+    @Autowired
+    private YwConditionerMeterMapper meterMapper;
+    @Autowired
+    private ConditionerBizService conditionerBizService;
+
+    @Override
+    public PageData<YwConditionerMeter> findPage(PageWrap<YwConditionerMeter> pageWrap) {
+        IPage<YwConditionerMeter> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        YwConditionerMeter model = pageWrap.getModel() == null ? new YwConditionerMeter() : pageWrap.getModel();
+        Utils.MP.blankToNull(model);
+        MPJLambdaWrapper<YwConditionerMeter> qw = new MPJLambdaWrapper<>();
+        qw.selectAll(YwConditionerMeter.class)
+                .eq(YwConditionerMeter::getIsdeleted, Constants.ZERO)
+                .eq(StringUtils.isNotBlank(model.getWgMacFilter()), YwConditionerMeter::getWgMac, model.getWgMacFilter())
+                .and(StringUtils.isNotBlank(model.getKeyword()), w -> w
+                        .like(YwConditionerMeter::getDbName, model.getKeyword())
+                        .or().like(YwConditionerMeter::getDbAdr, model.getKeyword())
+                        .or().like(YwConditionerMeter::getWgMac, model.getKeyword()))
+                .orderByDesc(YwConditionerMeter::getId);
+        return PageData.from(meterMapper.selectJoinPage(page, YwConditionerMeter.class, qw));
+    }
+
+    @Override
+    public String syncAll() {
+        return conditionerBizService.syncMeters();
+    }
+
+    @Override
+    public String queryEnergy(Integer meterId, LoginUserInfo user) {
+        return conditionerBizService.queryMeterEnergy(meterId, user);
+    }
+
+    @Override
+    public String queryPower(Integer meterId, LoginUserInfo user) {
+        return conditionerBizService.queryMeterPower(meterId, user);
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerReportServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerReportServiceImpl.java
new file mode 100644
index 0000000..243ae40
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerReportServiceImpl.java
@@ -0,0 +1,155 @@
+package com.doumee.service.business.impl;
+
+import com.doumee.core.conditoner.ConditionerUtil;
+import com.doumee.core.conditoner.model.request.CompanyGsManageRequest;
+import com.doumee.core.conditoner.model.response.CompanyGsInfoResponse;
+import com.doumee.core.conditoner.model.response.ConditionerBaseResponse;
+import com.doumee.dao.business.dto.YwConditionerReportQueryDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportPageDTO;
+import com.doumee.dao.business.dto.YwConditionerUsageReportVO;
+import com.doumee.service.business.ConditionerBizService;
+import com.doumee.service.business.YwConditionerReportService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service
+public class YwConditionerReportServiceImpl implements YwConditionerReportService {
+
+    @Autowired
+    private ConditionerBizService conditionerBizService;
+
+    @Override
+    public YwConditionerUsageReportPageDTO findPage(YwConditionerReportQueryDTO query) {
+        return conditionerBizService.queryUsageReport(query);
+    }
+
+    @Override
+    public String syncUsage(YwConditionerReportQueryDTO query) {
+        return conditionerBizService.syncUsage(query);
+    }
+
+    @Override
+    public List<Map<String, Object>> merchantOptions() {
+        conditionerBizService.ensureLogin();
+        CompanyGsManageRequest req = new CompanyGsManageRequest();
+        ConditionerBaseResponse<List<CompanyGsInfoResponse>> resp = ConditionerUtil.getGs(req);
+        if (resp == null || resp.getData() == null) {
+            return Collections.emptyList();
+        }
+        return resp.getData().stream().map(gs -> {
+            Map<String, Object> m = new LinkedHashMap<>();
+            m.put("gsId", gs.getId());
+            m.put("gsName", gs.getGs_name());
+            return m;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public void exportExcel(YwConditionerReportQueryDTO query, HttpServletResponse response) {
+        if (query == null) {
+            query = new YwConditionerReportQueryDTO();
+        }
+        query.setPage(1);
+        query.setCapacity(1_000_000);
+        YwConditionerUsageReportPageDTO page = findPage(query);
+        try {
+            String fileName = URLEncoder.encode("绌鸿皟鐢ㄩ噺鎶ヨ〃.csv", StandardCharsets.UTF_8.name());
+            response.setContentType("text/csv;charset=UTF-8");
+            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+            PrintWriter writer = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8));
+            writer.write('\ufeff');
+            List<String> cols = page.getDateColumns() != null ? page.getDateColumns() : Collections.emptyList();
+            StringBuilder header = new StringBuilder("璁惧ID,璁惧鍚嶇О,鎬绘椂闀�(h),鎬荤數閲�(kWh),鎬荤數璐�(鍏�)");
+            for (String col : cols) {
+                header.append(',').append(col).append("鐢甸噺(kWh),")
+                        .append(col).append("鏃堕暱(h),")
+                        .append(col).append("鐢佃垂(鍏�)");
+            }
+            writer.println(header);
+            if (page.getRecords() != null) {
+                for (YwConditionerUsageReportVO row : page.getRecords()) {
+                    StringBuilder line = new StringBuilder();
+                    line.append(csv(row.getDevId())).append(',')
+                            .append(csv(formatDevDisplayName(row))).append(',')
+                            .append(csv(row.getTotalTime())).append(',')
+                            .append(csv(row.getTotalDl())).append(',')
+                            .append(csv(row.getTotalDf()));
+                    for (String col : cols) {
+                        String dateKey = colToDateKey(query, col);
+                        YwConditionerUsageReportVO.YwConditionerUsageDailyVO day =
+                                row.getDaily() != null && dateKey != null ? row.getDaily().get(dateKey) : null;
+                        line.append(',').append(csv(day != null ? day.getDl() : ""))
+                                .append(',').append(csv(day != null ? day.getTime() : ""))
+                                .append(',').append(csv(day != null ? day.getDf() : ""));
+                    }
+                    writer.println(line);
+                }
+            }
+            writer.flush();
+        } catch (Exception e) {
+            throw new RuntimeException("瀵煎嚭澶辫触", e);
+        }
+    }
+
+    private static String csv(Object val) {
+        if (val == null) {
+            return "";
+        }
+        String s = String.valueOf(val).replace("\"", "\"\"");
+        if (s.contains(",") || s.contains("\"") || s.contains("\n")) {
+            return "\"" + s + "\"";
+        }
+        return s;
+    }
+
+    private static String formatDevDisplayName(YwConditionerUsageReportVO row) {
+        if (row == null) {
+            return "-";
+        }
+        StringBuilder sb = new StringBuilder();
+        appendDisplayPart(sb, row.getFloorName());
+        appendDisplayPart(sb, row.getRoomName());
+        appendDisplayPart(sb, row.getDevName());
+        return sb.length() == 0 ? "-" : sb.toString();
+    }
+
+    private static void appendDisplayPart(StringBuilder sb, String part) {
+        if (StringUtils.isBlank(part)) {
+            return;
+        }
+        if (sb.length() > 0) {
+            sb.append('/');
+        }
+        sb.append(part.trim());
+    }
+
+    private String colToDateKey(YwConditionerReportQueryDTO query, String col) {
+        if (query != null && "day".equalsIgnoreCase(query.getReportType())) {
+            return col;
+        }
+        String month = query != null ? query.getMonth() : null;
+        if (month == null || col == null || !col.contains(".")) {
+            return col;
+        }
+        String[] parts = col.split("\\.");
+        if (parts.length != 2) {
+            return col;
+        }
+        String year = month.split("-")[0];
+        int m = Integer.parseInt(parts[0]);
+        int d = Integer.parseInt(parts[1]);
+        return String.format("%s-%02d-%02d", year, m, d);
+    }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerServiceImpl.java
index 3f563dc..8c74504 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwConditionerServiceImpl.java
@@ -8,8 +8,15 @@
 import com.doumee.core.utils.Constants;
 import com.doumee.core.utils.DateUtil;
 import com.doumee.core.utils.Utils;
+import com.doumee.dao.business.dto.YwConditionerLockDTO;
+import com.doumee.dao.business.dto.YwConditionerOperateDTO;
+import com.doumee.dao.business.YwConditionerGatewayMapper;
 import com.doumee.dao.business.YwConditionerMapper;
 import com.doumee.dao.business.model.YwConditioner;
+import com.doumee.dao.business.model.YwConditionerActions;
+import com.doumee.dao.business.model.YwConditionerGateway;
+import com.doumee.service.business.ConditionerBizService;
+import com.doumee.service.business.YwConditionerActionsService;
 import com.doumee.service.business.YwConditionerService;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
@@ -22,8 +29,11 @@
 import org.springframework.util.CollectionUtils;
 
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * 绌鸿皟璁惧淇℃伅Service瀹炵幇
@@ -35,6 +45,12 @@
 
     @Autowired
     private YwConditionerMapper ywConditionerMapper;
+    @Autowired
+    private YwConditionerGatewayMapper gatewayMapper;
+    @Autowired
+    private ConditionerBizService conditionerBizService;
+    @Autowired
+    private YwConditionerActionsService ywConditionerActionsService;
 
     @Override
     public Integer create(YwConditioner ywConditioner) {
@@ -125,4 +141,115 @@
         IPage<YwConditioner> iPage = ywConditionerMapper.selectJoinPage(page, YwConditioner.class, queryWrapper);
         return PageData.from(iPage);
     }
+
+    @Override
+    public PageData<YwConditioner> findCardPage(PageWrap<YwConditioner> pageWrap) {
+        IPage<YwConditioner> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+        Utils.MP.blankToNull(pageWrap.getModel());
+        YwConditioner model = pageWrap.getModel() == null ? new YwConditioner() : pageWrap.getModel();
+        MPJLambdaWrapper<YwConditioner> queryWrapper = new MPJLambdaWrapper<>();
+        queryWrapper.selectAll(YwConditioner.class)
+                .eq(YwConditioner::getIsdeleted, Constants.ZERO)
+                .isNotNull(YwConditioner::getPlatformDevId)
+                .and(StringUtils.isNotBlank(model.getDevKeyword()), w -> w
+                        .like(YwConditioner::getName, model.getDevKeyword())
+                        .or().like(YwConditioner::getCode, model.getDevKeyword())
+                        .or().like(YwConditioner::getRoomName, model.getDevKeyword()))
+                .eq(StringUtils.isNotBlank(model.getOnlineFilter()), YwConditioner::getOnline, model.getOnlineFilter())
+                .eq(model.getPwrFilter() != null, YwConditioner::getPwr, model.getPwrFilter())
+                .eq(StringUtils.isNotBlank(model.getWgMacFilter()), YwConditioner::getWgMac, model.getWgMacFilter())
+                .orderByAsc(YwConditioner::getWgQid)
+                .orderByAsc(YwConditioner::getId);
+        PageData<YwConditioner> pageData = PageData.from(
+                ywConditionerMapper.selectJoinPage(page, YwConditioner.class, queryWrapper));
+        fillGatewayBzFromGateway(pageData.getRecords());
+        return pageData;
+    }
+
+    /**
+     * 浠庢湰鍦扮綉鍏抽暅鍍忚〃 yw_conditioner_gateway 鍏宠仈濉厖澶囨敞锛堜紭鍏堝钩鍙扮綉鍏矷D锛屽叾娆� MAC锛�
+     */
+    private void fillGatewayBzFromGateway(List<YwConditioner> records) {
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        List<String> macs = records.stream()
+                .map(YwConditioner::getWgMac)
+                .filter(StringUtils::isNotBlank)
+                .distinct()
+                .collect(Collectors.toList());
+        List<Integer> platformWgIds = records.stream()
+                .map(YwConditioner::getWgId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (macs.isEmpty() && platformWgIds.isEmpty()) {
+            return;
+        }
+        QueryWrapper<YwConditionerGateway> gwQuery = new QueryWrapper<>();
+        gwQuery.lambda().eq(YwConditionerGateway::getIsdeleted, Constants.ZERO);
+        if (!macs.isEmpty() && !platformWgIds.isEmpty()) {
+            gwQuery.lambda().and(w -> w.in(YwConditionerGateway::getWgMac, macs)
+                    .or().in(YwConditionerGateway::getPlatformWgId, platformWgIds));
+        } else if (!macs.isEmpty()) {
+            gwQuery.lambda().in(YwConditionerGateway::getWgMac, macs);
+        } else {
+            gwQuery.lambda().in(YwConditionerGateway::getPlatformWgId, platformWgIds);
+        }
+        List<YwConditionerGateway> gateways = gatewayMapper.selectList(gwQuery);
+        Map<String, String> bzByMac = new HashMap<>();
+        Map<Integer, String> bzByPlatformWgId = new HashMap<>();
+        for (YwConditionerGateway gw : gateways) {
+            if (StringUtils.isNotBlank(gw.getWgBz())) {
+                if (StringUtils.isNotBlank(gw.getWgMac())) {
+                    bzByMac.putIfAbsent(gw.getWgMac().trim(), gw.getWgBz());
+                }
+                if (gw.getPlatformWgId() != null) {
+                    bzByPlatformWgId.putIfAbsent(gw.getPlatformWgId(), gw.getWgBz());
+                }
+            }
+        }
+        for (YwConditioner row : records) {
+            String bz = null;
+            if (StringUtils.isNotBlank(row.getWgMac())) {
+                bz = bzByMac.get(row.getWgMac().trim());
+            }
+            if (StringUtils.isBlank(bz) && row.getWgId() != null) {
+                bz = bzByPlatformWgId.get(row.getWgId());
+            }
+            if (StringUtils.isNotBlank(bz)) {
+                row.setWgBz(bz);
+            }
+        }
+    }
+
+    @Override
+    public String syncAll() {
+        return conditionerBizService.syncAll();
+    }
+
+    @Override
+    public String syncDevicesAndStatus() {
+        return conditionerBizService.syncIndoorUnits();
+    }
+
+    @Override
+    public String operate(YwConditionerOperateDTO dto, LoginUserInfo user) {
+        return conditionerBizService.operate(dto, user);
+    }
+
+    @Override
+    public String lock(YwConditionerLockDTO dto, LoginUserInfo user) {
+        return conditionerBizService.lock(dto, user);
+    }
+
+    @Override
+    public PageData<YwConditionerActions> historyPage(PageWrap<YwConditionerActions> pageWrap) {
+        return ywConditionerActionsService.findPage(pageWrap);
+    }
+
+    @Override
+    public List<Map<String, Object>> gatewayOptions() {
+        return conditionerBizService.gatewayOptions();
+    }
 }

--
Gitblit v1.9.3