From 4e59754839c5e78128730f97af2136c3f5e0e947 Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期三, 27 五月 2026 17:18:20 +0800
Subject: [PATCH] 新增智能电表、空调管理
---
admin/src/views/business/components/YwCustomerElectricalRechargePanel.vue | 134 ++++++
admin/src/views/business/components/YwCustomerConditionerRechargePanel.vue | 106 ++++
admin/src/views/business/ywcustomerrechargerecord.vue | 169 +++++++
admin/src/views/business/components/YwCustomerRechargeWindow.vue | 48 ++
admin/src/views/business/components/YwCustomerConditionerTab.vue | 292 +++++++++++++
admin/src/views/business/components/YwCustomerDeviceWindow.vue | 97 ++++
admin/src/views/business/components/YwCustomerElectricalTab.vue | 175 +++++++
admin/src/api/business/ywcustomerrecharge.js | 83 +++
admin/src/views/business/ywcustomerrecharge.vue | 170 +++++++
9 files changed, 1,274 insertions(+), 0 deletions(-)
diff --git a/admin/src/api/business/ywcustomerrecharge.js b/admin/src/api/business/ywcustomerrecharge.js
new file mode 100644
index 0000000..82a9a6f
--- /dev/null
+++ b/admin/src/api/business/ywcustomerrecharge.js
@@ -0,0 +1,83 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywCustomerRecharge'
+
+export function merchantPage (data) {
+ return request.post(base + '/merchantPage', data, { trim: true })
+}
+
+export function getDetail (customerId) {
+ return request.get(base + '/' + customerId + '/detail')
+}
+
+export function electricalPage (customerId, data) {
+ return request.post(base + '/electrical/page?customerId=' + customerId, data, { trim: true })
+}
+
+export function selectableElectricalPage (customerId, data) {
+ return request.post(base + '/electrical/selectablePage?customerId=' + customerId, data, { trim: true })
+}
+
+export function saveElectrical (data) {
+ return request.post(base + '/electrical/save', data)
+}
+
+export function deleteElectrical (customerId, electricalId) {
+ return request.get(base + '/electrical/delete?customerId=' + customerId + '&electricalId=' + electricalId)
+}
+
+export function conditionerPage (customerId, data) {
+ return request.post(base + '/conditioner/page?customerId=' + customerId, data, { trim: true })
+}
+
+export function getGsConfig (customerId) {
+ return request.get(base + '/conditioner/gsConfig?customerId=' + customerId)
+}
+
+export function saveGsConfig (data) {
+ return request.post(base + '/conditioner/saveGsConfig', data)
+}
+
+export function rechargeElectrical (data) {
+ return request.post(base + '/recharge/electrical', data)
+}
+
+export function resetElectrical (data) {
+ return request.post(base + '/reset/electrical', data)
+}
+
+export function readMeter (customerId, electricalId) {
+ return request.get(base + '/readMeter?customerId=' + customerId + '&electricalId=' + electricalId)
+}
+
+export function getElectricalRemoteInfo (electricalId) {
+ return request.get(base + '/electrical/remoteInfo?electricalId=' + electricalId)
+}
+
+export function getConditionerRechargeInfo (customerId) {
+ return request.get(base + '/recharge/conditioner/info?customerId=' + customerId)
+}
+
+export function rechargeConditioner (data) {
+ return request.post(base + '/recharge/conditioner', data)
+}
+
+export function cleanConditioner (customerId) {
+ return request.post(base + '/clean/conditioner?customerId=' + customerId)
+}
+
+export function rechargeRecordPage (data) {
+ return request.post(base + '/rechargeRecord/page', data, { trim: true })
+}
+
+export function retryRecharge (id) {
+ return request.post(base + '/rechargeRecord/retry/' + id)
+}
+
+export function syncRechargeStatus (id) {
+ return request.post(base + '/rechargeRecord/sync/' + id)
+}
+
+export function exportRechargeRecord (data) {
+ return request.post(base + '/rechargeRecord/exportExcel', data, { responseType: 'blob', trim: true })
+}
diff --git a/admin/src/views/business/components/YwCustomerConditionerRechargePanel.vue b/admin/src/views/business/components/YwCustomerConditionerRechargePanel.vue
new file mode 100644
index 0000000..dc8fe75
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerConditionerRechargePanel.vue
@@ -0,0 +1,106 @@
+<template>
+ <div v-loading="loading">
+ <div class="info-block">
+ <p>瀹㈡埛鍚嶇О锛歿{ customer.name }}</p>
+ <p>鍓╀綑閲戦(鍏�)锛歿{ leftMoney }}</p>
+ <p>浣欓鍚屾鏃堕棿锛歿{ syncDate || '-' }}</p>
+ <p v-if="platformInfo">鎬荤數閲忥細{{ platformInfo.left_money_y != null ? platformInfo.left_money_y : '-' }}</p>
+ </div>
+ <el-form label-width="120px" size="small">
+ <el-form-item label="鍏呭�奸噾棰�">
+ <el-input-number v-model="form.money" :min="0" :precision="2" style="width: 200px"/>
+ </el-form-item>
+ <el-form-item label="鍏呭�煎娉�">
+ <el-input v-model="form.remark" maxlength="300" style="width: 400px"/>
+ </el-form-item>
+ </el-form>
+ <div class="footer-btns">
+ <el-button type="primary" :loading="isOperating" v-permissions="['business:ywcustomerrecharge:recharge']" @click="confirmRecharge">纭鍏呭��</el-button>
+ <el-button type="warning" plain :loading="isOperating" v-permissions="['business:ywcustomerrecharge:recharge']" @click="confirmClean">璐︽埛娓呴浂</el-button>
+ <el-button @click="loadInfo">鍒锋柊浣欓</el-button>
+ </div>
+ </div>
+</template>
+
+<script>
+import * as api from '@/api/business/ywcustomerrecharge'
+
+export default {
+ name: 'YwCustomerConditionerRechargePanel',
+ props: {
+ customer: { type: Object, default: () => ({}) }
+ },
+ data () {
+ return {
+ loading: false,
+ isOperating: false,
+ gsConfig: null,
+ platformInfo: null,
+ form: { money: 0, remark: '' }
+ }
+ },
+ computed: {
+ leftMoney () {
+ if (this.gsConfig && this.gsConfig.leftMoney != null) return this.gsConfig.leftMoney
+ return '-'
+ },
+ syncDate () {
+ return this.gsConfig && this.gsConfig.syncDate
+ }
+ },
+ mounted () {
+ this.loadInfo()
+ },
+ methods: {
+ loadInfo () {
+ this.loading = true
+ api.getConditionerRechargeInfo(this.customer.id)
+ .then(res => {
+ this.gsConfig = res.gsConfig || null
+ this.platformInfo = res.platformInfo || null
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.loading = false })
+ },
+ confirmRecharge () {
+ this.$dialog.actionConfirm('纭鍏呭�煎悧锛�', '鎿嶄綔纭')
+ .then(() => {
+ this.isOperating = true
+ return api.rechargeConditioner({
+ customerId: this.customer.id,
+ money: this.form.money,
+ remark: this.form.remark
+ })
+ })
+ .then(msg => {
+ this.$tip.success(msg || '鍏呭�兼垚鍔�')
+ this.form.money = 0
+ this.loadInfo()
+ this.$emit('success')
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ .finally(() => { this.isOperating = false })
+ },
+ confirmClean () {
+ this.$dialog.actionConfirm('纭娓呴浂绌鸿皟璐︽埛鍚楋紵', '鎿嶄綔纭')
+ .then(() => {
+ this.isOperating = true
+ return api.cleanConditioner(this.customer.id)
+ })
+ .then(msg => {
+ this.$tip.success(msg || '娓呴浂鎴愬姛')
+ this.loadInfo()
+ this.$emit('success')
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ .finally(() => { this.isOperating = false })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.info-block p { margin: 4px 0; line-height: 26px; }
+.footer-btns { text-align: right; margin-top: 16px; }
+.footer-btns .el-button { margin-left: 8px; }
+</style>
diff --git a/admin/src/views/business/components/YwCustomerConditionerTab.vue b/admin/src/views/business/components/YwCustomerConditionerTab.vue
new file mode 100644
index 0000000..e731bc2
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerConditionerTab.vue
@@ -0,0 +1,292 @@
+<template>
+ <div v-loading="loading" class="conditioner-tab">
+ <section class="config-section">
+ <div class="section-title">璁¤垂閰嶇疆</div>
+ <el-form label-width="150px" size="small" class="config-form">
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="璁¤垂寮�鍏�">
+ <el-switch v-model="form.isPwr" :active-value="1" :inactive-value="0" active-text="寮�鍚�" inactive-text="鍏抽棴"/>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="18:00-09:00 涓嶅仠鏈�">
+ <el-switch v-model="form.isRestStop" :active-value="1" :inactive-value="0" active-text="鏄�" inactive-text="鍚�"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="24">
+ <el-col :span="12">
+ <el-form-item label="娆犺垂棰濆害锛堝厓锛�">
+ <el-input-number v-model="form.stopMoney" :min="0" :precision="2" :step="10" controls-position="right" style="width: 100%"/>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="澶囨敞" class="remark-item">
+ <el-input
+ v-model="form.gsBz"
+ type="textarea"
+ :rows="2"
+ maxlength="500"
+ show-word-limit
+ placeholder="璇疯緭鍏ュ娉紙閫夊~锛�"
+ />
+ </el-form-item>
+ </el-form>
+ </section>
+
+ <section class="config-section">
+ <div class="section-header">
+ <span class="section-title">鍏宠仈鍐呮満</span>
+ <el-button type="primary" size="small" icon="el-icon-plus" @click="openSelector">娣诲姞鍐呮満</el-button>
+ </div>
+ <el-table :data="form.conditioners" stripe size="small" class="device-table" empty-text="鏆傛湭鍏宠仈鍐呮満锛岃鐐瑰嚮娣诲姞">
+ <el-table-column label="璁惧" min-width="200" align="left" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ deviceLabel(row) }}</template>
+ </el-table-column>
+ <el-table-column prop="platformDevId" label="骞冲彴璁惧ID" min-width="110" align="center"/>
+ <el-table-column label="鍦ㄧ嚎" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span :class="row.online === '鍦ㄧ嚎' ? 'green' : 'red'">{{ row.online || '-' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="鐢佃垂鍗犳瘮%" min-width="130" align="center">
+ <template slot-scope="{ row }">
+ <el-input-number v-model="row.devRatio" :min="1" :max="100" size="small" controls-position="right"/>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
+ <template slot-scope="{ $index }">
+ <el-button type="text" class="red" @click="form.conditioners.splice($index, 1)">绉婚櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </section>
+
+ <div class="footer-btns">
+ <el-button type="primary" :loading="saving" v-permissions="['business:ywcustomerrecharge:bindDevice']" @click="save">淇濆瓨閰嶇疆</el-button>
+ </div>
+
+ <GlobalWindow title="閫夋嫨绌鸿皟鍐呮満" :visible.sync="selectorVisible" width="780px" @confirm="confirmSelect">
+ <el-form inline @submit.native.prevent class="selector-form">
+ <el-form-item label="鍏抽敭瀛�">
+ <el-input v-model="selectorKeyword" placeholder="鍚嶇О/缂栧彿" clearable @keypress.enter.native="searchDevices"/>
+ </el-form-item>
+ <el-button type="primary" @click="searchDevices">鏌ヨ</el-button>
+ </el-form>
+ <el-table ref="devTable" v-loading="selectorLoading" :data="selectorList" stripe size="small" @selection-change="rows => selectedRows = rows">
+ <el-table-column type="selection" width="45"/>
+ <el-table-column label="璁惧" min-width="180" align="left" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ deviceLabel(row) }}</template>
+ </el-table-column>
+ <el-table-column prop="platformDevId" label="骞冲彴璁惧ID" min-width="110" align="center"/>
+ <el-table-column prop="online" label="鍦ㄧ嚎" min-width="80" align="center"/>
+ </el-table>
+ <pagination @current-change="p => { selectorPagination.pageIndex = p; loadDevices() }" :pagination="selectorPagination"/>
+ </GlobalWindow>
+ </div>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import * as rechargeApi from '@/api/business/ywcustomerrecharge'
+import * as conditionerApi from '@/api/business/ywconditioner'
+
+export default {
+ name: 'YwCustomerConditionerTab',
+ components: { GlobalWindow, Pagination },
+ props: {
+ customerId: Number,
+ active: Boolean
+ },
+ data () {
+ return {
+ loading: false,
+ saving: false,
+ form: {
+ isPwr: 1,
+ isRestStop: 0,
+ stopMoney: 0,
+ gsBz: '',
+ conditioners: []
+ },
+ selectorVisible: false,
+ selectorLoading: false,
+ selectorKeyword: '',
+ selectorList: [],
+ selectorPagination: { pageIndex: 1, pageSize: 10, total: 0 },
+ selectedRows: []
+ }
+ },
+ watch: {
+ active (val) {
+ if (val) this.loadConfig()
+ },
+ customerId () {
+ if (this.active) this.loadConfig()
+ }
+ },
+ mounted () {
+ if (this.active) this.loadConfig()
+ },
+ methods: {
+ deviceLabel (row) {
+ const parts = [row.floorName, row.roomName, row.name].filter(Boolean)
+ return parts.length ? parts.join('/') : (row.name || row.code || '-')
+ },
+ loadConfig () {
+ if (!this.customerId) return
+ this.loading = true
+ Promise.all([
+ rechargeApi.getGsConfig(this.customerId),
+ rechargeApi.conditionerPage(this.customerId, { page: 1, capacity: 500, model: {} })
+ ]).then(([gs, page]) => {
+ if (gs) {
+ this.form.isPwr = gs.isPwr != null ? gs.isPwr : 1
+ this.form.isRestStop = gs.isRestStop != null ? gs.isRestStop : 0
+ this.form.stopMoney = gs.stopMoney != null ? gs.stopMoney : 0
+ this.form.gsBz = gs.gsBz || ''
+ } else {
+ this.form.isPwr = 1
+ this.form.isRestStop = 0
+ this.form.stopMoney = 0
+ this.form.gsBz = ''
+ }
+ this.form.conditioners = (page.records || []).map(c => ({
+ conditionerId: c.id,
+ platformDevId: c.platformDevId,
+ name: c.name,
+ floorName: c.floorName,
+ roomName: c.roomName,
+ online: c.online,
+ devRatio: c.devRatio != null ? c.devRatio : 100
+ }))
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.loading = false })
+ },
+ save () {
+ if (!this.form.conditioners.length) {
+ this.$tip.warning('璇疯嚦灏戝叧鑱斾竴鍙板唴鏈�')
+ return
+ }
+ this.saving = true
+ rechargeApi.saveGsConfig({
+ customerId: this.customerId,
+ isPwr: this.form.isPwr,
+ isRestStop: this.form.isRestStop,
+ stopMoney: this.form.stopMoney,
+ gsBz: this.form.gsBz,
+ conditioners: this.form.conditioners.map(c => ({
+ conditionerId: c.conditionerId,
+ devRatio: c.devRatio
+ }))
+ }).then(() => {
+ this.$tip.success('淇濆瓨鎴愬姛')
+ this.loadConfig()
+ this.$emit('success')
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.saving = false })
+ },
+ openSelector () {
+ this.selectorVisible = true
+ this.selectorKeyword = ''
+ this.selectedRows = []
+ this.selectorPagination.pageIndex = 1
+ this.loadDevices()
+ },
+ loadDevices () {
+ this.selectorLoading = true
+ conditionerApi.fetchList({
+ page: this.selectorPagination.pageIndex,
+ capacity: this.selectorPagination.pageSize,
+ model: this.selectorKeyword ? { devKeyword: this.selectorKeyword } : {}
+ }).then(data => {
+ const boundIds = new Set(this.form.conditioners.map(c => c.conditionerId))
+ this.selectorList = (data.records || []).filter(r => !boundIds.has(r.id))
+ this.selectorPagination.total = data.total || 0
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.selectorLoading = false })
+ },
+ searchDevices () {
+ this.selectorPagination.pageIndex = 1
+ this.loadDevices()
+ },
+ confirmSelect () {
+ if (!this.selectedRows.length) {
+ this.$tip.warning('璇烽�夋嫨鍐呮満')
+ return
+ }
+ this.selectedRows.forEach(r => {
+ this.form.conditioners.push({
+ conditionerId: r.id,
+ platformDevId: r.platformDevId,
+ name: r.name,
+ floorName: r.floorName,
+ roomName: r.roomName,
+ online: r.online,
+ devRatio: 100
+ })
+ })
+ this.selectorVisible = false
+ }
+ }
+}
+</script>
+
+<style scoped>
+.conditioner-tab {
+ padding-top: 4px;
+}
+.config-section {
+ margin-bottom: 16px;
+ padding: 16px;
+ background: #fff;
+ border: 1px solid #ebeef5;
+ border-radius: 4px;
+}
+.section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+}
+.section-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 22px;
+}
+.section-header .section-title {
+ margin-bottom: 0;
+}
+.config-section > .section-title {
+ margin-bottom: 12px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #ebeef5;
+}
+.config-form {
+ margin-bottom: 0;
+}
+.config-form ::v-deep .el-form-item {
+ margin-bottom: 12px;
+}
+.config-form ::v-deep .remark-item {
+ margin-bottom: 0;
+}
+.config-form ::v-deep .remark-item .el-textarea {
+ max-width: 100%;
+}
+.config-form ::v-deep .el-switch {
+ width: auto;
+}
+.device-table {
+ width: 100%;
+}
+.footer-btns {
+ text-align: right;
+ padding-top: 4px;
+}
+.selector-form {
+ margin-bottom: 12px;
+}
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/components/YwCustomerDeviceWindow.vue b/admin/src/views/business/components/YwCustomerDeviceWindow.vue
new file mode 100644
index 0000000..d8e873a
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerDeviceWindow.vue
@@ -0,0 +1,97 @@
+<template>
+ <GlobalWindow title="鍏宠仈璁惧" :visible.sync="visible" width="920px" :show-confirm="false">
+ <div class="merchant-info">
+ <div class="merchant-info__item">
+ <span class="merchant-info__label">瀹㈡埛绫诲瀷</span>
+ <span class="merchant-info__value">{{ customerTypeText }}</span>
+ </div>
+ <div class="merchant-info__item">
+ <span class="merchant-info__label">瀹㈡埛鍚嶇О</span>
+ <span class="merchant-info__value">{{ customer.name || '-' }}</span>
+ </div>
+ <div class="merchant-info__item">
+ <span class="merchant-info__label">鑱旂郴浜�</span>
+ <span class="merchant-info__value">{{ customer.memberName || '-' }}</span>
+ </div>
+ <div class="merchant-info__item">
+ <span class="merchant-info__label">鑱旂郴鏂瑰紡</span>
+ <span class="merchant-info__value">{{ customer.memberPhone || '-' }}</span>
+ </div>
+ </div>
+ <el-tabs v-model="activeTab" class="device-tabs">
+ <el-tab-pane label="鍏宠仈鐢佃〃" name="electrical">
+ <YwCustomerElectricalTab :customer-id="customer.id" :active="activeTab === 'electrical'" @success="$emit('success')"/>
+ </el-tab-pane>
+ <el-tab-pane label="鍏宠仈绌鸿皟" name="conditioner">
+ <YwCustomerConditionerTab :customer-id="customer.id" :active="activeTab === 'conditioner'" @success="$emit('success')"/>
+ </el-tab-pane>
+ </el-tabs>
+ </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import YwCustomerElectricalTab from './YwCustomerElectricalTab'
+import YwCustomerConditionerTab from './YwCustomerConditionerTab'
+
+export default {
+ name: 'YwCustomerDeviceWindow',
+ components: { GlobalWindow, YwCustomerElectricalTab, YwCustomerConditionerTab },
+ data () {
+ return {
+ visible: false,
+ activeTab: 'electrical',
+ customer: {}
+ }
+ },
+ computed: {
+ customerTypeText () {
+ const t = this.customer.type
+ return t === 0 || t === '0' ? '涓汉' : '浼佷笟'
+ }
+ },
+ methods: {
+ open (row, tab) {
+ this.customer = {
+ id: row.id,
+ type: row.type,
+ name: row.name,
+ memberName: row.memberName,
+ memberPhone: row.memberPhone
+ }
+ this.activeTab = tab || 'electrical'
+ this.visible = true
+ }
+ }
+}
+</script>
+
+<style scoped>
+.merchant-info {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px 24px;
+ margin-bottom: 16px;
+ padding: 30px 16px 30px 16px;
+ background: #f5f7fa;
+ border-radius: 4px;
+}
+.merchant-info__item {
+ min-width: 180px;
+ line-height: 22px;
+}
+.merchant-info__label {
+ color: #909399;
+ margin-right: 8px;
+}
+.merchant-info__label::after {
+ content: '锛�';
+}
+.merchant-info__value {
+ color: #303133;
+ font-weight: 500;
+}
+.device-tabs {
+ margin-top: 4px;
+}
+</style>
diff --git a/admin/src/views/business/components/YwCustomerElectricalRechargePanel.vue b/admin/src/views/business/components/YwCustomerElectricalRechargePanel.vue
new file mode 100644
index 0000000..eabda81
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerElectricalRechargePanel.vue
@@ -0,0 +1,134 @@
+<template>
+ <div v-loading="loading">
+ <el-form label-width="120px" size="small">
+ <el-form-item label="閫夋嫨鐢佃〃">
+ <el-select v-model="electricalId" placeholder="璇烽�夋嫨鐢佃〃" filterable style="width: 360px" @change="loadRemoteInfo">
+ <el-option v-for="item in electricalList" :key="item.id" :label="item.name + ' (' + item.address + ')'" :value="item.id"/>
+ </el-select>
+ </el-form-item>
+ </el-form>
+ <account-recharge-panel
+ v-if="electricalId"
+ :info="info"
+ :latest="latest"
+ :form="form"
+ :is-operating="isOperating"
+ :purchase-count="purchaseCount"
+ mode="recharge"
+ @read="readMeter"
+ @confirm="confirmRecharge"
+ />
+ <!-- <div v-if="electricalId" class="extra-btns">
+ <el-button type="warning" plain :loading="isOperating" v-permissions="['business:ywcustomerrecharge:recharge']" @click="resetAccount('resetPrepay')">娓呴浂(棰勪粯璐�)</el-button>
+ <el-button type="warning" plain :loading="isOperating" v-permissions="['business:ywcustomerrecharge:recharge']" @click="resetAccount('resetPostpay')">娓呴浂(鍚庝粯璐�)</el-button>
+ </div>
+ -->
+ <div v-if="!electricalList.length && !loading" class="empty-tip">璇ュ晢鎴峰皻鏈叧鑱旂數琛紝璇峰厛鍦ㄥ叧鑱旇澶囦腑娣诲姞</div>
+ </div>
+</template>
+
+<script>
+import AccountRechargePanel from './AccountRechargePanel'
+import * as api from '@/api/business/ywcustomerrecharge'
+
+export default {
+ name: 'YwCustomerElectricalRechargePanel',
+ components: { AccountRechargePanel },
+ props: {
+ customer: { type: Object, default: () => ({}) }
+ },
+ data () {
+ return {
+ loading: false,
+ electricalList: [],
+ electricalId: null,
+ info: {},
+ latest: null,
+ purchaseCount: '0',
+ form: { money: 0, remark: '' },
+ isOperating: false
+ }
+ },
+ mounted () {
+ this.loadElectricalList()
+ },
+ methods: {
+ loadElectricalList () {
+ this.loading = true
+ api.electricalPage(this.customer.id, { page: 1, capacity: 500, model: {} })
+ .then(data => {
+ this.electricalList = data.records || []
+ if (this.electricalList.length) {
+ this.electricalId = this.electricalList[0].id
+ this.loadRemoteInfo()
+ }
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.loading = false })
+ },
+ loadRemoteInfo () {
+ if (!this.electricalId) return
+ api.getElectricalRemoteInfo(this.electricalId).then(res => {
+ this.info = res.electrical || {}
+ this.latest = res.latestData
+ this.purchaseCount = res.purchaseCount || '0'
+ }).catch(e => this.$tip.apiFailed(e))
+ },
+ readMeter () {
+ if (!this.electricalId) return
+ this.isOperating = true
+ api.readMeter(this.customer.id, this.electricalId)
+ .then(res => {
+ this.info = res.electrical || this.info
+ this.latest = res.latestData
+ this.$tip.success(res.message || '鎶勮〃瀹屾垚')
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.isOperating = false })
+ },
+ confirmRecharge () {
+ this.$dialog.actionConfirm('纭鍏呭�煎悧锛�', '鎿嶄綔纭')
+ .then(() => {
+ this.isOperating = true
+ return api.rechargeElectrical({
+ customerId: this.customer.id,
+ electricalId: this.electricalId,
+ money: this.form.money,
+ remark: this.form.remark
+ })
+ })
+ .then(msg => {
+ this.$tip.success(msg || '鎻愪氦鎴愬姛锛岃鍦ㄥ厖鍊艰褰曚腑鏌ョ湅缁撴灉')
+ this.loadRemoteInfo()
+ this.$emit('success')
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ .finally(() => { this.isOperating = false })
+ },
+ resetAccount (resetAction) {
+ const label = resetAction === 'resetPrepay' ? '棰勪粯璐�' : '鍚庝粯璐�'
+ this.$dialog.actionConfirm('纭娓呴浂骞跺垏鎹㈠埌' + label + '妯″紡鍚楋紵', '鎿嶄綔纭')
+ .then(() => {
+ this.isOperating = true
+ return api.resetElectrical({
+ customerId: this.customer.id,
+ electricalId: this.electricalId,
+ resetAction
+ })
+ })
+ .then(msg => {
+ this.$tip.success(msg || '鎻愪氦鎴愬姛')
+ this.loadRemoteInfo()
+ this.$emit('success')
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ .finally(() => { this.isOperating = false })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.extra-btns { margin-top: 12px; }
+.empty-tip { padding: 24px; color: #909399; text-align: center; }
+</style>
diff --git a/admin/src/views/business/components/YwCustomerElectricalTab.vue b/admin/src/views/business/components/YwCustomerElectricalTab.vue
new file mode 100644
index 0000000..4282abc
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerElectricalTab.vue
@@ -0,0 +1,175 @@
+<template>
+ <div>
+ <div class="toolbar-row">
+ <el-button type="primary" size="small" v-permissions="['business:ywcustomerrecharge:bindDevice']" @click="openSelector">鍘婚�夋嫨鐢佃〃</el-button>
+ </div>
+ <el-table v-loading="loading" :data="list" stripe size="small">
+ <el-table-column prop="name" label="鍚嶇О" min-width="120" align="center" show-overflow-tooltip/>
+ <el-table-column prop="address" label="琛ㄥ湴鍧�" min-width="130" align="center" show-overflow-tooltip/>
+ <el-table-column prop="accountId" label="寮�鎴峰彿" min-width="100" align="center"/>
+ <el-table-column prop="roomNames" label="鎴块棿" min-width="120" align="center" show-overflow-tooltip/>
+ <el-table-column label="鍦ㄧ嚎" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span :class="row.online === 1 ? 'green' : 'red'">{{ row.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column label="缁х數鍣�" min-width="80" align="center">
+ <template slot-scope="{ row }">{{ relayText(row.relayStatus) }}</template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <el-button type="text" class="red" v-permissions="['business:ywcustomerrecharge:bindDevice']" @click="remove(row)">绉婚櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination small @size-change="onSizeChange" @current-change="onPageChange" :pagination="pagination"/>
+
+ <GlobalWindow title="閫夋嫨鐢佃〃" :visible.sync="selectorVisible" width="780px" @confirm="confirmSelect">
+ <el-form inline @submit.native.prevent>
+ <el-form-item label="鍏抽敭瀛�">
+ <el-input v-model="selectorKeyword" placeholder="鍚嶇О/鍦板潃" clearable @keypress.enter.native="searchSelectable"/>
+ </el-form-item>
+ <el-button type="primary" @click="searchSelectable">鏌ヨ</el-button>
+ </el-form>
+ <el-table ref="selectTable" v-loading="selectorLoading" :data="selectorList" stripe size="small" @selection-change="onSelectionChange">
+ <el-table-column type="selection" width="45"/>
+ <el-table-column prop="name" label="鍚嶇О" min-width="120" align="center"/>
+ <el-table-column prop="address" label="琛ㄥ湴鍧�" min-width="130" align="center"/>
+ <el-table-column prop="roomNames" label="鎴块棿" min-width="120" align="center" show-overflow-tooltip/>
+ <el-table-column label="鍦ㄧ嚎" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span :class="row.online === 1 ? 'green' : 'red'">{{ row.online === 1 ? '鍦ㄧ嚎' : '绂荤嚎' }}</span>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination small @current-change="onSelectorPageChange" :pagination="selectorPagination"/>
+ </GlobalWindow>
+ </div>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import Pagination from '@/components/common/Pagination'
+import * as api from '@/api/business/ywcustomerrecharge'
+
+export default {
+ name: 'YwCustomerElectricalTab',
+ components: { GlobalWindow, Pagination },
+ props: {
+ customerId: Number,
+ active: Boolean
+ },
+ data () {
+ return {
+ loading: false,
+ list: [],
+ pagination: { pageIndex: 1, pageSize: 10, total: 0 },
+ selectorVisible: false,
+ selectorLoading: false,
+ selectorList: [],
+ selectorKeyword: '',
+ selectorPagination: { pageIndex: 1, pageSize: 10, total: 0 },
+ selectedRows: []
+ }
+ },
+ watch: {
+ active (val) {
+ if (val) this.loadList()
+ },
+ customerId () {
+ if (this.active) this.loadList()
+ }
+ },
+ mounted () {
+ if (this.active) this.loadList()
+ },
+ methods: {
+ relayText (v) {
+ if (v === '0' || v === 0) return '鎷夐椄'
+ if (v === '1' || v === 1) return '鍚堥椄'
+ return v || '-'
+ },
+ loadList () {
+ if (!this.customerId) return
+ this.loading = true
+ api.electricalPage(this.customerId, {
+ page: this.pagination.pageIndex,
+ capacity: this.pagination.pageSize,
+ model: {}
+ }).then(data => {
+ this.list = data.records || []
+ this.pagination.total = data.total || 0
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.loading = false })
+ },
+ onPageChange (p) {
+ this.pagination.pageIndex = p
+ this.loadList()
+ },
+ onSizeChange (s) {
+ this.pagination.pageSize = s
+ this.pagination.pageIndex = 1
+ this.loadList()
+ },
+ remove (row) {
+ this.$dialog.actionConfirm('纭绉婚櫎璇ョ數琛ㄥ叧鑱斿悧锛�', '鎿嶄綔纭')
+ .then(() => api.deleteElectrical(this.customerId, row.id))
+ .then(() => {
+ this.$tip.success('宸茬Щ闄�')
+ this.loadList()
+ this.$emit('success')
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ },
+ openSelector () {
+ this.selectorVisible = true
+ this.selectorKeyword = ''
+ this.selectedRows = []
+ this.selectorPagination.pageIndex = 1
+ this.loadSelectable()
+ },
+ loadSelectable () {
+ this.selectorLoading = true
+ api.selectableElectricalPage(this.customerId, {
+ page: this.selectorPagination.pageIndex,
+ capacity: this.selectorPagination.pageSize,
+ model: this.selectorKeyword ? { name: this.selectorKeyword } : {}
+ }).then(data => {
+ this.selectorList = data.records || []
+ this.selectorPagination.total = data.total || 0
+ }).catch(e => this.$tip.apiFailed(e)).finally(() => { this.selectorLoading = false })
+ },
+ searchSelectable () {
+ this.selectorPagination.pageIndex = 1
+ this.loadSelectable()
+ },
+ onSelectorPageChange (p) {
+ this.selectorPagination.pageIndex = p
+ this.loadSelectable()
+ },
+ onSelectionChange (rows) {
+ this.selectedRows = rows
+ },
+ confirmSelect () {
+ if (!this.selectedRows.length) {
+ this.$tip.warning('璇烽�夋嫨鐢佃〃')
+ return
+ }
+ api.saveElectrical({
+ customerId: this.customerId,
+ electricalIds: this.selectedRows.map(r => r.id)
+ }).then(() => {
+ this.$tip.success('淇濆瓨鎴愬姛')
+ this.selectorVisible = false
+ this.loadList()
+ this.$emit('success')
+ }).catch(e => this.$tip.apiFailed(e))
+ }
+ }
+}
+</script>
+
+<style scoped>
+.toolbar-row { margin-bottom: 12px; }
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+</style>
diff --git a/admin/src/views/business/components/YwCustomerRechargeWindow.vue b/admin/src/views/business/components/YwCustomerRechargeWindow.vue
new file mode 100644
index 0000000..f2c99e4
--- /dev/null
+++ b/admin/src/views/business/components/YwCustomerRechargeWindow.vue
@@ -0,0 +1,48 @@
+<template>
+ <GlobalWindow :title="'鍏呭�� - ' + (customer.name || '')" :visible.sync="visible" width="820px" :show-confirm="false">
+ <el-tabs v-model="activeTab">
+ <el-tab-pane label="鐢佃〃鍏呭��" name="electrical">
+ <YwCustomerElectricalRechargePanel
+ v-if="activeTab === 'electrical'"
+ :customer="customer"
+ @success="onSuccess"
+ />
+ </el-tab-pane>
+ <el-tab-pane label="绌鸿皟鍏呭��" name="conditioner">
+ <YwCustomerConditionerRechargePanel
+ v-if="activeTab === 'conditioner'"
+ :customer="customer"
+ @success="onSuccess"
+ />
+ </el-tab-pane>
+ </el-tabs>
+ </GlobalWindow>
+</template>
+
+<script>
+import GlobalWindow from '@/components/common/GlobalWindow'
+import YwCustomerElectricalRechargePanel from './YwCustomerElectricalRechargePanel'
+import YwCustomerConditionerRechargePanel from './YwCustomerConditionerRechargePanel'
+
+export default {
+ name: 'YwCustomerRechargeWindow',
+ components: { GlobalWindow, YwCustomerElectricalRechargePanel, YwCustomerConditionerRechargePanel },
+ data () {
+ return {
+ visible: false,
+ activeTab: 'electrical',
+ customer: {}
+ }
+ },
+ methods: {
+ open (row, tab) {
+ this.customer = { id: row.id, name: row.name }
+ this.activeTab = tab || 'electrical'
+ this.visible = true
+ },
+ onSuccess () {
+ this.$emit('success')
+ }
+ }
+}
+</script>
diff --git a/admin/src/views/business/ywcustomerrecharge.vue b/admin/src/views/business/ywcustomerrecharge.vue
new file mode 100644
index 0000000..c5c0e8a
--- /dev/null
+++ b/admin/src/views/business/ywcustomerrecharge.vue
@@ -0,0 +1,170 @@
+<template>
+ <TableLayout :permissions="['business:ywcustomerrecharge:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="nameKeyword">
+ <el-input v-model="searchForm.nameKeyword" placeholder="瀹㈡埛鍚嶇О" clearable @keypress.enter.native="search"/>
+ </el-form-item>
+ <el-form-item label="鐢佃〃鐘舵��" prop="electricalStatusFilter">
+ <el-select v-model="searchForm.electricalStatusFilter" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+ <el-option label="鍏ㄥ湪绾�" :value="1"/>
+ <el-option label="瀛樺湪绂荤嚎" :value="2"/>
+ <el-option label="鏃犺澶�" :value="3"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="绌鸿皟鐘舵��" prop="conditionerStatusFilter">
+ <el-select v-model="searchForm.conditionerStatusFilter" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+ <el-option label="鍏ㄥ湪绾�" :value="1"/>
+ <el-option label="瀛樺湪绂荤嚎" :value="2"/>
+ <el-option label="鏃犺澶�" :value="3"/>
+ </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>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+ <el-table-column prop="type" label="瀹㈡埛绫诲瀷" min-width="80" align="center">
+ <template slot-scope="{ row }">
+ <span>{{ row.type == 0 || row.type === '0' ? '涓汉' : '浼佷笟' }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="name" label="瀹㈡埛鍚嶇О" min-width="140" align="center" show-overflow-tooltip/>
+ <el-table-column prop="memberName" label="鑱旂郴浜�" min-width="120" align="center" show-overflow-tooltip>
+ <template slot-scope="{ row }">{{ row.memberName || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="memberPhone" label="鑱旂郴鐢佃瘽" min-width="130" align="center">
+ <template slot-scope="{ row }">{{ row.memberPhone || '-' }}</template>
+ </el-table-column>
+ <el-table-column prop="electricalCount" label="鍏宠仈鐢佃〃鏁�" min-width="100" align="center"/>
+ <el-table-column label="鐢佃〃浣欓" min-width="140" align="center">
+ <template slot-scope="{ row }">
+ <span v-if="!row.electricalBalances || !row.electricalBalances.length" class="balance-text">-</span>
+ <el-tooltip v-else placement="top" effect="dark">
+ <div slot="content" class="balance-tooltip">
+ <div v-for="(item, idx) in row.electricalBalances" :key="idx" class="balance-tooltip-line">
+ {{ item.name }}锛坽{ item.address }}锛夛細{{ formatBalance(item.balance) }}
+ </div>
+ </div>
+ <span class="balance-text balance-summary">
+ <template v-for="(item, idx) in row.electricalBalances">
+ <span :key="'b-' + idx" :class="{ red: isAmountLow(item.balance) }">{{ formatBalance(item.balance) }}</span><span v-if="idx < row.electricalBalances.length - 1" :key="'s-' + idx">/</span>
+ </template>
+ </span>
+ </el-tooltip>
+ </template>
+ </el-table-column>
+ <el-table-column prop="conditionerCount" label="绌鸿皟鍐呮満鏁�" min-width="100" align="center"/>
+ <el-table-column label="绌鸿皟浣欓" min-width="110" align="center">
+ <template slot-scope="{ row }">
+ <span v-if="row.acBalance == null" class="balance-text">-</span>
+ <span v-else class="balance-text" :class="{ red: isAmountLow(row.acBalance) }">{{ row.acBalance }}</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="createDate" label="鍒涘缓鏃堕棿" min-width="160" align="center"/>
+ <el-table-column label="鎿嶄綔" min-width="180" align="center" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button type="text" v-permissions="['business:ywcustomerrecharge:bindDevice']" @click="openDevice(row)">鍏宠仈璁惧</el-button>
+ <el-button type="text" v-permissions="['business:ywcustomerrecharge:recharge']" @click="openRecharge(row)">鍏呭��</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <pagination @size-change="handleSizeChange" @current-change="handlePageChange" :pagination="tableData.pagination"/>
+ </template>
+ <YwCustomerDeviceWindow ref="deviceWindow" @success="search"/>
+ <YwCustomerRechargeWindow ref="rechargeWindow" @success="search"/>
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import * as api from '@/api/business/ywcustomerrecharge'
+import YwCustomerDeviceWindow from './components/YwCustomerDeviceWindow'
+import YwCustomerRechargeWindow from './components/YwCustomerRechargeWindow'
+
+export default {
+ name: 'YwCustomerRecharge',
+ extends: BaseTable,
+ components: { TableLayout, Pagination, YwCustomerDeviceWindow, YwCustomerRechargeWindow },
+ data () {
+ return {
+ searchForm: {
+ nameKeyword: '',
+ electricalStatusFilter: null,
+ conditionerStatusFilter: null
+ }
+ }
+ },
+ created () {
+ this.search()
+ },
+ methods: {
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.loadList()
+ },
+ loadList () {
+ this.isWorking.search = true
+ api.merchantPage({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel()
+ }).then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ }).catch(() => {}).finally(() => { this.isWorking.search = false })
+ },
+ search () {
+ this.tableData.pagination.pageIndex = 1
+ this.loadList()
+ },
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.nameKeyword) model.nameKeyword = this.searchForm.nameKeyword
+ if (this.searchForm.electricalStatusFilter != null) model.electricalStatusFilter = this.searchForm.electricalStatusFilter
+ if (this.searchForm.conditionerStatusFilter != null) model.conditionerStatusFilter = this.searchForm.conditionerStatusFilter
+ return model
+ },
+ reset () {
+ this.searchForm = { nameKeyword: '', electricalStatusFilter: null, conditionerStatusFilter: null }
+ this.search()
+ },
+ openDevice (row) {
+ this.$refs.deviceWindow.open(row)
+ },
+ openRecharge (row) {
+ this.$refs.rechargeWindow.open(row)
+ },
+ formatBalance (val) {
+ if (val == null || val === '') return '0'
+ const n = parseFloat(val)
+ return isNaN(n) ? val : String(val)
+ },
+ isAmountLow (val) {
+ if (val == null || val === '' || val === '-') return false
+ const n = parseFloat(val)
+ return !isNaN(n) && n <= 0
+ }
+ }
+}
+</script>
+
+<style scoped>
+.red { color: #f56c6c; }
+.balance-text { white-space: nowrap; }
+.balance-summary {
+ cursor: default;
+}
+</style>
+
+<style>
+.balance-tooltip {
+ line-height: 22px;
+}
+.balance-tooltip-line {
+ white-space: nowrap;
+}
+</style>
diff --git a/admin/src/views/business/ywcustomerrechargerecord.vue b/admin/src/views/business/ywcustomerrechargerecord.vue
new file mode 100644
index 0000000..1c451d4
--- /dev/null
+++ b/admin/src/views/business/ywcustomerrechargerecord.vue
@@ -0,0 +1,169 @@
+<template>
+ <TableLayout :permissions="['business:ywcustomerrechargerecord:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="100px" inline>
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customerName">
+ <el-input v-model="searchForm.customerName" placeholder="瀹㈡埛鍚嶇О" clearable @keypress.enter.native="search"/>
+ </el-form-item>
+ <el-form-item label="涓氬姟绫诲瀷" prop="type">
+ <el-select v-model="searchForm.type" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+ <el-option label="鐢佃〃" :value="0"/>
+ <el-option label="绌鸿皟" :value="1"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍏呭�肩姸鎬�" prop="status">
+ <el-select v-model="searchForm.status" clearable placeholder="鍏ㄩ儴" style="min-width: 120px">
+ <el-option label="鍏呭�间腑" :value="0"/>
+ <el-option label="鍏呭�兼垚鍔�" :value="1"/>
+ <el-option label="鍏呭�煎け璐�" :value="2"/>
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎻愪氦鏃堕棿">
+ <el-date-picker
+ v-model="searchForm.dateRange"
+ type="datetimerange"
+ value-format="yyyy-MM-dd HH:mm:ss"
+ 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:ywcustomerrechargerecord:exportExcel']">瀵煎嚭</el-button>
+ </li>
+ </ul>
+ <el-table v-loading="isWorking.search" :data="tableData.list" stripe>
+ <el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" min-width="130" align="center" show-overflow-tooltip/>
+ <el-table-column label="涓氬姟绫诲瀷" min-width="90" align="center">
+ <template slot-scope="{ row }">{{ row.type === 1 ? '绌鸿皟' : '鐢佃〃' }}</template>
+ </el-table-column>
+ <el-table-column prop="deviceInfo" label="璁惧淇℃伅" min-width="180" align="center" show-overflow-tooltip/>
+ <el-table-column prop="money" label="鍏呭�奸噾棰�(鍏�)" min-width="110" align="center"/>
+ <el-table-column prop="banlance" label="鍏呭�煎墠浣欓" min-width="110" align="center"/>
+ <el-table-column prop="balanceAfter" label="鍏呭�煎悗浣欓" min-width="110" align="center"/>
+ <el-table-column label="鐘舵��" min-width="100" align="center">
+ <template slot-scope="{ row }">
+ <span v-if="row.status === 0" class="orange">鍏呭�间腑</span>
+ <span v-else-if="row.status === 1" class="green">鍏呭�兼垚鍔�</span>
+ <span v-else-if="row.status === 2" class="red">鍏呭�煎け璐�</span>
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="oprId" label="浠诲姟ID" min-width="180" align="center" show-overflow-tooltip/>
+ <el-table-column prop="remark" label="澶囨敞" min-width="120" align="center" show-overflow-tooltip/>
+ <el-table-column prop="statusInfo" label="鐘舵�佽鏄�" min-width="140" align="center" show-overflow-tooltip/>
+ <el-table-column prop="createDate" label="鎻愪氦鏃堕棿" min-width="160" align="center"/>
+ <el-table-column prop="statusTime" label="鐘舵�佹洿鏂版椂闂�" min-width="160" align="center"/>
+ <el-table-column label="鎿嶄綔" min-width="160" align="center" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button v-if="row.status === 2" type="text" v-permissions="['business:ywcustomerrechargerecord:retry']" @click="handleRetry(row)">鍐嶆鎻愪氦</el-button>
+ <el-button v-if="row.status === 0" type="text" v-permissions="['business:ywcustomerrechargerecord:syncStatus']" @click="handleSync(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 api from '@/api/business/ywcustomerrecharge'
+
+export default {
+ name: 'YwCustomerRechargeRecord',
+ extends: BaseTable,
+ components: { TableLayout, Pagination },
+ data () {
+ return {
+ searchForm: {
+ customerName: '',
+ type: null,
+ status: null,
+ dateRange: null
+ }
+ }
+ },
+ created () {
+ this.search()
+ },
+ methods: {
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.loadList()
+ },
+ loadList () {
+ this.isWorking.search = true
+ api.rechargeRecordPage({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel()
+ }).then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ }).catch(() => {}).finally(() => { this.isWorking.search = false })
+ },
+ search () {
+ this.tableData.pagination.pageIndex = 1
+ this.loadList()
+ },
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.customerName) model.customerName = this.searchForm.customerName
+ if (this.searchForm.type !== '' && this.searchForm.type !== null) model.type = this.searchForm.type
+ if (this.searchForm.status !== '' && this.searchForm.status !== null) model.status = this.searchForm.status
+ if (this.searchForm.dateRange && this.searchForm.dateRange.length === 2) {
+ model.createTimeBegin = this.searchForm.dateRange[0]
+ model.createTimeEnd = this.searchForm.dateRange[1]
+ }
+ return model
+ },
+ reset () {
+ this.searchForm = { customerName: '', type: null, status: null, dateRange: null }
+ this.search()
+ },
+ exportExcel () {
+ this.$dialog.exportConfirm('纭瀵煎嚭鍚楋紵')
+ .then(() => {
+ this.isWorking.export = true
+ api.exportRechargeRecord({ page: 1, capacity: 1000000, model: this.buildSearchModel() })
+ .then(response => { this.download(response) })
+ .catch(e => this.$tip.apiFailed(e))
+ .finally(() => { this.isWorking.export = false })
+ })
+ .catch(() => {})
+ },
+ handleRetry (row) {
+ this.$dialog.actionConfirm('纭鍐嶆鎻愪氦璇ュ厖鍊煎悧锛�', '鎿嶄綔纭')
+ .then(() => api.retryRecharge(row.id))
+ .then(msg => {
+ this.$tip.success(msg || '宸叉彁浜�')
+ this.loadList()
+ })
+ .catch(e => { if (e !== 'cancel') this.$tip.apiFailed(e) })
+ },
+ handleSync (row) {
+ api.syncRechargeStatus(row.id)
+ .then(msg => {
+ this.$tip.success(msg || '鍚屾瀹屾垚')
+ this.loadList()
+ })
+ .catch(e => this.$tip.apiFailed(e))
+ }
+ }
+}
+</script>
+
+<style scoped>
+.green { color: #67c23a; }
+.red { color: #f56c6c; }
+.orange { color: #e6a23c; }
+</style>
--
Gitblit v1.9.3