From 77094dd01f0c6ff59b4fb4fa1105addf34b2398c Mon Sep 17 00:00:00 2001
From: doum <doum>
Date: 星期二, 16 六月 2026 18:49:03 +0800
Subject: [PATCH] 新增智能电表、空调管理
---
h5/pages/customer/contract/detail.vue | 596 ++++
h5/pages/customer/login.vue | 73
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5AuthServiceImpl.java | 63
admin/src/assets/style/style.scss | 50
h5/pages/index.vue | 111
h5/pages/login.vue | 121
h5/pages/customer/electricity/list.vue | 96
admin/src/views/business/ywcustomerrecharge.vue | 4
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwWxPayOrder.java | 54
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwH5BannerServiceImpl.java | 169 +
server/system_service/src/main/java/com/doumee/config/cloudfilter/LoginHandlerInterceptor.java | 19
server/visits/dmvisit_service/src/main/java/com/doumee/core/haikang/model/HKConstants.java | 1
admin/src/views/client/components/OperaYwCustomerWindow.vue | 83
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5BizServiceImpl.java | 752 +++++
h5/styles/customer.scss | 2024 ++++++++++++++
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractBill.java | 17
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerContractQueryDTO.java | 8
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceQueryDTO.java | 9
h5/pages.json | 225
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerPayCreateDTO.java | 16
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwH5Banner.java | 31
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwMaterialServiceImpl.java | 1
h5/utils/config.js | 19
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java | 142
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwH5BannerService.java | 25
server/visits/dmvisit_admin/src/main/resources/application.yml | 6
h5/utils/service.js | 34
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/EditRecordDataVO.java | 9
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContract.java | 18
h5/utils/loginSms.js | 32
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerBillQueryDTO.java | 8
admin/src/views/business/components/YwCustomerDeviceWindow.vue | 20
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractRevenueServiceImpl.java | 3
h5/utils/roleSwitch.js | 19
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/SmsEmailServiceImpl.java | 143
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractServiceImpl.java | 14
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomerElectrical.java | 6
h5/pages/customer/electricity/recharge.vue | 79
h5/pages/customer/index.vue | 81
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalCharge.java | 2
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractRevenue.java | 7
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerWxPayService.java | 16
admin/src/views/business/components/YwCustomerConditionerTab.vue | 27
admin/src/views/business/components/YwH5BannerEdit.vue | 147 +
admin/src/views/business/ywh5banner.vue | 182 +
h5/api/customer.js | 23
h5/utils/wechatAuth.js | 55
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerWxPayServiceImpl.java | 257 +
h5/pages/customer/bill/list.vue | 263 +
server/visits/dmvisit_admin/src/main/resources/bootstrap.yml | 2
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerRechargeRecordH5QueryDTO.java | 13
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomer.java | 3
h5/pages/customer/contract/list.vue | 103
h5/pages/customer/recharge/record.vue | 84
h5/pages/roleSelect.vue | 49
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerDeviceAutoBindService.java | 18
admin/src/views/client/staffList.vue | 5
h5/store/index.js | 10
h5/pages/customer/pay/result.vue | 34
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/SmsEmailService.java | 3
h5/pages/customer/conditioner/recharge.vue | 77
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerServiceImpl.java | 3
h5/pages/customer/bill/pay.vue | 60
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwH5BannerMapper.java | 7
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java | 1
server/system_service/src/main/java/com/doumee/dao/business/model/SmsEmail.java | 4
server/db/business.yw_h5_banner.menu.sql | 35
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerH5Controller.java | 212 +
server/db/business.yw_h5_banner.permissions.sql | 17
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceH5VO.java | 24
h5/pages/customer/bill/detail.vue | 330 ++
h5/api/index.js | 1
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwWxPayOrderMapper.java | 7
server/system_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java | 33
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5BizService.java | 36
server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwCustomerRechargeRecordQueryDTO.java | 1
server/visits/dmvisit_service/src/main/java/com/doumee/core/wx/WxJsapiPayUtil.java | 135
/dev/null | 50
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5AuthService.java | 3
server/db/business.yw_customer_h5.sql | 70
h5/utils/wxpay.js | 22
server/pom.xml | 4
server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerDeviceAutoBindServiceImpl.java | 218 +
admin/src/api/business/ywh5banner.js | 27
server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwH5BannerCloudController.java | 78
h5/App.vue | 1
admin/src/views/business/components/YwCustomerElectricalTab.vue | 12
admin/src/views/client/components/staffEdit.vue | 303 +
88 files changed, 7,726 insertions(+), 529 deletions(-)
diff --git a/admin/src/api/business/ywh5banner.js b/admin/src/api/business/ywh5banner.js
new file mode 100644
index 0000000..d7ef981
--- /dev/null
+++ b/admin/src/api/business/ywh5banner.js
@@ -0,0 +1,27 @@
+import request from '../../utils/request'
+
+const base = '/visitsAdmin/cloudService/business/ywH5Banner'
+
+export function fetchList (data) {
+ return request.post(base + '/page', data, { trim: true })
+}
+
+export function create (data) {
+ return request.post(base + '/create', data)
+}
+
+export function getInfoById (id) {
+ return request.get(base + '/' + id)
+}
+
+export function updateById (data) {
+ return request.post(base + '/updateById', data)
+}
+
+export function deleteById (id) {
+ return request.get(base + '/delete/' + id)
+}
+
+export function deleteByIdInBatch (ids) {
+ return request.get(base + '/delete/batch', { params: { ids } })
+}
diff --git a/admin/src/assets/style/style.scss b/admin/src/assets/style/style.scss
index afecdf3..d28370f 100644
--- a/admin/src/assets/style/style.scss
+++ b/admin/src/assets/style/style.scss
@@ -254,7 +254,7 @@
margin-bottom: 20px;
}
.red{
- color: red !important;
+ color: #f56c6c !important;
}
.green{
color: #00BA67;
@@ -348,3 +348,51 @@
.tac{
text-align: center;
}
+
+/* 鍒犻櫎鎸夐挳缁熶竴鏍峰紡 */
+$btn-delete-color: #f56c6c;
+$btn-delete-hover: #f78989;
+$btn-delete-bg: #fef0f0;
+$btn-delete-border: #fbc4c4;
+
+.el-button.btn-delete,
+.el-button--text.btn-delete {
+ color: $btn-delete-color !important;
+
+ &:hover,
+ &:focus {
+ color: $btn-delete-hover !important;
+ }
+}
+
+.el-button--text:has(.el-icon-delete) {
+ color: $btn-delete-color;
+
+ &:hover,
+ &:focus {
+ color: $btn-delete-hover;
+ }
+}
+
+.el-button:not(.el-button--text):not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--info):not(.el-button--danger):not(.is-disabled):has(.el-icon-delete) {
+ color: $btn-delete-color;
+ border-color: $btn-delete-border;
+ background-color: $btn-delete-bg;
+
+ &:hover,
+ &:focus {
+ color: #fff;
+ background-color: $btn-delete-color;
+ border-color: $btn-delete-color;
+ }
+}
+
+span.btn-delete,
+.cu.btn-delete {
+ color: $btn-delete-color !important;
+ cursor: pointer;
+
+ &:hover {
+ color: $btn-delete-hover !important;
+ }
+}
diff --git a/admin/src/views/business/components/YwCustomerConditionerTab.vue b/admin/src/views/business/components/YwCustomerConditionerTab.vue
index 7a1e404..f0e4219 100644
--- a/admin/src/views/business/components/YwCustomerConditionerTab.vue
+++ b/admin/src/views/business/components/YwCustomerConditionerTab.vue
@@ -12,6 +12,7 @@
:min="0"
:precision="2"
:step="10"
+ :disabled="readonly"
controls-position="right"
class="stop-money-input"
/>
@@ -23,12 +24,12 @@
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="璁¤垂寮�鍏�" prop="isPwr">
- <el-switch v-model="form.isPwr" :active-value="1" :inactive-value="0" active-text="寮�鍚�" inactive-text="鍏抽棴"/>
+ <el-switch v-model="form.isPwr" :active-value="1" :inactive-value="0" :disabled="readonly" active-text="寮�鍚�" inactive-text="鍏抽棴"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="18:00-09:00 涓嶅仠鏈�" prop="isRestStop">
- <el-switch v-model="form.isRestStop" :active-value="1" :inactive-value="0" active-text="鏄�" inactive-text="鍚�"/>
+ <el-switch v-model="form.isRestStop" :active-value="1" :inactive-value="0" :disabled="readonly" active-text="鏄�" inactive-text="鍚�"/>
</el-form-item>
</el-col>
</el-row>
@@ -38,6 +39,7 @@
type="textarea"
:rows="2"
maxlength="500"
+ :disabled="readonly"
show-word-limit
placeholder="璇疯緭鍏ュ娉紙閫夊~锛�"
/>
@@ -46,11 +48,11 @@
<section class="config-section">
<div class="section-header">
- <span class="section-title required-title">鍏宠仈鍐呮満</span>
- <el-button type="primary" size="small" icon="el-icon-plus" @click="openSelector">娣诲姞鍐呮満</el-button>
+ <span class="section-title" :class="{ 'required-title': !readonly }">鍏宠仈鍐呮満</span>
+ <el-button v-if="!readonly" type="primary" size="small" icon="el-icon-plus" @click="openSelector">娣诲姞鍐呮満</el-button>
</div>
<el-form-item prop="conditioners" label-width="0" class="conditioners-form-item">
- <el-table :data="form.conditioners" stripe size="small" class="device-table" empty-text="鏆傛湭鍏宠仈鍐呮満锛岃鐐瑰嚮娣诲姞">
+ <el-table :data="form.conditioners" stripe size="small" class="device-table" :empty-text="readonly ? '鏆傛棤鍏宠仈鍐呮満' : '鏆傛湭鍏宠仈鍐呮満锛岃鐐瑰嚮娣诲姞'">
<el-table-column label="璁惧" min-width="200" align="left" show-overflow-tooltip>
<template slot-scope="{ row }">{{ deviceLabel(row) }}</template>
</el-table-column>
@@ -62,10 +64,11 @@
</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"/>
+ <el-input-number v-if="!readonly" v-model="row.devRatio" :min="1" :max="100" size="small" controls-position="right"/>
+ <span v-else>{{ row.devRatio != null ? row.devRatio : '-' }}</span>
</template>
</el-table-column>
- <el-table-column label="鎿嶄綔" width="80" align="center" fixed="right">
+ <el-table-column v-if="!readonly" label="鎿嶄綔" width="80" align="center" fixed="right">
<template slot-scope="{ $index }">
<el-button type="text" class="red" @click="removeConditioner($index)">绉婚櫎</el-button>
</template>
@@ -75,11 +78,11 @@
</section>
</el-form>
- <div class="footer-btns">
+ <div v-if="!readonly" 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">
+ <GlobalWindow v-if="!readonly" 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"/>
@@ -110,7 +113,11 @@
components: { GlobalWindow, Pagination },
props: {
customerId: Number,
- active: Boolean
+ active: Boolean,
+ readonly: {
+ type: Boolean,
+ default: false
+ }
},
data () {
return {
diff --git a/admin/src/views/business/components/YwCustomerDeviceWindow.vue b/admin/src/views/business/components/YwCustomerDeviceWindow.vue
index d8e873a..a96a320 100644
--- a/admin/src/views/business/components/YwCustomerDeviceWindow.vue
+++ b/admin/src/views/business/components/YwCustomerDeviceWindow.vue
@@ -1,5 +1,6 @@
<template>
<GlobalWindow title="鍏宠仈璁惧" :visible.sync="visible" width="920px" :show-confirm="false">
+ <div v-if="readonly" class="readonly-tip">璁惧鐢辩璧佸悎鍚岃嚜鍔ㄥ叧鑱旓紝浠呮敮鎸佹煡鐪�</div>
<div class="merchant-info">
<div class="merchant-info__item">
<span class="merchant-info__label">瀹㈡埛绫诲瀷</span>
@@ -20,10 +21,10 @@
</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')"/>
+ <YwCustomerElectricalTab :customer-id="customer.id" :active="activeTab === 'electrical'" :readonly="readonly"/>
</el-tab-pane>
<el-tab-pane label="鍏宠仈绌鸿皟" name="conditioner">
- <YwCustomerConditionerTab :customer-id="customer.id" :active="activeTab === 'conditioner'" @success="$emit('success')"/>
+ <YwCustomerConditionerTab :customer-id="customer.id" :active="activeTab === 'conditioner'" :readonly="readonly"/>
</el-tab-pane>
</el-tabs>
</GlobalWindow>
@@ -37,6 +38,13 @@
export default {
name: 'YwCustomerDeviceWindow',
components: { GlobalWindow, YwCustomerElectricalTab, YwCustomerConditionerTab },
+ props: {
+ /** 鍙鏌ョ湅锛堝悎鍚岃嚜鍔ㄥ叧鑱旓紝涓嶅彲鎵嬪姩澧炲垹锛� */
+ readonly: {
+ type: Boolean,
+ default: true
+ }
+ },
data () {
return {
visible: false,
@@ -94,4 +102,12 @@
.device-tabs {
margin-top: 4px;
}
+.readonly-tip {
+ margin-bottom: 12px;
+ padding: 8px 12px;
+ font-size: 13px;
+ color: #909399;
+ background: #fdf6ec;
+ border-radius: 4px;
+}
</style>
diff --git a/admin/src/views/business/components/YwCustomerElectricalTab.vue b/admin/src/views/business/components/YwCustomerElectricalTab.vue
index 4282abc..3e58fcf 100644
--- a/admin/src/views/business/components/YwCustomerElectricalTab.vue
+++ b/admin/src/views/business/components/YwCustomerElectricalTab.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <div class="toolbar-row">
+ <div v-if="!readonly" 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">
@@ -16,7 +16,7 @@
<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">
+ <el-table-column v-if="!readonly" 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>
@@ -24,7 +24,7 @@
</el-table>
<pagination small @size-change="onSizeChange" @current-change="onPageChange" :pagination="pagination"/>
- <GlobalWindow title="閫夋嫨鐢佃〃" :visible.sync="selectorVisible" width="780px" @confirm="confirmSelect">
+ <GlobalWindow v-if="!readonly" 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"/>
@@ -57,7 +57,11 @@
components: { GlobalWindow, Pagination },
props: {
customerId: Number,
- active: Boolean
+ active: Boolean,
+ readonly: {
+ type: Boolean,
+ default: false
+ }
},
data () {
return {
diff --git a/admin/src/views/business/components/YwH5BannerEdit.vue b/admin/src/views/business/components/YwH5BannerEdit.vue
new file mode 100644
index 0000000..7f1b348
--- /dev/null
+++ b/admin/src/views/business/components/YwH5BannerEdit.vue
@@ -0,0 +1,147 @@
+<template>
+ <GlobalWindow
+ :title="title"
+ :visible.sync="visible"
+ :confirm-working="isWorking"
+ width="640px"
+ @confirm="confirm"
+ >
+ <el-form ref="form" :model="form" :rules="rules" label-width="96px" class="banner-edit-form">
+ <el-form-item label="鏍囬" prop="title">
+ <el-input v-model="form.title" placeholder="閫夊~锛岀敤浜庡悗鍙拌瘑鍒�" maxlength="200" clearable v-trim />
+ </el-form-item>
+ <el-form-item label="杞挱鍥剧墖" prop="imageUrl">
+ <UploadAvatarImage
+ :file="imageFile"
+ tips-label="涓婁紶鍥剧墖"
+ custom-style="width: 320px; height: 120px;"
+ :upload-data="{ folder: 'ywH5Banner' }"
+ @uploadSuccess="onUploadSuccess"
+ @uploadBegin="isUploading = true"
+ @uploadEnd="isUploading = false"
+ />
+ <p class="form-tip">寤鸿灏哄 750脳320 宸﹀彸锛屾敮鎸� jpg/png</p>
+ </el-form-item>
+ <el-form-item label="璺宠浆閾炬帴" prop="linkUrl">
+ <el-input v-model="form.linkUrl" placeholder="閫夊~锛孒5 鍐呴摼鎴栧閾�" clearable v-trim />
+ </el-form-item>
+ <el-form-item label="鎺掑簭" prop="sortnum">
+ <el-input-number v-model="form.sortnum" :min="0" :max="9999" controls-position="right" />
+ <span class="inline-tip">鏁板�艰秺灏忚秺闈犲墠</span>
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-radio-group v-model="form.status">
+ <el-radio :label="0">鍚敤</el-radio>
+ <el-radio :label="1">绂佺敤</el-radio>
+ </el-radio-group>
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="閫夊~" maxlength="500" show-word-limit />
+ </el-form-item>
+ </el-form>
+ </GlobalWindow>
+</template>
+
+<script>
+import BaseOpera from '@/components/base/BaseOpera'
+import GlobalWindow from '@/components/common/GlobalWindow'
+import UploadAvatarImage from '@/components/common/UploadAvatarImage'
+
+const defaultForm = () => ({
+ id: null,
+ title: '',
+ imageUrl: '',
+ linkUrl: '',
+ sortnum: 0,
+ status: 0,
+ remark: ''
+})
+
+export default {
+ name: 'YwH5BannerEdit',
+ extends: BaseOpera,
+ components: { GlobalWindow, UploadAvatarImage },
+ data () {
+ return {
+ form: defaultForm(),
+ imageFile: { imgurl: '', imgurlfull: '' },
+ isUploading: false,
+ rules: {
+ imageUrl: [{ required: true, message: '璇蜂笂浼犺疆鎾浘鐗�', trigger: 'change' }],
+ sortnum: [{ required: true, message: '璇疯緭鍏ユ帓搴�', trigger: 'change' }]
+ }
+ }
+ },
+ created () {
+ this.config({
+ api: '/business/ywh5banner',
+ 'field.id': 'id'
+ })
+ },
+ methods: {
+ open (title, target) {
+ this.title = title
+ this.visible = true
+ this.isUploading = false
+ if (target == null) {
+ this.$nextTick(() => {
+ this.form = defaultForm()
+ this.imageFile = { imgurl: '', imgurlfull: '' }
+ this.$refs.form && this.$refs.form.clearValidate()
+ })
+ return
+ }
+ this.$nextTick(() => {
+ this.form = Object.assign(defaultForm(), target)
+ this.imageFile = {
+ imgurl: target.imageUrl || '',
+ imgurlfull: target.imageUrl || ''
+ }
+ this.$refs.form && this.$refs.form.clearValidate()
+ })
+ },
+ onUploadSuccess ({ imgurlfull, imgurl }) {
+ this.form.imageUrl = imgurlfull || imgurl || ''
+ this.imageFile.imgurl = imgurl || this.form.imageUrl
+ this.imageFile.imgurlfull = imgurlfull || this.form.imageUrl
+ this.$refs.form && this.$refs.form.validateField('imageUrl')
+ },
+ confirm () {
+ if (this.isUploading) {
+ this.$message.warning('鍥剧墖涓婁紶涓紝璇风◢鍊�')
+ return
+ }
+ if (this.form.id == null || this.form.id === '') {
+ this.__confirmCreate()
+ return
+ }
+ this.__confirmEdit()
+ }
+ }
+}
+</script>
+
+<style scoped lang="scss">
+.banner-edit-form {
+ padding: 8px 0 12px;
+}
+
+.banner-edit-form ::v-deep .avatar-uploader-icon {
+ line-height: 120px;
+}
+
+.form-tip,
+.inline-tip {
+ margin: 6px 0 0;
+ font-size: 12px;
+ color: #909399;
+ line-height: 1.5;
+}
+
+.inline-tip {
+ display: inline-block;
+ margin-left: 10px;
+ margin-top: 0;
+ vertical-align: middle;
+}
+</style>
diff --git a/admin/src/views/business/ywcustomerrecharge.vue b/admin/src/views/business/ywcustomerrecharge.vue
index ecc712b..ca0a0c4 100644
--- a/admin/src/views/business/ywcustomerrecharge.vue
+++ b/admin/src/views/business/ywcustomerrecharge.vue
@@ -65,14 +65,14 @@
<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:query']" @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"/>
+ <YwCustomerDeviceWindow ref="deviceWindow"/>
<YwCustomerRechargeWindow ref="rechargeWindow" @success="search"/>
</TableLayout>
</template>
diff --git a/admin/src/views/business/ywh5banner.vue b/admin/src/views/business/ywh5banner.vue
new file mode 100644
index 0000000..80b73a2
--- /dev/null
+++ b/admin/src/views/business/ywh5banner.vue
@@ -0,0 +1,182 @@
+<template>
+ <TableLayout :permissions="['business:ywh5banner:query']">
+ <el-form ref="searchForm" slot="search-form" :model="searchForm" label-width="88px" inline>
+ <el-form-item label="鏍囬" prop="title">
+ <el-input v-model="searchForm.title" placeholder="璇疯緭鍏ユ爣棰�" clearable @keypress.enter.native="search" />
+ </el-form-item>
+ <el-form-item label="鐘舵��" prop="status">
+ <el-select v-model="searchForm.status" clearable placeholder="鍏ㄩ儴">
+ <el-option :value="0" label="鍚敤" />
+ <el-option :value="1" label="绂佺敤" />
+ </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"
+ icon="el-icon-plus"
+ @click="$refs.editWindow.open('鏂板缓杞挱鍥�')"
+ v-permissions="['business:ywh5banner:create']"
+ >鏂板缓</el-button>
+ </li>
+ <li>
+ <el-button
+ icon="el-icon-delete"
+ @click="deleteByIdInBatch"
+ v-permissions="['business:ywh5banner:delete']"
+ >鎵归噺鍒犻櫎</el-button>
+ </li>
+ </ul>
+
+ <el-table
+ v-loading="isWorking.search"
+ :data="tableData.list"
+ stripe
+ @selection-change="handleSelectionChange"
+ >
+ <el-table-column type="selection" width="48" />
+ <el-table-column label="棰勮" width="160" align="center">
+ <template slot-scope="{ row }">
+ <el-image
+ v-if="row.imageUrl"
+ :src="row.imageUrl"
+ :preview-src-list="[row.imageUrl]"
+ fit="cover"
+ class="banner-thumb"
+ />
+ <span v-else>-</span>
+ </template>
+ </el-table-column>
+ <el-table-column prop="title" label="鏍囬" min-width="140" show-overflow-tooltip />
+ <el-table-column prop="sortnum" label="鎺掑簭" width="80" align="center" />
+ <el-table-column label="鐘舵��" width="100" align="center">
+ <template slot-scope="{ row }">
+ <el-switch
+ v-model="row.status"
+ :active-value="0"
+ :inactive-value="1"
+ :disabled="!containPermissions(['business:ywh5banner:update'])"
+ @change="changeStatus(row)"
+ />
+ </template>
+ </el-table-column>
+ <el-table-column prop="linkUrl" label="璺宠浆閾炬帴" min-width="160" show-overflow-tooltip />
+ <el-table-column prop="editDate" label="鏇存柊鏃堕棿" width="168" align="center" />
+ <el-table-column label="鎿嶄綔" width="140" align="center" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button
+ type="text"
+ @click="$refs.editWindow.open('缂栬緫杞挱鍥�', row)"
+ v-permissions="['business:ywh5banner:update']"
+ >缂栬緫</el-button>
+ <el-button
+ type="text"
+ class="red"
+ @click="deleteById(row)"
+ v-permissions="['business:ywh5banner:delete']"
+ >鍒犻櫎</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+
+ <pagination
+ @size-change="handleSizeChange"
+ @current-change="handlePageChange"
+ :pagination="tableData.pagination"
+ />
+ </template>
+
+ <YwH5BannerEdit ref="editWindow" @success="handlePageChange" />
+ </TableLayout>
+</template>
+
+<script>
+import BaseTable from '@/components/base/BaseTable'
+import TableLayout from '@/layouts/TableLayout'
+import Pagination from '@/components/common/Pagination'
+import YwH5BannerEdit from './components/YwH5BannerEdit'
+import { updateById } from '@/api/business/ywh5banner'
+import { Message } from 'element-ui'
+
+export default {
+ name: 'YwH5Banner',
+ extends: BaseTable,
+ components: { TableLayout, Pagination, YwH5BannerEdit },
+ data () {
+ return {
+ searchForm: {
+ title: '',
+ status: null
+ }
+ }
+ },
+ created () {
+ this.config({
+ module: 'H5杞挱鍥�',
+ api: '/business/ywh5banner',
+ 'field.id': 'id',
+ 'field.main': 'title'
+ })
+ this.search()
+ },
+ methods: {
+ buildSearchModel () {
+ const model = {}
+ if (this.searchForm.title) model.title = this.searchForm.title
+ if (this.searchForm.status !== null && this.searchForm.status !== '') {
+ model.status = this.searchForm.status
+ }
+ return model
+ },
+ reset () {
+ this.searchForm = { title: '', status: null }
+ this.search()
+ },
+ handlePageChange (pageIndex) {
+ this.tableData.pagination.pageIndex = pageIndex || this.tableData.pagination.pageIndex
+ this.isWorking.search = true
+ this.api.fetchList({
+ page: this.tableData.pagination.pageIndex,
+ capacity: this.tableData.pagination.pageSize,
+ model: this.buildSearchModel(),
+ sorts: this.tableData.sorts
+ })
+ .then(data => {
+ this.tableData.list = data.records
+ this.tableData.pagination.total = data.total
+ })
+ .catch(() => {})
+ .finally(() => { this.isWorking.search = false })
+ },
+ changeStatus (row) {
+ updateById({ id: row.id, status: row.status })
+ .then(() => {
+ Message.success('鐘舵�佸凡鏇存柊')
+ })
+ .catch(() => {
+ row.status = row.status === 0 ? 1 : 0
+ })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.banner-thumb {
+ width: 128px;
+ height: 56px;
+ border-radius: 4px;
+ border: 1px solid #ebeef5;
+}
+
+.red {
+ color: #f56c6c;
+}
+</style>
diff --git a/admin/src/views/client/components/OperaYwCustomerWindow.vue b/admin/src/views/client/components/OperaYwCustomerWindow.vue
index c966c3f..bc8ada4 100644
--- a/admin/src/views/client/components/OperaYwCustomerWindow.vue
+++ b/admin/src/views/client/components/OperaYwCustomerWindow.vue
@@ -171,6 +171,47 @@
})
},
methods: {
+ defaultForm() {
+ return {
+ id: null,
+ validity: '',
+ creator: '',
+ createDate: '',
+ editor: '',
+ editDate: '',
+ isdeleted: '',
+ remark: '',
+ industryId: '',
+ type: 1,
+ name: '',
+ phone: '',
+ idcardNo: '',
+ idcardDecode: '',
+ code: '',
+ status: '',
+ memberName: '',
+ lastLoginDate: '',
+ loginNum: '',
+ userId: '',
+ memberId: '',
+ accountBank: '',
+ accountNo: '',
+ accountPhone: '',
+ creditCard: '',
+ fpType: '',
+ accountAddr: '',
+ email: '',
+ selLangTime: false,
+ member: {
+ name: '',
+ phone: '',
+ highCheckor: 0,
+ idcardType: 0,
+ idcardNo: '',
+ email: ''
+ }
+ }
+ },
openTrade() {
this.$refs.OperaCategoryWindowRef.open('鏂板琛屼笟')
},
@@ -189,37 +230,31 @@
this.cateList = res
})
},
+ applyDetail(res) {
+ const base = this.defaultForm()
+ this.form = Object.assign(base, res, {
+ member: Object.assign({}, base.member, res.member || {})
+ })
+ this.form.selLangTime = this.form.validity === '2099-12-31'
+ },
open(title, target) {
this.title = title
this.visible = true
this.initData()
- // 鏂板缓
+ this.form = this.defaultForm()
+ this.clientList = []
+ this.$nextTick(() => {
+ this.$refs.form && this.$refs.form.clearValidate()
+ })
if (target == null) {
- this.$nextTick(() => {
- this.$refs.form.resetFields()
- this.form.validity = ''
- this.form.id = ''
- this.form.member = {
- name: "",
- phone: "",
- highCheckor: 0,
- idcardType: 0,
- idcardNo: '',
- email: '',
- }
- })
- this.form.type = 1
return
}
- // 缂栬緫
- this.$nextTick(() => {
- if (title == '缂栬緫瀹㈡埛') {
- this.getClient(target.id)
- detailById(target.id).then(res => {
- this.form = res
- })
- }
- })
+ if (title === '缂栬緫瀹㈡埛') {
+ this.getClient(target.id)
+ detailById(target.id).then(res => {
+ this.applyDetail(res)
+ })
+ }
},
getClient(customerId) {
fetchList({
diff --git a/admin/src/views/client/components/staffEdit.vue b/admin/src/views/client/components/staffEdit.vue
index ffe8cd5..4390a98 100644
--- a/admin/src/views/client/components/staffEdit.vue
+++ b/admin/src/views/client/components/staffEdit.vue
@@ -1,53 +1,105 @@
<template>
- <GlobalWindow :title="title" width="820px" :visible.sync="visible" :confirm-working="isWorking" @confirm="confirm">
- <el-form :model="form" ref="form" label-position="top" :rules="rules">
- <div class="list">
- <el-form-item label="瀹㈡埛鍚嶇О" prop="customerId">
- <el-select v-model="form.customerId" :disabled="form.id || customerId != ''" clearable filterable>
- <el-option v-for="item in clientList" :value="item.id" :label="item.name" />
- </el-select>
- </el-form-item>
- <el-form-item label="濮撳悕" prop="name">
- <div class="df">
- <el-input v-model="form.name" placeholder="璇疯緭鍏�" v-trim />
- </div>
- </el-form-item>
- <el-form-item label="鎵嬫満鍙�" prop="phone">
- <el-input v-model="form.phone" placeholder="璇疯緭鍏ユ墜鏈哄彿" v-trim />
- </el-form-item>
- <el-form-item label="韬唤">
- <el-select v-model="form.highCheckor" filterable>
- <el-option :value="0" label="鑰佹澘/瓒呯骇绠$悊鍛�" />
- <el-option :value="1" label="浜轰簨/绠$悊鍛�" />
- <el-option :value="2" label="鍛樺伐/鏅�氬憳宸�" />
- </el-select>
- </el-form-item>
- <el-form-item label="璇佷欢绫诲瀷">
- <el-select v-model="form.idcardType" filterable>
- <el-option :value="0" label="韬唤璇�" />
- <el-option :value="1" label="娓境璇佷欢" />
- <el-option :value="2" label="鎶ょ収" />
- </el-select>
- </el-form-item>
- <el-form-item label="璇佷欢鍙风爜" prop="member.idcardNo">
- <el-input v-model="form.idcardNo" placeholder="璇疯緭鍏�" v-trim />
- </el-form-item>
- <el-form-item label="閭" prop="email" :rules="[
- { required: false, type: 'email', message: '璇疯緭鍏ユ纭殑閭鏍煎紡'}
- ]">
- <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" v-trim />
- </el-form-item>
- <el-form-item label="鎬у埆">
- <el-select v-model="form.sex" filterable>
- <el-option :value="1" label="鐢�" />
- <el-option :value="2" label="濂�" />
- </el-select>
- </el-form-item>
- <el-form-item label="鍑虹敓鏃ユ湡">
- <el-date-picker type="date" v-model="form.birthday" value-format="yyyy-MM-dd" placeholder="璇烽�夋嫨" />
- </el-form-item>
+ <GlobalWindow :title="title" width="720px" :visible.sync="visible" :confirm-working="isWorking" @confirm="confirm">
+ <el-form
+ ref="form"
+ :model="form"
+ :rules="rules"
+ label-width="88px"
+ label-position="right"
+ class="staff-edit-form"
+ >
+ <section class="form-section">
+ <div class="form-section__title">褰掑睘淇℃伅</div>
+ <div class="form-grid">
+ <el-form-item label="瀹㈡埛鍚嶇О" prop="customerId" class="col-full">
+ <el-select
+ v-model="form.customerId"
+ :disabled="form.id || customerId != ''"
+ clearable
+ filterable
+ placeholder="璇烽�夋嫨瀹㈡埛"
+ >
+ <el-option v-for="item in clientList" :key="item.id" :value="item.id" :label="item.name" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="韬唤">
+ <el-select v-model="form.highCheckor" filterable placeholder="璇烽�夋嫨韬唤">
+ <el-option :value="0" label="鑰佹澘/瓒呯骇绠$悊鍛�" />
+ <el-option :value="1" label="浜轰簨/绠$悊鍛�" />
+ <el-option :value="2" label="鍛樺伐/鏅�氬憳宸�" />
+ </el-select>
+ </el-form-item>
+ </div>
+ </section>
- </div>
+ <section class="form-section">
+ <div class="form-section__title">鍩烘湰淇℃伅</div>
+ <div class="form-grid">
+ <el-form-item label="濮撳悕" prop="name">
+ <el-input v-model="form.name" placeholder="璇疯緭鍏ュ鍚�" v-trim />
+ </el-form-item>
+ <el-form-item label="鎵嬫満鍙�" prop="phone">
+ <el-input v-model="form.phone" placeholder="璇疯緭鍏ユ墜鏈哄彿" v-trim maxlength="11" />
+ </el-form-item>
+ <el-form-item label="鎬у埆">
+ <el-select v-model="form.sex" filterable clearable placeholder="璇烽�夋嫨鎬у埆">
+ <el-option :value="1" label="鐢�" />
+ <el-option :value="2" label="濂�" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鍑虹敓鏃ユ湡">
+ <el-date-picker
+ v-model="form.birthday"
+ type="date"
+ value-format="yyyy-MM-dd"
+ placeholder="璇烽�夋嫨鍑虹敓鏃ユ湡"
+ />
+ </el-form-item>
+ <el-form-item
+ label="閭"
+ prop="email"
+ class="col-full"
+ :rules="[{ required: false, type: 'email', message: '璇疯緭鍏ユ纭殑閭鏍煎紡' }]"
+ >
+ <el-input v-model="form.email" placeholder="璇疯緭鍏ラ偖绠�" v-trim />
+ </el-form-item>
+ </div>
+ </section>
+
+ <section class="form-section">
+ <div class="form-section__title">璇佷欢淇℃伅</div>
+ <div class="form-grid">
+ <el-form-item label="璇佷欢绫诲瀷">
+ <el-select v-model="form.idcardType" filterable placeholder="璇烽�夋嫨璇佷欢绫诲瀷">
+ <el-option :value="0" label="韬唤璇�" />
+ <el-option :value="1" label="娓境璇佷欢" />
+ <el-option :value="2" label="鎶ょ収" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="璇佷欢鍙风爜" :prop="form.id ? '' : 'idcardNo'" class="col-full">
+ <el-input
+ v-if="!form.id"
+ v-model="form.idcardNo"
+ placeholder="璇疯緭鍏ヨ瘉浠跺彿鐮�"
+ v-trim
+ clearable
+ />
+ <div v-else class="idcard-field">
+ <div class="idcard-field__current">
+ <i class="el-icon-bank-card" />
+ <span class="idcard-field__label">褰撳墠璇佷欢鍙�</span>
+ <span class="idcard-field__value">{{ form.idcardDecode || '鏈櫥璁�' }}</span>
+ </div>
+ <el-input
+ v-model="form.idcardNoNew"
+ placeholder="濡傞渶鍙樻洿锛岃杈撳叆鏂拌瘉浠跺彿锛堢暀绌鸿〃绀轰笉淇敼锛�"
+ v-trim
+ clearable
+ />
+ </div>
+ </el-form-item>
+ </div>
+ </section>
</el-form>
</GlobalWindow>
</template>
@@ -55,17 +107,15 @@
<script>
import BaseOpera from '@/components/base/BaseOpera'
import GlobalWindow from '@/components/common/GlobalWindow'
-import { fetchCateList } from '@/api/business/category'
import { staffRules } from './config'
-import { detailById } from '@/api/client/ywCustomer'
import { fetchList } from '@/api/client/ywCustomer'
+
export default {
name: 'OperaYwCustomerWindow',
extends: BaseOpera,
components: { GlobalWindow },
data() {
return {
- // 琛ㄥ崟鏁版嵁
form: {
id: null,
customerId: '',
@@ -81,28 +131,25 @@
sex: '',
status: '',
memberName: '',
-
lastLoginDate: '',
loginNum: '',
userId: '',
-
accountBank: '',
accountNo: '',
accountPhone: '',
creditCard: '',
fpType: '',
accountAddr: '',
- // identityType: '0',
- name: "",
- phone: "",
+ name: '',
+ phone: '',
highCheckor: 0,
idcardType: 0,
idcardNo: '',
- email: '',
+ idcardNoNew: '',
+ email: ''
},
customerId: '',
clientList: [],
- // 楠岃瘉瑙勫垯
rules: staffRules
}
},
@@ -112,7 +159,6 @@
'field.id': 'id'
})
},
-
methods: {
initData() {
fetchList({
@@ -127,11 +173,7 @@
this.title = title
this.visible = true
this.customerId = ''
- console.log(this.customerId);
- console.log(this.form.id);
-
this.initData()
- // 鏂板缓
if (target == null) {
this.$nextTick(() => {
this.form = {
@@ -149,70 +191,147 @@
sex: '',
status: '',
memberName: '',
-
lastLoginDate: '',
loginNum: '',
userId: '',
-
accountBank: '',
accountNo: '',
accountPhone: '',
creditCard: '',
fpType: '',
accountAddr: '',
- // identityType: '0',
- name: "",
- phone: "",
+ name: '',
+ phone: '',
highCheckor: 0,
idcardType: 0,
idcardNo: '',
- email: '',
+ idcardNoNew: '',
+ email: ''
}
})
- // this.$refs.form.resetFields()
return
}
- // 缂栬緫
this.$nextTick(() => {
for (const key in this.form) {
this.form[key] = target[key]
}
+ this.form.idcardNo = ''
+ this.form.idcardNoNew = ''
})
},
changeValid(e) {
this.$set(this.form, 'validity', e ? '2099-12-31' : '')
- },
+ }
}
}
</script>
-<style lang='scss' scoped>
+
+<style lang="scss" scoped>
@import '@/assets/style/variables.scss';
-div {
- box-sizing: border-box;
+.staff-edit-form {
+ padding-top: 4px;
+
+ ::v-deep .el-form-item {
+ margin-bottom: 14px;
+ }
+
+ ::v-deep .el-form-item__label {
+ color: #606266;
+ font-size: 13px;
+ line-height: 32px;
+ padding-right: 12px;
+ }
+
+ ::v-deep .el-input,
+ ::v-deep .el-select,
+ ::v-deep .el-date-editor {
+ width: 100%;
+ }
}
-.title {
- width: 100%;
- font-weight: 500;
- font-size: 15px;
- margin-top: 16px;
-}
+.form-section {
+ margin-bottom: 14px;
+ padding: 16px 16px 2px;
+ background: #fff;
+ border: 1px solid #ebeef5;
+ border-radius: 6px;
-.list {
- /* padding-top: 14px; */
- display: flex;
- flex-wrap: wrap;
+ &:last-child {
+ margin-bottom: 0;
+ }
- .el-form-item {
- width: 33.33%;
- margin-bottom: 12px;
- padding: 0 12px;
+ &__title {
+ margin-bottom: 14px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #f0f2f5;
+ font-size: 14px;
+ font-weight: 600;
+ color: #303133;
+ line-height: 22px;
- .la {
- color: #7f7f7f;
- margin-top: 2px;
+ &::before {
+ content: '';
+ display: inline-block;
+ width: 3px;
+ height: 14px;
+ margin-right: 8px;
+ background: $primary-color;
+ border-radius: 2px;
+ vertical-align: -2px;
}
}
}
+
+.form-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ column-gap: 20px;
+
+ .col-full {
+ grid-column: 1 / -1;
+ }
+}
+
+.idcard-field {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ &__current {
+ display: flex;
+ align-items: center;
+ min-height: 32px;
+ padding: 0 12px;
+ background: linear-gradient(90deg, #f8fafc 0%, #f5f7fa 100%);
+ border: 1px solid #e4e7ed;
+ border-radius: 4px;
+ font-size: 13px;
+ color: #606266;
+ }
+
+ .el-icon-bank-card {
+ margin-right: 6px;
+ font-size: 15px;
+ color: $primary-color;
+ }
+
+ &__label {
+ flex-shrink: 0;
+ margin-right: 8px;
+ color: #909399;
+ font-size: 12px;
+ }
+
+ &__value {
+ flex: 1;
+ min-width: 0;
+ color: #303133;
+ font-weight: 500;
+ letter-spacing: 0.4px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+}
</style>
diff --git a/admin/src/views/client/staffList.vue b/admin/src/views/client/staffList.vue
index fdb031d..4bc1161 100644
--- a/admin/src/views/client/staffList.vue
+++ b/admin/src/views/client/staffList.vue
@@ -10,6 +10,9 @@
<el-form-item prop="name">
<el-input v-model="searchForm.name" placeholder="璇疯緭鍏ヤ汉鍛樺鍚�/鎵嬫満鍙�" @keypress.enter.native="search"></el-input>
</el-form-item>
+ <el-form-item label="韬唤璇佸彿" prop="idcardNo">
+ <el-input v-model="searchForm.idcardNo" placeholder="璇疯緭鍏ヨ韩浠借瘉鍙�" @keypress.enter.native="search"></el-input>
+ </el-form-item>
<section>
<el-button type="primary" @click="search">鎼滅储</el-button>
<el-button type="primary" :loading="isWorking.export" v-permissions="['business:ywcustomer:exportExcel']"
@@ -29,6 +32,7 @@
<el-table-column prop="customerName" label="瀹㈡埛鍚嶇О" min-width="100px"></el-table-column>
<el-table-column prop="name" label="鑱旂郴浜�" min-width="100px"></el-table-column>
<el-table-column prop="phone" label="鑱旂郴鐢佃瘽" min-width="100px"></el-table-column>
+ <el-table-column prop="idcardDecode" label="韬唤璇佸彿" min-width="140px"></el-table-column>
<el-table-column prop="" label="韬唤" min-width="100px">
<template slot-scope="{row}">
<span v-if="row.highCheckor == 0">鑰佹澘/瓒呯骇绠$悊鍛�</span>
@@ -82,6 +86,7 @@
searchForm: {
customerId: '',
name: '',
+ idcardNo: '',
},
clientList: []
}
diff --git a/h5/App.vue b/h5/App.vue
index 526bb3b..2e698cd 100644
--- a/h5/App.vue
+++ b/h5/App.vue
@@ -15,6 +15,7 @@
<style lang="scss">
/*姣忎釜椤甸潰鍏叡css */
@import "uview-ui/index.scss";
+@import "./styles/customer.scss";
// @import "./uni_modules/uview-ui/index.scss";
body{
font-size: 28rpx;
diff --git a/h5/api/customer.js b/h5/api/customer.js
new file mode 100644
index 0000000..7ca8f0d
--- /dev/null
+++ b/h5/api/customer.js
@@ -0,0 +1,23 @@
+import { http } from '@/utils/service.js'
+
+const prefix = 'visitsAdmin/cloudService/web/customer'
+
+export const customerLogin = (data) => http({ url: `${prefix}/loginByPhone`, method: 'post', data })
+export const customerSendLoginSms = (data) => http({
+ url: `${prefix}/sendLoginSms`,
+ method: 'post',
+ data: { phone: data.phone }
+})
+export const customerGetUserInfo = () => http({ url: `${prefix}/getUserInfo`, method: 'get' })
+export const customerWxAuthorize = (data) => http({ url: 'visitsAdmin/cloudService/web/visitor/ywWxAuthorize', method: 'get', data: { ...data, userType: 1 } })
+export const customerBanners = () => http({ url: `${prefix}/banners`, method: 'get' })
+export const customerHome = () => http({ url: `${prefix}/home`, method: 'get' })
+export const customerDevicePage = (data) => http({ url: `${prefix}/device/page`, method: 'post', data })
+export const customerDeviceDetail = (data) => http({ url: `${prefix}/device/detail`, method: 'get', data })
+export const customerRechargeRecordPage = (data) => http({ url: `${prefix}/rechargeRecord/page`, method: 'post', data })
+export const customerContractPage = (data) => http({ url: `${prefix}/contract/page`, method: 'post', data })
+export const customerContractDetail = (id, billType = 0) => http({ url: `${prefix}/contract/${id}`, method: 'get', data: { billType } })
+export const customerBillPage = (data) => http({ url: `${prefix}/bill/page`, method: 'post', data })
+export const customerBillDetail = (id) => http({ url: `${prefix}/bill/${id}`, method: 'get' })
+export const customerPayCreate = (data) => http({ url: `${prefix}/pay/createOrder`, method: 'post', data })
+export const customerPayQuery = (orderNo) => http({ url: `${prefix}/pay/query/${orderNo}`, method: 'get' })
diff --git a/h5/api/index.js b/h5/api/index.js
index c931054..8e9931f 100644
--- a/h5/api/index.js
+++ b/h5/api/index.js
@@ -2,6 +2,7 @@
export * from '@/utils/config.js'
export * from './staff'
export * from './yw'
+export * from './customer'
diff --git a/h5/pages.json b/h5/pages.json
index f5f619d..0ad9c86 100644
--- a/h5/pages.json
+++ b/h5/pages.json
@@ -2,13 +2,17 @@
"easycom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},
- "pages": [
- {
- "path": "pages/login",
- "style": {
- "navigationBarTitleText": "鐧诲綍"
- }
- },
+ "pages": [
+ {
+ "path": "pages/roleSelect",
+ "style": { "navigationBarTitleText": "閫夋嫨韬唤" }
+ },
+ {
+ "path": "pages/login",
+ "style": {
+ "navigationBarTitleText": "鐧诲綍"
+ }
+ },
{
"path": "pages/index",
"style": {
@@ -27,105 +31,114 @@
"style": {
"navigationBarTitleText": "宸ュ崟璇︽儏"
}
- },
- {
- "path" : "pages/workOrder/edit",
- "style" :
- {
- "navigationBarTitleText" : "鏂板宸ュ崟"
- }
- },
- {
- "path" : "pages/operation/record",
- "style" :
- {
- "navigationBarTitleText" : "杩愮淮璁板綍"
- }
- },
- {
- "path" : "pages/operation/detail",
- "style" :
- {
- "navigationBarTitleText" : "杩愮淮璇︽儏"
- }
- },
- {
- "path" : "pages/operation/device",
- "style" :
- {
- "navigationBarTitleText" : "璁惧杩愮淮"
- }
- },
- {
- "path" : "pages/polling/task",
- "style" :
- {
- "navigationBarTitleText" : "宸℃浠诲姟"
- }
- },
- {
- "path" : "pages/polling/detail",
- "style" :
- {
- "navigationBarTitleText" : "浠诲姟璇︽儏"
- }
- },
- {
- "path" : "pages/polling/point",
- "style" :
- {
- "navigationBarTitleText" : "宸℃鐐�"
- }
- },
- {
- "path" : "pages/common/memberSel",
- "style" :
- {
- "navigationBarTitleText" : "閫夋嫨浜哄憳"
- }
- },
- {
- "path" : "pages/inventory/index",
- "style" :
- {
- "navigationBarTitleText" : "搴撳瓨鐩樼偣"
- }
- },
- {
- "path" : "pages/inventory/detail",
- "style" :
- {
- "navigationBarTitleText" : "鐩樼偣鍗�"
- }
- },
- {
- "path" : "pages/workOrder/problemEdit",
- "style" :
- {
- "navigationBarTitleText" : "闂涓婃姤"
- }
- },
- {
- "path" : "pages/workOrder/result",
- "style" :
- {
- "navigationBarTitleText" : "闂涓婃姤"
- }
- },
- {
- "path" : "pages/workOrder/wait",
- "style" :
- {
- "navigationBarTitleText" : "浠诲姟涓績"
- }
- },
- {
- "path" : "pages/polling/empty",
- "style" :
- {
- "navigationBarTitleText" : "鎵爜宸℃"
- }
- }
+ },
+ {
+ "path" : "pages/workOrder/edit",
+ "style" :
+ {
+ "navigationBarTitleText" : "鏂板宸ュ崟"
+ }
+ },
+ {
+ "path" : "pages/operation/record",
+ "style" :
+ {
+ "navigationBarTitleText" : "杩愮淮璁板綍"
+ }
+ },
+ {
+ "path" : "pages/operation/detail",
+ "style" :
+ {
+ "navigationBarTitleText" : "杩愮淮璇︽儏"
+ }
+ },
+ {
+ "path" : "pages/operation/device",
+ "style" :
+ {
+ "navigationBarTitleText" : "璁惧杩愮淮"
+ }
+ },
+ {
+ "path" : "pages/polling/task",
+ "style" :
+ {
+ "navigationBarTitleText" : "宸℃浠诲姟"
+ }
+ },
+ {
+ "path" : "pages/polling/detail",
+ "style" :
+ {
+ "navigationBarTitleText" : "浠诲姟璇︽儏"
+ }
+ },
+ {
+ "path" : "pages/polling/point",
+ "style" :
+ {
+ "navigationBarTitleText" : "宸℃鐐�"
+ }
+ },
+ {
+ "path" : "pages/common/memberSel",
+ "style" :
+ {
+ "navigationBarTitleText" : "閫夋嫨浜哄憳"
+ }
+ },
+ {
+ "path" : "pages/inventory/index",
+ "style" :
+ {
+ "navigationBarTitleText" : "搴撳瓨鐩樼偣"
+ }
+ },
+ {
+ "path" : "pages/inventory/detail",
+ "style" :
+ {
+ "navigationBarTitleText" : "鐩樼偣鍗�"
+ }
+ },
+ {
+ "path" : "pages/workOrder/problemEdit",
+ "style" :
+ {
+ "navigationBarTitleText" : "闂涓婃姤"
+ }
+ },
+ {
+ "path" : "pages/workOrder/result",
+ "style" :
+ {
+ "navigationBarTitleText" : "闂涓婃姤"
+ }
+ },
+ {
+ "path" : "pages/workOrder/wait",
+ "style" :
+ {
+ "navigationBarTitleText" : "浠诲姟涓績"
+ }
+ },
+ {
+ "path": "pages/polling/empty",
+ "style": { "navigationBarTitleText": "鎵爜宸℃" }
+ },
+ { "path": "pages/customer/login", "style": { "navigationBarTitleText": "鍟嗘埛鐧诲綍", "navigationBarBackgroundColor": "#dce9ff" } },
+ { "path": "pages/customer/index", "style": { "navigationBarTitleText": "鍟嗘埛宸ヤ綔鍙�", "navigationBarBackgroundColor": "#2080f7", "navigationBarTextStyle": "white" } },
+ { "path": "pages/customer/electricity/list", "style": { "navigationBarTitleText": "浜ょ數璐�", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/electricity/recharge", "style": { "navigationBarTitleText": "鐢佃垂鍏呭��", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/conditioner/recharge", "style": { "navigationBarTitleText": "绌鸿皟鍏呭��", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/pay/result", "style": { "navigationBarTitleText": "鏀粯缁撴灉", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/recharge/record", "style": { "navigationBarTitleText": "鍏呭�艰褰�", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/contract/list", "style": { "navigationBarTitleText": "鎴戠殑鍚堝悓", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/contract/detail", "style": { "navigationBarTitleText": "鍚堝悓璇︽儏", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/bill/list", "style": { "navigationBarTitleText": "鎴戠殑璐﹀崟", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/bill/detail", "style": { "navigationBarTitleText": "璐﹀崟璇︽儏", "navigationBarBackgroundColor": "#f4f6fb" } },
+ { "path": "pages/customer/bill/pay", "style": { "navigationBarTitleText": "璐﹀崟缂磋垂", "navigationBarBackgroundColor": "#f4f6fb" } }
],
"globalStyle": {
"navigationBarTextStyle": "black",
diff --git a/h5/pages/customer/bill/detail.vue b/h5/pages/customer/bill/detail.vue
new file mode 100644
index 0000000..3638804
--- /dev/null
+++ b/h5/pages/customer/bill/detail.vue
@@ -0,0 +1,330 @@
+<template>
+
+ <view :class="['cu-page cu-bill-detail', needPayAmount > 0 ? 'cu-page--with-footer' : '']" v-if="bill">
+
+ <view :class="['cu-bill-detail-hero', heroClass]">
+
+ <view class="cu-bill-detail-hero__bg" />
+
+ <view class="cu-bill-detail-hero__content">
+
+ <view class="cu-bill-detail-hero__top">
+
+ <view>
+
+ <text class="cu-bill-detail-hero__type">{{ costTypeText(bill.costType) }}</text>
+
+ <view class="cu-bill-detail-hero__code">{{ bill.code }}</view>
+
+ </view>
+
+ <text class="cu-bill-detail-hero__status">{{ payText(bill.payStatus) }}</text>
+
+ </view>
+
+
+
+ <view class="cu-bill-detail-hero__amount-wrap">
+
+ <text class="cu-bill-detail-hero__amount-label">{{ needPayAmount > 0 ? '闇�浠橀噾棰�' : '搴斾粯閲戦' }}</text>
+
+ <text class="cu-bill-detail-hero__amount">
+
+ 楼{{ formatMoney(needPayAmount > 0 ? needPayAmount : bill.receivableFee) }}
+
+ </text>
+
+ <text v-if="bill.planPayDate" class="cu-bill-detail-hero__due">搴斾粯鏃ユ湡 {{ bill.planPayDate }}</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-bill-stat-row">
+
+ <view class="cu-bill-stat">
+
+ <text class="cu-bill-stat__label">搴斾粯閲戦</text>
+
+ <text class="cu-bill-stat__value cu-bill-stat__value--danger">楼{{ formatMoney(bill.receivableFee) }}</text>
+
+ </view>
+
+ <view class="cu-bill-stat">
+
+ <text class="cu-bill-stat__label">瀹炰粯閲戦</text>
+
+ <text class="cu-bill-stat__value">楼{{ formatMoney(paidAmount) }}</text>
+
+ </view>
+
+ <view class="cu-bill-stat">
+
+ <text class="cu-bill-stat__label">闇�浠橀噾棰�</text>
+
+ <text :class="['cu-bill-stat__value', needPayAmount > 0 ? 'cu-bill-stat__value--danger' : '']">
+
+ 楼{{ formatMoney(needPayAmount) }}
+
+ </text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-panel cu-bill-panel">
+
+ <view class="cu-panel__title">鍏宠仈鍚堝悓</view>
+
+ <view class="cu-bill-info-grid">
+
+ <view class="cu-bill-info-item">
+
+ <text class="cu-bill-info-item__label">鍚堝悓缂栧彿</text>
+
+ <text class="cu-bill-info-item__value">{{ bill.contractCode || '-' }}</text>
+
+ </view>
+
+ <view class="cu-bill-info-item cu-bill-info-item--full">
+
+ <text class="cu-bill-info-item__label">鍚堝悓鏈夋晥鏈�</text>
+
+ <text class="cu-bill-info-item__value">{{ contractPeriod(bill) }}</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+ <view class="cu-panel cu-bill-panel">
+ <view class="cu-panel__title">鎴挎簮淇℃伅</view>
+ <view v-if="bill.roomList && bill.roomList.length" class="cu-room-list">
+ <view v-for="(room, idx) in bill.roomList" :key="idx" class="cu-room-item">
+ <text class="cu-room-item__name">{{ formatRoomLine(room) }}</text>
+ <text class="cu-room-item__area">{{ formatArea(room.rentArea) }}</text>
+ </view>
+ </view>
+ <view v-else class="cu-bill-info-grid">
+ <view class="cu-bill-info-item cu-bill-info-item--full">
+ <text class="cu-bill-info-item__value cu-kv__value--muted">{{ bill.roomInfo || '鏆傛棤鎴挎簮淇℃伅' }}</text>
+ </view>
+ </view>
+ </view>
+
+ <view class="cu-panel cu-bill-panel">
+ <view class="cu-panel__title">璐﹀崟淇℃伅</view>
+
+ <view class="cu-bill-info-grid">
+
+ <view class="cu-bill-info-item">
+
+ <text class="cu-bill-info-item__label">璐圭敤绫诲瀷</text>
+
+ <text class="cu-bill-info-item__value">{{ costTypeText(bill.costType) }}</text>
+
+ </view>
+
+ <view class="cu-bill-info-item">
+
+ <text class="cu-bill-info-item__label">鏀粯鐘舵��</text>
+
+ <text class="cu-bill-info-item__value">{{ payText(bill.payStatus) }}</text>
+
+ </view>
+
+ <view class="cu-bill-info-item cu-bill-info-item--full">
+
+ <text class="cu-bill-info-item__label">璁¤垂鍛ㄦ湡</text>
+
+ <text class="cu-bill-info-item__value">{{ bill.startDate }} ~ {{ bill.endDate }}</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-panel cu-bill-panel">
+ <view class="cu-panel__title">鏀舵敮娴佹按</view>
+ <view v-if="revenues.length" class="cu-revenue-list">
+ <view v-for="r in revenues" :key="r.id" class="cu-revenue-card">
+ <view class="cu-revenue-card__head">
+ <text :class="['cu-revenue-card__type', r.revenueType === 0 ? 'cu-revenue-card__type--out' : '']">
+ {{ revenueTypeText(r.revenueType) }}
+ </text>
+ <text class="cu-revenue-card__amount">楼{{ formatMoney(r.actReceivableFee) }}</text>
+ </view>
+ <view class="cu-revenue-card__row">
+ <text class="cu-revenue-card__label">浠樻鏂�</text>
+ <text class="cu-revenue-card__value">{{ r.customerName || '-' }}</text>
+ </view>
+ <view class="cu-revenue-card__row">
+ <text class="cu-revenue-card__label">鏀舵鏂瑰紡</text>
+ <text class="cu-revenue-card__value">{{ payTypeText(r.payType) }}</text>
+ </view>
+ <view class="cu-revenue-card__row">
+ <text class="cu-revenue-card__label">鍏ヨ处鏃ユ湡</text>
+ <text class="cu-revenue-card__value">{{ formatDate(r.actPayDate) || '-' }}</text>
+ </view>
+ <view class="cu-revenue-card__row">
+ <text class="cu-revenue-card__label">鍒涘缓鏃堕棿</text>
+ <text class="cu-revenue-card__value">{{ formatTime(r.createDate) }}</text>
+ </view>
+ <view v-if="r.remark" class="cu-revenue-card__row">
+ <text class="cu-revenue-card__label">澶囨敞</text>
+ <text class="cu-revenue-card__value">{{ r.remark }}</text>
+ </view>
+ </view>
+ </view>
+ <view v-else class="cu-bill-empty-record cu-bill-empty-record--inset">
+ <text class="cu-bill-empty-record__icon">馃搵</text>
+ <text class="cu-bill-empty-record__text">鏆傛棤鏀舵敮娴佹按</text>
+ </view>
+ </view>
+
+
+
+ <view v-if="needPayAmount > 0" class="cu-page-footer cu-bill-detail-footer">
+
+ <view class="cu-bill-detail-footer__info">
+
+ <text class="cu-bill-detail-footer__label">寰呮敮浠�</text>
+
+ <text class="cu-bill-detail-footer__amount">楼{{ formatMoney(needPayAmount) }}</text>
+
+ </view>
+
+ <view class="cu-btn cu-btn--primary cu-bill-detail-footer__btn" @click="goPay">绔嬪嵆缂磋垂</view>
+
+ </view>
+
+ </view>
+
+</template>
+
+
+
+<script>
+
+import { customerBillDetail } from '@/api'
+
+
+
+const COST_TYPE_MAP = {
+
+ 0: '绉熻祦璐�',
+
+ 1: '鐗╀笟璐�',
+
+ 2: '绉熻祦鎶奸噾',
+
+ 3: '鐗╀笟鎶奸噾',
+
+ 4: '姘寸數璐�',
+
+ 5: '鏉傞」璐�',
+
+ 6: '鍏朵粬',
+
+ 7: '淇濊瘉閲�'
+
+}
+
+
+
+export default {
+
+ data () { return { id: null, bill: null, revenues: [], paidAmount: 0, needPayAmount: 0 } },
+
+ computed: {
+
+ heroClass () {
+
+ if (!this.bill) return ''
+
+ if (this.bill.payStatus === 1) return 'cu-bill-detail-hero--ok'
+
+ if (this.needPayAmount > 0) return 'cu-bill-detail-hero--warn'
+
+ return ''
+
+ }
+
+ },
+
+ onLoad (q) { this.id = q.id; this.load() },
+
+ methods: {
+
+ load () {
+
+ customerBillDetail(this.id).then(res => {
+ this.bill = res.data.bill
+ this.revenues = res.data.revenues || (res.data.bill && res.data.bill.ywContractRevenueList) || []
+ this.paidAmount = res.data.paidAmount != null ? res.data.paidAmount : (res.data.bill && res.data.bill.actReceivableFee)
+ this.needPayAmount = res.data.needPayAmount != null ? res.data.needPayAmount : (res.data.bill && res.data.bill.needReceivableFee)
+ })
+
+ },
+
+ costTypeText (type) { return COST_TYPE_MAP[type] || '璐﹀崟' },
+
+ formatMoney (val) {
+
+ if (val === null || val === undefined || val === '') return '0.00'
+
+ return Number(val).toFixed(2)
+
+ },
+
+ formatTime (t) { return t ? String(t).replace('T', ' ').substring(0, 19) : '-' },
+
+ contractPeriod (item) {
+ if (!item) return '-'
+ const start = this.formatDate(item.contractStartDate)
+ const end = this.formatDate(item.contractEndDate)
+ if (!start && !end) return '-'
+ return `${start || '-'} ~ ${end || '-'}`
+ },
+ formatDate (val) {
+ if (!val) return ''
+ return String(val).replace('T', ' ').substring(0, 10)
+ },
+ formatArea (area) {
+ if (area === null || area === undefined || area === '') return '-'
+ return `${area}銕
+ },
+ formatRoomLine (room) {
+ if (!room) return '-'
+ const parts = [room.projectName, room.buildingName, room.floorName, room.roomNum].filter(Boolean)
+ return parts.length ? parts.join('/') : '-'
+ },
+ revenueTypeText (type) {
+ if (type === 1) return '鏀舵'
+ return '浠樻'
+ },
+ payTypeText (type) {
+ const map = { 0: '鐜伴噾', 1: '缃戦摱杞处', 2: 'POS鏈�', 3: '鏀粯瀹�', 4: '寰俊', 5: '杞处鏀エ', 6: '鍏朵粬' }
+ return map[type] || '-'
+ },
+ payText (s) { return { 0: '寰呮敮浠�', 1: '宸叉敮浠�', 2: '閮ㄥ垎鏀粯' }[s] || '-' },
+
+ goPay () { uni.navigateTo({ url: `/pages/customer/bill/pay?id=${this.id}&amount=${this.needPayAmount}` }) }
+
+ }
+
+}
+
+</script>
+
+
diff --git a/h5/pages/customer/bill/list.vue b/h5/pages/customer/bill/list.vue
new file mode 100644
index 0000000..3f19a89
--- /dev/null
+++ b/h5/pages/customer/bill/list.vue
@@ -0,0 +1,263 @@
+<template>
+
+ <view class="cu-page cu-bill-page">
+
+ <view class="cu-bill-page__header">
+
+ <view class="cu-tabs cu-bill-tabs">
+
+ <view
+
+ v-for="(t, i) in tabs"
+
+ :key="i"
+
+ :class="['cu-tab', tabIdx === i ? 'cu-tab--active' : '']"
+
+ @click="tabIdx = i; load()"
+
+ >{{ t }}</view>
+
+ </view>
+
+ <view class="cu-bill-summary">
+
+ <text class="cu-bill-summary__count">{{ list.length }}</text>
+
+ <text class="cu-bill-summary__label">绗旇处鍗�</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-list-wrap">
+
+ <view
+
+ v-for="item in list"
+
+ :key="item.id"
+
+ class="cu-bill-card"
+
+ @click="goDetail(item.id)"
+
+ >
+
+ <view class="cu-bill-card__accent" :class="accentClass(item)" />
+
+
+
+ <view class="cu-bill-card__body">
+
+ <view class="cu-bill-card__head">
+
+ <view class="cu-bill-card__head-main">
+
+ <text class="cu-bill-card__type">{{ costTypeText(item.costType) }}</text>
+
+ <text class="cu-bill-card__code">{{ item.code }}</text>
+
+ </view>
+
+ <text :class="['cu-status', payStatusClass(item.payStatus)]">{{ payText(item.payStatus) }}</text>
+
+ </view>
+
+
+
+ <view class="cu-bill-card__amount-box">
+
+ <view class="cu-bill-card__amount-main">
+
+ <text class="cu-bill-card__amount-label">搴斾粯閲戦</text>
+
+ <text class="cu-bill-card__amount-value">楼{{ formatMoney(item.receivableFee) }}</text>
+
+ </view>
+
+ <view class="cu-bill-card__amount-side">
+
+ <text class="cu-bill-card__amount-side-label">瀹炰粯</text>
+
+ <text class="cu-bill-card__amount-side-value">楼{{ formatMoney(item.actReceivableFee) }}</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view v-if="isOverdue(item)" class="cu-bill-card__overdue">宸查�炬湡锛岃灏藉揩缂磋垂</view>
+
+
+
+ <view class="cu-bill-card__contract">
+
+ <view class="cu-bill-card__contract-row">
+
+ <text class="cu-bill-card__contract-label">鍚堝悓缂栧彿</text>
+
+ <text class="cu-bill-card__contract-value">{{ item.contractCode || '-' }}</text>
+
+ </view>
+
+ <view class="cu-bill-card__contract-row">
+
+ <text class="cu-bill-card__contract-label">鍚堝悓鏈夋晥鏈�</text>
+
+ <text class="cu-bill-card__contract-value">{{ contractPeriod(item) }}</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-bill-card__meta">
+ <view class="cu-bill-card__meta-item cu-bill-card__meta-item--full">
+ <text class="cu-bill-card__meta-label">璁¤垂鍛ㄦ湡</text>
+ <text class="cu-bill-card__meta-value">{{ item.startDate }} ~ {{ item.endDate }}</text>
+ </view>
+ </view>
+
+
+
+ <view class="cu-bill-card__foot">
+
+ <text class="cu-bill-card__foot-hint">鏌ョ湅璐﹀崟鏄庣粏涓庢敮浠樿褰�</text>
+
+ <text class="cu-bill-card__foot-link">璇︽儏 鈫�</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+ <u-empty v-if="!list.length" text="鏆傛棤璐﹀崟" margin-top="80" />
+
+ </view>
+
+ </view>
+
+</template>
+
+
+
+<script>
+
+import { customerBillPage } from '@/api'
+
+
+
+const COST_TYPE_MAP = {
+
+ 0: '绉熻祦璐�',
+
+ 1: '鐗╀笟璐�',
+
+ 2: '绉熻祦鎶奸噾',
+
+ 3: '鐗╀笟鎶奸噾',
+
+ 4: '姘寸數璐�',
+
+ 5: '鏉傞」璐�',
+
+ 6: '鍏朵粬',
+
+ 7: '淇濊瘉閲�'
+
+}
+
+
+
+export default {
+
+ data () { return { list: [], tabIdx: 0, tabs: ['鍏ㄩ儴', '寰呮敮浠�', '宸叉敮浠�'] } },
+
+ onShow () { this.load() },
+
+ methods: {
+
+ load () {
+
+ const payTab = this.tabIdx === 0 ? null : (this.tabIdx === 1 ? 0 : 1)
+
+ customerBillPage({ page: 1, capacity: 50, model: { payTab } })
+
+ .then(res => { this.list = (res.data && res.data.records) || [] })
+
+ },
+
+ costTypeText (type) { return COST_TYPE_MAP[type] || '璐﹀崟' },
+
+ formatMoney (val) {
+
+ if (val === null || val === undefined || val === '') return '0.00'
+
+ return Number(val).toFixed(2)
+
+ },
+
+ payText (s) { return { 0: '寰呮敮浠�', 1: '宸叉敮浠�', 2: '閮ㄥ垎鏀粯' }[s] || '-' },
+
+ payStatusClass (s) {
+
+ if (s === 1) return 'cu-status--ok'
+
+ if (s === 0) return 'cu-status--warn'
+
+ return 'cu-status--muted'
+
+ },
+
+ accentClass (item) {
+
+ if (item.payStatus === 1) return 'cu-bill-card__accent--ok'
+
+ if (this.isOverdue(item)) return 'cu-bill-card__accent--danger'
+
+ if (item.payStatus === 0) return 'cu-bill-card__accent--warn'
+
+ return ''
+
+ },
+
+ isOverdue (item) {
+
+ if (!item || item.payStatus === 1 || !item.planPayDate) return false
+
+ const today = new Date()
+
+ today.setHours(0, 0, 0, 0)
+
+ const due = new Date(String(item.planPayDate).replace(/-/g, '/'))
+
+ return due < today
+
+ },
+
+ contractPeriod (item) {
+ if (!item) return '-'
+ const start = this.formatDate(item.contractStartDate)
+ const end = this.formatDate(item.contractEndDate)
+ if (!start && !end) return '-'
+ return `${start || '-'} ~ ${end || '-'}`
+ },
+ formatDate (val) {
+ if (!val) return ''
+ return String(val).replace('T', ' ').substring(0, 10)
+ },
+ goDetail (id) { uni.navigateTo({ url: `/pages/customer/bill/detail?id=${id}` }) }
+
+ }
+
+}
+
+</script>
+
+
diff --git a/h5/pages/customer/bill/pay.vue b/h5/pages/customer/bill/pay.vue
new file mode 100644
index 0000000..9910c28
--- /dev/null
+++ b/h5/pages/customer/bill/pay.vue
@@ -0,0 +1,60 @@
+<template>
+ <view class="cu-page cu-page--with-footer" v-if="billId">
+ <view class="cu-detail-hero cu-detail-hero--warm">
+ <view class="cu-detail-hero__label">鏈缂磋垂</view>
+ <view class="cu-detail-hero__amount">
+ <text class="cu-detail-hero__amount-value">楼{{ amount || '0.00' }}</text>
+ </view>
+ </view>
+
+ <view class="cu-pay-amount-box">
+ <view class="cu-pay-amount-box__label">璋冩暣缂磋垂閲戦</view>
+ <view class="cu-pay-amount-box__input-wrap">
+ <text class="cu-pay-amount-box__symbol">楼</text>
+ <input v-model="amount" class="cu-pay-amount-box__input" type="digit" placeholder="0.00" />
+ </view>
+ <view class="cu-pay-amount-box__remark">
+ <text class="cu-pay-amount-box__remark-label">澶囨敞</text>
+ <input v-model="remark" placeholder="閫夊~" />
+ </view>
+ </view>
+
+ <view class="cu-page-footer">
+ <view class="cu-btn cu-btn--primary" @click="submit">纭缂磋垂{{ amount ? ' 楼' + amount : '' }}</view>
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerBillDetail, customerPayCreate } from '@/api'
+import { invokeWxPay } from '@/utils/wxpay.js'
+export default {
+ data () { return { billId: null, amount: '', remark: '' } },
+ onLoad (q) {
+ this.billId = q.id
+ this.amount = q.amount || ''
+ if (!this.amount) {
+ customerBillDetail(q.id).then(res => { this.amount = String(res.data.needPayAmount || '') })
+ }
+ },
+ methods: {
+ submit () {
+ if (!this.amount) return uni.showToast({ title: '璇疯緭鍏ラ噾棰�', icon: 'none' })
+ customerPayCreate({
+ orderType: 2,
+ billId: Number(this.billId),
+ amount: Number(this.amount),
+ remark: this.remark,
+ openid: this.$store.state.openId
+ }).then(async res => {
+ try {
+ await invokeWxPay(res.data)
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=1&orderNo=${res.data.orderNo}&type=bill&billId=${this.billId}` })
+ } catch (e) {
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=0&orderNo=${res.data.orderNo}&type=bill` })
+ }
+ })
+ }
+ }
+}
+</script>
diff --git a/h5/pages/customer/conditioner/recharge.vue b/h5/pages/customer/conditioner/recharge.vue
new file mode 100644
index 0000000..9c06412
--- /dev/null
+++ b/h5/pages/customer/conditioner/recharge.vue
@@ -0,0 +1,77 @@
+<template>
+ <view class="cu-page cu-page--with-footer">
+ <view class="cu-device-summary" v-if="device">
+ <view class="cu-row cu-row--between">
+ <text class="cu-name">{{ device.deviceName }}</text>
+ <text class="cu-status cu-status--ok">{{ device.statusText }}</text>
+ </view>
+ <view class="cu-line">鎴块棿锛歿{ device.roomInfo }}</view>
+ <view class="cu-device-summary__balance">
+ <view class="cu-device-summary__balance-label">褰撳墠璐︽埛浣欓</view>
+ <view class="cu-device-summary__balance-value">{{ device.balance }}</view>
+ </view>
+ </view>
+
+ <view class="cu-pay-amount-box">
+ <view class="cu-pay-amount-box__label">鍏呭�奸噾棰�</view>
+ <view class="cu-pay-amount-box__input-wrap">
+ <text class="cu-pay-amount-box__symbol">楼</text>
+ <input v-model="amount" class="cu-pay-amount-box__input" type="digit" placeholder="0.00" />
+ </view>
+ <view class="cu-quick-amounts">
+ <text
+ v-for="q in quickAmounts"
+ :key="q"
+ :class="['cu-quick-amount', String(amount) === String(q) ? 'cu-quick-amount--active' : '']"
+ @click="amount = String(q)"
+ >{{ q }}鍏�</text>
+ </view>
+ <view class="cu-pay-amount-box__remark">
+ <text class="cu-pay-amount-box__remark-label">澶囨敞</text>
+ <input v-model="remark" placeholder="閫夊~" />
+ </view>
+ </view>
+
+ <view class="cu-page-footer">
+ <view class="cu-btn cu-btn--primary" @click="submit">纭鍏呭�納{ amount ? ' 楼' + amount : '' }}</view>
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerDeviceDetail, customerPayCreate } from '@/api'
+import { invokeWxPay } from '@/utils/wxpay.js'
+export default {
+ data () {
+ return {
+ deviceId: null,
+ device: null,
+ amount: '',
+ remark: '',
+ quickAmounts: [50, 100, 200, 500]
+ }
+ },
+ onLoad (q) { this.deviceId = q.id; this.load() },
+ methods: {
+ load () {
+ customerDeviceDetail({ deviceType: 1, deviceId: this.deviceId }).then(res => { this.device = res.data })
+ },
+ submit () {
+ if (!this.amount) return uni.showToast({ title: '璇疯緭鍏ラ噾棰�', icon: 'none' })
+ customerPayCreate({
+ orderType: 1,
+ amount: Number(this.amount),
+ remark: this.remark,
+ openid: this.$store.state.openId
+ }).then(async res => {
+ try {
+ await invokeWxPay(res.data)
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=1&orderNo=${res.data.orderNo}&type=recharge` })
+ } catch (e) {
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=0&orderNo=${res.data.orderNo}&type=recharge` })
+ }
+ })
+ }
+ }
+}
+</script>
diff --git a/h5/pages/customer/contract/detail.vue b/h5/pages/customer/contract/detail.vue
new file mode 100644
index 0000000..5085398
--- /dev/null
+++ b/h5/pages/customer/contract/detail.vue
@@ -0,0 +1,596 @@
+<template>
+
+ <view class="cu-page cu-page--with-footer" v-if="contract">
+
+ <view :class="['cu-detail-hero', contract.status === 1 ? 'cu-detail-hero--green' : '']">
+
+ <view class="cu-detail-hero__top">
+
+ <view>
+
+ <view class="cu-detail-hero__label">鍚堝悓缂栧彿</view>
+
+ <view class="cu-detail-hero__code">{{ contract.code }}</view>
+
+ </view>
+
+ <text class="cu-status">{{ statusText(contract.status) }}</text>
+
+ </view>
+
+ <view class="cu-detail-hero__amount">
+
+ <text class="cu-detail-hero__amount-label">绉熻祦闈㈢Н</text>
+
+ <text class="cu-detail-hero__amount-value">{{ formatArea(contract.totalArea) }}</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view v-if="contract.billStatusTip" :class="['cu-bill-tip', 'cu-bill-tip--inset', billTipClass(contract.billStatusType)]">
+
+ <text class="cu-bill-tip__icon">{{ billTipIcon(contract.billStatusType) }}</text>
+
+ <text class="cu-bill-tip__text">{{ contract.billStatusTip }}</text>
+
+ </view>
+
+
+
+ <view class="cu-segment">
+
+ <view :class="['cu-segment__item', tab === 0 ? 'cu-segment__item--active' : '']" @click="tab = 0">鍚堝悓淇℃伅</view>
+
+ <view :class="['cu-segment__item', tab === 1 ? 'cu-segment__item--active' : '']" @click="tab = 1">鍏宠仈璐﹀崟</view>
+
+ </view>
+
+
+
+ <view v-if="tab === 0">
+
+ <view class="cu-panel">
+
+ <view class="cu-panel__title">鎴挎簮淇℃伅</view>
+
+ <view v-if="contract.roomList && contract.roomList.length" class="cu-room-list">
+
+ <view v-for="(room, idx) in contract.roomList" :key="idx" class="cu-room-item">
+
+ <text class="cu-room-item__name">{{ formatRoomLine(room) }}</text>
+
+ <text class="cu-room-item__area">{{ formatArea(room.rentArea) }}</text>
+
+ </view>
+
+ </view>
+
+ <view v-else class="cu-kv__item">
+
+ <text class="cu-kv__value cu-kv__value--muted">{{ contract.roomInfo || '鏆傛棤鎴块棿淇℃伅' }}</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-panel">
+
+ <view class="cu-panel__title">鍩烘湰淇℃伅</view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">寮�濮嬫棩鏈�</text>
+
+ <text class="cu-kv__value">{{ contract.startDate || '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">缁撴潫鏃ユ湡</text>
+
+ <text class="cu-kv__value">{{ contract.endDate || '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">绛捐鏃ユ湡</text>
+
+ <text class="cu-kv__value">{{ contract.signDate || '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">浠樻鏂瑰紡</text>
+
+ <text class="cu-kv__value">{{ contract.payTypeText || '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">鍏嶇鏈�</text>
+
+ <text class="cu-kv__value">{{ contract.freeRentPeriod || '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">绉熻祦鎶奸噾</text>
+
+ <text class="cu-kv__value">楼{{ contract.zlDeposit != null ? contract.zlDeposit : '-' }}</text>
+
+ </view>
+
+ <view class="cu-kv__item">
+
+ <text class="cu-kv__label">鍚堝悓鍗曚环</text>
+
+ <text class="cu-kv__value cu-kv__value--danger">楼{{ contract.zlFirstPrice != null ? contract.zlFirstPrice : '-' }}/鏈�</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-panel">
+
+ <view class="cu-panel__title">鍚堝悓闄勪欢</view>
+
+ <view v-if="contract.fileList && contract.fileList.length">
+
+ <view
+
+ v-for="file in contract.fileList"
+
+ :key="file.id"
+
+ class="cu-file-item"
+
+ @click="openFile(file)"
+
+ >
+
+ <text class="cu-file-item__icon">馃搸</text>
+
+ <view class="cu-file-item__main">
+
+ <text class="cu-file-item__name">{{ file.name || '闄勪欢' }}</text>
+
+ <text class="cu-file-item__time">{{ file.createDate || '' }}</text>
+
+ </view>
+
+ <text class="cu-file-item__action">涓嬭浇</text>
+
+ </view>
+
+ </view>
+
+ <view v-else class="cu-kv__item">
+
+ <text class="cu-kv__value cu-kv__value--muted">鏆傛棤闄勪欢</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+
+
+ <template v-else>
+
+ <view class="cu-contract-bill-panel">
+
+ <view class="cu-contract-bill-switch">
+
+ <view
+
+ :class="['cu-contract-bill-switch__item', 'cu-contract-bill-switch__item--pay', billType === 0 ? 'cu-contract-bill-switch__item--active' : '']"
+
+ @click="switchBillType(0)"
+
+ >
+
+ <text class="cu-contract-bill-switch__icon">浠�</text>
+
+ <view class="cu-contract-bill-switch__text">
+
+ <text class="cu-contract-bill-switch__title">浠樻璐﹀崟</text>
+
+ <text class="cu-contract-bill-switch__desc">闇�鍚戠墿涓氱即绾�</text>
+
+ </view>
+
+ </view>
+
+ <view
+
+ :class="['cu-contract-bill-switch__item', 'cu-contract-bill-switch__item--in', billType === 1 ? 'cu-contract-bill-switch__item--active' : '']"
+
+ @click="switchBillType(1)"
+
+ >
+
+ <text class="cu-contract-bill-switch__icon">鏀�</text>
+
+ <view class="cu-contract-bill-switch__text">
+
+ <text class="cu-contract-bill-switch__title">鏀舵璐﹀崟</text>
+
+ <text class="cu-contract-bill-switch__desc">鐗╀笟搴旈��杩�/鏀粯</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-contract-bill-summary">
+
+ <text class="cu-contract-bill-summary__label">{{ billType === 0 ? '寰呯即璐﹀崟' : '寰呮敹璐﹀崟' }}</text>
+
+ <text class="cu-contract-bill-summary__count">{{ bills.length }} 绗�</text>
+
+ </view>
+
+
+
+ <view v-if="billsLoading" class="cu-contract-bill-loading">
+
+ <text>鍔犺浇涓�...</text>
+
+ </view>
+
+
+
+ <view v-else-if="bills.length" class="cu-contract-bill-list">
+
+ <view
+
+ v-for="b in bills"
+
+ :key="b.id"
+
+ :class="['cu-contract-bill-card', billType === 0 ? 'cu-contract-bill-card--pay' : 'cu-contract-bill-card--in']"
+
+ @click="goBillDetail(b.id)"
+
+ >
+
+ <view class="cu-contract-bill-card__top">
+
+ <view class="cu-contract-bill-card__title-wrap">
+
+ <text class="cu-contract-bill-card__type">{{ costTypeText(b.costType) }}</text>
+
+ <text class="cu-contract-bill-card__code">{{ b.code || '-' }}</text>
+
+ </view>
+
+ <text :class="['cu-contract-bill-card__status', billPayStatusClass(b.payStatus)]">{{ payStatusText(b.payStatus) }}</text>
+
+ </view>
+
+
+
+ <view class="cu-contract-bill-card__amounts">
+
+ <view class="cu-contract-bill-card__amount-item">
+
+ <text class="cu-contract-bill-card__amount-label">{{ receivableLabel }}</text>
+
+ <text class="cu-contract-bill-card__amount-value">楼{{ formatMoney(b.receivableFee) }}</text>
+
+ </view>
+
+ <view class="cu-contract-bill-card__amount-divider" />
+
+ <view class="cu-contract-bill-card__amount-item">
+
+ <text class="cu-contract-bill-card__amount-label">{{ receivedLabel }}</text>
+
+ <text class="cu-contract-bill-card__amount-value cu-contract-bill-card__amount-value--muted">楼{{ formatMoney(b.actReceivableFee) }}</text>
+
+ </view>
+
+ </view>
+
+
+
+ <view v-if="b.isOverdue === 1" class="cu-contract-bill-card__overdue">宸查�炬湡</view>
+
+
+
+ <view class="cu-contract-bill-card__meta">
+
+ <view class="cu-contract-bill-card__meta-row">
+
+ <text class="cu-contract-bill-card__meta-label">璐﹀崟閲戦</text>
+
+ <text class="cu-contract-bill-card__meta-value">楼{{ formatMoney(b.totleFee) }}</text>
+
+ </view>
+
+ <view class="cu-contract-bill-card__meta-row">
+
+ <text class="cu-contract-bill-card__meta-label">璁¤垂鍛ㄦ湡</text>
+
+ <text class="cu-contract-bill-card__meta-value">{{ formatDate(b.startDate) || '-' }} ~ {{ formatDate(b.endDate) || '-' }}</text>
+
+ </view>
+
+ <view class="cu-contract-bill-card__meta-row">
+
+ <text class="cu-contract-bill-card__meta-label">{{ dueDateLabel }}</text>
+
+ <text class="cu-contract-bill-card__meta-value">{{ formatDate(b.planPayDate) || '-' }}</text>
+
+ </view>
+
+ <view class="cu-contract-bill-card__meta-row">
+
+ <text class="cu-contract-bill-card__meta-label">鏄惁閫炬湡</text>
+
+ <text :class="['cu-contract-bill-card__meta-value', b.isOverdue === 1 ? 'cu-contract-bill-card__meta-value--danger' : '']">
+
+ {{ overdueText(b) }}
+
+ </text>
+
+ </view>
+
+ </view>
+
+
+
+ <view class="cu-contract-bill-card__foot">
+
+ <text>鏌ョ湅璇︽儏</text>
+
+ <text class="cu-contract-bill-card__arrow">鈫�</text>
+
+ </view>
+
+ </view>
+
+ </view>
+
+
+
+ <u-empty v-else :text="billType === 0 ? '鏆傛棤浠樻璐﹀崟' : '鏆傛棤鏀舵璐﹀崟'" margin-top="60" />
+
+ </view>
+
+ </template>
+
+ </view>
+
+</template>
+
+
+
+<script>
+
+import { customerContractDetail } from '@/api'
+
+const COST_TYPE_MAP = {
+ 0: '绉熻祦璐�',
+ 1: '鐗╀笟璐�',
+ 2: '绉熻祦鎶奸噾',
+ 3: '鐗╀笟鎶奸噾',
+ 4: '姘寸數璐�',
+ 5: '鏉傞」璐�',
+ 6: '鍏朵粬',
+ 7: '淇濊瘉閲�'
+}
+
+export default {
+
+ data () { return { id: null, contract: null, bills: [], tab: 0, billType: 0, billsLoading: false } },
+
+ computed: {
+ receivableLabel () { return this.billType === 0 ? '搴斾粯閲戦' : '搴旀敹閲戦' },
+ receivedLabel () { return this.billType === 0 ? '瀹炰粯閲戦' : '瀹炴敹閲戦' },
+ dueDateLabel () { return this.billType === 0 ? '搴斾粯鏃ユ湡' : '搴旀敹鏃ユ湡' }
+ },
+
+ onLoad (q) { this.id = q.id; this.load() },
+
+ methods: {
+
+ load (reloadContract = true) {
+
+ if (!reloadContract) this.billsLoading = true
+
+ customerContractDetail(this.id, this.billType).then(res => {
+
+ if (reloadContract) this.contract = res.data.contract
+
+ this.bills = res.data.bills || []
+
+ }).finally(() => {
+
+ this.billsLoading = false
+
+ })
+
+ },
+
+ switchBillType (type) {
+
+ if (this.billType === type) return
+
+ this.billType = type
+
+ this.bills = []
+
+ this.load(false)
+
+ },
+
+ costTypeText (type) { return COST_TYPE_MAP[type] || '璐﹀崟' },
+
+ formatMoney (val) {
+
+ if (val === null || val === undefined || val === '') return '0.00'
+
+ return Number(val).toFixed(2)
+
+ },
+
+ formatDate (val) {
+
+ if (!val) return ''
+
+ return String(val).replace('T', ' ').substring(0, 10)
+
+ },
+
+ formatArea (area) {
+
+ if (area === null || area === undefined || area === '') return '-'
+
+ return `${area}銕
+
+ },
+
+ formatRoomLine (room) {
+
+ if (!room) return '-'
+
+ const parts = [room.projectName, room.buildingName, room.floorName, room.roomNum].filter(Boolean)
+
+ return parts.length ? parts.join('/') : '-'
+
+ },
+
+ statusText (s) { return { 0: '寰呮墽琛�', 1: '鎵ц涓�', 2: '宸插埌鏈�', 3: '閫�绉熶腑', 4: '宸查��绉�' }[s] || '-' },
+
+ payStatusText (s) {
+
+ const common = { 1: '宸茬粨娓�', 2: '閮ㄥ垎缁撴竻', 5: '宸插叧闂�' }
+
+ if (common[s]) return common[s]
+
+ if (this.billType === 0) {
+
+ return { 0: '寰呬粯娆�', 3: '寰呬粯娆�', 4: '寰呴��娆�' }[s] || '-'
+
+ }
+
+ return { 0: '寰呮敹娆�', 3: '寰呮敹娆�', 4: '寰呴��娆�' }[s] || '-'
+
+ },
+
+ billPayStatusClass (s) {
+
+ if (s === 1) return 'cu-contract-bill-card__status--ok'
+
+ if (s === 0 || s === 3) return 'cu-contract-bill-card__status--warn'
+
+ if (s === 4) return 'cu-contract-bill-card__status--bad'
+
+ return 'cu-contract-bill-card__status--muted'
+
+ },
+
+ overdueText (item) {
+
+ if (!item) return '-'
+
+ return item.isOverdue === 1 ? '鏄�' : '鍚�'
+
+ },
+
+ goBillDetail (id) { uni.navigateTo({ url: `/pages/customer/bill/detail?id=${id}` }) },
+
+ billTipClass (type) {
+
+ if (type === 'danger') return 'cu-bill-tip--danger'
+
+ if (type === 'warn') return 'cu-bill-tip--warn'
+
+ return 'cu-bill-tip--ok'
+
+ },
+
+ billTipIcon (type) {
+
+ if (type === 'danger') return '鈿狅笍'
+
+ if (type === 'warn') return '鈴�'
+
+ return '鉁�'
+
+ },
+
+ openFile (file) {
+
+ const url = file.fileurlFull || file.fileurl
+
+ if (!url) {
+
+ uni.showToast({ title: '闄勪欢鍦板潃鏃犳晥', icon: 'none' })
+
+ return
+
+ }
+
+ // #ifdef H5
+
+ window.open(url, '_blank')
+
+ // #endif
+
+ // #ifndef H5
+
+ uni.showLoading({ title: '涓嬭浇涓�' })
+
+ uni.downloadFile({
+
+ url,
+
+ success: (res) => {
+
+ if (res.statusCode === 200) {
+
+ uni.openDocument({ filePath: res.tempFilePath, showMenu: true })
+
+ } else {
+
+ uni.showToast({ title: '涓嬭浇澶辫触', icon: 'none' })
+
+ }
+
+ },
+
+ fail: () => uni.showToast({ title: '涓嬭浇澶辫触', icon: 'none' }),
+
+ complete: () => uni.hideLoading()
+
+ })
+
+ // #endif
+
+ }
+
+ }
+
+}
+
+</script>
+
+
diff --git a/h5/pages/customer/contract/list.vue b/h5/pages/customer/contract/list.vue
new file mode 100644
index 0000000..73c3e01
--- /dev/null
+++ b/h5/pages/customer/contract/list.vue
@@ -0,0 +1,103 @@
+<template>
+ <view class="cu-page">
+ <scroll-view scroll-x class="cu-tabs cu-tabs--scroll">
+ <view
+ v-for="(t, i) in tabs"
+ :key="t.value"
+ :class="['cu-tab', tabIdx === i ? 'cu-tab--active' : '']"
+ @click="tabIdx = i; load()"
+ >{{ t.label }}</view>
+ </scroll-view>
+
+ <view class="cu-list-header">
+ <text class="cu-list-header__count">鍏� {{ list.length }} 浠藉悎鍚�</text>
+ </view>
+
+ <view class="cu-list-wrap">
+ <view v-for="item in list" :key="item.id" class="cu-list-card cu-list-card--clickable" @click="goDetail(item.id)">
+ <view class="cu-list-card__head">
+ <view class="cu-list-card__icon cu-list-card__icon--contract">馃搫</view>
+ <view class="cu-list-card__main">
+ <view class="cu-list-card__title-row">
+ <text class="cu-list-card__title">{{ item.code }}</text>
+ <text :class="['cu-status', contractStatusClass(item.status)]">{{ statusText(item.status) }}</text>
+ </view>
+ <text class="cu-list-card__sub">{{ item.roomInfo || '鏆傛棤鎴块棿淇℃伅' }}</text>
+ <view class="cu-period-chip">{{ item.startDate }} ~ {{ item.endDate }}</view>
+ </view>
+ </view>
+
+ <view class="cu-info-grid">
+ <view class="cu-info-cell">
+ <text class="cu-info-cell__label">绉熻祦闈㈢Н</text>
+ <text class="cu-info-cell__value cu-info-cell__value--primary">{{ formatArea(item.totalArea) }}</text>
+ </view>
+ <view class="cu-info-cell">
+ <text class="cu-info-cell__label">浠樻鏂瑰紡</text>
+ <text class="cu-info-cell__value">{{ item.payTypeText || '-' }}</text>
+ </view>
+ </view>
+
+ <view v-if="item.billStatusTip" :class="['cu-bill-tip', billTipClass(item.billStatusType)]">
+ <text class="cu-bill-tip__icon">{{ billTipIcon(item.billStatusType) }}</text>
+ <text class="cu-bill-tip__text">{{ item.billStatusTip }}</text>
+ </view>
+
+ <view class="cu-list-card__foot">
+ <text class="cu-time">鐐瑰嚮鏌ョ湅瀹屾暣鍚堝悓淇℃伅</text>
+ <text class="cu-list-card__arrow">璇︽儏 鈫�</text>
+ </view>
+ </view>
+ <u-empty v-if="!list.length" text="鏆傛棤鍚堝悓" margin-top="80" />
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerContractPage } from '@/api'
+export default {
+ data () {
+ return {
+ list: [],
+ tabIdx: 0,
+ tabs: [
+ { label: '鍏ㄩ儴', value: null }, { label: '寰呮墽琛�', value: 0 }, { label: '鎵ц涓�', value: 1 },
+ { label: '宸插埌鏈�', value: 2 }, { label: '閫�绉熶腑', value: 3 }, { label: '宸查��绉�', value: 4 }
+ ]
+ }
+ },
+ onShow () { this.load() },
+ methods: {
+ load () {
+ customerContractPage({ page: 1, capacity: 50, model: { status: this.tabs[this.tabIdx].value } })
+ .then(res => { this.list = (res.data && res.data.records) || [] })
+ },
+ formatArea (area) {
+ if (area === null || area === undefined || area === '') return '-'
+ return `${area}銕
+ },
+ statusText (s) {
+ const map = { 0: '寰呮墽琛�', 1: '鎵ц涓�', 2: '宸插埌鏈�', 3: '閫�绉熶腑', 4: '宸查��绉�' }
+ return map[s] || '-'
+ },
+ contractStatusClass (s) {
+ if (s === 1) return 'cu-status--ok'
+ if (s === 2 || s === 4) return 'cu-status--muted'
+ if (s === 3) return 'cu-status--warn'
+ return 'cu-status--muted'
+ },
+ billTipClass (type) {
+ if (type === 'danger') return 'cu-bill-tip--danger'
+ if (type === 'warn') return 'cu-bill-tip--warn'
+ return 'cu-bill-tip--ok'
+ },
+ billTipIcon (type) {
+ if (type === 'danger') return '鈿狅笍'
+ if (type === 'warn') return '鈴�'
+ return '鉁�'
+ },
+ goDetail (id) { uni.navigateTo({ url: `/pages/customer/contract/detail?id=${id}` }) }
+ }
+}
+</script>
+
\ No newline at end of file
diff --git a/h5/pages/customer/electricity/list.vue b/h5/pages/customer/electricity/list.vue
new file mode 100644
index 0000000..97e7891
--- /dev/null
+++ b/h5/pages/customer/electricity/list.vue
@@ -0,0 +1,96 @@
+<template>
+ <view class="cu-page">
+ <view class="cu-filters">
+ <picker :range="typeOptions" range-key="label" @change="onTypeChange">
+ <view class="cu-filter">{{ typeLabel }} 鈻�</view>
+ </picker>
+ <picker :range="statusOptions" range-key="label" @change="onStatusChange">
+ <view class="cu-filter">{{ statusLabel }} 鈻�</view>
+ </picker>
+ </view>
+
+ <view class="cu-list-header">
+ <text class="cu-list-header__count">鍏� {{ list.length }} 鍙拌澶�</text>
+ </view>
+
+ <view class="cu-list-wrap">
+ <view v-for="item in list" :key="item.deviceType + '-' + item.deviceId" class="cu-list-card">
+ <view class="cu-list-card__head">
+ <view :class="['cu-list-card__icon', item.deviceType === 0 ? 'cu-list-card__icon--electric' : 'cu-list-card__icon--conditioner']">
+ {{ item.deviceType === 0 ? '鈿�' : '鉂勶笍' }}
+ </view>
+ <view class="cu-list-card__main">
+ <view class="cu-list-card__title-row">
+ <text class="cu-list-card__title">{{ item.deviceName }}</text>
+ <text :class="['cu-status', item.statusCode === 1 ? 'cu-status--ok' : 'cu-status--bad']">{{ item.statusText }}</text>
+ </view>
+ <text class="cu-list-card__sub">{{ item.roomInfo || '鏆傛棤鎴块棿淇℃伅' }}</text>
+ </view>
+ </view>
+
+ <view v-if="item.alarmTags && item.alarmTags.length" class="cu-list-card__tags">
+ <text v-for="tag in item.alarmTags" :key="tag" class="cu-tag">{{ tag }}</text>
+ </view>
+
+ <view class="cu-info-grid">
+ <view v-if="item.deviceType === 0" class="cu-info-cell">
+ <text class="cu-info-cell__label">鐢佃〃鎴峰彿</text>
+ <text class="cu-info-cell__value">{{ item.meterAccountNo || '-' }}</text>
+ </view>
+ <view :class="['cu-info-cell', item.deviceType !== 0 ? 'cu-info-cell--full' : '']">
+ <text class="cu-info-cell__label">璐︽埛浣欓</text>
+ <text :class="['cu-info-cell__value', item.balanceLow ? 'cu-info-cell__value--danger' : '']">{{ item.balance }}</text>
+ </view>
+ </view>
+
+ <view class="cu-list-card__foot">
+ <text class="cu-time">鏇存柊 {{ formatTime(item.updateTime) }}</text>
+ <text class="cu-list-card__arrow" @click="goRecharge(item)">鍘诲厖鍊� 鈫�</text>
+ </view>
+ </view>
+ <u-empty v-if="!list.length" text="鏆傛棤璁惧" margin-top="80" />
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerDevicePage } from '@/api'
+export default {
+ data () {
+ return {
+ list: [],
+ typeOptions: [{ label: '鍏ㄩ儴绫诲瀷', value: null }, { label: '鐢佃〃', value: 0 }, { label: '绌鸿皟', value: 1 }],
+ statusOptions: [{ label: '鍏ㄩ儴鐘舵��', value: null }, { label: '姝e父', value: 1 }, { label: '寮傚父', value: 2 }],
+ typeIdx: 0,
+ statusIdx: 0,
+ page: 1
+ }
+ },
+ computed: {
+ typeLabel () { return this.typeOptions[this.typeIdx].label },
+ statusLabel () { return this.statusOptions[this.statusIdx].label }
+ },
+ onShow () { this.load() },
+ methods: {
+ load () {
+ customerDevicePage({
+ page: this.page,
+ capacity: 20,
+ model: {
+ deviceType: this.typeOptions[this.typeIdx].value,
+ statusFilter: this.statusOptions[this.statusIdx].value
+ }
+ }).then(res => { this.list = (res.data && res.data.records) || [] })
+ },
+ onTypeChange (e) { this.typeIdx = Number(e.detail.value); this.load() },
+ onStatusChange (e) { this.statusIdx = Number(e.detail.value); this.load() },
+ formatTime (t) { return t ? String(t).replace('T', ' ').substring(0, 19) : '-' },
+ goRecharge (item) {
+ const url = item.deviceType === 0
+ ? `/pages/customer/electricity/recharge?id=${item.deviceId}`
+ : `/pages/customer/conditioner/recharge?id=${item.deviceId}`
+ uni.navigateTo({ url })
+ }
+ }
+}
+</script>
diff --git a/h5/pages/customer/electricity/recharge.vue b/h5/pages/customer/electricity/recharge.vue
new file mode 100644
index 0000000..94bacbc
--- /dev/null
+++ b/h5/pages/customer/electricity/recharge.vue
@@ -0,0 +1,79 @@
+<template>
+ <view class="cu-page cu-page--with-footer">
+ <view class="cu-device-summary" v-if="device">
+ <view class="cu-row cu-row--between">
+ <text class="cu-name">{{ device.deviceName }}</text>
+ <text class="cu-status cu-status--ok">{{ device.statusText }}</text>
+ </view>
+ <view class="cu-line">鎴块棿锛歿{ device.roomInfo }}</view>
+ <view class="cu-line">鎴峰彿锛歿{ device.meterAccountNo }}</view>
+ <view class="cu-device-summary__balance">
+ <view class="cu-device-summary__balance-label">褰撳墠璐︽埛浣欓</view>
+ <view class="cu-device-summary__balance-value">{{ device.balance }}</view>
+ </view>
+ </view>
+
+ <view class="cu-pay-amount-box">
+ <view class="cu-pay-amount-box__label">鍏呭�奸噾棰�</view>
+ <view class="cu-pay-amount-box__input-wrap">
+ <text class="cu-pay-amount-box__symbol">楼</text>
+ <input v-model="amount" class="cu-pay-amount-box__input" type="digit" placeholder="0.00" />
+ </view>
+ <view class="cu-quick-amounts">
+ <text
+ v-for="q in quickAmounts"
+ :key="q"
+ :class="['cu-quick-amount', String(amount) === String(q) ? 'cu-quick-amount--active' : '']"
+ @click="amount = String(q)"
+ >{{ q }}鍏�</text>
+ </view>
+ <view class="cu-pay-amount-box__remark">
+ <text class="cu-pay-amount-box__remark-label">澶囨敞</text>
+ <input v-model="remark" placeholder="閫夊~" />
+ </view>
+ </view>
+
+ <view class="cu-page-footer">
+ <view class="cu-btn cu-btn--primary" @click="submit">纭鍏呭�納{ amount ? ' 楼' + amount : '' }}</view>
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerDeviceDetail, customerPayCreate } from '@/api'
+import { invokeWxPay } from '@/utils/wxpay.js'
+export default {
+ data () {
+ return {
+ deviceId: null,
+ device: null,
+ amount: '',
+ remark: '',
+ quickAmounts: [50, 100, 200, 500]
+ }
+ },
+ onLoad (q) { this.deviceId = q.id; this.load() },
+ methods: {
+ load () {
+ customerDeviceDetail({ deviceType: 0, deviceId: this.deviceId }).then(res => { this.device = res.data })
+ },
+ submit () {
+ if (!this.amount) return uni.showToast({ title: '璇疯緭鍏ラ噾棰�', icon: 'none' })
+ customerPayCreate({
+ orderType: 0,
+ electricalId: Number(this.deviceId),
+ amount: Number(this.amount),
+ remark: this.remark,
+ openid: this.$store.state.openId
+ }).then(async res => {
+ try {
+ await invokeWxPay(res.data)
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=1&orderNo=${res.data.orderNo}&type=recharge` })
+ } catch (e) {
+ uni.redirectTo({ url: `/pages/customer/pay/result?success=0&orderNo=${res.data.orderNo}&type=recharge` })
+ }
+ })
+ }
+ }
+}
+</script>
diff --git a/h5/pages/customer/index.vue b/h5/pages/customer/index.vue
new file mode 100644
index 0000000..78c5031
--- /dev/null
+++ b/h5/pages/customer/index.vue
@@ -0,0 +1,81 @@
+<template>
+ <view class="cu-page cu-page--home">
+ <view class="cu-hero">
+ <view class="cu-hero__greet">
+ <view class="cu-avatar">{{ customerInitial }}</view>
+ <view>
+ <view class="cu-hero__hi">{{ greeting }}</view>
+ <view class="cu-hero__name">{{ home.customerName || '鍟嗘埛鐢ㄦ埛' }}</view>
+ </view>
+ </view>
+ </view>
+
+ <view class="cu-home-body">
+ <view v-if="banners.length" class="cu-banner-wrap">
+ <u-swiper :list="banners" keyName="imageUrl" height="160" radius="12" indicator indicatorMode="dot" />
+ </view>
+
+ <view class="cu-section-title">涓撳睘鏈嶅姟</view>
+ <view class="cu-service-grid">
+ <view class="cu-service-item cu-service-item--electric" @click="go('/pages/customer/electricity/list')">
+ <view class="cu-service-item__icon">鈿�</view>
+ <text class="cu-service-item__label">浜ょ數璐�</text>
+ <text class="cu-service-item__desc">鐢佃〃 / 绌鸿皟鍏呭��</text>
+ </view>
+ <view class="cu-service-item cu-service-item--contract" @click="go('/pages/customer/contract/list')">
+ <view class="cu-service-item__icon">馃搫</view>
+ <text class="cu-service-item__label">鏌ュ悎鍚�</text>
+ <text class="cu-service-item__desc">绉熻祦鍚堝悓鏌ヨ</text>
+ </view>
+ <view class="cu-service-item cu-service-item--bill" @click="go('/pages/customer/bill/list')">
+ <view class="cu-service-item__icon">馃挸</view>
+ <text class="cu-service-item__label">鏌ヨ处鍗�</text>
+ <text class="cu-service-item__desc">鍦ㄧ嚎缂磋垂</text>
+ </view>
+ <view class="cu-service-item cu-service-item--record" @click="go('/pages/customer/recharge/record')">
+ <view class="cu-service-item__icon">馃搵</view>
+ <text class="cu-service-item__label">鍏呭�艰褰�</text>
+ <text class="cu-service-item__desc">鍘嗗彶鍏呭�兼槑缁�</text>
+ </view>
+ </view>
+
+ <view class="cu-footer-bar">
+ <view class="cu-footer-btn cu-footer-btn--primary" @click="onSwitchRole">鍒囨崲瑙掕壊</view>
+ <view class="cu-footer-btn" @click="logout">閫�鍑虹櫥褰�</view>
+ </view>
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerBanners, customerHome } from '@/api'
+import { switchRole, goRoleSelect } from '@/utils/roleSwitch.js'
+export default {
+ data () {
+ return { banners: [], home: {} }
+ },
+ computed: {
+ greeting () {
+ const h = new Date().getHours()
+ if (h < 12) return '鏃╀笂濂�'
+ if (h < 18) return '涓嬪崍濂�'
+ return '鏅氫笂濂�'
+ },
+ customerInitial () {
+ const name = (this.home.customerName || '鍟�').trim()
+ return name.charAt(0)
+ }
+ },
+ onShow () {
+ customerBanners().then(res => {
+ this.banners = (res.data || []).map(b => ({ imageUrl: b.imageUrl, title: b.title }))
+ })
+ customerHome().then(res => { this.home = res.data || {} })
+ },
+ methods: {
+ go (url) { uni.navigateTo({ url }) },
+ onSwitchRole () { switchRole() },
+ logout () { goRoleSelect() }
+ }
+}
+</script>
diff --git a/h5/pages/customer/login.vue b/h5/pages/customer/login.vue
new file mode 100644
index 0000000..492b5b7
--- /dev/null
+++ b/h5/pages/customer/login.vue
@@ -0,0 +1,73 @@
+<template>
+ <view class="cu-login">
+ <view class="cu-login__brand">
+ <view class="cu-login__title">鍟嗘埛鐧诲綍</view>
+ <view class="cu-login__sub">闃滃畞鏂囦綋涓績 路 鍟嗘埛鏈嶅姟骞冲彴</view>
+ </view>
+
+ <view v-if="devMockTip" class="cu-login__tip">{{ devMockTip }}</view>
+
+ <view class="cu-input-wrap">
+ <input v-model="form.phone" maxlength="11" type="number" placeholder="璇疯緭鍏ユ墜鏈哄彿" />
+ </view>
+ <view class="cu-input-wrap">
+ <input v-model="form.code" placeholder="璇疯緭鍏ラ獙璇佺爜" />
+ <view v-if="downTime == 0" class="cu-sms-btn" @click="sendSms">鑾峰彇楠岃瘉鐮�</view>
+ <view v-else class="cu-sms-btn cu-sms-btn--disabled">{{ downTime }}s</view>
+ </view>
+
+ <view class="cu-btn cu-btn--primary" @click="onLogin">鐧诲綍</view>
+ </view>
+</template>
+
+<script>
+import { customerLogin, customerGetUserInfo, customerWxAuthorize, customerSendLoginSms } from '@/api'
+import { devWechatMock } from '@/utils/config.js'
+import { runWechatOAuthFlow } from '@/utils/wechatAuth.js'
+import { requestLoginSmsCode } from '@/utils/loginSms.js'
+import { mapMutations } from 'vuex'
+
+export default {
+ data () {
+ return {
+ form: { phone: '', code: '' },
+ downTime: 0,
+ devMockTip: devWechatMock.enabled ? `寮�鍙戞ā寮忥細妯℃嫙 openid ${devWechatMock.openId}` : ''
+ }
+ },
+ onShow () {
+ uni.setStorageSync('userType', 1)
+ runWechatOAuthFlow({
+ authorizeApi: customerWxAuthorize,
+ onSuccess: (res) => {
+ if (res.data.openid) this.setOpenId(res.data.openid)
+ if (res.data.token) {
+ this.setToken(res.data.token)
+ this.setUserType(1)
+ customerGetUserInfo().then(r => this.setUserInfo(r.data))
+ uni.redirectTo({ url: '/pages/customer/index' })
+ }
+ }
+ })
+ },
+ methods: {
+ ...mapMutations(['setToken', 'setUserInfo', 'setOpenId', 'setUserType']),
+ onLogin () {
+ if (!this.form.phone || !this.form.code) {
+ return uni.showToast({ title: '璇峰~鍐欐墜鏈哄彿鍜岄獙璇佺爜', icon: 'none' })
+ }
+ customerLogin({ ...this.form, openid: this.$store.state.openId, userType: 1 }).then(res => {
+ if (res.code === 200) {
+ this.setToken(res.data)
+ this.setUserType(1)
+ customerGetUserInfo().then(r => this.setUserInfo(r.data))
+ uni.redirectTo({ url: '/pages/customer/index' })
+ }
+ })
+ },
+ sendSms () {
+ requestLoginSmsCode(this, this.form.phone, customerSendLoginSms)
+ }
+ }
+}
+</script>
diff --git a/h5/pages/customer/pay/result.vue b/h5/pages/customer/pay/result.vue
new file mode 100644
index 0000000..fe628e4
--- /dev/null
+++ b/h5/pages/customer/pay/result.vue
@@ -0,0 +1,34 @@
+<template>
+ <view class="cu-result">
+ <view :class="['cu-result__icon', success ? 'cu-result__icon--ok' : 'cu-result__icon--fail']">
+ {{ success ? '鉁�' : '鉁�' }}
+ </view>
+ <view class="cu-result__title">{{ success ? '鏀粯鎴愬姛' : '鏀粯澶辫触' }}</view>
+ <view class="cu-result__sub">
+ {{ success ? (type === 'bill' ? '璐﹀崟鏀粯鎴愬姛锛屾劅璋㈡偍鐨勭即璐�' : '鍏呭�兼垚鍔燂紝棰勮 10 鍒嗛挓鍐呭埌璐�') : '璇风◢鍚庨噸璇曪紝鎴栬仈绯荤鐞嗗憳澶勭悊' }}
+ </view>
+ <view class="cu-btn cu-btn--primary" @click="goRecord">{{ type === 'bill' ? '鏌ョ湅璐﹀崟鏄庣粏' : '鏌ョ湅鍏呭�艰褰�' }}</view>
+ <view class="cu-btn" @click="goHome">杩斿洖涓婚〉</view>
+ </view>
+</template>
+
+<script>
+export default {
+ data () { return { success: false, type: 'recharge', orderNo: '', billId: '' } },
+ onLoad (q) {
+ this.success = q.success === '1'
+ this.type = q.type || 'recharge'
+ this.orderNo = q.orderNo || ''
+ this.billId = q.billId || ''
+ },
+ methods: {
+ goRecord () {
+ const url = this.type === 'bill' && this.billId
+ ? `/pages/customer/bill/detail?id=${this.billId}`
+ : '/pages/customer/recharge/record'
+ uni.redirectTo({ url })
+ },
+ goHome () { uni.reLaunch({ url: '/pages/customer/index' }) }
+ }
+}
+</script>
diff --git a/h5/pages/customer/recharge/record.vue b/h5/pages/customer/recharge/record.vue
new file mode 100644
index 0000000..7b4d931
--- /dev/null
+++ b/h5/pages/customer/recharge/record.vue
@@ -0,0 +1,84 @@
+<template>
+ <view class="cu-page">
+ <view class="cu-filters">
+ <picker :range="statusOptions" range-key="label" @change="onStatusChange">
+ <view class="cu-filter">{{ statusLabel }} 鈻�</view>
+ </picker>
+ <picker mode="date" fields="month" @change="onMonthChange">
+ <view class="cu-filter">{{ month || '鍏呭�兼湀浠�' }} 鈻�</view>
+ </picker>
+ </view>
+
+ <view class="cu-list-header">
+ <text class="cu-list-header__count">鍏� {{ list.length }} 鏉¤褰�</text>
+ </view>
+
+ <view class="cu-list-wrap">
+ <view v-for="item in list" :key="item.id" class="cu-list-card">
+ <view class="cu-list-card__head">
+ <view class="cu-list-card__icon cu-list-card__icon--record">馃搵</view>
+ <view class="cu-list-card__main">
+ <view class="cu-list-card__title-row">
+ <text class="cu-list-card__title">{{ item.deviceInfo || item.name || '鍏呭�艰褰�' }}</text>
+ <text :class="['cu-status', statusClass(item.status)]">{{ item.statusText }}</text>
+ </view>
+ <text class="cu-list-card__sub" v-if="item.address">鎴峰彿 {{ item.address }}</text>
+ </view>
+ </view>
+
+ <view class="cu-info-grid">
+ <view class="cu-info-cell">
+ <text class="cu-info-cell__label">鍏呭�奸噾棰�</text>
+ <text class="cu-info-cell__value cu-info-cell__value--primary">楼{{ item.money }}</text>
+ </view>
+ <view class="cu-info-cell">
+ <text class="cu-info-cell__label">鍏呭悗浣欓</text>
+ <text class="cu-info-cell__value">{{ item.balanceAfter }}</text>
+ </view>
+ <view class="cu-info-cell cu-info-cell--full">
+ <text class="cu-info-cell__label">鍏呭�兼椂闂�</text>
+ <text class="cu-info-cell__value">{{ item.createDate }}</text>
+ </view>
+ </view>
+ </view>
+ <u-empty v-if="!list.length" text="鏆傛棤璁板綍" margin-top="80" />
+ </view>
+ </view>
+</template>
+
+<script>
+import { customerRechargeRecordPage } from '@/api'
+export default {
+ data () {
+ return {
+ list: [],
+ month: '',
+ statusIdx: 0,
+ statusOptions: [
+ { label: '鍏ㄩ儴鐘舵��', value: null },
+ { label: '鍏呭�兼垚鍔�', value: 1 },
+ { label: '鍏呭�煎け璐�', value: 2 },
+ { label: '鍏呭�间腑', value: 0 }
+ ]
+ }
+ },
+ computed: { statusLabel () { return this.statusOptions[this.statusIdx].label } },
+ onShow () { this.load() },
+ methods: {
+ load () {
+ customerRechargeRecordPage({
+ page: 1,
+ capacity: 50,
+ model: { status: this.statusOptions[this.statusIdx].value, month: this.month || null }
+ }).then(res => { this.list = (res.data && res.data.records) || [] })
+ },
+ onStatusChange (e) { this.statusIdx = Number(e.detail.value); this.load() },
+ onMonthChange (e) { this.month = e.detail.value; this.load() },
+ statusClass (s) {
+ if (s === 1) return 'cu-status--ok'
+ if (s === 2) return 'cu-status--bad'
+ return 'cu-status--warn'
+ }
+ }
+}
+</script>
diff --git a/h5/pages/index.vue b/h5/pages/index.vue
index f64142b..ba479e2 100644
--- a/h5/pages/index.vue
+++ b/h5/pages/index.vue
@@ -17,19 +17,23 @@
<view class="list">
<view v-for="item in list2" class="item" @click="itemClick(item)">
<image :src="item.img"></image>
- <view class="name">{{item.name}}</view>
+ <view class="name">{{item.name}}</view>
<view v-if="item.name == '寰呭姙涓績' && taskNum" class="superscript">{{taskNum}}</view>
</view>
</view>
- <view class="loginout" @click="loginOut">閫�鍑虹櫥闄�</view>
+ <view class="footer-actions">
+ <view class="switch-role" @click="switchRole">鍒囨崲瑙掕壊</view>
+ <view class="loginout" @click="loginOut">閫�鍑虹櫥褰�</view>
+ </view>
</view>
</template>
<script>
import {
- logoutPost,
+ logoutPost,
myNoticesH5
} from '@/api'
+ import { switchRole as doSwitchRole, goRoleSelect } from '@/utils/roleSwitch.js'
export default {
data() {
return {
@@ -51,12 +55,12 @@
url: '/pages/operation/device',
img: require('@/static/home/ic_fangkebaobe@2x.png'),
auth: 'weixin:menu:visitcar'
- },
- {
- name: '搴撳瓨鐩樼偣',
- url: '/pages/inventory/index',
- img: require('@/static/home/ic_pandian@2x.png'),
- auth: 'weixin:menu:visitcar'
+ },
+ {
+ name: '搴撳瓨鐩樼偣',
+ url: '/pages/inventory/index',
+ img: require('@/static/home/ic_pandian@2x.png'),
+ auth: 'weixin:menu:visitcar'
},
],
list2: [{
@@ -70,20 +74,20 @@
url: '/pages/operation/record',
img: require('@/static/home/ic_wodehuiyi@2x.png'),
auth: 'weixin:menu:visitcar'
- },
- {
- name: '寰呭姙涓績',
- url: '/pages/workOrder/wait',
- img: require('@/static/home/ic_daiban@2x.png'),
- auth: 'weixin:menu:visitcar'
},
- ],
+ {
+ name: '寰呭姙涓績',
+ url: '/pages/workOrder/wait',
+ img: require('@/static/home/ic_daiban@2x.png'),
+ auth: 'weixin:menu:visitcar'
+ },
+ ],
taskNum: 0
}
},
onShow() {
- myNoticesH5({ page: 1, capacity: 1,model: {status: 0}}).then(res => {
- this.taskNum = res.data.total
+ myNoticesH5({ page: 1, capacity: 1,model: {status: 0}}).then(res => {
+ this.taskNum = res.data.total
})
},
methods: {
@@ -92,16 +96,11 @@
url: item.url
})
},
+ switchRole () {
+ doSwitchRole(logoutPost)
+ },
loginOut() {
- logoutPost().then(res => {
- this.$store.commit('empty')
- setTimeout(() => {
- uni.redirectTo({
- url: '/pages/login'
- })
- }, 300)
- })
- // window.location.href= 'https://zhcg.fnwtzx.com/fn_h5'
+ logoutPost().catch(() => {}).finally(() => goRoleSelect())
},
}
@@ -149,7 +148,7 @@
display: flex;
flex-direction: column;
align-items: center;
- width: 25%;
+ width: 25%;
position: relative;
image {
width: 88rpx;
@@ -160,37 +159,53 @@
.name {
font-size: 26rpx;
}
- .superscript{
- height: 40rpx;
- width: 40rpx;
- position: absolute;
- top: -16rpx;
- right: 24rpx;
- background-color: red;
- color: #fff;
- font-size: 24rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- }
+ .superscript{
+ height: 40rpx;
+ width: 40rpx;
+ position: absolute;
+ top: -16rpx;
+ right: 24rpx;
+ background-color: red;
+ color: #fff;
+ font-size: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ }
}
}
- .loginout {
+ .footer-actions {
position: fixed;
bottom: 88rpx;
- left: 50%;
- transform: translate(-50%, 0);
- width: 152rpx;
+ left: 0;
+ right: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 24rpx;
+ }
+
+ .switch-role,
+ .loginout {
height: 60rpx;
+ padding: 0 32rpx;
border-radius: 30rpx;
- border: 1rpx solid $primaryColor;
- color: $primaryColor;
font-size: 26rpx;
display: flex;
justify-content: center;
align-items: center;
}
+
+ .switch-role {
+ border: 1rpx solid $primaryColor;
+ color: $primaryColor;
+ }
+
+ .loginout {
+ border: 1rpx solid #ccc;
+ color: #666;
+ }
}
</style>
\ No newline at end of file
diff --git a/h5/pages/login.vue b/h5/pages/login.vue
index bfa42da..a7ce852 100644
--- a/h5/pages/login.vue
+++ b/h5/pages/login.vue
@@ -2,6 +2,7 @@
<view class="login">
<view class="login_title">娆㈣繋鐧诲綍</view>
<view class="login_title login_title2">闃滃畞鏂囦綋涓績</view>
+ <view v-if="devMockTip" class="dev-tip">{{ devMockTip }}</view>
<view class="login_list">
<view class="login_list_item">
<image src="@/static/login_ic_phone@2x.png" mode="widthFix" />
@@ -33,6 +34,9 @@
getRecordByUserPoint
} from '@/api'
+ import { devWechatMock } from '@/utils/config.js'
+ import { runWechatOAuthFlow } from '@/utils/wechatAuth.js'
+ import { requestLoginSmsCode } from '@/utils/loginSms.js'
import {
mapState,
mapMutations
@@ -48,7 +52,8 @@
},
ywinfo: {},
downTime: 0,
- code: ''
+ code: '',
+ devMockTip: devWechatMock.enabled ? `寮�鍙戞ā寮忥細妯℃嫙 openid ${devWechatMock.openId}` : ''
}
},
onLoad(option) {
@@ -67,65 +72,44 @@
}
},
onShow() {
- // return
- var that = this
- let url = window.location.href
- if (url.indexOf('code=') !== -1 || this.code) {
- let code = ''
- const query = url.split('?')
- for (const q of query) {
- if (q.indexOf('code=') !== -1) {
- let statusIndex = q.indexOf('&state')
- code = q.substring(q.indexOf('code=') + 5, statusIndex)
- }
- }
- ywWxAuthorize({
- code: code || this.code
- }).then(res => {
- if (res.code === 200) {
- // console.log('res', res);
+ const that = this
+ runWechatOAuthFlow({
+ authorizeApi: ywWxAuthorize,
+ fallbackCode: this.code,
+ onSuccess: (res) => {
+ if (res.data.openid) {
that.$store.commit('setOpenId', res.data.openid)
- if (res.data.token && res.data.token != '') {
- that.$store.commit('setToken', res.data.token)
- getUserInfo().then(ress => {
- that.$store.commit('setUserInfo', ress.data)
- })
- const ywinfo = this.ywinfo
- if (ywinfo.ywid && (ywinfo.type || ywinfo.type == 0)) {
- getRecordByUserPoint({
- pointCode: ywinfo.ywid
- }).then(res => {
- if (res.data && res.data.id) {
- uni.redirectTo({
- url: "/pages/polling/point?id=" + res.data.id
- })
- } else {
- uni.redirectTo({
- url: "/pages/polling/empty?message=" + res.message
- })
- }
- })
- } else {
- setTimeout(() => {
+ }
+ if (res.data.token && res.data.token != '') {
+ that.$store.commit('setToken', res.data.token)
+ getUserInfo().then(ress => {
+ that.$store.commit('setUserInfo', ress.data)
+ })
+ const ywinfo = this.ywinfo
+ if (ywinfo.ywid && (ywinfo.type || ywinfo.type == 0)) {
+ getRecordByUserPoint({
+ pointCode: ywinfo.ywid
+ }).then(res => {
+ if (res.data && res.data.id) {
uni.redirectTo({
- url: "/pages/index"
+ url: "/pages/polling/point?id=" + res.data.id
})
- }, 300)
- }
+ } else {
+ uni.redirectTo({
+ url: "/pages/polling/empty?message=" + res.message
+ })
+ }
+ })
+ } else {
+ setTimeout(() => {
+ uni.redirectTo({
+ url: "/pages/index"
+ })
+ }, 300)
}
}
- })
- } else {
- let url = 'https://zhcg.fnwtzx.com/fn_h5'
- // const appID = 'wx95ac1efb67f0330d'
- //let url = 'https://dmtest.ahapp.net/yunwei_h5'
- const appID = 'wx15dfdae9a19177f3'
- let uri = encodeURIComponent(url)
- let authURL =
- `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appID}&redirect_uri=${uri}&response_type=code&scope=snsapi_base#wechat_redirect`
- window.location.href = authURL
- }
-
+ }
+ })
},
methods: {
...mapMutations(["setToken", "setUserInfo"]),
@@ -182,20 +166,7 @@
},
sendSms() {
- this.downTime = 60
- let timer = setInterval(() => {
- if (this.downTime == 0) return clearInterval(timer)
- this.downTime = this.downTime - 1
- }, 1000)
- const {
- form
- } = this
- sendSMsPost({
- phone: form.phone,
- type: 0
- }).then(res => {
- this.showToast('鐭俊鍙戦�佹垚鍔�')
- })
+ requestLoginSmsCode(this, this.form.phone, sendSMsPost, { phone: this.form.phone, userType: 0 })
},
}
}
@@ -223,7 +194,17 @@
.login_title2 {
margin-top: 10rpx;
- margin-bottom: 80rpx;
+ margin-bottom: 40rpx;
+ }
+
+ .dev-tip {
+ width: 100%;
+ padding: 0 60rpx;
+ box-sizing: border-box;
+ font-size: 24rpx;
+ color: #e6a23c;
+ margin-bottom: 40rpx;
+ line-height: 1.5;
}
.login_list {
diff --git a/h5/pages/roleSelect.vue b/h5/pages/roleSelect.vue
new file mode 100644
index 0000000..68df7e0
--- /dev/null
+++ b/h5/pages/roleSelect.vue
@@ -0,0 +1,49 @@
+<template>
+ <view class="page">
+ <view class="title">璇烽�夋嫨鐧诲綍韬唤</view>
+ <view class="sub-title">鍒囨崲瑙掕壊鍚庨渶浣跨敤瀵瑰簲韬唤閲嶆柊鐧诲綍</view>
+ <view class="card" @click="goOps">
+ <view class="name">杩愮淮浜哄憳</view>
+ <view class="desc">宸ュ崟銆佸贰妫�銆佽澶囪繍缁�</view>
+ </view>
+ <view class="card merchant" @click="goMerchant">
+ <view class="name">鍟嗘埛</view>
+ <view class="desc">浜ょ數璐广�佹煡鍚堝悓銆佹煡璐﹀崟</view>
+ </view>
+ </view>
+</template>
+
+<script>
+export default {
+ onLoad (option) {
+ if (option && option.switch === '1') return
+ const userType = uni.getStorageSync('userType')
+ const token = uni.getStorageSync('token')
+ if (userType === 0 && token) {
+ uni.redirectTo({ url: '/pages/index' })
+ } else if (userType === 1 && token) {
+ uni.redirectTo({ url: '/pages/customer/index' })
+ }
+ },
+ methods: {
+ goOps () {
+ uni.setStorageSync('userType', 0)
+ uni.redirectTo({ url: '/pages/login' })
+ },
+ goMerchant () {
+ uni.setStorageSync('userType', 1)
+ uni.redirectTo({ url: '/pages/customer/login' })
+ }
+ }
+}
+</script>
+
+<style lang="scss" scoped>
+.page { min-height: 100vh; padding: 120rpx 48rpx; background: linear-gradient(180deg, #e8f0ff 0%, #fff 100%); }
+.title { font-size: 44rpx; font-weight: 600; margin-bottom: 16rpx; color: #222; }
+.sub-title { font-size: 26rpx; color: #999; margin-bottom: 48rpx; }
+.card { background: #fff; border-radius: 24rpx; padding: 40rpx; margin-bottom: 32rpx; box-shadow: 0 8rpx 24rpx rgba(0,0,0,.06); }
+.card.merchant { border: 2rpx solid #3c7cff; }
+.name { font-size: 36rpx; font-weight: 600; color: #222; }
+.desc { margin-top: 12rpx; font-size: 26rpx; color: #888; }
+</style>
diff --git a/h5/store/index.js b/h5/store/index.js
index a0a9693..4b7a6b3 100644
--- a/h5/store/index.js
+++ b/h5/store/index.js
@@ -19,14 +19,14 @@
const store = new Vuex.Store({
state: {
- // openId: openId || '061kuG0006hxcS13TT200w9VIp4kuG09',
- openId: openId || '123123',
+ openId: openId || '',
member: member || null,
statusbarHeight: statusbarHeight || '0',
navHeight: navHeight || '0',
token: token || null,
time: time || null,
userInfo: userInfo || {},
+ userType: uni.getStorageSync('userType'),
driverInfo: driverInfo || {},
height: height || '0',
sessionKey: sessionKey || '',
@@ -68,6 +68,10 @@
state.userInfo = obj
uni.setStorageSync('userInfo', obj)
},
+ setUserType(state, val) {
+ state.userType = val
+ uni.setStorageSync('userType', val)
+ },
// 璁剧疆鍙告満淇℃伅
setDriverInfo(state, obj) {
state.driverInfo = obj
@@ -83,9 +87,11 @@
state.token = ''
state.userInfo = {}
state.driverInfo = {}
+ state.userType = null
uni.removeStorageSync('userInfo')
uni.removeStorageSync('driverInfo')
uni.removeStorageSync('token')
+ uni.removeStorageSync('userType')
}
},
actions: {
diff --git a/h5/styles/customer.scss b/h5/styles/customer.scss
new file mode 100644
index 0000000..4ab120a
--- /dev/null
+++ b/h5/styles/customer.scss
@@ -0,0 +1,2024 @@
+/* 鍟嗘埛绔� H5 缁熶竴 UI */
+
+$cu-primary: #2080f7;
+$cu-primary-dark: #1659ac;
+$cu-primary-light: #e8f2ff;
+$cu-success: #19be6b;
+$cu-warning: #ff9900;
+$cu-danger: #fa3534;
+$cu-text: #1a1a2e;
+$cu-text-secondary: #5c6370;
+$cu-text-muted: #9aa3b2;
+$cu-bg: #f4f6fb;
+$cu-card-bg: #ffffff;
+$cu-radius: 24rpx;
+$cu-radius-sm: 16rpx;
+$cu-shadow: 0 8rpx 32rpx rgba(15, 35, 95, 0.06);
+
+.cu-page {
+ min-height: 100vh;
+ background: $cu-bg;
+ box-sizing: border-box;
+}
+
+.cu-page--padded {
+ padding: 24rpx;
+}
+
+.cu-page--white {
+ background: #fff;
+}
+
+/* 棣栭〉椤堕儴 */
+.cu-hero {
+ padding: 32rpx 32rpx 48rpx;
+ background: linear-gradient(145deg, #2080f7 0%, #4a9bff 55%, #6eb3ff 100%);
+ border-radius: 0 0 40rpx 40rpx;
+}
+
+.cu-hero__greet {
+ display: flex;
+ align-items: center;
+}
+
+.cu-avatar {
+ width: 88rpx;
+ height: 88rpx;
+ margin-right: 24rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.25);
+ border: 2rpx solid rgba(255, 255, 255, 0.5);
+ color: #fff;
+ font-size: 36rpx;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.cu-hero__hi {
+ font-size: 26rpx;
+ color: rgba(255, 255, 255, 0.85);
+ margin-bottom: 6rpx;
+}
+
+.cu-hero__name {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #fff;
+}
+
+.cu-home-body {
+ margin-top: -28rpx;
+ padding: 0 24rpx 48rpx;
+}
+
+.cu-banner-wrap {
+ margin-bottom: 28rpx;
+ border-radius: $cu-radius;
+ overflow: hidden;
+ box-shadow: $cu-shadow;
+}
+
+.cu-section-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: $cu-text;
+ margin-bottom: 24rpx;
+ padding-left: 8rpx;
+}
+
+.cu-service-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20rpx;
+}
+
+.cu-service-item {
+ width: calc(50% - 10rpx);
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ padding: 32rpx 24rpx;
+ box-shadow: $cu-shadow;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+}
+
+.cu-service-item__icon {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 36rpx;
+ margin-bottom: 16rpx;
+}
+
+.cu-service-item__label {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-service-item__desc {
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-top: 6rpx;
+}
+
+.cu-service-item--electric .cu-service-item__icon { background: #fff7e6; }
+.cu-service-item--contract .cu-service-item__icon { background: #e8f7ef; }
+.cu-service-item--bill .cu-service-item__icon { background: #eef2ff; }
+.cu-service-item--record .cu-service-item__icon { background: #fce8f3; }
+
+.cu-footer-bar {
+ margin-top: 64rpx;
+ display: flex;
+ justify-content: center;
+ gap: 24rpx;
+}
+
+.cu-footer-btn {
+ font-size: 28rpx;
+ padding: 18rpx 48rpx;
+ border-radius: 999rpx;
+ background: #fff;
+ color: $cu-text-secondary;
+ box-shadow: $cu-shadow;
+}
+
+.cu-footer-btn--primary {
+ color: $cu-primary;
+ border: 1rpx solid rgba(32, 128, 247, 0.35);
+}
+
+/* 鍗$墖 */
+.cu-card {
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ padding: 28rpx;
+ margin-bottom: 20rpx;
+ box-shadow: $cu-shadow;
+}
+
+.cu-card--clickable:active {
+ opacity: 0.92;
+ transform: scale(0.995);
+}
+
+.cu-card__title {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+ margin-bottom: 20rpx;
+ padding-bottom: 16rpx;
+ border-bottom: 1rpx solid #f0f2f5;
+}
+
+.cu-row {
+ display: flex;
+ align-items: center;
+}
+
+.cu-row--between {
+ justify-content: space-between;
+}
+
+.cu-name {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-line {
+ font-size: 26rpx;
+ color: $cu-text-secondary;
+ margin-top: 12rpx;
+ line-height: 1.5;
+}
+
+.cu-line__label {
+ color: $cu-text-muted;
+}
+
+.cu-card__footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 20rpx;
+ padding-top: 16rpx;
+ border-top: 1rpx solid #f5f6f8;
+}
+
+.cu-time {
+ font-size: 22rpx;
+ color: $cu-text-muted;
+}
+
+.cu-link {
+ font-size: 28rpx;
+ color: $cu-primary;
+ font-weight: 500;
+}
+
+/* 鐘舵�� */
+.cu-status {
+ font-size: 24rpx;
+ padding: 4rpx 16rpx;
+ border-radius: 999rpx;
+ font-weight: 500;
+}
+
+.cu-status--ok {
+ color: $cu-success;
+ background: rgba(25, 190, 107, 0.12);
+}
+
+.cu-status--bad {
+ color: $cu-danger;
+ background: rgba(250, 53, 52, 0.1);
+}
+
+.cu-status--warn {
+ color: $cu-warning;
+ background: rgba(255, 153, 0, 0.12);
+}
+
+.cu-status--muted {
+ color: $cu-text-muted;
+ background: #f0f2f5;
+}
+
+.cu-text-danger {
+ color: $cu-danger !important;
+ font-weight: 600;
+}
+
+/* 鏍囩 */
+.cu-tag {
+ display: inline-block;
+ background: rgba(250, 53, 52, 0.08);
+ color: $cu-danger;
+ font-size: 22rpx;
+ padding: 6rpx 14rpx;
+ border-radius: 8rpx;
+ margin-right: 8rpx;
+ margin-bottom: 8rpx;
+}
+
+/* 绛涢�� */
+.cu-filters {
+ display: flex;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+ padding: 0 24rpx;
+ padding-top: 24rpx;
+}
+
+.cu-filter {
+ background: #fff;
+ padding: 16rpx 28rpx;
+ border-radius: 999rpx;
+ font-size: 26rpx;
+ color: $cu-text-secondary;
+ box-shadow: $cu-shadow;
+}
+
+/* Tab */
+.cu-tabs {
+ display: flex;
+ padding: 24rpx;
+ gap: 16rpx;
+ background: $cu-bg;
+}
+
+.cu-tabs--scroll {
+ white-space: nowrap;
+ flex-wrap: nowrap;
+}
+
+scroll-view.cu-tabs {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+scroll-view.cu-tabs .cu-tab {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 16rpx;
+}
+
+scroll-view.cu-tabs .cu-tab:last-child {
+ margin-right: 24rpx;
+}
+
+.cu-tab {
+ flex-shrink: 0;
+ padding: 14rpx 28rpx;
+ font-size: 26rpx;
+ color: $cu-text-secondary;
+ background: #fff;
+ border-radius: 999rpx;
+ box-shadow: $cu-shadow;
+}
+
+.cu-tab--active {
+ color: #fff;
+ background: linear-gradient(135deg, $cu-primary, #4a9bff);
+ box-shadow: 0 6rpx 20rpx rgba(32, 128, 247, 0.28);
+}
+
+.cu-list-wrap {
+ padding: 0 24rpx 24rpx;
+}
+
+/* 琛ㄥ崟 */
+.cu-form-line {
+ display: flex;
+ align-items: center;
+ margin: 20rpx 0;
+ font-size: 28rpx;
+ color: $cu-text;
+}
+
+.cu-form-line__label {
+ width: 160rpx;
+ flex-shrink: 0;
+ color: $cu-text-secondary;
+}
+
+.cu-form-line input {
+ flex: 1;
+ height: 72rpx;
+ padding: 0 20rpx;
+ background: #f8fafc;
+ border-radius: 12rpx;
+ font-size: 28rpx;
+}
+
+.cu-form-suffix {
+ margin-left: 12rpx;
+ color: $cu-text-muted;
+ font-size: 26rpx;
+}
+
+/* 鎸夐挳 */
+.cu-btn {
+ height: 96rpx;
+ line-height: 96rpx;
+ text-align: center;
+ border-radius: 999rpx;
+ font-size: 32rpx;
+ font-weight: 500;
+ margin-top: 40rpx;
+ background: #fff;
+ color: $cu-text-secondary;
+ border: 1rpx solid #e8ecf0;
+}
+
+.cu-btn--primary {
+ background: linear-gradient(135deg, $cu-primary 0%, #4a9bff 100%);
+ color: #fff;
+ border: none;
+ box-shadow: 0 12rpx 32rpx rgba(32, 128, 247, 0.35);
+}
+
+.cu-btn--block {
+ margin-left: 24rpx;
+ margin-right: 24rpx;
+}
+
+.cu-btn:active {
+ opacity: 0.9;
+}
+
+/* 鐧诲綍椤� */
+.cu-login {
+ min-height: 100vh;
+ padding: 120rpx 48rpx 48rpx;
+ background: linear-gradient(180deg, #dce9ff 0%, #f4f6fb 45%, #fff 100%);
+}
+
+.cu-login__brand {
+ margin-bottom: 64rpx;
+}
+
+.cu-login__title {
+ font-size: 52rpx;
+ font-weight: 700;
+ color: $cu-text;
+ margin-bottom: 12rpx;
+}
+
+.cu-login__sub {
+ font-size: 28rpx;
+ color: $cu-text-muted;
+}
+
+.cu-login__tip {
+ font-size: 24rpx;
+ color: $cu-warning;
+ margin-bottom: 32rpx;
+ line-height: 1.5;
+ padding: 16rpx 20rpx;
+ background: rgba(255, 153, 0, 0.1);
+ border-radius: 12rpx;
+}
+
+.cu-input-wrap {
+ display: flex;
+ align-items: center;
+ background: #fff;
+ border-radius: 999rpx;
+ padding: 0 32rpx;
+ height: 100rpx;
+ margin-bottom: 24rpx;
+ box-shadow: $cu-shadow;
+}
+
+.cu-input-wrap input {
+ flex: 1;
+ font-size: 30rpx;
+}
+
+.cu-sms-btn {
+ color: $cu-primary;
+ font-size: 28rpx;
+ font-weight: 500;
+ flex-shrink: 0;
+ padding-left: 20rpx;
+}
+
+.cu-sms-btn--disabled {
+ color: $cu-text-muted;
+}
+
+/* 鏀粯缁撴灉 */
+.cu-result {
+ min-height: 100vh;
+ padding: 160rpx 48rpx;
+ text-align: center;
+ background: linear-gradient(180deg, #f4f6fb 0%, #fff 40%);
+}
+
+.cu-result__icon {
+ width: 140rpx;
+ height: 140rpx;
+ line-height: 140rpx;
+ border-radius: 50%;
+ margin: 0 auto 40rpx;
+ color: #fff;
+ font-size: 72rpx;
+ font-weight: 600;
+}
+
+.cu-result__icon--ok {
+ background: linear-gradient(145deg, #19be6b, #3dd68c);
+ box-shadow: 0 16rpx 40rpx rgba(25, 190, 107, 0.35);
+}
+
+.cu-result__icon--fail {
+ background: linear-gradient(145deg, #fa3534, #ff6b6b);
+ box-shadow: 0 16rpx 40rpx rgba(250, 53, 52, 0.3);
+}
+
+.cu-result__title {
+ font-size: 44rpx;
+ font-weight: 700;
+ color: $cu-text;
+}
+
+.cu-result__sub {
+ margin: 20rpx 0 80rpx;
+ color: $cu-text-muted;
+ font-size: 28rpx;
+ line-height: 1.6;
+}
+
+/* ========== 鍒楄〃椤靛寮� ========== */
+.cu-list-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8rpx 24rpx 16rpx;
+}
+
+.cu-list-header__count {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-list-card {
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ padding: 0;
+ margin-bottom: 20rpx;
+ box-shadow: $cu-shadow;
+ overflow: hidden;
+}
+
+.cu-list-card--clickable:active {
+ opacity: 0.95;
+}
+
+.cu-list-card--inset {
+ margin: 20rpx 24rpx 0;
+}
+
+
+.cu-list-card__head {
+ display: flex;
+ align-items: flex-start;
+ padding: 28rpx 28rpx 20rpx;
+}
+
+.cu-list-card__icon {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 38rpx;
+ margin-right: 20rpx;
+ flex-shrink: 0;
+}
+
+.cu-list-card__icon--electric { background: linear-gradient(135deg, #fff7e6, #ffe8b3); }
+.cu-list-card__icon--conditioner { background: linear-gradient(135deg, #e8f7ef, #c8f0dc); }
+.cu-list-card__icon--contract { background: linear-gradient(135deg, #eef2ff, #d6e4ff); }
+.cu-list-card__icon--bill { background: linear-gradient(135deg, #fce8f3, #f5d0e8); }
+.cu-list-card__icon--record { background: linear-gradient(135deg, #e8f4ff, #cce5ff); }
+
+.cu-list-card__main {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-list-card__title-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12rpx;
+ margin-bottom: 8rpx;
+}
+
+.cu-list-card__title {
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.cu-list-card__sub {
+ display: block;
+ font-size: 24rpx;
+ color: $cu-text-muted;
+ line-height: 1.4;
+}
+
+.cu-list-card__meta {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ margin-top: 8rpx;
+}
+
+.cu-list-card__tags {
+ padding: 0 28rpx 12rpx;
+}
+
+.cu-info-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 0 20rpx 20rpx;
+ background: #f8fafc;
+ border-radius: 16rpx;
+ overflow: hidden;
+}
+
+.cu-info-cell {
+ width: 50%;
+ padding: 20rpx 24rpx;
+ box-sizing: border-box;
+ border-bottom: 1rpx solid #f0f2f5;
+}
+
+.cu-info-cell:nth-child(odd) {
+ border-right: 1rpx solid #f0f2f5;
+}
+
+.cu-info-cell:nth-last-child(-n+2) {
+ border-bottom: none;
+}
+
+.cu-info-cell--full {
+ width: 100%;
+ border-right: none !important;
+}
+
+.cu-info-cell__label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-info-cell__value {
+ display: block;
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-info-cell__value--danger {
+ color: $cu-danger;
+}
+
+.cu-info-cell__value--primary {
+ color: $cu-primary;
+}
+
+.cu-list-card__foot {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16rpx 28rpx 24rpx;
+ border-top: 1rpx solid #f5f6f8;
+}
+
+.cu-list-card__arrow {
+ font-size: 26rpx;
+ color: $cu-primary;
+ font-weight: 500;
+}
+
+.cu-period-chip {
+ display: inline-flex;
+ align-items: center;
+ margin-top: 12rpx;
+ padding: 8rpx 16rpx;
+ background: $cu-primary-light;
+ border-radius: 8rpx;
+ font-size: 22rpx;
+ color: $cu-primary;
+}
+
+/* ========== 璇︽儏椤靛寮� ========== */
+.cu-page--with-footer {
+ padding-bottom: 160rpx;
+}
+
+.cu-detail-hero {
+ margin: 24rpx 24rpx 0;
+ padding: 36rpx 32rpx;
+ border-radius: $cu-radius;
+ background: linear-gradient(145deg, #2080f7 0%, #4a9bff 100%);
+ color: #fff;
+ box-shadow: 0 12rpx 40rpx rgba(32, 128, 247, 0.3);
+}
+
+.cu-detail-hero--warm {
+ background: linear-gradient(145deg, #ff8f3d 0%, #ffb347 100%);
+ box-shadow: 0 12rpx 40rpx rgba(255, 143, 61, 0.28);
+}
+
+.cu-detail-hero--green {
+ background: linear-gradient(145deg, #19be6b 0%, #3dd68c 100%);
+ box-shadow: 0 12rpx 40rpx rgba(25, 190, 107, 0.28);
+}
+
+.cu-detail-hero__top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ margin-bottom: 24rpx;
+}
+
+.cu-detail-hero__label {
+ font-size: 24rpx;
+ opacity: 0.85;
+ margin-bottom: 8rpx;
+}
+
+.cu-detail-hero__code {
+ font-size: 32rpx;
+ font-weight: 600;
+ letter-spacing: 0.5rpx;
+}
+
+.cu-detail-hero__amount {
+ margin-top: 8rpx;
+}
+
+.cu-detail-hero__amount-label {
+ display: block;
+ font-size: 24rpx;
+ opacity: 0.85;
+ margin-bottom: 8rpx;
+}
+
+.cu-detail-hero__amount-value {
+ font-size: 56rpx;
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+.cu-detail-hero__amount-unit {
+ font-size: 28rpx;
+ font-weight: 500;
+ margin-left: 4rpx;
+}
+
+.cu-detail-hero .cu-status {
+ background: rgba(255, 255, 255, 0.22);
+ color: #fff;
+ flex-shrink: 0;
+}
+
+.cu-segment {
+ display: flex;
+ margin: 24rpx 24rpx 0;
+ padding: 6rpx;
+ background: #e8ecf2;
+ border-radius: 16rpx;
+}
+
+.cu-segment__item {
+ flex: 1;
+ text-align: center;
+ padding: 18rpx 0;
+ font-size: 28rpx;
+ color: $cu-text-secondary;
+ border-radius: 12rpx;
+ transition: all 0.2s;
+}
+
+.cu-segment__item--active {
+ background: #fff;
+ color: $cu-primary;
+ font-weight: 600;
+ box-shadow: 0 4rpx 12rpx rgba(15, 35, 95, 0.08);
+}
+
+.cu-panel {
+ margin: 20rpx 24rpx 0;
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ box-shadow: $cu-shadow;
+ overflow: hidden;
+}
+
+.cu-panel__title {
+ padding: 24rpx 28rpx 16rpx;
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-kv__item {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ padding: 22rpx 28rpx;
+ border-top: 1rpx solid #f5f6f8;
+ gap: 24rpx;
+}
+
+.cu-kv__label {
+ flex-shrink: 0;
+ font-size: 26rpx;
+ color: $cu-text-muted;
+ min-width: 160rpx;
+}
+
+.cu-kv__value {
+ flex: 1;
+ text-align: right;
+ font-size: 28rpx;
+ color: $cu-text;
+ font-weight: 500;
+ word-break: break-all;
+}
+
+.cu-kv__value--danger {
+ color: $cu-danger;
+ font-weight: 600;
+}
+
+.cu-kv__value--muted {
+ color: $cu-text-muted;
+ font-weight: 400;
+ text-align: left;
+}
+
+.cu-bill-tip {
+ display: flex;
+ align-items: flex-start;
+ gap: 12rpx;
+ margin: 0 28rpx 20rpx;
+ padding: 20rpx 24rpx;
+ border-radius: 12rpx;
+ font-size: 24rpx;
+ line-height: 1.5;
+}
+
+.cu-bill-tip--inset {
+ margin: 0 24rpx 20rpx;
+}
+
+.cu-list-card .cu-bill-tip {
+ margin: 0 28rpx 16rpx;
+}
+
+.cu-bill-tip--ok {
+ background: #edf9f0;
+ color: #2f9a4f;
+}
+
+.cu-bill-tip--warn {
+ background: #fff8e8;
+ color: #d48806;
+}
+
+.cu-bill-tip--danger {
+ background: #fff1f0;
+ color: #cf1322;
+}
+
+.cu-bill-tip__icon {
+ flex-shrink: 0;
+ font-size: 28rpx;
+ line-height: 1.4;
+}
+
+.cu-bill-tip__text {
+ flex: 1;
+}
+
+.cu-room-list {
+ padding: 0 28rpx 20rpx;
+}
+
+.cu-room-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20rpx;
+ padding: 18rpx 0;
+ border-top: 1rpx solid #f5f6f8;
+}
+
+.cu-room-item__name {
+ flex: 1;
+ font-size: 26rpx;
+ color: $cu-text;
+ word-break: break-all;
+}
+
+.cu-room-item__area {
+ flex-shrink: 0;
+ font-size: 26rpx;
+ color: $cu-primary;
+ font-weight: 500;
+}
+
+.cu-file-item {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ padding: 22rpx 28rpx;
+ border-top: 1rpx solid #f5f6f8;
+}
+
+.cu-file-item__icon {
+ font-size: 32rpx;
+}
+
+.cu-file-item__main {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-file-item__name {
+ display: block;
+ font-size: 28rpx;
+ color: $cu-text;
+ word-break: break-all;
+}
+
+.cu-file-item__time {
+ display: block;
+ margin-top: 6rpx;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+}
+
+.cu-file-item__action {
+ flex-shrink: 0;
+ font-size: 26rpx;
+ color: $cu-primary;
+}
+
+.cu-timeline {
+ padding: 8rpx 28rpx 20rpx;
+}
+
+.cu-timeline__item {
+ display: flex;
+ align-items: flex-start;
+ padding: 20rpx 0;
+ border-bottom: 1rpx solid #f5f6f8;
+}
+
+.cu-timeline__item:last-child {
+ border-bottom: none;
+}
+
+.cu-timeline__dot {
+ width: 16rpx;
+ height: 16rpx;
+ border-radius: 50%;
+ background: $cu-primary;
+ margin-top: 10rpx;
+ margin-right: 20rpx;
+ flex-shrink: 0;
+ box-shadow: 0 0 0 6rpx rgba(32, 128, 247, 0.15);
+}
+
+.cu-timeline__body {
+ flex: 1;
+}
+
+.cu-timeline__title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+ margin-bottom: 6rpx;
+}
+
+.cu-timeline__time {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-page-footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 20rpx 24rpx calc(20rpx + env(safe-area-inset-bottom));
+ background: rgba(255, 255, 255, 0.96);
+ backdrop-filter: blur(12px);
+ box-shadow: 0 -8rpx 32rpx rgba(15, 35, 95, 0.08);
+ z-index: 100;
+}
+
+.cu-page-footer .cu-btn {
+ margin-top: 0;
+}
+
+/* 鍏呭��/缂磋垂椤� */
+.cu-device-summary {
+ margin: 24rpx 24rpx 0;
+ padding: 28rpx;
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ box-shadow: $cu-shadow;
+}
+
+.cu-device-summary__balance {
+ margin-top: 20rpx;
+ padding: 24rpx;
+ background: linear-gradient(135deg, #fff5f5, #fff);
+ border-radius: 16rpx;
+ border: 1rpx solid rgba(250, 53, 52, 0.12);
+ text-align: center;
+}
+
+.cu-device-summary__balance-label {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-device-summary__balance-value {
+ font-size: 48rpx;
+ font-weight: 700;
+ color: $cu-danger;
+}
+
+.cu-pay-amount-box {
+ margin: 24rpx 24rpx 0;
+ padding: 40rpx 28rpx;
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ box-shadow: $cu-shadow;
+ text-align: center;
+}
+
+.cu-pay-amount-box__label {
+ font-size: 26rpx;
+ color: $cu-text-muted;
+ margin-bottom: 16rpx;
+}
+
+.cu-pay-amount-box__input-wrap {
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ margin-bottom: 24rpx;
+}
+
+.cu-pay-amount-box__symbol {
+ font-size: 40rpx;
+ font-weight: 600;
+ color: $cu-text;
+ margin-right: 8rpx;
+}
+
+.cu-pay-amount-box__input {
+ font-size: 72rpx;
+ font-weight: 700;
+ color: $cu-text;
+ text-align: center;
+ min-width: 200rpx;
+ height: 88rpx;
+ line-height: 88rpx;
+}
+
+.cu-pay-amount-box__remark {
+ display: flex;
+ align-items: center;
+ margin-top: 8rpx;
+ padding: 20rpx 24rpx;
+ background: #f8fafc;
+ border-radius: 12rpx;
+ font-size: 26rpx;
+}
+
+.cu-pay-amount-box__remark-label {
+ color: $cu-text-muted;
+ margin-right: 16rpx;
+ flex-shrink: 0;
+}
+
+.cu-pay-amount-box__remark input {
+ flex: 1;
+ font-size: 26rpx;
+ text-align: left;
+}
+
+.cu-quick-amounts {
+ display: flex;
+ gap: 16rpx;
+ margin-top: 20rpx;
+ margin-bottom: 32rpx;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.cu-quick-amount {
+ padding: 12rpx 28rpx;
+ background: #f0f4fa;
+ border-radius: 999rpx;
+ font-size: 26rpx;
+ color: $cu-text-secondary;
+}
+
+.cu-quick-amount--active {
+ background: $cu-primary-light;
+ color: $cu-primary;
+ font-weight: 600;
+}
+
+/* ========== 璐﹀崟鍒楄〃 / 璇︽儏 ========== */
+.cu-bill-page__header {
+ padding: 16rpx 24rpx 8rpx;
+}
+
+.cu-bill-tabs {
+ margin-bottom: 20rpx;
+}
+
+.cu-bill-summary {
+ display: flex;
+ align-items: baseline;
+ gap: 8rpx;
+ padding: 0 8rpx 12rpx;
+}
+
+.cu-bill-summary__count {
+ font-size: 44rpx;
+ font-weight: 700;
+ color: $cu-text;
+ line-height: 1;
+}
+
+.cu-bill-summary__label {
+ font-size: 26rpx;
+ color: $cu-text-muted;
+}
+
+.cu-bill-card {
+ position: relative;
+ margin: 0 24rpx 20rpx;
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ box-shadow: $cu-shadow;
+ overflow: hidden;
+}
+
+.cu-bill-card__accent {
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 8rpx;
+ background: $cu-primary;
+}
+
+.cu-bill-card__accent--ok {
+ background: $cu-success;
+}
+
+.cu-bill-card__accent--warn {
+ background: $cu-warning;
+}
+
+.cu-bill-card__accent--danger {
+ background: $cu-danger;
+}
+
+.cu-bill-card__body {
+ padding: 28rpx 28rpx 24rpx 32rpx;
+}
+
+.cu-bill-card__head {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16rpx;
+ margin-bottom: 24rpx;
+}
+
+.cu-bill-card__head-main {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-bill-card__type {
+ display: inline-flex;
+ padding: 6rpx 14rpx;
+ background: #f3f6fc;
+ border-radius: 8rpx;
+ font-size: 22rpx;
+ color: $cu-primary;
+ font-weight: 500;
+ margin-bottom: 10rpx;
+}
+
+.cu-bill-card__code {
+ display: block;
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+ word-break: break-all;
+ line-height: 1.4;
+}
+
+.cu-bill-card__amount-box {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 20rpx;
+ padding: 24rpx;
+ background: linear-gradient(135deg, #f8fbff 0%, #f4f7fd 100%);
+ border-radius: 16rpx;
+ border: 1rpx solid rgba(32, 128, 247, 0.08);
+ margin-bottom: 20rpx;
+}
+
+.cu-bill-card__amount-label,
+.cu-bill-card__amount-side-label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-bill-card__amount-value {
+ font-size: 40rpx;
+ font-weight: 700;
+ color: $cu-danger;
+ line-height: 1.1;
+}
+
+.cu-bill-card__amount-side {
+ text-align: right;
+ flex-shrink: 0;
+}
+
+.cu-bill-card__amount-side-value {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text-secondary;
+}
+
+.cu-bill-card__overdue {
+ margin: -8rpx 0 16rpx;
+ padding: 12rpx 16rpx;
+ background: #fff1f0;
+ border-radius: 10rpx;
+ font-size: 24rpx;
+ color: $cu-danger;
+}
+
+.cu-bill-card__contract {
+ padding: 20rpx;
+ background: #fafbfc;
+ border-radius: 14rpx;
+ margin-bottom: 16rpx;
+}
+
+.cu-bill-card__contract-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 16rpx;
+ font-size: 24rpx;
+ line-height: 1.5;
+}
+
+.cu-bill-card__contract-row + .cu-bill-card__contract-row {
+ margin-top: 12rpx;
+}
+
+.cu-bill-card__contract-label {
+ flex-shrink: 0;
+ width: 140rpx;
+ color: $cu-text-muted;
+}
+
+.cu-bill-card__contract-value {
+ flex: 1;
+ color: $cu-text-secondary;
+ word-break: break-all;
+}
+
+.cu-bill-card__meta {
+ display: flex;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+}
+
+.cu-bill-card__meta-item {
+ flex: 1;
+ padding: 16rpx;
+ background: #fff;
+ border: 1rpx solid #eef1f6;
+ border-radius: 12rpx;
+}
+
+.cu-bill-card__meta-item--full {
+ flex: none;
+ width: 100%;
+}
+
+.cu-bill-card__meta-label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-bill-card__meta-value {
+ display: block;
+ font-size: 24rpx;
+ color: $cu-text;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.cu-bill-card__foot {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-top: 18rpx;
+ border-top: 1rpx solid #f0f2f6;
+}
+
+.cu-bill-card__foot-hint {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-bill-card__foot-link {
+ font-size: 26rpx;
+ color: $cu-primary;
+ font-weight: 500;
+}
+
+.cu-bill-card--inset {
+ margin: 0 24rpx 24rpx;
+}
+
+.cu-contract-bill-panel {
+ padding: 0 24rpx 32rpx;
+}
+
+.cu-contract-bill-switch {
+ display: flex;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+}
+
+.cu-contract-bill-switch__item {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ padding: 24rpx 20rpx;
+ border-radius: 16rpx;
+ background: #f5f7fb;
+ border: 2rpx solid transparent;
+ transition: all 0.2s ease;
+}
+
+.cu-contract-bill-switch__item--pay.cu-contract-bill-switch__item--active {
+ background: linear-gradient(135deg, #fff7f0 0%, #ffe8d6 100%);
+ border-color: #ffb36b;
+ box-shadow: 0 8rpx 24rpx rgba(255, 143, 61, 0.15);
+}
+
+.cu-contract-bill-switch__item--in.cu-contract-bill-switch__item--active {
+ background: linear-gradient(135deg, #edf9f0 0%, #d8f3e0 100%);
+ border-color: #5ec98a;
+ box-shadow: 0 8rpx 24rpx rgba(47, 154, 79, 0.15);
+}
+
+.cu-contract-bill-switch__icon {
+ width: 56rpx;
+ height: 56rpx;
+ line-height: 56rpx;
+ text-align: center;
+ border-radius: 14rpx;
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #fff;
+ flex-shrink: 0;
+}
+
+.cu-contract-bill-switch__item--pay .cu-contract-bill-switch__icon {
+ background: linear-gradient(135deg, #ff8f3d, #ffb347);
+}
+
+.cu-contract-bill-switch__item--in .cu-contract-bill-switch__icon {
+ background: linear-gradient(135deg, #2f9a4f, #5ec98a);
+}
+
+.cu-contract-bill-switch__text {
+ min-width: 0;
+}
+
+.cu-contract-bill-switch__title {
+ display: block;
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+ line-height: 1.3;
+}
+
+.cu-contract-bill-switch__desc {
+ display: block;
+ margin-top: 6rpx;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ line-height: 1.3;
+}
+
+.cu-contract-bill-summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 20rpx;
+ padding: 0 4rpx;
+}
+
+.cu-contract-bill-summary__label {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-contract-bill-summary__count {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-loading {
+ padding: 80rpx 0;
+ text-align: center;
+ font-size: 26rpx;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.cu-contract-bill-card {
+ background: #fff;
+ border-radius: 18rpx;
+ overflow: hidden;
+ box-shadow: 0 8rpx 28rpx rgba(15, 35, 75, 0.06);
+ border: 1rpx solid #eef1f6;
+}
+
+.cu-contract-bill-card--pay {
+ border-top: 6rpx solid #ff8f3d;
+}
+
+.cu-contract-bill-card--in {
+ border-top: 6rpx solid #2f9a4f;
+}
+
+.cu-contract-bill-card__top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16rpx;
+ padding: 24rpx 24rpx 0;
+}
+
+.cu-contract-bill-card__title-wrap {
+ min-width: 0;
+ flex: 1;
+}
+
+.cu-contract-bill-card__type {
+ display: block;
+ font-size: 30rpx;
+ font-weight: 600;
+ color: $cu-text;
+}
+
+.cu-contract-bill-card__code {
+ display: block;
+ margin-top: 8rpx;
+ font-size: 24rpx;
+ color: $cu-text-muted;
+ word-break: break-all;
+}
+
+.cu-contract-bill-card__status {
+ flex-shrink: 0;
+ padding: 8rpx 16rpx;
+ border-radius: 999rpx;
+ font-size: 22rpx;
+ font-weight: 500;
+ background: #f0f2f6;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-card__status--ok {
+ background: #edf9f0;
+ color: #2f9a4f;
+}
+
+.cu-contract-bill-card__status--warn {
+ background: #fff7e6;
+ color: #e68a00;
+}
+
+.cu-contract-bill-card__status--bad {
+ background: #fff1f0;
+ color: $cu-danger;
+}
+
+.cu-contract-bill-card__status--muted {
+ background: #f0f2f6;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-card__amounts {
+ display: flex;
+ align-items: stretch;
+ margin: 20rpx 24rpx 0;
+ padding: 20rpx;
+ border-radius: 14rpx;
+ background: #f8fafc;
+}
+
+.cu-contract-bill-card__amount-item {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-contract-bill-card__amount-divider {
+ width: 1rpx;
+ margin: 0 20rpx;
+ background: #e3e8ef;
+}
+
+.cu-contract-bill-card__amount-label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-contract-bill-card__amount-value {
+ display: block;
+ font-size: 32rpx;
+ font-weight: 700;
+ color: $cu-text;
+}
+
+.cu-contract-bill-card__amount-value--muted {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #5c6b7f;
+}
+
+.cu-contract-bill-card--pay .cu-contract-bill-card__amount-value {
+ color: #e68a00;
+}
+
+.cu-contract-bill-card--in .cu-contract-bill-card__amount-value {
+ color: #2f9a4f;
+}
+
+.cu-contract-bill-card__overdue {
+ margin: 16rpx 24rpx 0;
+ padding: 10rpx 16rpx;
+ border-radius: 8rpx;
+ background: #fff1f0;
+ color: $cu-danger;
+ font-size: 22rpx;
+ font-weight: 500;
+}
+
+.cu-contract-bill-card__meta {
+ margin: 20rpx 24rpx 0;
+ padding-top: 20rpx;
+ border-top: 1rpx dashed #e8edf3;
+}
+
+.cu-contract-bill-card__meta-row {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 24rpx;
+ margin-bottom: 14rpx;
+}
+
+.cu-contract-bill-card__meta-row:last-child {
+ margin-bottom: 0;
+}
+
+.cu-contract-bill-card__meta-label {
+ flex-shrink: 0;
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-card__meta-value {
+ text-align: right;
+ font-size: 24rpx;
+ color: $cu-text;
+ line-height: 1.5;
+ word-break: break-all;
+}
+
+.cu-contract-bill-card__meta-value--danger {
+ color: $cu-danger;
+ font-weight: 600;
+}
+
+.cu-contract-bill-card__foot {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 20rpx;
+ padding: 18rpx 24rpx;
+ background: #fafbfd;
+ font-size: 24rpx;
+ color: $cu-primary;
+}
+
+.cu-contract-bill-card__arrow {
+ font-weight: 600;
+}
+
+.cu-contract-bill-toolbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8rpx 24rpx 20rpx;
+}
+
+.cu-contract-bill-tabs {
+ display: flex;
+ background: #eef2f8;
+ border-radius: 12rpx;
+ padding: 6rpx;
+}
+
+.cu-contract-bill-tabs__item {
+ min-width: 148rpx;
+ padding: 14rpx 24rpx;
+ text-align: center;
+ font-size: 26rpx;
+ color: $cu-text-muted;
+ border-radius: 10rpx;
+}
+
+.cu-contract-bill-tabs__item--active {
+ background: #fff;
+ color: $cu-primary;
+ font-weight: 600;
+ box-shadow: 0 4rpx 12rpx rgba(32, 128, 247, 0.12);
+}
+
+.cu-contract-bill-toolbar__count {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-contract-bill-grid {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: 20rpx;
+ padding-top: 20rpx;
+ border-top: 1rpx solid #eef1f6;
+}
+
+.cu-contract-bill-grid__item {
+ width: 50%;
+ margin-bottom: 18rpx;
+ padding-right: 12rpx;
+ box-sizing: border-box;
+}
+
+.cu-contract-bill-grid__item--full {
+ width: 100%;
+ padding-right: 0;
+}
+
+.cu-contract-bill-grid__label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-contract-bill-grid__value {
+ display: block;
+ font-size: 26rpx;
+ color: $cu-text;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.cu-contract-bill-grid__value--danger {
+ color: $cu-danger;
+ font-weight: 600;
+}
+
+.cu-bill-detail-hero {
+ position: relative;
+ margin: 24rpx 24rpx 0;
+ border-radius: $cu-radius;
+ overflow: hidden;
+ box-shadow: 0 16rpx 48rpx rgba(32, 128, 247, 0.22);
+}
+
+.cu-bill-detail-hero__bg {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(145deg, #2080f7 0%, #4a9bff 55%, #6eb3ff 100%);
+}
+
+.cu-bill-detail-hero--warn .cu-bill-detail-hero__bg {
+ background: linear-gradient(145deg, #ff8f3d 0%, #ffb347 100%);
+ box-shadow: 0 16rpx 48rpx rgba(255, 143, 61, 0.22);
+}
+
+.cu-bill-detail-hero--ok .cu-bill-detail-hero__bg {
+ background: linear-gradient(145deg, #19be6b 0%, #3dd68c 100%);
+}
+
+.cu-bill-detail-hero__content {
+ position: relative;
+ z-index: 1;
+ padding: 36rpx 32rpx;
+ color: #fff;
+}
+
+.cu-bill-detail-hero__top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16rpx;
+ margin-bottom: 28rpx;
+}
+
+.cu-bill-detail-hero__type {
+ display: inline-flex;
+ padding: 6rpx 14rpx;
+ background: rgba(255, 255, 255, 0.18);
+ border-radius: 8rpx;
+ font-size: 22rpx;
+ margin-bottom: 12rpx;
+}
+
+.cu-bill-detail-hero__code {
+ font-size: 30rpx;
+ font-weight: 600;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.cu-bill-detail-hero__status {
+ flex-shrink: 0;
+ padding: 8rpx 18rpx;
+ background: rgba(255, 255, 255, 0.22);
+ border-radius: 999rpx;
+ font-size: 24rpx;
+}
+
+.cu-bill-detail-hero__amount-label {
+ display: block;
+ font-size: 24rpx;
+ opacity: 0.88;
+ margin-bottom: 10rpx;
+}
+
+.cu-bill-detail-hero__amount {
+ display: block;
+ font-size: 60rpx;
+ font-weight: 700;
+ line-height: 1.1;
+}
+
+.cu-bill-detail-hero__due {
+ display: block;
+ margin-top: 14rpx;
+ font-size: 24rpx;
+ opacity: 0.9;
+}
+
+.cu-bill-stat-row {
+ display: flex;
+ gap: 16rpx;
+ margin: 20rpx 24rpx 0;
+}
+
+.cu-bill-stat {
+ flex: 1;
+ padding: 22rpx 16rpx;
+ background: $cu-card-bg;
+ border-radius: 16rpx;
+ box-shadow: $cu-shadow;
+ text-align: center;
+}
+
+.cu-bill-stat__label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 10rpx;
+}
+
+.cu-bill-stat__value {
+ display: block;
+ font-size: 28rpx;
+ font-weight: 700;
+ color: $cu-text;
+}
+
+.cu-bill-stat__value--danger {
+ color: $cu-danger;
+}
+
+.cu-bill-panel {
+ margin-top: 20rpx;
+}
+
+.cu-bill-info-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
+ padding: 0 28rpx 24rpx;
+}
+
+.cu-bill-info-item {
+ width: calc(50% - 8rpx);
+ padding: 18rpx 20rpx;
+ background: #fafbfc;
+ border-radius: 14rpx;
+}
+
+.cu-bill-info-item--full {
+ width: 100%;
+}
+
+.cu-bill-info-item__label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 8rpx;
+}
+
+.cu-bill-info-item__value {
+ display: block;
+ font-size: 26rpx;
+ color: $cu-text;
+ font-weight: 500;
+ line-height: 1.4;
+ word-break: break-all;
+}
+
+.cu-bill-timeline {
+ padding: 0 28rpx 12rpx;
+}
+
+.cu-bill-timeline__item {
+ display: flex;
+ align-items: flex-start;
+ gap: 18rpx;
+ padding: 22rpx 0;
+ border-bottom: 1rpx solid #f0f2f6;
+}
+
+.cu-bill-timeline__item:last-child {
+ border-bottom: none;
+}
+
+.cu-bill-timeline__icon {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 18rpx;
+ background: linear-gradient(135deg, #eef2ff, #d6e4ff);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 30rpx;
+ flex-shrink: 0;
+}
+
+.cu-bill-timeline__main {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-bill-timeline__title {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: $cu-text;
+ margin-bottom: 6rpx;
+}
+
+.cu-bill-timeline__amount {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: $cu-success;
+ margin-bottom: 6rpx;
+}
+
+.cu-bill-timeline__time {
+ font-size: 24rpx;
+ color: $cu-text-muted;
+}
+
+.cu-bill-empty-record {
+ margin: 20rpx 24rpx 0;
+ padding: 48rpx 24rpx;
+ background: $cu-card-bg;
+ border-radius: $cu-radius;
+ box-shadow: $cu-shadow;
+ text-align: center;
+}
+
+.cu-bill-empty-record__icon {
+ display: block;
+ font-size: 48rpx;
+ margin-bottom: 12rpx;
+}
+
+.cu-bill-empty-record__text {
+ font-size: 26rpx;
+ color: $cu-text-muted;
+}
+
+.cu-bill-empty-record--inset {
+ margin: 0;
+ box-shadow: none;
+ padding: 32rpx 24rpx 40rpx;
+}
+
+.cu-revenue-list {
+ padding: 0 28rpx 24rpx;
+}
+
+.cu-revenue-card {
+ padding: 24rpx;
+ background: #fafbfc;
+ border-radius: 16rpx;
+ border: 1rpx solid #eef1f6;
+ margin-bottom: 16rpx;
+}
+
+.cu-revenue-card:last-child {
+ margin-bottom: 0;
+}
+
+.cu-revenue-card__head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16rpx;
+ margin-bottom: 18rpx;
+ padding-bottom: 16rpx;
+ border-bottom: 1rpx solid #eef1f6;
+}
+
+.cu-revenue-card__type {
+ padding: 6rpx 14rpx;
+ background: #edf9f0;
+ color: #2f9a4f;
+ border-radius: 8rpx;
+ font-size: 22rpx;
+ font-weight: 500;
+}
+
+.cu-revenue-card__type--out {
+ background: #fff1f0;
+ color: $cu-danger;
+}
+
+.cu-revenue-card__amount {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: $cu-text;
+}
+
+.cu-revenue-card__row {
+ display: flex;
+ align-items: flex-start;
+ gap: 16rpx;
+ padding: 10rpx 0;
+ font-size: 24rpx;
+ line-height: 1.5;
+}
+
+.cu-revenue-card__label {
+ flex-shrink: 0;
+ width: 140rpx;
+ color: $cu-text-muted;
+}
+
+.cu-revenue-card__value {
+ flex: 1;
+ color: $cu-text-secondary;
+ word-break: break-all;
+}
+
+.cu-bill-detail-footer {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+}
+
+.cu-bill-detail-footer__info {
+ flex: 1;
+ min-width: 0;
+}
+
+.cu-bill-detail-footer__label {
+ display: block;
+ font-size: 22rpx;
+ color: $cu-text-muted;
+ margin-bottom: 4rpx;
+}
+
+.cu-bill-detail-footer__amount {
+ display: block;
+ font-size: 36rpx;
+ font-weight: 700;
+ color: $cu-danger;
+}
+
+.cu-bill-detail-footer__btn {
+ flex-shrink: 0;
+ min-width: 220rpx;
+ margin-top: 0 !important;
+}
diff --git a/h5/utils/config.js b/h5/utils/config.js
index c779cdc..32bb700 100644
--- a/h5/utils/config.js
+++ b/h5/utils/config.js
@@ -1,8 +1,23 @@
// export const baseUrl = 'gateway_interface/'
-//export const baseUrl = 'http://localhost:10010/gateway_interface/'
-export const baseUrl = 'https://zhcg.fnwtzx.com/gateway_interface/'
+export const baseUrl = 'http://localhost:10010/'
+//export const baseUrl = 'https://zhcg.fnwtzx.com/gateway_interface/'
//export const baseUrl = 'https://dmtest.ahapp.net/gateway_interface/'
+/** 寰俊鍏紬鍙� AppId锛圤Auth 璺宠浆鐢紝鐢熶骇鐜淇濇寔鐪熷疄閰嶇疆锛� */
+export const wxAppId = 'wx15dfdae9a19177f3'
+/** OAuth 鍥炶皟鍦板潃锛岄渶涓庡叕浼楀彿鍚庡彴閰嶇疆涓�鑷� */
+export const wxRedirectUri = 'https://zhcg.fnwtzx.com/fn_h5'
+
+/**
+ * 寮�鍙戠幆澧冨井淇� OAuth 妯℃嫙锛堣烦杩囧井淇℃祻瑙堝櫒闄愬埗锛�
+ * 鐢熶骇鏋勫缓/涓婄嚎鍓嶅姟蹇呰缃� enabled: false
+ */
+export const devWechatMock = {
+ enabled: true,
+ openId: 'dev_h5_openid_merchant_001',
+ mockCode: 'DEV_MOCK'
+}
+
export const uploadAvatar = `${baseUrl}visitsAdmin/cloudService/web/public/uploadFtp.do`
export const uploadUrl = `${baseUrl}visitsAdmin/cloudService/public/uploadBatch`
diff --git a/h5/utils/loginSms.js b/h5/utils/loginSms.js
new file mode 100644
index 0000000..2d00ebe
--- /dev/null
+++ b/h5/utils/loginSms.js
@@ -0,0 +1,32 @@
+/**
+ * H5 鐧诲綍楠岃瘉鐮侊細鍏堟牎楠屾墜鏈哄彿锛屾垚鍔熷悗鍐嶅�掕鏃�
+ * @param {Object} vm 椤甸潰瀹炰緥锛堝惈 downTime锛�
+ * @param {string} phone 鎵嬫満鍙�
+ * @param {Function} sendApi 鍙戦�佹帴鍙�
+ * @param {Object} [payload] 璇锋眰浣擄紝榛樿 { phone }
+ */
+export function requestLoginSmsCode (vm, phone, sendApi, payload) {
+ if (!phone) {
+ uni.showToast({ title: '璇疯緭鍏ユ墜鏈哄彿', icon: 'none' })
+ return
+ }
+ const data = payload || { phone }
+ if (!data.phone) {
+ data.phone = phone
+ }
+ sendApi(data).then(res => {
+ if (res.code !== 200) {
+ // service.js 宸插闈� 200 寮� toast锛屾澶勪粎闃绘鍊掕鏃�
+ return
+ }
+ uni.showToast({ title: '宸插彂閫�', icon: 'none' })
+ vm.downTime = 60
+ const timer = setInterval(() => {
+ if (vm.downTime <= 0) {
+ clearInterval(timer)
+ return
+ }
+ vm.downTime--
+ }, 1000)
+ })
+}
diff --git a/h5/utils/roleSwitch.js b/h5/utils/roleSwitch.js
new file mode 100644
index 0000000..9933ca5
--- /dev/null
+++ b/h5/utils/roleSwitch.js
@@ -0,0 +1,19 @@
+import store from '@/store/index.js'
+
+/** 娓呴櫎鐧诲綍鎬佸苟杩斿洖瑙掕壊閫夋嫨椤� */
+export function goRoleSelect () {
+ store.commit('empty')
+ uni.reLaunch({ url: '/pages/roleSelect' })
+}
+
+/**
+ * 鍒囨崲瑙掕壊锛堝彲閫夊厛璋冨悗绔� logout锛�
+ * @param {Function|null} logoutApi 杩斿洖 Promise 鐨勯��鍑烘帴鍙o紝濡� logoutPost
+ */
+export function switchRole (logoutApi) {
+ if (logoutApi) {
+ logoutApi().catch(() => {}).finally(() => goRoleSelect())
+ } else {
+ goRoleSelect()
+ }
+}
diff --git a/h5/utils/service.js b/h5/utils/service.js
index 6df5176..f3b1672 100644
--- a/h5/utils/service.js
+++ b/h5/utils/service.js
@@ -25,20 +25,30 @@
let data = res.data
// 鎺у埗鍙版樉绀烘暟鎹俊鎭�
uni.hideLoading()
- // 鐧诲綍杩囨湡
- if (data.code !== 200) {
- setTimeout(() => {
- uni.showToast({
- title: data.message,
- icon: "none",
- duration: 2000
- })
+ // Spring Boot / Gateway 榛樿閿欒浣擄紙HTTP 500 鏃舵棤 code 瀛楁锛�
+ if (data && data.status && data.code == null) {
+ const errMsg = data.message || data.error || '鏈嶅姟寮傚父锛岃绋嶅悗閲嶈瘯'
+ uni.showToast({
+ title: errMsg,
+ icon: 'none',
+ duration: 2500
})
- if (data.code === 500 || data.code === 5112) {
+ return resolve({ code: data.status, message: errMsg })
+ }
+ // 涓氬姟澶辫触
+ if (data.code !== 200) {
+ const msg = data.message || '鎿嶄綔澶辫触'
+ uni.showToast({
+ title: msg,
+ icon: 'none',
+ duration: 2500
+ })
+ // 浠呮湭鐧诲綍(5112)璺宠浆鐧诲綍椤碉紝閬垮厤鍟嗘埛鍙戠爜绛変笟鍔¢敊璇璺宠浆
+ if (data.code === 5112) {
+ const userType = uni.getStorageSync('userType')
uni.clearStorageSync()
- return uni.navigateTo({
- url: '/pages/login'
- })
+ const loginUrl = userType === 1 ? '/pages/customer/login' : '/pages/login'
+ return uni.navigateTo({ url: loginUrl })
}
return resolve(data)
}
diff --git a/h5/utils/wechatAuth.js b/h5/utils/wechatAuth.js
new file mode 100644
index 0000000..f6987a8
--- /dev/null
+++ b/h5/utils/wechatAuth.js
@@ -0,0 +1,55 @@
+import { devWechatMock, wxAppId, wxRedirectUri } from './config.js'
+
+/** 浠庡綋鍓� URL 瑙f瀽寰俊 OAuth code */
+export function extractOAuthCodeFromUrl () {
+ if (typeof window === 'undefined') return ''
+ const href = window.location.href
+ if (href.indexOf('code=') === -1) return ''
+ const parts = href.split('?')
+ for (const q of parts) {
+ if (q.indexOf('code=') !== -1) {
+ const stateIdx = q.indexOf('&state')
+ return q.substring(q.indexOf('code=') + 5, stateIdx > -1 ? stateIdx : q.length)
+ }
+ }
+ return ''
+}
+
+/** 璺宠浆寰俊 OAuth锛堜粎闈炴ā鎷熺幆澧冿級 */
+export function redirectToWechatOAuth () {
+ const uri = encodeURIComponent(wxRedirectUri)
+ window.location.href =
+ `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${wxAppId}&redirect_uri=${uri}&response_type=code&scope=snsapi_base#wechat_redirect`
+}
+
+/**
+ * 寰俊 OAuth锛氬紑鍙戠幆澧冪敤妯℃嫙 openid锛岀敓浜х幆澧冭蛋寰俊鎺堟潈
+ * @param {Object} options
+ * @param {Function} options.authorizeApi (data) => Promise
+ * @param {Function} options.onSuccess (res) => void
+ * @param {string} [options.fallbackCode] 椤甸潰鍐呯紦瀛樼殑 code
+ */
+export function runWechatOAuthFlow ({ authorizeApi, onSuccess, fallbackCode = '' }) {
+ if (typeof window === 'undefined') return
+
+ const handleAuthorize = (code) => {
+ if (!code) return
+ authorizeApi({ code }).then(res => {
+ if (res.code === 200) onSuccess(res)
+ })
+ }
+
+ if (devWechatMock.enabled) {
+ const mockOpenId = uni.getStorageSync('openId') || devWechatMock.openId
+ uni.setStorageSync('openId', mockOpenId)
+ handleAuthorize(devWechatMock.mockCode)
+ return
+ }
+
+ const code = extractOAuthCodeFromUrl() || fallbackCode
+ if (code) {
+ handleAuthorize(code)
+ return
+ }
+ redirectToWechatOAuth()
+}
diff --git a/h5/utils/wxpay.js b/h5/utils/wxpay.js
new file mode 100644
index 0000000..a39fc7f
--- /dev/null
+++ b/h5/utils/wxpay.js
@@ -0,0 +1,22 @@
+export function invokeWxPay (payParams) {
+ return new Promise((resolve, reject) => {
+ const onBridgeReady = () => {
+ window.WeixinJSBridge.invoke('getBrandWCPayRequest', {
+ appId: payParams.appId,
+ timeStamp: payParams.timeStamp,
+ nonceStr: payParams.nonceStr,
+ package: payParams.package,
+ signType: payParams.signType || 'MD5',
+ paySign: payParams.paySign
+ }, (res) => {
+ if (res.err_msg === 'get_brand_wcpay_request:ok') resolve(res)
+ else reject(res)
+ })
+ }
+ if (typeof window.WeixinJSBridge === 'undefined') {
+ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
+ } else {
+ onBridgeReady()
+ }
+ })
+}
diff --git a/server/db/business.yw_customer_h5.sql b/server/db/business.yw_customer_h5.sql
new file mode 100644
index 0000000..c00a1f0
--- /dev/null
+++ b/server/db/business.yw_customer_h5.sql
@@ -0,0 +1,70 @@
+-- 鍟嗘埛 H5锛氬井淇℃敮浠樿鍗曘�佽疆鎾浘銆佸鎴烽娆″厖鍊兼爣璁般�佸叧鑱旀墿灞�
+
+CREATE TABLE IF NOT EXISTS `yw_wx_pay_order` (
+ `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` tinyint DEFAULT 0,
+ `remark` varchar(500) DEFAULT NULL,
+ `order_no` varchar(64) NOT NULL COMMENT '鍟嗘埛璁㈠崟鍙�',
+ `customer_id` int NOT NULL COMMENT '浠樻鍟嗘埛',
+ `order_type` tinyint NOT NULL COMMENT '0鐢佃〃鍏呭�� 1绌鸿皟鍏呭�� 2璐﹀崟缂磋垂',
+ `biz_ref_id` int DEFAULT NULL COMMENT 'electrical_id/customer_id/bill_id',
+ `biz_record_id` int DEFAULT NULL COMMENT 'yw_electrical_charge.id 鎴� yw_contract_revenue.id',
+ `amount` decimal(12,2) NOT NULL,
+ `status` tinyint DEFAULT 0 COMMENT '0寰呮敮浠� 1鎴愬姛 2澶辫触 3鍏抽棴',
+ `wx_transaction_id` varchar(64) DEFAULT NULL,
+ `pay_time` datetime DEFAULT NULL,
+ `openid` varchar(128) DEFAULT NULL,
+ `request_snapshot` text COMMENT '涓嬪崟鍙傛暟JSON',
+ `status_info` varchar(500) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_order_no` (`order_no`),
+ KEY `idx_customer` (`customer_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='寰俊H5鏀粯璁㈠崟';
+
+CREATE TABLE IF NOT EXISTS `yw_h5_banner` (
+ `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` tinyint DEFAULT 0,
+ `remark` varchar(500) DEFAULT NULL,
+ `title` varchar(200) DEFAULT NULL,
+ `image_url` varchar(500) NOT NULL,
+ `link_url` varchar(500) DEFAULT NULL,
+ `sortnum` int DEFAULT 0,
+ `status` tinyint DEFAULT 0 COMMENT '0鍚敤 1绂佺敤',
+ `scope` tinyint DEFAULT 1 COMMENT '1鍟嗘埛宸ヤ綔鍙�',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='H5杞挱鍥�';
+
+SET @db = DATABASE();
+
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yw_customer' AND COLUMN_NAME = 'first_recharge_done') = 0,
+ 'ALTER TABLE `yw_customer` ADD COLUMN `first_recharge_done` tinyint DEFAULT 0 COMMENT ''鏄惁宸插畬鎴愰娆″厖鍊煎埗'' AFTER `openid`',
+ 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yw_customer_electrical' AND COLUMN_NAME = 'bind_source') = 0,
+ 'ALTER TABLE `yw_customer_electrical` ADD COLUMN `bind_source` tinyint DEFAULT 1 COMMENT ''0鎵嬪姩 1鍚堝悓鑷姩'' AFTER `electrical_id`, ADD COLUMN `contract_id` int DEFAULT NULL AFTER `bind_source`',
+ 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yw_electrical_charge' AND COLUMN_NAME = 'wx_order_no') = 0,
+ 'ALTER TABLE `yw_electrical_charge` ADD COLUMN `wx_order_no` varchar(64) DEFAULT NULL COMMENT ''寰俊鏀粯璁㈠崟鍙�'' AFTER `customer_id`',
+ 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+SET @sql = IF((SELECT COUNT(*) FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'yw_contract_revenue' AND COLUMN_NAME = 'wx_order_no') = 0,
+ 'ALTER TABLE `yw_contract_revenue` ADD COLUMN `wx_order_no` varchar(64) DEFAULT NULL COMMENT ''寰俊鏀粯璁㈠崟鍙�'' AFTER `bill_id`',
+ 'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
diff --git a/server/db/business.yw_h5_banner.menu.sql b/server/db/business.yw_h5_banner.menu.sql
new file mode 100644
index 0000000..38f97a4
--- /dev/null
+++ b/server/db/business.yw_h5_banner.menu.sql
@@ -0,0 +1,35 @@
+-- H5 杞挱鍥剧鐞嗚彍鍗曪紙鎸傚湪銆屽晢鎴峰厖鍊笺�嶄笅锛屽彲閲嶅鎵ц锛�
+
+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`, 'H5杞挱鍥�', '/business/ywh5banner', '鍟嗘埛H5宸ヤ綔鍙拌疆鎾浘缁存姢', NULL, 0,
+ IFNULL((SELECT MAX(sm.`SORT`) FROM `SYSTEM_MENU` sm WHERE sm.`PARENT_ID` = p.`ID` AND sm.`DELETED` = 0), 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/ywh5banner')
+LIMIT 1;
+
+INSERT INTO `SYSTEM_ROLE_MENU` (`ROLE_ID`, `MENU_ID`, `CREATE_TIME`, `UPDATE_TIME`, `CREATE_USER`, `UPDATE_USER`, `DELETED`)
+SELECT r.`ID`, menu.`ID`, CURRENT_TIMESTAMP, NULL, 1, NULL, 0
+FROM `SYSTEM_ROLE` r
+INNER JOIN `SYSTEM_MENU` menu ON menu.`PATH` = '/business/ywh5banner' AND menu.`DELETED` = 0
+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
+ );
+
+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:ywh5banner:query',
+ 'business:ywh5banner:create',
+ 'business:ywh5banner:update',
+ 'business:ywh5banner:delete'
+) AND p.`DELETED` = 0
+WHERE r.`DELETED` = 0 AND (r.`CODE` = 'admin' OR r.`NAME` IN ('瓒呯骇绠$悊鍛�', '绠$悊鍛�'))
+ AND NOT EXISTS (
+ SELECT 1 FROM `SYSTEM_ROLE_PERMISSION` rp
+ WHERE rp.`ROLE_ID` = r.`ID` AND rp.`PERMISSION_ID` = p.`ID` AND rp.`DELETED` = 0
+ );
diff --git a/server/db/business.yw_h5_banner.permissions.sql b/server/db/business.yw_h5_banner.permissions.sql
new file mode 100644
index 0000000..1d1d319
--- /dev/null
+++ b/server/db/business.yw_h5_banner.permissions.sql
@@ -0,0 +1,17 @@
+-- H5 鍟嗘埛宸ヤ綔鍙拌疆鎾浘锛氭寜閽潈闄愶紙鍙噸澶嶆墽琛岋級
+
+INSERT INTO `SYSTEM_PERMISSION` (`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywh5banner:create', '鏂板缓H5杞挱鍥�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` p WHERE p.`CODE` = 'business:ywh5banner:create' AND p.`DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION` (`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywh5banner:delete', '鍒犻櫎H5杞挱鍥�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` p WHERE p.`CODE` = 'business:ywh5banner:delete' AND p.`DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION` (`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywh5banner:update', '淇敼H5杞挱鍥�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` p WHERE p.`CODE` = 'business:ywh5banner:update' AND p.`DELETED` = 0);
+
+INSERT INTO `SYSTEM_PERMISSION` (`CODE`, `NAME`, `REMARK`, `FIXED`, `CREATE_USER`, `CREATE_TIME`, `UPDATE_USER`, `UPDATE_TIME`, `DELETED`)
+SELECT 'business:ywh5banner:query', '鏌ヨH5杞挱鍥�', '', 0, 1, CURRENT_TIMESTAMP, NULL, NULL, 0
+WHERE NOT EXISTS (SELECT 1 FROM `SYSTEM_PERMISSION` p WHERE p.`CODE` = 'business:ywh5banner:query' AND p.`DELETED` = 0);
diff --git a/server/pom.xml b/server/pom.xml
index fec4ff3..1f9c6ef 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -11,11 +11,11 @@
<name>dmvisit</name>
<description></description>
<modules>
- <module>visits</module>
+ <module>emaysms</module>
<module>system_service</module>
+ <module>visits</module>
<module>system_timer</module>
<module>system_gateway</module>
- <module>emaysms</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
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 abd19e5..52c092d 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
@@ -1,10 +1,12 @@
package com.doumee.config.cloudfilter;
+import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.doumee.config.annotation.CloudRequiredPermission;
import com.doumee.config.annotation.LoginNoRequired;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.utils.Constants;
import org.apache.commons.lang3.StringUtils;
@@ -47,7 +49,7 @@
//鑾峰彇token
Cookie[] cookies = request.getCookies();
String token = request.getHeader(Constants.HEADER_USER_TOKEN); // 浠� http 璇锋眰澶翠腑鍙栧嚭 token
- if(StringUtils.isBlank(token)){
+ if(StringUtils.isBlank(token) && cookies != null){
for(Cookie c :cookies){
if(StringUtils.equals(c.getName(),Constants.HEADER_USER_TOKEN)){
token = c.getValue();
@@ -74,7 +76,7 @@
}
}
if (!hasPermission) {
- throw new BusinessException(ResponseStatus.NOT_ALLOWED.getCode(), "娌℃湁璇ユ搷浣滄潈闄�");
+ return writeBusinessError(response, ResponseStatus.NOT_ALLOWED.getCode(), "娌℃湁璇ユ搷浣滄潈闄�");
}
}
}
@@ -89,16 +91,23 @@
}catch (Exception e){
}*/
} else {
- throw new BusinessException(ResponseStatus.NO_LOGIN.getCode(),"鏈櫥褰�");
+ return writeBusinessError(response, ResponseStatus.NO_LOGIN.getCode(), "鏈櫥褰�");
}
}
- }else{
- throw new BusinessException(ResponseStatus.NO_LOGIN.getCode(),"鏈櫥褰�");
+ } else {
+ return writeBusinessError(response, ResponseStatus.NO_LOGIN.getCode(), "鏈櫥褰�");
}
return true;
}
+ private boolean writeBusinessError(HttpServletResponse response, Integer code, String message) throws IOException {
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setHeader("content-type", "application/json;charset=UTF-8");
+ response.getWriter().write(JSON.toJSONString(ApiResponse.failed(code, message)));
+ return false;
+ }
+
private String getRequestBody(HttpServletRequest request) {
// 瀹炵幇浠巖equest鑾峰彇璇锋眰浣撶殑閫昏緫
String body = null;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java b/server/system_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java
similarity index 69%
rename from server/visits/dmvisit_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java
rename to server/system_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java
index 73838f3..eec03e0 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java
+++ b/server/system_service/src/main/java/com/doumee/config/handler/GlobalExceptionHandler.java
@@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.UnauthorizedException;
+import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -17,50 +18,42 @@
/**
* 鍏ㄥ眬寮傚父澶勭悊
- * @author doumee
- * @date 2023/03/21 14:49
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
- /**
- * 涓氬姟寮傚父澶勭悊
- */
@ExceptionHandler(BusinessException.class)
- public <T> ApiResponse<T> handleBusinessException (BusinessException e) {
- log.error(e.getMessage(), e);
+ public <T> ApiResponse<T> handleBusinessException(BusinessException e) {
+ log.warn("BusinessException: {}", e.getMessage());
return ApiResponse.failed(e.getCode(), e.getMessage());
}
- /**
- * 鏃犳潈闄愬紓甯稿鐞�
- */
@ExceptionHandler(UnauthorizedException.class)
- public <T> ApiResponse<T> handleUnauthorizedException (UnauthorizedException e) {
+ public <T> ApiResponse<T> handleUnauthorizedException(UnauthorizedException e) {
log.error(e.getMessage(), e);
return ApiResponse.failed("娌℃湁鎿嶄綔鏉冮檺");
}
- /**
- * 鍙傛暟楠岃瘉鏈�氳繃寮傚父澶勭悊
- */
@ExceptionHandler(MethodArgumentNotValidException.class)
- public <T> ApiResponse<T> handleMethodArgumentNotValidException (MethodArgumentNotValidException e) {
+ public <T> ApiResponse<T> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
List<String> errors = new ArrayList<>();
- for(FieldError fieldError : bindingResult.getFieldErrors()){
+ for (FieldError fieldError : bindingResult.getFieldErrors()) {
errors.add(fieldError.getDefaultMessage());
}
return ApiResponse.failed(ResponseStatus.BAD_REQUEST.getCode(), StringUtils.join(errors));
}
- /**
- * 鍏跺畠寮傚父澶勭悊
- */
+ @ExceptionHandler(HttpMessageNotReadableException.class)
+ public <T> ApiResponse<T> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
+ log.error(e.getMessage(), e);
+ return ApiResponse.failed(ResponseStatus.BAD_REQUEST.getCode(), "璇锋眰鍙傛暟鏍煎紡閿欒");
+ }
+
@ExceptionHandler(Exception.class)
- public <T> ApiResponse<T> handleException (Exception e) {
+ public <T> ApiResponse<T> handleException(Exception e) {
log.error(e.getMessage(), e);
return ApiResponse.failed(ResponseStatus.SERVER_ERROR, e);
}
diff --git a/server/system_service/src/main/java/com/doumee/dao/business/model/SmsEmail.java b/server/system_service/src/main/java/com/doumee/dao/business/model/SmsEmail.java
index f28469d..e2eabe4 100644
--- a/server/system_service/src/main/java/com/doumee/dao/business/model/SmsEmail.java
+++ b/server/system_service/src/main/java/com/doumee/dao/business/model/SmsEmail.java
@@ -82,6 +82,10 @@
@ExcelColumn(name="绫诲瀷 0鐭俊 1閭欢")
private Integer type;
+ @ApiModelProperty(value = "H5鐧诲綍瑙掕壊 0杩愮淮 1鍟嗘埛")
+ @TableField(exist = false)
+ private Integer userType;
+
@ApiModelProperty(value = "鍏宠仈瀵硅薄缂栫爜", example = "1")
@ExcelColumn(name="鍏宠仈瀵硅薄缂栫爜")
private Integer objId;
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwH5BannerCloudController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwH5BannerCloudController.java
new file mode 100644
index 0000000..b4972a8
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/admin/YwH5BannerCloudController.java
@@ -0,0 +1,78 @@
+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.YwH5Banner;
+import com.doumee.service.business.YwH5BannerService;
+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 = "H5杞挱鍥�")
+@RestController
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/business/ywH5Banner")
+public class YwH5BannerCloudController extends BaseController {
+
+ @Autowired
+ private YwH5BannerService ywH5BannerService;
+
+ @PreventRepeat
+ @ApiOperation("鏂板缓")
+ @PostMapping("/create")
+ @CloudRequiredPermission("business:ywh5banner:create")
+ public ApiResponse<Integer> create(@RequestBody YwH5Banner ywH5Banner,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ ywH5Banner.setLoginUserInfo(this.getLoginUser(token));
+ return ApiResponse.success(ywH5BannerService.create(ywH5Banner));
+ }
+
+ @ApiOperation("鏍规嵁ID鍒犻櫎")
+ @GetMapping("/delete/{id}")
+ @CloudRequiredPermission("business:ywh5banner:delete")
+ public ApiResponse<Void> deleteById(@PathVariable Integer id,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ ywH5BannerService.deleteById(id, this.getLoginUser(token));
+ return ApiResponse.success(null);
+ }
+
+ @ApiOperation("鎵归噺鍒犻櫎")
+ @GetMapping("/delete/batch")
+ @CloudRequiredPermission("business:ywh5banner:delete")
+ public ApiResponse<Void> deleteByIdInBatch(@RequestParam String ids,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ ywH5BannerService.deleteByIdInBatch(this.getIdList(ids), this.getLoginUser(token));
+ return ApiResponse.success(null);
+ }
+
+ @ApiOperation("鏍规嵁ID淇敼")
+ @PostMapping("/updateById")
+ @CloudRequiredPermission("business:ywh5banner:update")
+ public ApiResponse<Void> updateById(@RequestBody YwH5Banner ywH5Banner,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ ywH5Banner.setLoginUserInfo(this.getLoginUser(token));
+ ywH5BannerService.updateById(ywH5Banner);
+ return ApiResponse.success(null);
+ }
+
+ @ApiOperation("鍒嗛〉鏌ヨ")
+ @PostMapping("/page")
+ @CloudRequiredPermission("business:ywh5banner:query")
+ public ApiResponse<PageData<YwH5Banner>> findPage(@RequestBody PageWrap<YwH5Banner> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywH5BannerService.findPage(pageWrap));
+ }
+
+ @ApiOperation("鏍规嵁ID鏌ヨ")
+ @GetMapping("/{id}")
+ @CloudRequiredPermission("business:ywh5banner:query")
+ public ApiResponse<YwH5Banner> findById(@PathVariable Integer id,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ return ApiResponse.success(ywH5BannerService.findById(id));
+ }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerH5Controller.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerH5Controller.java
new file mode 100644
index 0000000..0cf420e
--- /dev/null
+++ b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerH5Controller.java
@@ -0,0 +1,212 @@
+package com.doumee.cloud.web;
+
+import com.doumee.api.BaseController;
+import com.doumee.config.annotation.LoginNoRequired;
+import com.doumee.core.annotation.pr.PreventRepeat;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.ApiResponse;
+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.dao.business.dto.YwCustomerRechargeRecordVO;
+import com.doumee.dao.business.dto.h5.*;
+import com.doumee.dao.business.model.YwContract;
+import com.doumee.dao.business.model.YwContractBill;
+import com.doumee.dao.business.model.YwH5Banner;
+import com.doumee.dao.business.model.YwWxPayOrder;
+import com.doumee.dao.system.dto.LoginPhoneDTO;
+import com.doumee.service.business.SmsEmailService;
+import com.doumee.service.business.YwCustomerH5AuthService;
+import com.doumee.service.business.YwCustomerH5BizService;
+import com.doumee.service.business.YwCustomerWxPayService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+
+@Api(tags = "銆愬叕浼楀彿銆戝晢鎴稨5")
+@RestController
+@Slf4j
+@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/web/customer")
+public class YwCustomerH5Controller extends BaseController {
+
+ @Autowired
+ private YwCustomerH5BizService ywCustomerH5BizService;
+ @Autowired
+ private YwCustomerWxPayService ywCustomerWxPayService;
+ @Autowired
+ private YwCustomerH5AuthService ywCustomerH5AuthService;
+ @Autowired
+ private SmsEmailService smsEmailService;
+
+ private LoginUserInfo requireCustomerUser(String token) {
+ LoginUserInfo user = getLoginUser(token);
+ if (user == null || !Constants.equalsInteger(user.getH5UserType(), LoginUserInfo.H5_USER_CUSTOMER)) {
+ throw new RuntimeException("鐧诲綍宸插け鏁�");
+ }
+ return user;
+ }
+
+ @LoginNoRequired
+ @PreventRepeat
+ @ApiOperation("鍟嗘埛鐧诲綍鍙戦�侀獙璇佺爜")
+ @PostMapping("/sendLoginSms")
+ public ApiResponse<Integer> sendLoginSms(@RequestBody(required = false) LoginPhoneDTO dto) {
+ try {
+ if (dto == null || StringUtils.isBlank(dto.getPhone())) {
+ return ApiResponse.failed(ResponseStatus.BAD_REQUEST.getCode(), "鎵嬫満鍙蜂笉鑳戒负绌�");
+ }
+ return ApiResponse.success(smsEmailService.sendMerchantLoginSms(dto.getPhone().trim()));
+ } catch (BusinessException e) {
+ return ApiResponse.failed(e.getCode(), e.getMessage());
+ } catch (Throwable e) {
+ log.error("sendLoginSms failed", e);
+ return ApiResponse.failed(ResponseStatus.SERVER_ERROR.getCode(), "鍙戦�侀獙璇佺爜澶辫触锛岃绋嶅悗閲嶈瘯");
+ }
+ }
+
+ @LoginNoRequired
+ @PreventRepeat
+ @ApiOperation("鍟嗘埛鐭俊楠岃瘉鐮佺櫥褰�")
+ @PostMapping("/loginByPhone")
+ public ApiResponse<String> loginByPhone(@Validated @RequestBody LoginPhoneDTO dto) {
+ try {
+ return ApiResponse.success(ywCustomerH5AuthService.loginByPhone(dto));
+ } catch (BusinessException e) {
+ return ApiResponse.failed(e.getCode(), e.getMessage());
+ } catch (Exception e) {
+ return ApiResponse.failed(ResponseStatus.SERVER_ERROR);
+ }
+ }
+
+ @ApiOperation("鑾峰彇褰撳墠鍟嗘埛鐧诲綍淇℃伅")
+ @GetMapping("/getUserInfo")
+ public ApiResponse<LoginUserInfo> getUserInfo(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = this.getLoginUser(token);
+ if (user == null || !Constants.equalsInteger(user.getH5UserType(), LoginUserInfo.H5_USER_CUSTOMER)) {
+ return ApiResponse.failed("鐧诲綍宸插け鏁�");
+ }
+ return ApiResponse.success(ywCustomerH5AuthService.buildLoginUserInfo(user.getCustomerId()));
+ }
+
+ @ApiOperation("宸ヤ綔鍙拌疆鎾浘")
+ @GetMapping("/banners")
+ @LoginNoRequired
+ public ApiResponse<List<YwH5Banner>> banners() {
+ return ApiResponse.success(ywCustomerH5BizService.listBanners());
+ }
+
+ @ApiOperation("宸ヤ綔鍙伴椤�")
+ @GetMapping("/home")
+ public ApiResponse<Map<String, Object>> home(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.home(user.getCustomerId()));
+ }
+
+ @ApiOperation("浜ょ數璐硅澶囧垪琛�")
+ @PostMapping("/device/page")
+ public ApiResponse<PageData<CustomerDeviceH5VO>> devicePage(
+ @RequestBody PageWrap<CustomerDeviceQueryDTO> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.devicePage(pageWrap, user.getCustomerId()));
+ }
+
+ @ApiOperation("璁惧璇︽儏")
+ @GetMapping("/device/detail")
+ public ApiResponse<CustomerDeviceH5VO> deviceDetail(
+ @RequestParam Integer deviceType,
+ @RequestParam Integer deviceId,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.deviceDetail(deviceType, deviceId, user.getCustomerId()));
+ }
+
+ @ApiOperation("鍏呭�艰褰�")
+ @PostMapping("/rechargeRecord/page")
+ public ApiResponse<PageData<YwCustomerRechargeRecordVO>> rechargeRecordPage(
+ @RequestBody PageWrap<CustomerRechargeRecordH5QueryDTO> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.rechargeRecordPage(pageWrap, user.getCustomerId()));
+ }
+
+ @ApiOperation("鍚堝悓鍒楄〃")
+ @PostMapping("/contract/page")
+ public ApiResponse<PageData<YwContract>> contractPage(
+ @RequestBody PageWrap<CustomerContractQueryDTO> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.contractPage(pageWrap, user.getCustomerId()));
+ }
+
+ @ApiOperation("鍚堝悓璇︽儏")
+ @GetMapping("/contract/{id}")
+ public ApiResponse<Map<String, Object>> contractDetail(
+ @PathVariable("id") Integer id,
+ @RequestParam(value = "billType", required = false, defaultValue = "0") Integer billType,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.contractDetail(id, user.getCustomerId(), billType));
+ }
+
+ @ApiOperation("璐﹀崟鍒楄〃")
+ @PostMapping("/bill/page")
+ public ApiResponse<PageData<YwContractBill>> billPage(
+ @RequestBody PageWrap<CustomerBillQueryDTO> pageWrap,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.billPage(pageWrap, user.getCustomerId()));
+ }
+
+ @ApiOperation("璐﹀崟璇︽儏")
+ @GetMapping("/bill/{id}")
+ public ApiResponse<Map<String, Object>> billDetail(
+ @PathVariable("id") Integer id,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerH5BizService.billDetail(id, user.getCustomerId()));
+ }
+
+ @ApiOperation("鍒涘缓鏀粯璁㈠崟")
+ @PostMapping("/pay/createOrder")
+ public ApiResponse<Map<String, String>> createOrder(
+ @RequestBody CustomerPayCreateDTO dto,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token,
+ HttpServletRequest request) {
+ LoginUserInfo user = requireCustomerUser(token);
+ String ip = request.getHeader("X-Forwarded-For");
+ if (ip != null && ip.contains(",")) {
+ ip = ip.split(",")[0].trim();
+ }
+ if (ip == null) {
+ ip = request.getRemoteAddr();
+ }
+ return ApiResponse.success(ywCustomerWxPayService.createOrder(dto, user, ip));
+ }
+
+ @ApiOperation("鏌ヨ鏀粯缁撴灉")
+ @GetMapping("/pay/query/{orderNo}")
+ public ApiResponse<YwWxPayOrder> queryPay(
+ @PathVariable String orderNo,
+ @RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
+ LoginUserInfo user = requireCustomerUser(token);
+ return ApiResponse.success(ywCustomerWxPayService.queryOrder(orderNo, user.getCustomerId()));
+ }
+
+ @ApiOperation("寰俊鏀粯鍥炶皟")
+ @PostMapping("/pay/notify")
+ @LoginNoRequired
+ public String payNotify(@RequestBody String xmlBody) {
+ return ywCustomerWxPayService.handleNotify(xmlBody);
+ }
+}
diff --git a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerWebController.java b/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerWebController.java
deleted file mode 100644
index 2e38ea4..0000000
--- a/server/visits/dmvisit_admin/src/main/java/com/doumee/cloud/web/YwCustomerWebController.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.doumee.cloud.web;
-
-import com.doumee.api.BaseController;
-import com.doumee.config.annotation.LoginNoRequired;
-import com.doumee.core.annotation.pr.PreventRepeat;
-import com.doumee.core.constants.ResponseStatus;
-import com.doumee.core.exception.BusinessException;
-import com.doumee.core.model.ApiResponse;
-import com.doumee.core.model.LoginUserInfo;
-import com.doumee.core.utils.Constants;
-import com.doumee.dao.system.dto.LoginPhoneDTO;
-import com.doumee.service.business.YwCustomerH5AuthService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.*;
-
-@Api(tags = "銆愬叕浼楀彿銆戝晢鎴风櫥褰�")
-@RestController
-@LoginNoRequired
-@RequestMapping(Constants.CLOUD_SERVICE_URL_INDEX + "/web/customer")
-public class YwCustomerWebController extends BaseController {
-
- @Autowired
- private YwCustomerH5AuthService ywCustomerH5AuthService;
-
- @PreventRepeat
- @ApiOperation("鍟嗘埛鐭俊楠岃瘉鐮佺櫥褰�")
- @PostMapping("/loginByPhone")
- public ApiResponse<String> loginByPhone(@Validated @RequestBody LoginPhoneDTO dto) {
- try {
- return ApiResponse.success(ywCustomerH5AuthService.loginByPhone(dto));
- } catch (BusinessException e) {
- return ApiResponse.failed(e.getCode(), e.getMessage());
- } catch (Exception e) {
- return ApiResponse.failed(ResponseStatus.SERVER_ERROR);
- }
- }
-
- @ApiOperation("鑾峰彇褰撳墠鍟嗘埛鐧诲綍淇℃伅")
- @GetMapping("/getUserInfo")
- public ApiResponse<LoginUserInfo> getUserInfo(@RequestHeader(Constants.HEADER_USER_TOKEN) String token) {
- LoginUserInfo user = this.getLoginUser(token);
- if (user == null || !Constants.equalsInteger(user.getH5UserType(), LoginUserInfo.H5_USER_CUSTOMER)) {
- return ApiResponse.failed("鐧诲綍宸插け鏁�");
- }
- return ApiResponse.success(ywCustomerH5AuthService.buildLoginUserInfo(user.getCustomerId()));
- }
-}
diff --git a/server/visits/dmvisit_admin/src/main/resources/application.yml b/server/visits/dmvisit_admin/src/main/resources/application.yml
index 8a4ad1c..0417ffa 100644
--- a/server/visits/dmvisit_admin/src/main/resources/application.yml
+++ b/server/visits/dmvisit_admin/src/main/resources/application.yml
@@ -84,4 +84,10 @@
pwdParamName: password #鐢ㄦ埛鐧诲綍璁よ瘉瀵嗙爜鍙傛暟鍚嶇О
useDefaultController: true # 鏄惁浣跨敤榛樿鐨凧wtAuthController
+# H5 寰俊 OAuth 寮�鍙戞ā鎷燂紙鐢熶骇鐜鍔″繀 mock-enabled: false锛�
+h5:
+ wechat:
+ mock-enabled: true
+ mock-openid: dev_h5_openid_merchant_001
+ mock-code: DEV_MOCK
diff --git a/server/visits/dmvisit_admin/src/main/resources/bootstrap.yml b/server/visits/dmvisit_admin/src/main/resources/bootstrap.yml
index 2df12af..9dc10e9 100644
--- a/server/visits/dmvisit_admin/src/main/resources/bootstrap.yml
+++ b/server/visits/dmvisit_admin/src/main/resources/bootstrap.yml
@@ -1,6 +1,6 @@
spring:
profiles:
- active: pro
+ active: dev
application:
name: visitsAdmin
# 瀹夊叏閰嶇疆
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/haikang/model/HKConstants.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/haikang/model/HKConstants.java
index 532b31e..0b00076 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/core/haikang/model/HKConstants.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/haikang/model/HKConstants.java
@@ -1,7 +1,6 @@
package com.doumee.core.haikang.model;
import com.doumee.core.utils.Constants;
-import javafx.scene.effect.BlendMode;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/core/wx/WxJsapiPayUtil.java b/server/visits/dmvisit_service/src/main/java/com/doumee/core/wx/WxJsapiPayUtil.java
new file mode 100644
index 0000000..5cbdae0
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/core/wx/WxJsapiPayUtil.java
@@ -0,0 +1,135 @@
+package com.doumee.core.wx;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.XmlUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import cn.hutool.http.HttpUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.*;
+
+/**
+ * 寰俊鍏紬鍙� JSAPI 鏀粯锛圴2锛�
+ */
+@Component
+@Slf4j
+public class WxJsapiPayUtil {
+
+ private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+ private static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
+
+ @Value("${wx.pay.appId:}")
+ private String appId;
+ @Value("${wx.pay.mchId:}")
+ private String mchId;
+ @Value("${wx.pay.mchKey:}")
+ private String mchKey;
+ @Value("${wx.pay.notifyUrl:}")
+ private String notifyUrl;
+
+ public Map<String, String> createJsapiOrder(String orderNo, String openid, String body, BigDecimal amountYuan, String clientIp) {
+ int totalFee = amountYuan.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).intValue();
+ Map<String, String> params = new TreeMap<>();
+ params.put("appid", appId);
+ params.put("mch_id", mchId);
+ params.put("nonce_str", RandomUtil.randomString(32));
+ params.put("body", StringUtils.defaultIfBlank(body, "鍟嗘埛缂磋垂"));
+ params.put("out_trade_no", orderNo);
+ params.put("total_fee", String.valueOf(totalFee));
+ params.put("spbill_create_ip", StringUtils.defaultIfBlank(clientIp, "127.0.0.1"));
+ params.put("notify_url", notifyUrl);
+ params.put("trade_type", "JSAPI");
+ params.put("openid", openid);
+ params.put("sign", sign(params));
+
+ String xml = mapToXml(params);
+ String respXml = HttpUtil.post(UNIFIED_ORDER_URL, xml);
+ Map<String, String> resp = xmlToMap(respXml);
+ if (!"SUCCESS".equals(resp.get("return_code")) || !"SUCCESS".equals(resp.get("result_code"))) {
+ throw new RuntimeException(StringUtils.defaultIfBlank(resp.get("err_code_des"), resp.get("return_msg")));
+ }
+ String prepayId = resp.get("prepay_id");
+ return buildJsapiPayParams(prepayId);
+ }
+
+ public Map<String, String> queryOrder(String orderNo) {
+ Map<String, String> params = new TreeMap<>();
+ params.put("appid", appId);
+ params.put("mch_id", mchId);
+ params.put("out_trade_no", orderNo);
+ params.put("nonce_str", RandomUtil.randomString(32));
+ params.put("sign", sign(params));
+ String respXml = HttpUtil.post(ORDER_QUERY_URL, mapToXml(params));
+ return xmlToMap(respXml);
+ }
+
+ public boolean verifyNotifySign(Map<String, String> data) {
+ if (data == null || !data.containsKey("sign")) {
+ return false;
+ }
+ String sign = data.get("sign");
+ Map<String, String> copy = new TreeMap<>(data);
+ copy.remove("sign");
+ return sign.equals(sign(copy));
+ }
+
+ public Map<String, String> parseNotifyXml(String xml) {
+ return xmlToMap(xml);
+ }
+
+ private Map<String, String> buildJsapiPayParams(String prepayId) {
+ Map<String, String> pay = new LinkedHashMap<>();
+ pay.put("appId", appId);
+ pay.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
+ pay.put("nonceStr", RandomUtil.randomString(32));
+ pay.put("package", "prepay_id=" + prepayId);
+ pay.put("signType", "MD5");
+ Map<String, String> signMap = new TreeMap<>();
+ signMap.put("appId", pay.get("appId"));
+ signMap.put("timeStamp", pay.get("timeStamp"));
+ signMap.put("nonceStr", pay.get("nonceStr"));
+ signMap.put("package", pay.get("package"));
+ signMap.put("signType", pay.get("signType"));
+ pay.put("paySign", sign(signMap));
+ return pay;
+ }
+
+ private String sign(Map<String, String> params) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, String> e : params.entrySet()) {
+ if (StringUtils.isNotBlank(e.getValue())) {
+ sb.append(e.getKey()).append("=").append(e.getValue()).append("&");
+ }
+ }
+ sb.append("key=").append(mchKey);
+ return DigestUtil.md5Hex(sb.toString()).toUpperCase();
+ }
+
+ private String mapToXml(Map<String, String> params) {
+ StringBuilder sb = new StringBuilder("<xml>");
+ for (Map.Entry<String, String> e : params.entrySet()) {
+ sb.append("<").append(e.getKey()).append("><![CDATA[")
+ .append(e.getValue()).append("]]></").append(e.getKey()).append(">");
+ }
+ sb.append("</xml>");
+ return sb.toString();
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, String> xmlToMap(String xml) {
+ Map<String, String> map = new HashMap<>();
+ if (StringUtils.isBlank(xml)) {
+ return map;
+ }
+ Map<String, Object> raw = XmlUtil.xmlToMap(xml);
+ for (Map.Entry<String, Object> e : raw.entrySet()) {
+ map.put(e.getKey(), e.getValue() == null ? null : String.valueOf(e.getValue()));
+ }
+ return map;
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwH5BannerMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwH5BannerMapper.java
new file mode 100644
index 0000000..72e57d3
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwH5BannerMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwH5Banner;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwH5BannerMapper extends MPJBaseMapper<YwH5Banner> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwWxPayOrderMapper.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwWxPayOrderMapper.java
new file mode 100644
index 0000000..839a53f
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/YwWxPayOrderMapper.java
@@ -0,0 +1,7 @@
+package com.doumee.dao.business;
+
+import com.doumee.dao.business.model.YwWxPayOrder;
+import com.github.yulichang.base.MPJBaseMapper;
+
+public interface YwWxPayOrderMapper extends MPJBaseMapper<YwWxPayOrder> {
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/EditRecordDataVO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/EditRecordDataVO.java
similarity index 67%
rename from server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/EditRecordDataVO.java
rename to server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/EditRecordDataVO.java
index 8221220..de13c2c 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/vo/EditRecordDataVO.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/EditRecordDataVO.java
@@ -1,17 +1,12 @@
-package com.doumee.dao.business.vo;
+package com.doumee.dao.business.dto;
-import com.doumee.dao.business.model.Approve;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
-import java.util.List;
/**
- * Created by IntelliJ IDEA.
- *
- * @Author : Rk
- * @create 2024/5/23 14:56
+ * 鎿嶄綔璁板綍
*/
@Data
public class EditRecordDataVO {
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwCustomerRechargeRecordQueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwCustomerRechargeRecordQueryDTO.java
index 0411ce7..21dfca4 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwCustomerRechargeRecordQueryDTO.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/YwCustomerRechargeRecordQueryDTO.java
@@ -10,6 +10,7 @@
public class YwCustomerRechargeRecordQueryDTO {
private String customerName;
+ private Integer customerId;
private Integer type;
private Integer status;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerBillQueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerBillQueryDTO.java
new file mode 100644
index 0000000..2706afe
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerBillQueryDTO.java
@@ -0,0 +1,8 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+@Data
+public class CustomerBillQueryDTO {
+ private Integer payTab;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerContractQueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerContractQueryDTO.java
new file mode 100644
index 0000000..1102ba7
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerContractQueryDTO.java
@@ -0,0 +1,8 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+@Data
+public class CustomerContractQueryDTO {
+ private Integer status;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceH5VO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceH5VO.java
new file mode 100644
index 0000000..9c3e768
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceH5VO.java
@@ -0,0 +1,24 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class CustomerDeviceH5VO {
+ private Integer deviceType;
+ private Integer deviceId;
+ private String deviceName;
+ private String statusText;
+ private Integer statusCode;
+ private List<String> alarmTags;
+ private String roomInfo;
+ private List<String> roomList;
+ private String meterAccountNo;
+ private BigDecimal totalUsage;
+ private BigDecimal balance;
+ private Boolean balanceLow;
+ private Date updateTime;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceQueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceQueryDTO.java
new file mode 100644
index 0000000..40aab43
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerDeviceQueryDTO.java
@@ -0,0 +1,9 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+@Data
+public class CustomerDeviceQueryDTO {
+ private Integer deviceType;
+ private Integer statusFilter;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerPayCreateDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerPayCreateDTO.java
new file mode 100644
index 0000000..cbf19d4
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerPayCreateDTO.java
@@ -0,0 +1,16 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class CustomerPayCreateDTO {
+ /** 0鐢佃〃 1绌鸿皟 2璐﹀崟 */
+ private Integer orderType;
+ private Integer electricalId;
+ private Integer billId;
+ private BigDecimal amount;
+ private String remark;
+ private String openid;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerRechargeRecordH5QueryDTO.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerRechargeRecordH5QueryDTO.java
new file mode 100644
index 0000000..f1b5ac3
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/dto/h5/CustomerRechargeRecordH5QueryDTO.java
@@ -0,0 +1,13 @@
+package com.doumee.dao.business.dto.h5;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class CustomerRechargeRecordH5QueryDTO {
+ private Integer status;
+ private Date createTimeBegin;
+ private Date createTimeEnd;
+ private String month;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContract.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContract.java
index e476672..a3872bb 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContract.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContract.java
@@ -310,6 +310,24 @@
@TableField(exist = false)
private YwContractBillDTO zlBillDTO;
+ @ApiModelProperty(value = "鎴块棿淇℃伅鎽樿锛圚5锛�")
+ @TableField(exist = false)
+ private String roomInfo;
+ @ApiModelProperty(value = "浠樻鏂瑰紡鏂囨锛圚5锛�")
+ @TableField(exist = false)
+ private String payTypeText;
+
+ @ApiModelProperty(value = "璐﹀崟鐘舵�佹彁绀猴紙H5锛�")
+ @TableField(exist = false)
+ private String billStatusTip;
+
+ @ApiModelProperty(value = "璐﹀崟鐘舵�佹彁绀虹被鍨� ok/warn/danger锛圚5锛�")
+ @TableField(exist = false)
+ private String billStatusType;
+
+ @ApiModelProperty(value = "鍏嶇鏈熸枃妗堬紙H5锛�")
+ @TableField(exist = false)
+ private String freeRentPeriod;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractBill.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractBill.java
index dadcb8f..d39f034 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractBill.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractBill.java
@@ -179,6 +179,16 @@
@TableField(exist = false)
private String contractCode;
+ @ApiModelProperty(value = "鍚堝悓寮�濮嬫棩鏈�")
+ @TableField(exist = false)
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date contractStartDate;
+
+ @ApiModelProperty(value = "鍚堝悓缁撴潫鏃ユ湡")
+ @TableField(exist = false)
+ @JsonFormat(pattern = "yyyy-MM-dd")
+ private Date contractEndDate;
+
@ApiModelProperty(value = "鍚堝悓鐘舵��", example = "1")
@TableField(exist = false)
private Integer contractStatus;
@@ -254,5 +264,12 @@
@TableField(exist = false)
private Integer wyPayType;
+ @ApiModelProperty(value = "鎴块棿淇℃伅鎽樿锛圚5锛�")
+ @TableField(exist = false)
+ private String roomInfo;
+
+ @ApiModelProperty(value = "鎴挎簮瀵硅薄闆嗗悎锛圚5锛�")
+ @TableField(exist = false)
+ private List<YwRoom> roomList;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractRevenue.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractRevenue.java
index 70281cd..22574c0 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractRevenue.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwContractRevenue.java
@@ -2,9 +2,8 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.doumee.core.annotation.excel.ExcelColumn;
-import com.doumee.core.constants.OperaType;
import com.doumee.core.model.LoginUserModel;
-import com.doumee.dao.business.vo.EditRecordDataVO;
+import com.doumee.dao.business.dto.EditRecordDataVO;
import com.doumee.dao.system.model.Multifile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@@ -14,7 +13,6 @@
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
-import javax.validation.constraints.NotBlank;
import java.util.Date;
import java.math.BigDecimal;
import java.util.List;
@@ -83,6 +81,9 @@
@ApiModelProperty(value = "璐﹀崟涓婚敭锛堝叧鑱攜w_contract_bill锛�", example = "1")
private Integer billId;
+ @ApiModelProperty(value = "寰俊鏀粯璁㈠崟鍙�")
+ private String wxOrderNo;
+
@ApiModelProperty(value = "鏀舵敮绫诲瀷锛�0=鏀跺叆锛�1=鏀嚭", example = "1")
@ExcelColumn(name="鏀舵敮绫诲瀷",index = 4,width = 10,valueMapping = "0=鏀跺叆;1=鏀嚭")
private Integer revenueType;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomer.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomer.java
index 1fb1190..eab0b90 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomer.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomer.java
@@ -72,6 +72,9 @@
@ApiModelProperty(value = "寰俊openid")
private String openid;
+ @ApiModelProperty(value = "鏄惁宸插畬鎴愰娆″厖鍊煎埗 0鍚� 1鏄�")
+ private Integer firstRechargeDone;
+
@ApiModelProperty(value = "韬唤璇佸彿锛堝姞瀵嗭級")
@ExcelColumn(name="韬唤璇佸彿锛堝姞瀵嗭級")
private String idcardNo;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomerElectrical.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomerElectrical.java
index 8772028..26dc2af 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomerElectrical.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwCustomerElectrical.java
@@ -29,4 +29,10 @@
@ApiModelProperty("鐢佃〃 ID")
private Integer electricalId;
+
+ @ApiModelProperty("0鎵嬪姩 1鍚堝悓鑷姩")
+ private Integer bindSource;
+
+ @ApiModelProperty("鏉ユ簮鍚堝悓ID")
+ private Integer contractId;
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalCharge.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalCharge.java
index 520a07b..7970889 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalCharge.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwElectricalCharge.java
@@ -65,6 +65,8 @@
@ApiModelProperty("瀹㈡埛涓婚敭锛堝叧鑱攜w_customer)")
@ExcelColumn(name="瀹㈡埛涓婚敭锛堝叧鑱攜w_customer)",index=14 ,width=10)
private Integer customerId;
+ @ApiModelProperty("寰俊鏀粯璁㈠崟鍙�")
+ private String wxOrderNo;
@ApiModelProperty("鍏ヨ处鏃ユ湡")
@ExcelColumn(name="鍏ヨ处鏃ユ湡",index=15 ,width=10)
private Date incomeTime;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwH5Banner.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwH5Banner.java
new file mode 100644
index 0000000..73fa302
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwH5Banner.java
@@ -0,0 +1,31 @@
+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 io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+@ApiModel("H5杞挱鍥�")
+@TableName("yw_h5_banner")
+public class YwH5Banner extends LoginUserModel {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+ private Integer creator;
+ private Date createDate;
+ private Integer editor;
+ private Date editDate;
+ private Integer isdeleted;
+ private String remark;
+ private String title;
+ private String imageUrl;
+ private String linkUrl;
+ private Integer sortnum;
+ private Integer status;
+ private Integer scope;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwWxPayOrder.java b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwWxPayOrder.java
new file mode 100644
index 0000000..21687ad
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/dao/business/model/YwWxPayOrder.java
@@ -0,0 +1,54 @@
+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 io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@ApiModel("寰俊H5鏀粯璁㈠崟")
+@TableName("yw_wx_pay_order")
+public class YwWxPayOrder extends LoginUserModel {
+
+ @TableId(type = IdType.AUTO)
+ private Integer id;
+ private Integer creator;
+ private Date createDate;
+ private Integer editor;
+ private Date editDate;
+ private Integer isdeleted;
+ private String remark;
+
+ @ApiModelProperty("鍟嗘埛璁㈠崟鍙�")
+ private String orderNo;
+ @ApiModelProperty("浠樻鍟嗘埛")
+ private Integer customerId;
+ @ApiModelProperty("0鐢佃〃 1绌鸿皟 2璐﹀崟")
+ private Integer orderType;
+ @ApiModelProperty("涓氬姟寮曠敤ID")
+ private Integer bizRefId;
+ @ApiModelProperty("涓氬姟璁板綍ID")
+ private Integer bizRecordId;
+ private BigDecimal amount;
+ @ApiModelProperty("0寰呮敮浠� 1鎴愬姛 2澶辫触 3鍏抽棴")
+ private Integer status;
+ private String wxTransactionId;
+ private Date payTime;
+ private String openid;
+ private String requestSnapshot;
+ private String statusInfo;
+
+ public static final int TYPE_ELECTRICAL = 0;
+ public static final int TYPE_CONDITIONER = 1;
+ public static final int TYPE_BILL = 2;
+ public static final int STATUS_WAIT = 0;
+ public static final int STATUS_SUCCESS = 1;
+ public static final int STATUS_FAIL = 2;
+ public static final int STATUS_CLOSED = 3;
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/SmsEmailService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/SmsEmailService.java
index 94f8efa..ce5be08 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/SmsEmailService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/SmsEmailService.java
@@ -21,6 +21,9 @@
*/
Integer create(SmsEmail smsEmail);
Integer sendSms(SmsEmail smsEmail);
+
+ /** 鍟嗘埛 H5 鐧诲綍楠岃瘉鐮侊紙浠呮牎楠� yw_customer锛� */
+ Integer sendMerchantLoginSms(String phone);
void validateCode(String code,String phone);
/**
* 涓婚敭鍒犻櫎
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerDeviceAutoBindService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerDeviceAutoBindService.java
new file mode 100644
index 0000000..9b2dbfa
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerDeviceAutoBindService.java
@@ -0,0 +1,18 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.LoginUserInfo;
+
+/**
+ * 鏍规嵁鍟嗘埛绉熻祦鍚堝悓鑷姩鍏宠仈鐢佃〃/绌鸿皟璁惧
+ */
+public interface YwCustomerDeviceAutoBindService {
+
+ /** 鎸夊悎鍚屽悓姝ヨ澶囧叧鑱旓紙鍒涘缓/鐢熸晥鏃惰皟鐢級 */
+ void syncByContractId(Integer contractId, LoginUserInfo user);
+
+ /** 鎸夊晢鎴峰悓姝ユ墍鏈夋湁鏁堝悎鍚屼笅鐨勮澶� */
+ void syncByCustomerId(Integer customerId, LoginUserInfo user);
+
+ /** 鍚堝悓閫�绉�/鍒版湡鏃惰В闄よ嚜鍔ㄥ叧鑱� */
+ void unbindByContractId(Integer contractId, LoginUserInfo user);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5AuthService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5AuthService.java
index 6800ebb..ca0cae6 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5AuthService.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5AuthService.java
@@ -12,4 +12,7 @@
String loginByOpenId(String openId);
LoginUserInfo buildLoginUserInfo(Integer customerId);
+
+ /** 鍙戦獙璇佺爜鍓嶆牎楠岋細鍟嗘埛鎵嬫満鍙烽』瀵瑰簲鏈夋晥 yw_customer锛堝惈鑱旂郴浜� member.phone锛� */
+ void assertActiveCustomerByPhone(String phone);
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5BizService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5BizService.java
new file mode 100644
index 0000000..30760de
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerH5BizService.java
@@ -0,0 +1,36 @@
+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.dto.YwCustomerRechargeRecordVO;
+import com.doumee.dao.business.dto.h5.*;
+import com.doumee.dao.business.model.YwContract;
+import com.doumee.dao.business.model.YwContractBill;
+import com.doumee.dao.business.model.YwH5Banner;
+
+import java.util.List;
+import java.util.Map;
+
+public interface YwCustomerH5BizService {
+
+ List<YwH5Banner> listBanners();
+
+ Map<String, Object> home(Integer customerId);
+
+ PageData<CustomerDeviceH5VO> devicePage(PageWrap<CustomerDeviceQueryDTO> pageWrap, Integer customerId);
+
+ CustomerDeviceH5VO deviceDetail(Integer deviceType, Integer deviceId, Integer customerId);
+
+ PageData<YwCustomerRechargeRecordVO> rechargeRecordPage(PageWrap<CustomerRechargeRecordH5QueryDTO> pageWrap, Integer customerId);
+
+ PageData<YwContract> contractPage(PageWrap<CustomerContractQueryDTO> pageWrap, Integer customerId);
+
+ Map<String, Object> contractDetail(Integer contractId, Integer customerId, Integer billType);
+
+ PageData<YwContractBill> billPage(PageWrap<CustomerBillQueryDTO> pageWrap, Integer customerId);
+
+ Map<String, Object> billDetail(Integer billId, Integer customerId);
+
+ void applyFirstRechargeIfNeeded(Integer customerId, LoginUserInfo user);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerWxPayService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerWxPayService.java
new file mode 100644
index 0000000..27a2314
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwCustomerWxPayService.java
@@ -0,0 +1,16 @@
+package com.doumee.service.business;
+
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.dao.business.dto.h5.CustomerPayCreateDTO;
+import com.doumee.dao.business.model.YwWxPayOrder;
+
+import java.util.Map;
+
+public interface YwCustomerWxPayService {
+
+ Map<String, String> createOrder(CustomerPayCreateDTO dto, LoginUserInfo user, String clientIp);
+
+ YwWxPayOrder queryOrder(String orderNo, Integer customerId);
+
+ String handleNotify(String xmlBody);
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwH5BannerService.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwH5BannerService.java
new file mode 100644
index 0000000..73a72f3
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/YwH5BannerService.java
@@ -0,0 +1,25 @@
+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.YwH5Banner;
+
+import java.util.List;
+
+public interface YwH5BannerService {
+
+ Integer create(YwH5Banner model);
+
+ void deleteById(Integer id, LoginUserInfo user);
+
+ void deleteByIdInBatch(List<Integer> ids, LoginUserInfo user);
+
+ void updateById(YwH5Banner model);
+
+ YwH5Banner findById(Integer id);
+
+ PageData<YwH5Banner> findPage(PageWrap<YwH5Banner> pageWrap);
+
+ List<YwH5Banner> listEnabledForCustomerWorkbench();
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
index d733728..563d197 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/MemberServiceImpl.java
@@ -135,7 +135,14 @@
@Value("${debug_model}")
private Boolean isDebug;
+ @Value("${h5.wechat.mock-enabled:false}")
+ private boolean h5WechatMockEnabled;
+ @Value("${h5.wechat.mock-openid:}")
+ private String h5WechatMockOpenid;
+
+ @Value("${h5.wechat.mock-code:DEV_MOCK}")
+ private String h5WechatMockCode;
@Override
@Transactional(rollbackFor = {BusinessException.class,Exception.class})
@@ -346,6 +353,7 @@
if (StringUtils.isNotBlank(member.getIdcardNo()) && !IdcardUtil.isValidCard(member.getIdcardNo())){
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"韬唤璇佸彿鏍煎紡鏈夎");
}
+ // 鍐呴儴鍛樺伐/鍙告満锛氫粎鍦� type=0,2 鑼冨洿鍐呭幓閲嶏紝涓嶄笌鍟嗘埛浜哄憳(type=3)浜掓枼
if(StringUtils.isNotBlank(member.getIdcardNo())){
if(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
.in(Member::getType,new Integer[]{Constants.ZERO,Constants.TWO})
@@ -1785,6 +1793,9 @@
if(StringUtils.isBlank(code)){
throw new BusinessException(ResponseStatus.BAD_REQUEST);
}
+ if (h5WechatMockEnabled && StringUtils.equals(code, h5WechatMockCode)) {
+ return buildYwWxAuthorizeVO(StringUtils.trimToEmpty(h5WechatMockOpenid), userType);
+ }
String appId = systemDictDataBiz.queryByCode(Constants.WX_PLATFORM,Constants.WX_PLATFORM_APPID).getCode();
String appSecret = systemDictDataBiz.queryByCode(Constants.WX_PLATFORM,Constants.WX_PLATFORM_SECRET).getCode();
String getTokenUrl = WXConstant.GET_USER_INFO_URL.replace("CODE", code)
@@ -1793,14 +1804,18 @@
JSONObject tokenJson = JSONObject.parseObject(HttpsUtil.get(getTokenUrl,true));
log.error("=========================tokenJson=====================" + tokenJson);
String openId = "";
- WxAuthorizeVO wxAuthorizeVO = new WxAuthorizeVO();
if(Objects.nonNull(tokenJson)&&!Objects.isNull(tokenJson.get("access_token"))){
openId = tokenJson.getString("openid");
}else{
if(StringUtils.isBlank(openId)){
- return wxAuthorizeVO;
+ return new WxAuthorizeVO();
}
}
+ return buildYwWxAuthorizeVO(openId, userType);
+ }
+
+ private WxAuthorizeVO buildYwWxAuthorizeVO(String openId, Integer userType) {
+ WxAuthorizeVO wxAuthorizeVO = new WxAuthorizeVO();
wxAuthorizeVO.setOpenid(openId);
if(Constants.equalsInteger(userType, LoginUserInfo.H5_USER_CUSTOMER)){
String token = ywCustomerH5AuthService.loginByOpenId(openId);
@@ -1809,7 +1824,6 @@
}
return wxAuthorizeVO;
}
- //鏍规嵁openId 鏌ヨ杩愮淮鐢ㄦ埛淇℃伅
SystemUser user = systemUserMapper.selectOne(new QueryWrapper<SystemUser>().lambda()
.eq(SystemUser::getOpenid,openId)
.eq(SystemUser::getDeleted,Boolean.FALSE)
@@ -2189,6 +2203,7 @@
member.setIsdeleted(Constants.ZERO);
member.setStatus(Constants.ZERO);
this.checkYwMember(member);
+ applyYwMemberIdcard(member);
memberMapper.insert(member);
return member;
}
@@ -2205,17 +2220,69 @@
){
throw new BusinessException(ResponseStatus.BAD_REQUEST);
}
+ Member existing = memberMapper.selectById(member.getId());
+ if (existing == null || Constants.equalsInteger(existing.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "浜哄憳淇℃伅涓嶅瓨鍦紝璇峰埛鏂板悗閲嶈瘯");
+ }
LoginUserInfo loginUserInfo = member.getLoginUserInfo();
member.setEditor(loginUserInfo.getId());
member.setCustomerId(null);
member.setEditDate(new Date());
member.setType(Constants.memberType.customer);
- member.setStatus(Constants.ZERO);
- member.setIsdeleted(Constants.ZERO);
- member.setStatus(Constants.ZERO);
this.checkYwMember(member);
+ if (StringUtils.isNotBlank(member.getIdcardNoNew())) {
+ applyYwMemberIdcardChange(member, existing);
+ } else {
+ member.setIdcardNo(existing.getIdcardNo());
+ member.setIdcardDecode(existing.getIdcardDecode());
+ }
+ member.setIdcardNoNew(null);
memberMapper.updateById(member);
return member;
+ }
+
+ /** 鏂板缓锛氭槑鏂囪瘉浠跺彿鍐欏叆 idcard_no(鍔犲瘑) + idcard_decode(鑴辨晱) */
+ private void applyYwMemberIdcard(Member member) {
+ if (StringUtils.isBlank(member.getIdcardNo())) {
+ return;
+ }
+ String plain = member.getIdcardNo().trim();
+ if (Constants.equalsInteger(member.getIdcardType(), Constants.ZERO)) {
+ member.setSex(Constants.getSexByCardNo(plain));
+ member.setBirthday(DateUtil.fromStringToDate("yyyyMMdd", IdcardUtil.getBirthByIdCard(plain)));
+ }
+ member.setIdcardDecode(Constants.getTuominStr(plain));
+ member.setIdcardNo(DESUtil.encrypt(Constants.EDS_PWD, plain));
+ }
+
+ /** 缂栬緫锛氶�氳繃 idcardNoNew 鍙樻洿璇佷欢鍙� */
+ private void applyYwMemberIdcardChange(Member member, Member existing) {
+ String plain = member.getIdcardNoNew().trim();
+ String encrypted = DESUtil.encrypt(Constants.EDS_PWD, plain);
+ if (StringUtils.equals(existing.getIdcardNo(), encrypted)) {
+ return;
+ }
+ if (Constants.equalsInteger(member.getIdcardType(), Constants.ZERO)) {
+ member.setSex(Constants.getSexByCardNo(plain));
+ member.setBirthday(DateUtil.fromStringToDate("yyyyMMdd", IdcardUtil.getBirthByIdCard(plain)));
+ }
+ member.setIdcardDecode(Constants.getTuominStr(plain));
+ member.setIdcardNo(encrypted);
+ }
+
+ /** 鍒楄〃灞曠ず锛氳ˉ鍏� idcard_decode */
+ private void fillMemberIdcardDecode(Member member) {
+ if (StringUtils.isNotBlank(member.getIdcardDecode()) || StringUtils.isBlank(member.getIdcardNo())) {
+ return;
+ }
+ try {
+ String plain = DESUtil.decrypt(Constants.EDS_PWD, member.getIdcardNo());
+ if (StringUtils.isNotBlank(plain)) {
+ member.setIdcardDecode(Constants.getTuominStr(plain));
+ }
+ } catch (Exception e) {
+ member.setIdcardDecode(Constants.getTuominStr(member.getIdcardNo()));
+ }
}
@@ -2232,27 +2299,41 @@
}
- public void checkYwMember(Member member){
- if (StringUtils.isBlank(member.getPhone())||!PhoneUtil.isPhone(member.getPhone())){
- throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"鐢佃瘽鍙风爜鏍煎紡鏈夎");
+ public void checkYwMember(Member member) {
+ if (StringUtils.isBlank(member.getPhone()) || !PhoneUtil.isPhone(member.getPhone())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鐢佃瘽鍙风爜鏍煎紡鏈夎");
}
- if (StringUtils.isNotBlank(member.getIdcardNo()) && Constants.equalsInteger(member.getIdcardType(),Constants.ZERO) && !IdcardUtil.isValidCard(member.getIdcardNo())){
- throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(),"韬唤璇佸彿鏍煎紡鏈夎");
+ if (memberMapper.selectCount(new QueryWrapper<Member>().lambda()
+ .ne(Objects.nonNull(member.getId()), Member::getId, member.getId())
+ .eq(Member::getPhone, member.getPhone())
+ .eq(Member::getType, Constants.memberType.customer)
+ .eq(Member::getIsdeleted, Constants.ZERO)) > Constants.ZERO) {
+ throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "鎵嬫満鍙枫��" + member.getPhone() + "銆戝凡琚娇鐢紝涓嶈兘閲嶅");
}
- if(StringUtils.isNotBlank(member.getIdcardNo() ) && Constants.equalsInteger(member.getIdcardType(),Constants.ZERO) ){
- if(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
- .ne(Objects.nonNull(member.getId()),Member::getId,member.getId())
- .eq(Member::getIdcardNo, DESUtil.encrypt(Constants.EDS_PWD, member.getIdcardNo()))
- .eq(Member::getIsdeleted,Constants.ZERO)) >0){
- throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "韬唤璇佸彿銆�"+member.getIdcardNo()+"銆戝凡琚娇鐢紝涓嶈兘閲嶅");
+ String plainIdcard = resolveYwPlainIdcard(member);
+ if (StringUtils.isNotBlank(plainIdcard)) {
+ if (Constants.equalsInteger(member.getIdcardType(), Constants.ZERO) && !IdcardUtil.isValidCard(plainIdcard)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "韬唤璇佸彿鏍煎紡鏈夎");
+ }
+ String encrypted = DESUtil.encrypt(Constants.EDS_PWD, plainIdcard);
+ if (memberMapper.selectCount(new QueryWrapper<Member>().lambda()
+ .ne(Objects.nonNull(member.getId()), Member::getId, member.getId())
+ .eq(Member::getType, Constants.memberType.customer)
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .eq(Member::getIdcardNo, encrypted)) > Constants.ZERO) {
+ throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "璇佷欢鍙枫��" + plainIdcard + "銆戝凡琚娇鐢紝涓嶈兘閲嶅");
}
}
-// if(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
-// .ne(Objects.nonNull(member.getId()),Member::getId,member.getId())
-// .eq(Member::getPhone, member.getPhone())
-// .eq(Member::getIsdeleted,Constants.ZERO) ) >0){
-// throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "鎵嬫満鍙枫��"+member.getPhone()+"銆戝凡琚娇鐢紝涓嶈兘閲嶅");
-// }
+ }
+
+ private String resolveYwPlainIdcard(Member member) {
+ if (StringUtils.isNotBlank(member.getIdcardNoNew())) {
+ return member.getIdcardNoNew().trim();
+ }
+ if (member.getId() == null && StringUtils.isNotBlank(member.getIdcardNo())) {
+ return member.getIdcardNo().trim();
+ }
+ return null;
}
@@ -2271,10 +2352,21 @@
.eq(Objects.nonNull(model)&&Objects.nonNull(model.getCustomerId()),Member::getCustomerId,model.getCustomerId())
.and(Objects.nonNull(model)&&StringUtils.isNotBlank(model.getName()),i->i.like(Member::getName,model.getName()).or().like(
Member::getPhone,model.getName()
- ))
+ ).or().like(Member::getIdcardDecode, model.getName()))
+ .and(Objects.nonNull(model) && StringUtils.isNotBlank(model.getIdcardNo()), w -> {
+ String idcard = model.getIdcardNo().trim();
+ w.eq(Member::getIdcardNo, DESUtil.encrypt(Constants.EDS_PWD, idcard))
+ .or().like(Member::getIdcardDecode, idcard);
+ })
.orderByDesc(Member::getCreateDate)
);
- return PageData.from(iPage);
+ PageData<Member> pageData = PageData.from(iPage);
+ if (pageData.getRecords() != null) {
+ for (Member item : pageData.getRecords()) {
+ fillMemberIdcardDecode(item);
+ }
+ }
+ return pageData;
}
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/SmsEmailServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/SmsEmailServiceImpl.java
index a50a208..4129a20 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/SmsEmailServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/SmsEmailServiceImpl.java
@@ -8,6 +8,7 @@
import com.doumee.biz.system.SystemDictDataBiz;
import com.doumee.core.constants.ResponseStatus;
import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
import com.doumee.core.utils.*;
@@ -16,6 +17,7 @@
import com.doumee.dao.business.dao.SmsEmailMapper;
import com.doumee.dao.business.model.*;
import com.doumee.dao.system.SystemUserMapper;
+import com.doumee.dao.system.model.SystemDictData;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.SmsEmailService;
import com.doumee.service.business.third.EmailService;
@@ -49,6 +51,10 @@
private SmsConfigMapper smsConfigMapper;
@Autowired
private SystemUserMapper systemUserMapper;
+ @Autowired
+ private YwCustomerMapper ywCustomerMapper;
+ @Autowired
+ private MemberMapper memberMapper;
@Value("${debug_model}")
private boolean debugModel;
@@ -87,44 +93,117 @@
}
@Override
- public Integer sendSms(SmsEmail smsEmail) {
- if(StringUtils.isBlank(smsEmail.getPhone())){
- throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ public Integer sendMerchantLoginSms(String phone) {
+ if (StringUtils.isBlank(phone)) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鎵嬫満鍙蜂笉鑳戒负绌�");
}
- //鏍规嵁鎵嬫満鍙锋煡璇㈢敤鎴�
- if(systemUserMapper.selectCount(new QueryWrapper<SystemUser>().lambda().eq(SystemUser::getMobile,smsEmail.getPhone()))==Constants.ZERO){
- throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),"瀵逛笉璧凤紝鎵嬫満鍙锋棤鏁堣妫�鏌ュ悗閲嶈瘯!");
- };
- String nowDate = DateUtil.getFomartDate(new Date(),"yyyy-MM-dd HH:mm:ss");
- if(smsEmailMapper.selectCount(new QueryWrapper<SmsEmail>().lambda()
- .eq(SmsEmail::getPhone,smsEmail.getPhone())
- .eq(SmsEmail::getType,Constants.ZERO)
- .between(SmsEmail::getCreateDate, DateUtil.getFomartDate(DateUtil.afterMinutesDate(-5),"yyyy-MM-dd HH:mm:ss"),nowDate)
- )>=3){
- throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(),"瀵逛笉璧凤紝瓒呭嚭鍙戦�佹鏁帮紝璇风◢鍚庨噸璇曪紒");
+ return doSendH5LoginSms(phone.trim(), LoginUserInfo.H5_USER_CUSTOMER);
+ }
+
+ @Override
+ public Integer sendSms(SmsEmail smsEmail) {
+ if (StringUtils.isBlank(smsEmail.getPhone())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ return doSendH5LoginSms(smsEmail.getPhone().trim(), LoginUserInfo.H5_USER_OPS);
+ }
+
+ private Integer doSendH5LoginSms(String phone, Integer h5UserType) {
+ assertH5LoginAccountExists(phone, h5UserType);
+ String nowDate = DateUtil.getFomartDate(new Date(), "yyyy-MM-dd HH:mm:ss");
+ if (smsEmailMapper.selectCount(new QueryWrapper<SmsEmail>().lambda()
+ .eq(SmsEmail::getPhone, phone)
+ .eq(SmsEmail::getType, Constants.ZERO)
+ .between(SmsEmail::getCreateDate, DateUtil.getFomartDate(DateUtil.afterMinutesDate(-5), "yyyy-MM-dd HH:mm:ss"), nowDate)
+ ) >= 3) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "瀵逛笉璧凤紝瓒呭嚭鍙戦�佹鏁帮紝璇风◢鍚庨噸璇曪紒");
}
String code = Constants.getRandom6Num();
SmsConfig smsConfig = smsConfigMapper.selectOne(new QueryWrapper<SmsConfig>().lambda().eq(SmsConfig::getObjType,
SmsConstants.inventCode).last(" limit 1 "));
- String comName = systemDictDataBiz.queryByCode(Constants.SMS,Constants.SMS_COMNAME).getCode();
- //寮�鍚煭淇¢�氱煡
- if(Objects.nonNull(smsConfig) || Constants.equalsInteger(smsConfig.getStatus(),Constants.ZERO)){
- if(StringUtils.isNotBlank(smsConfig.getContent())){
- String content = comName + smsConfig.getContent().replace("{楠岃瘉鐮亇",code);
- emayService.sendSingleSms(smsEmail.getPhone(),content);
- smsEmail.setRemark(code);
- smsEmail.setIsdeleted(Constants.ZERO);
- smsEmail.setCreateDate(new Date());
- smsEmail.setStatus(Constants.ZERO);
- smsEmail.setType(Constants.ZERO);
- smsEmail.setTitle("鐭俊楠岃瘉鐮�");
- smsEmail.setContent(content);
- smsEmail.setObjType(Constants.ZERO+"");
- smsEmailMapper.insert(smsEmail);
- return smsEmail.getId();
- }
+ SystemDictData comNameDict = systemDictDataBiz.queryByCode(Constants.SMS, Constants.SMS_COMNAME);
+ if (comNameDict == null || StringUtils.isBlank(comNameDict.getCode())) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "鐭俊绛惧悕鏈厤缃紝璇疯仈绯荤鐞嗗憳");
}
- return null;
+ String comName = comNameDict.getCode();
+ if (smsConfig == null || StringUtils.isBlank(smsConfig.getContent())) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "鐭俊鏈嶅姟鏈厤缃紝璇疯仈绯荤鐞嗗憳");
+ }
+ if (!Constants.equalsInteger(smsConfig.getStatus(), Constants.ZERO)) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "鐭俊鏈嶅姟鏈紑鍚紝璇疯仈绯荤鐞嗗憳");
+ }
+ String content = comName + smsConfig.getContent().replace("{楠岃瘉鐮亇", code);
+ if (!emayService.sendSingleSms(phone, content)) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "鐭俊鍙戦�佸け璐ワ紝璇风◢鍚庨噸璇�");
+ }
+ SmsEmail record = new SmsEmail();
+ record.setPhone(phone);
+ record.setRemark(code);
+ record.setIsdeleted(Constants.ZERO);
+ record.setCreateDate(new Date());
+ record.setStatus(Constants.ZERO);
+ record.setType(Constants.ZERO);
+ record.setTitle("鐭俊楠岃瘉鐮�");
+ record.setContent(content);
+ record.setObjType(Constants.ZERO + "");
+ smsEmailMapper.insert(record);
+ return record.getId();
+ }
+
+ /**
+ * H5 鐧诲綍鍙戠煭淇″墠鏍¢獙锛氬晢鎴锋煡 yw_customer锛岃繍缁存煡 system_user
+ */
+ private void assertH5LoginAccountExists(String phone, Integer h5UserType) {
+ if (Constants.equalsInteger(h5UserType, LoginUserInfo.H5_USER_CUSTOMER)) {
+ assertMerchantAccountExists(phone);
+ return;
+ }
+ if (systemUserMapper.selectCount(new QueryWrapper<SystemUser>().lambda()
+ .eq(SystemUser::getMobile, phone)
+ .eq(SystemUser::getDeleted, Boolean.FALSE)) == Constants.ZERO) {
+ throw new BusinessException(ResponseStatus.SERVER_ERROR.getCode(), "瀵逛笉璧凤紝鎵嬫満鍙锋棤鏁堣妫�鏌ュ悗閲嶈瘯!");
+ }
+ }
+
+ private void assertMerchantAccountExists(String phone) {
+ YwCustomer customer = findMerchantByPhone(phone);
+ if (customer == null) {
+ throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT.getCode(), "鍟嗘埛涓嶅瓨鍦ㄦ垨鏈敞鍐�");
+ }
+ if (customer.getStatus() != null && Constants.equalsInteger(customer.getStatus(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.NO_ALLOW_LOGIN.getCode(), "鍟嗘埛璐﹀彿宸茬鐢�");
+ }
+ }
+
+ private YwCustomer findMerchantByPhone(String phone) {
+ YwCustomer byCustomerPhone = ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .eq(YwCustomer::getPhone, phone)
+ .last(" limit 1 "));
+ if (byCustomerPhone != null) {
+ return byCustomerPhone;
+ }
+ Member member = memberMapper.selectOne(new QueryWrapper<Member>().lambda()
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .eq(Member::getType, Constants.memberType.customer)
+ .eq(Member::getPhone, phone)
+ .isNotNull(Member::getCustomerId)
+ .orderByDesc(Member::getId)
+ .last(" limit 1 "));
+ if (member == null || member.getCustomerId() == null) {
+ return null;
+ }
+ YwCustomer customer = ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getId, member.getCustomerId())
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .last(" limit 1 "));
+ if (customer != null) {
+ return customer;
+ }
+ return ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .eq(YwCustomer::getMemberId, member.getId())
+ .last(" limit 1 "));
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractRevenueServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractRevenueServiceImpl.java
index a462929..c1012e9 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractRevenueServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractRevenueServiceImpl.java
@@ -12,7 +12,7 @@
import com.doumee.dao.business.*;
import com.doumee.dao.business.dao.CompanyMapper;
import com.doumee.dao.business.model.*;
-import com.doumee.dao.business.vo.EditRecordDataVO;
+import com.doumee.dao.business.dto.EditRecordDataVO;
import com.doumee.dao.system.MultifileMapper;
import com.doumee.dao.system.SystemUserMapper;
import com.doumee.dao.system.model.Multifile;
@@ -24,7 +24,6 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.commons.lang3.StringUtils;
-import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractServiceImpl.java
index 383ca4c..20689e4 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwContractServiceImpl.java
@@ -19,6 +19,7 @@
import com.doumee.dao.system.model.Multifile;
import com.doumee.dao.system.model.SystemUser;
import com.doumee.service.business.YwContractService;
+import com.doumee.service.business.YwCustomerDeviceAutoBindService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -73,6 +74,8 @@
@Autowired
private SystemDictDataBiz systemDictDataBiz;
+ @Autowired
+ private YwCustomerDeviceAutoBindService ywCustomerDeviceAutoBindService;
@Override
@Transactional(rollbackFor = {BusinessException.class,Exception.class})
public Integer create(YwContract model) {
@@ -94,6 +97,9 @@
dealDetailListBiz(model,false);//澶勭悊鏉℃淇℃伅
dealMultifileBiz(model);//澶勭悊闄勪欢淇℃伅
dealRoomsForContract(model);//澶勭悊鎴挎簮鍏宠仈琛�
+ if (Constants.equalsInteger(model.getStatus(), Constants.ONE) && model.getRenterId() != null) {
+ ywCustomerDeviceAutoBindService.syncByContractId(model.getId(), model.getLoginUserInfo());
+ }
dealLogBiz(model,Constants.YwLogType.CONTRACT_CREATE,model.getLoginUserInfo().getRealname(),"銆�"+model.getRemark().replace("鍚堝悓鎽樿锛�","")+"銆�");//璁板綍鏂板缓鏃ュ織
return model.getId();
}
@@ -417,6 +423,14 @@
.in(YwRoom::getId,contractRoomList.stream().map(i->i.getRoomId()).collect(Collectors.toList()))
);
}
+ LoginUserInfo timerUser = new LoginUserInfo();
+ timerUser.setId(1);
+ timerUser.setRealname("timer");
+ for (YwContract c : listA) {
+ if (c.getRenterId() != null) {
+ ywCustomerDeviceAutoBindService.syncByContractId(c.getId(), timerUser);
+ }
+ }
}
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerDeviceAutoBindServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerDeviceAutoBindServiceImpl.java
new file mode 100644
index 0000000..07a3d71
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerDeviceAutoBindServiceImpl.java
@@ -0,0 +1,218 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.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.YwCustomerGsConfigDTO;
+import com.doumee.dao.business.model.*;
+import com.doumee.service.business.YwCustomerDeviceAutoBindService;
+import com.doumee.service.business.YwCustomerRechargeBizService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class YwCustomerDeviceAutoBindServiceImpl implements YwCustomerDeviceAutoBindService {
+
+ private static final int BIND_SOURCE_CONTRACT = 1;
+
+ @Autowired
+ private YwContractMapper ywContractMapper;
+ @Autowired
+ private YwContractRoomMapper ywContractRoomMapper;
+ @Autowired
+ private YwElectricalRoomMapper ywElectricalRoomMapper;
+ @Autowired
+ private YwCustomerElectricalMapper ywCustomerElectricalMapper;
+ @Autowired
+ private YwCustomerConditionerMapper ywCustomerConditionerMapper;
+ @Autowired
+ private YwConditionerMapper ywConditionerMapper;
+ @Autowired
+ private YwCustomerRechargeBizService ywCustomerRechargeBizService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void syncByContractId(Integer contractId, LoginUserInfo user) {
+ if (contractId == null) {
+ return;
+ }
+ YwContract contract = ywContractMapper.selectById(contractId);
+ if (contract == null || Objects.equals(contract.getIsdeleted(), Constants.ONE)) {
+ return;
+ }
+ if (contract.getRenterId() == null) {
+ return;
+ }
+ if (!isActiveContract(contract)) {
+ return;
+ }
+ List<Integer> roomIds = listContractRoomIds(contractId);
+ if (roomIds.isEmpty()) {
+ return;
+ }
+ bindElectricals(contract, roomIds, user);
+ bindConditioners(contract, roomIds, user);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void syncByCustomerId(Integer customerId, LoginUserInfo user) {
+ if (customerId == null) {
+ return;
+ }
+ List<YwContract> contracts = ywContractMapper.selectList(new QueryWrapper<YwContract>().lambda()
+ .eq(YwContract::getRenterId, customerId)
+ .eq(YwContract::getIsdeleted, Constants.ZERO)
+ .in(YwContract::getStatus, Arrays.asList(Constants.ZERO, Constants.ONE, Constants.THREE)));
+ for (YwContract c : contracts) {
+ if (isActiveContract(c)) {
+ syncByContractId(c.getId(), user);
+ }
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void unbindByContractId(Integer contractId, LoginUserInfo user) {
+ if (contractId == null) {
+ return;
+ }
+ Date now = new Date();
+ Integer editor = user != null ? user.getId() : null;
+ ywCustomerElectricalMapper.update(null, new UpdateWrapper<YwCustomerElectrical>().lambda()
+ .set(YwCustomerElectrical::getIsdeleted, Constants.ONE)
+ .set(YwCustomerElectrical::getEditDate, now)
+ .set(YwCustomerElectrical::getEditor, editor)
+ .eq(YwCustomerElectrical::getContractId, contractId)
+ .eq(YwCustomerElectrical::getBindSource, BIND_SOURCE_CONTRACT)
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO));
+ }
+
+ private boolean isActiveContract(YwContract contract) {
+ if (contract.getStartDate() == null || contract.getEndDate() == null) {
+ return false;
+ }
+ long now = System.currentTimeMillis();
+ return contract.getStartDate().getTime() <= now
+ && contract.getEndDate().getTime() >= now
+ && !Objects.equals(contract.getStatus(), Constants.FOUR);
+ }
+
+ private List<Integer> listContractRoomIds(Integer contractId) {
+ return ywContractRoomMapper.selectList(new QueryWrapper<YwContractRoom>().lambda()
+ .eq(YwContractRoom::getContractId, contractId)
+ .eq(YwContractRoom::getType, Constants.ZERO)
+ .eq(YwContractRoom::getIsdeleted, Constants.ZERO))
+ .stream().map(YwContractRoom::getRoomId).filter(Objects::nonNull).distinct()
+ .collect(Collectors.toList());
+ }
+
+ private void bindElectricals(YwContract contract, List<Integer> roomIds, LoginUserInfo user) {
+ List<YwElectricalRoom> relRooms = ywElectricalRoomMapper.selectList(new QueryWrapper<YwElectricalRoom>().lambda()
+ .in(YwElectricalRoom::getRoomId, roomIds)
+ .eq(YwElectricalRoom::getType, Constants.ZERO)
+ .eq(YwElectricalRoom::getIsdeleted, Constants.ZERO));
+ Set<Integer> electricalIds = relRooms.stream().map(YwElectricalRoom::getObjId)
+ .filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new));
+ if (electricalIds.isEmpty()) {
+ return;
+ }
+ Set<Integer> boundOthers = listBoundElectricalIdsExcept(contract.getRenterId());
+ Date now = new Date();
+ for (Integer eid : electricalIds) {
+ if (boundOthers.contains(eid)) {
+ log.warn("skip electrical {} already bound to other customer", eid);
+ continue;
+ }
+ YwCustomerElectrical exist = ywCustomerElectricalMapper.selectOne(new QueryWrapper<YwCustomerElectrical>().lambda()
+ .eq(YwCustomerElectrical::getCustomerId, contract.getRenterId())
+ .eq(YwCustomerElectrical::getElectricalId, eid)
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO)
+ .last("limit 1"));
+ if (exist != null) {
+ if (exist.getContractId() == null) {
+ exist.setContractId(contract.getId());
+ exist.setBindSource(BIND_SOURCE_CONTRACT);
+ exist.setEditDate(now);
+ exist.setEditor(user != null ? user.getId() : null);
+ ywCustomerElectricalMapper.updateById(exist);
+ }
+ continue;
+ }
+ YwCustomerElectrical rel = new YwCustomerElectrical();
+ rel.setCreator(user != null ? user.getId() : null);
+ rel.setCreateDate(now);
+ rel.setEditor(user != null ? user.getId() : null);
+ rel.setEditDate(now);
+ rel.setIsdeleted(Constants.ZERO);
+ rel.setCustomerId(contract.getRenterId());
+ rel.setElectricalId(eid);
+ rel.setBindSource(BIND_SOURCE_CONTRACT);
+ rel.setContractId(contract.getId());
+ ywCustomerElectricalMapper.insert(rel);
+ }
+ }
+
+ private void bindConditioners(YwContract contract, List<Integer> roomIds, LoginUserInfo user) {
+ Set<Integer> conditionerIds = new LinkedHashSet<>();
+ List<YwElectricalRoom> acRooms = ywElectricalRoomMapper.selectList(new QueryWrapper<YwElectricalRoom>().lambda()
+ .in(YwElectricalRoom::getRoomId, roomIds)
+ .eq(YwElectricalRoom::getType, Constants.ONE)
+ .eq(YwElectricalRoom::getIsdeleted, Constants.ZERO));
+ acRooms.stream().map(YwElectricalRoom::getObjId).filter(Objects::nonNull).forEach(conditionerIds::add);
+ if (conditionerIds.isEmpty()) {
+ List<YwConditioner> byRoom = ywConditionerMapper.selectList(new QueryWrapper<YwConditioner>().lambda()
+ .in(YwConditioner::getRoomId, roomIds)
+ .eq(YwConditioner::getIsdeleted, Constants.ZERO));
+ byRoom.stream().map(YwConditioner::getId).forEach(conditionerIds::add);
+ }
+ if (conditionerIds.isEmpty()) {
+ return;
+ }
+ YwCustomerGsConfigDTO dto = new YwCustomerGsConfigDTO();
+ dto.setCustomerId(contract.getRenterId());
+ dto.setIsPwr(Constants.ONE);
+ dto.setIsRestStop(Constants.ZERO);
+ dto.setGsBz("鍚堝悓鑷姩鍏宠仈");
+ dto.setStopMoney(BigDecimal.ZERO);
+ List<YwCustomerGsConfigDTO.ConditionerItem> items = new ArrayList<>();
+ for (Integer cid : conditionerIds) {
+ YwCustomerGsConfigDTO.ConditionerItem item = new YwCustomerGsConfigDTO.ConditionerItem();
+ item.setConditionerId(cid);
+ item.setDevRatio(100);
+ items.add(item);
+ }
+ dto.setConditioners(items);
+ try {
+ ywCustomerRechargeBizService.saveCustomerGsConfig(dto, user != null ? user : systemUser());
+ } catch (BusinessException e) {
+ log.warn("auto bind conditioner GS failed contractId={}: {}", contract.getId(), e.getMessage());
+ }
+ }
+
+ private Set<Integer> listBoundElectricalIdsExcept(Integer customerId) {
+ List<YwCustomerElectrical> list = ywCustomerElectricalMapper.selectList(new QueryWrapper<YwCustomerElectrical>().lambda()
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO)
+ .ne(customerId != null, YwCustomerElectrical::getCustomerId, customerId));
+ return list.stream().map(YwCustomerElectrical::getElectricalId).collect(Collectors.toSet());
+ }
+
+ private LoginUserInfo systemUser() {
+ LoginUserInfo u = new LoginUserInfo();
+ u.setId(1);
+ u.setRealname("system");
+ return u;
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5AuthServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5AuthServiceImpl.java
index 37fcf42..59d02f7 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5AuthServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5AuthServiceImpl.java
@@ -7,7 +7,9 @@
import com.doumee.core.exception.BusinessException;
import com.doumee.core.model.LoginUserInfo;
import com.doumee.core.utils.Constants;
+import com.doumee.dao.business.MemberMapper;
import com.doumee.dao.business.YwCustomerMapper;
+import com.doumee.dao.business.model.Member;
import com.doumee.dao.business.model.YwCustomer;
import com.doumee.dao.system.dto.LoginPhoneDTO;
import com.doumee.service.business.YwCustomerH5AuthService;
@@ -24,6 +26,8 @@
@Autowired
private YwCustomerMapper ywCustomerMapper;
+ @Autowired
+ private MemberMapper memberMapper;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@@ -67,19 +71,55 @@
return toLoginUserInfo(requireActiveCustomer(customerId));
}
+ @Override
+ public void assertActiveCustomerByPhone(String phone) {
+ findActiveByPhone(phone);
+ }
+
private YwCustomer findActiveByPhone(String phone) {
if (StringUtils.isBlank(phone)) {
throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鎵嬫満鍙蜂笉鑳戒负绌�");
}
- YwCustomer customer = ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
- .eq(YwCustomer::getIsdeleted, Constants.ZERO)
- .eq(YwCustomer::getPhone, phone.trim())
- .last(" limit 1 "));
+ YwCustomer customer = findCustomerByPhone(phone.trim());
if (customer == null) {
throw new BusinessException(ResponseStatus.ACCOUNT_INCORRECT.getCode(), "鍟嗘埛涓嶅瓨鍦ㄦ垨鏈敞鍐�");
}
assertCustomerEnabled(customer);
return customer;
+ }
+
+ /**
+ * 鍟嗘埛鎵嬫満鍙凤細浼樺厛 yw_customer.phone锛屽惁鍒欏尮閰嶈仈绯讳汉 member.phone
+ */
+ private YwCustomer findCustomerByPhone(String phone) {
+ YwCustomer byCustomerPhone = ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .eq(YwCustomer::getPhone, phone)
+ .last(" limit 1 "));
+ if (byCustomerPhone != null) {
+ return byCustomerPhone;
+ }
+ Member member = memberMapper.selectOne(new QueryWrapper<Member>().lambda()
+ .eq(Member::getIsdeleted, Constants.ZERO)
+ .eq(Member::getType, Constants.memberType.customer)
+ .eq(Member::getPhone, phone)
+ .isNotNull(Member::getCustomerId)
+ .orderByDesc(Member::getId)
+ .last(" limit 1 "));
+ if (member == null || member.getCustomerId() == null) {
+ return null;
+ }
+ YwCustomer customer = ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getId, member.getCustomerId())
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .last(" limit 1 "));
+ if (customer != null) {
+ return customer;
+ }
+ return ywCustomerMapper.selectOne(new QueryWrapper<YwCustomer>().lambda()
+ .eq(YwCustomer::getIsdeleted, Constants.ZERO)
+ .eq(YwCustomer::getMemberId, member.getId())
+ .last(" limit 1 "));
}
private YwCustomer requireActiveCustomer(Integer customerId) {
@@ -125,11 +165,24 @@
loginUserInfo.setId(customer.getId());
loginUserInfo.setH5UserType(LoginUserInfo.H5_USER_CUSTOMER);
loginUserInfo.setRealname(customer.getName());
- loginUserInfo.setMobile(customer.getPhone());
+ loginUserInfo.setMobile(resolveLoginMobile(customer));
loginUserInfo.setUsername("customer_" + customer.getId());
loginUserInfo.setSource(LoginUserInfo.SOURCE_H5_CUSTOMER);
loginUserInfo.setRoles(Collections.singletonList("h5_customer"));
loginUserInfo.setPermissions(Collections.emptyList());
return loginUserInfo;
}
+
+ private String resolveLoginMobile(YwCustomer customer) {
+ if (StringUtils.isNotBlank(customer.getPhone())) {
+ return customer.getPhone();
+ }
+ if (customer.getMemberId() != null) {
+ Member member = memberMapper.selectById(customer.getMemberId());
+ if (member != null && StringUtils.isNotBlank(member.getPhone())) {
+ return member.getPhone();
+ }
+ }
+ return customer.getPhone();
+ }
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5BizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5BizServiceImpl.java
new file mode 100644
index 0000000..78e0714
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerH5BizServiceImpl.java
@@ -0,0 +1,752 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.model.PageData;
+import com.doumee.core.model.PageWrap;
+import com.doumee.biz.system.SystemDictDataBiz;
+import com.doumee.core.utils.Constants;
+import com.doumee.core.utils.Utils;
+import com.doumee.dao.business.YwContractBillMapper;
+import com.doumee.dao.business.YwContractMapper;
+import com.doumee.dao.business.YwContractRevenueMapper;
+import com.doumee.dao.business.YwContractRoomMapper;
+import com.doumee.dao.business.YwCustomerElectricalMapper;
+import com.doumee.dao.business.YwCustomerMapper;
+import com.doumee.dao.business.YwElectricalMapper;
+import com.doumee.dao.business.YwRoomMapper;
+import com.doumee.dao.business.dto.YwCustomerRechargeElectricalDTO;
+import com.doumee.dao.business.dto.YwCustomerRechargeDetailVO;
+import com.doumee.dao.business.dto.YwCustomerRechargeRecordQueryDTO;
+import com.doumee.dao.business.dto.YwCustomerRechargeRecordVO;
+import com.doumee.dao.business.dto.h5.*;
+import com.doumee.dao.business.model.*;
+import com.doumee.dao.system.MultifileMapper;
+import com.doumee.dao.system.model.Multifile;
+import com.doumee.service.business.YwCustomerH5BizService;
+import com.doumee.service.business.YwCustomerRechargeBizService;
+import com.doumee.service.business.YwElectricalBizService;
+import com.doumee.service.business.YwH5BannerService;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class YwCustomerH5BizServiceImpl implements YwCustomerH5BizService {
+
+ private static final int BILL_DUE_SOON_DAYS = 7;
+
+ @Autowired
+ private YwH5BannerService ywH5BannerService;
+ @Autowired
+ private YwCustomerMapper ywCustomerMapper;
+ @Autowired
+ private YwCustomerRechargeBizService ywCustomerRechargeBizService;
+ @Autowired
+ private YwCustomerElectricalMapper ywCustomerElectricalMapper;
+ @Autowired
+ private YwElectricalMapper ywElectricalMapper;
+ @Autowired
+ private YwElectricalBizService ywElectricalBizService;
+ @Autowired
+ private YwContractMapper ywContractMapper;
+ @Autowired
+ private YwContractBillMapper ywContractBillMapper;
+ @Autowired
+ private YwContractRevenueMapper ywContractRevenueMapper;
+ @Autowired
+ private YwContractRoomMapper ywContractRoomMapper;
+ @Autowired
+ private YwRoomMapper ywRoomMapper;
+ @Autowired
+ private MultifileMapper multifileMapper;
+ @Autowired
+ private SystemDictDataBiz systemDictDataBiz;
+
+ @Override
+ public List<YwH5Banner> listBanners() {
+ return ywH5BannerService.listEnabledForCustomerWorkbench();
+ }
+
+ @Override
+ public Map<String, Object> home(Integer customerId) {
+ YwCustomer customer = requireCustomer(customerId);
+ YwCustomerRechargeDetailVO detail = ywCustomerRechargeBizService.getDetail(customerId);
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("customerName", customer.getName());
+ map.put("electricalCount", detail.getElectricalList() != null ? detail.getElectricalList().size() : 0);
+ map.put("conditionerCount", detail.getConditionerList() != null ? detail.getConditionerList().size() : 0);
+ map.put("gsConfig", detail.getGsConfig());
+ map.put("electricalList", detail.getElectricalList());
+ return map;
+ }
+
+ @Override
+ public PageData<CustomerDeviceH5VO> devicePage(PageWrap<CustomerDeviceQueryDTO> pageWrap, Integer customerId) {
+ requireCustomer(customerId);
+ CustomerDeviceQueryDTO q = pageWrap.getModel() != null ? pageWrap.getModel() : new CustomerDeviceQueryDTO();
+ List<CustomerDeviceH5VO> all = new ArrayList<>();
+ if (q.getDeviceType() == null || q.getDeviceType() == 0) {
+ all.addAll(buildElectricalDevices(customerId));
+ }
+ if (q.getDeviceType() == null || q.getDeviceType() == 1) {
+ all.addAll(buildConditionerDevices(customerId));
+ }
+ if (q.getStatusFilter() != null) {
+ all = all.stream().filter(d -> matchDeviceStatus(d, q.getStatusFilter())).collect(Collectors.toList());
+ }
+ int p = (int) Math.max(pageWrap.getPage(), 1);
+ int size = (int) Math.max(pageWrap.getCapacity(), 10);
+ int from = (p - 1) * size;
+ int to = Math.min(from + size, all.size());
+ PageData<CustomerDeviceH5VO> data = new PageData<>();
+ data.setTotal(all.size());
+ data.setPage(p);
+ data.setCapacity(size);
+ data.setRecords(from >= all.size() ? Collections.emptyList() : all.subList(from, to));
+ return data;
+ }
+
+ @Override
+ public CustomerDeviceH5VO deviceDetail(Integer deviceType, Integer deviceId, Integer customerId) {
+ PageWrap<CustomerDeviceQueryDTO> pw = new PageWrap<>();
+ pw.setPage(1);
+ pw.setCapacity(1000);
+ CustomerDeviceQueryDTO q = new CustomerDeviceQueryDTO();
+ q.setDeviceType(deviceType);
+ pw.setModel(q);
+ return devicePage(pw, customerId).getRecords().stream()
+ .filter(d -> Objects.equals(d.getDeviceType(), deviceType) && Objects.equals(d.getDeviceId(), deviceId))
+ .findFirst()
+ .orElseThrow(() -> new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "璁惧涓嶅瓨鍦�"));
+ }
+
+ @Override
+ public PageData<YwCustomerRechargeRecordVO> rechargeRecordPage(PageWrap<CustomerRechargeRecordH5QueryDTO> pageWrap, Integer customerId) {
+ PageWrap<YwCustomerRechargeRecordQueryDTO> wrap = new PageWrap<>();
+ wrap.setPage(pageWrap.getPage());
+ wrap.setCapacity(pageWrap.getCapacity());
+ YwCustomerRechargeRecordQueryDTO model = new YwCustomerRechargeRecordQueryDTO();
+ model.setCustomerId(customerId);
+ CustomerRechargeRecordH5QueryDTO q = pageWrap.getModel();
+ if (q != null) {
+ model.setStatus(q.getStatus());
+ model.setCreateTimeBegin(q.getCreateTimeBegin());
+ model.setCreateTimeEnd(q.getCreateTimeEnd());
+ if (StringUtils.isNotBlank(q.getMonth())) {
+ try {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
+ Date start = sdf.parse(q.getMonth());
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(start);
+ cal.add(Calendar.MONTH, 1);
+ model.setCreateTimeBegin(start);
+ model.setCreateTimeEnd(cal.getTime());
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ wrap.setModel(model);
+ return ywCustomerRechargeBizService.findRechargeRecordPage(wrap);
+ }
+
+ @Override
+ public PageData<YwContract> contractPage(PageWrap<CustomerContractQueryDTO> pageWrap, Integer customerId) {
+ requireCustomer(customerId);
+ IPage<YwContract> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ CustomerContractQueryDTO q = pageWrap.getModel();
+ QueryWrapper<YwContract> qw = new QueryWrapper<>();
+ qw.lambda()
+ .eq(YwContract::getRenterId, customerId)
+ .eq(YwContract::getIsdeleted, Constants.ZERO)
+ .eq(q != null && q.getStatus() != null, YwContract::getStatus, q != null ? q.getStatus() : null)
+ .orderByDesc(YwContract::getCreateDate);
+ IPage<YwContract> result = ywContractMapper.selectPage(page, qw);
+ enrichContractListForH5(result.getRecords());
+ return PageData.from(result);
+ }
+
+ @Override
+ public Map<String, Object> contractDetail(Integer contractId, Integer customerId, Integer billType) {
+ YwContract contract = requireCustomerContract(contractId, customerId);
+ enrichContractForH5(contract, true);
+ int type = billType != null ? billType : Constants.ZERO;
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("contract", contract);
+ map.put("bills", listContractBillsForH5(contractId, type));
+ map.put("billType", type);
+ return map;
+ }
+
+ @Override
+ public PageData<YwContractBill> billPage(PageWrap<CustomerBillQueryDTO> pageWrap, Integer customerId) {
+ List<Integer> contractIds = listCustomerContractIds(customerId);
+ if (contractIds.isEmpty()) {
+ return emptyBillPage(pageWrap);
+ }
+ IPage<YwContractBill> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ CustomerBillQueryDTO q = pageWrap.getModel();
+ MPJLambdaWrapper<YwContractBill> wrapper = new MPJLambdaWrapper<YwContractBill>()
+ .selectAll(YwContractBill.class)
+ .select(" ( select ifnull( sum( CASE WHEN t.bill_type = 0 and yw.REVENUE_TYPE = 0 THEN yw.ACT_RECEIVABLE_FEE when t.bill_type = 0 and yw.REVENUE_TYPE = 1 then -yw.ACT_RECEIVABLE_FEE when t.bill_type = 1 and yw.REVENUE_TYPE = 0 then -yw.ACT_RECEIVABLE_FEE else yw.ACT_RECEIVABLE_FEE END),0) from yw_contract_revenue yw where yw.bill_id = t.id and yw.status = 0 and yw.isdeleted = 0 ) as actReceivableFee ")
+ .in(YwContractBill::getContractId, contractIds)
+ .eq(YwContractBill::getIsdeleted, Constants.ZERO)
+ .eq(YwContractBill::getBillType, Constants.ZERO);
+ if (q != null && q.getPayTab() != null) {
+ if (q.getPayTab() == 0) {
+ wrapper.in(YwContractBill::getPayStatus, Arrays.asList(Constants.ZERO, Constants.TWO, Constants.THREE));
+ } else if (q.getPayTab() == 1) {
+ wrapper.eq(YwContractBill::getPayStatus, Constants.ONE);
+ }
+ }
+ wrapper.orderByDesc(YwContractBill::getPlanPayDate);
+ IPage<YwContractBill> result = ywContractBillMapper.selectJoinPage(page, YwContractBill.class, wrapper);
+ enrichBillsWithContract(result.getRecords());
+ return PageData.from(result);
+ }
+
+ @Override
+ public Map<String, Object> billDetail(Integer billId, Integer customerId) {
+ YwContractBill bill = ywContractBillMapper.selectById(billId);
+ if (bill == null || Objects.equals(bill.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "璐﹀崟涓嶅瓨鍦�");
+ }
+ requireCustomerContract(bill.getContractId(), customerId);
+ enrichBillWithContract(bill);
+ enrichBillWithRooms(bill);
+ List<YwContractRevenue> revenues = listBillRevenues(billId, bill);
+ BigDecimal paid = sumPaid(revenues, bill.getBillType());
+ BigDecimal needPay = bill.getReceivableFee() != null ? bill.getReceivableFee().subtract(paid) : BigDecimal.ZERO;
+ bill.setActReceivableFee(paid);
+ bill.setNeedReceivableFee(needPay);
+ bill.setYwContractRevenueList(revenues);
+ Map<String, Object> map = new LinkedHashMap<>();
+ map.put("bill", bill);
+ map.put("revenues", revenues);
+ map.put("paidAmount", paid);
+ map.put("needPayAmount", needPay);
+ return map;
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void applyFirstRechargeIfNeeded(Integer customerId, LoginUserInfo user) {
+ YwCustomer customer = requireCustomer(customerId);
+ if (Objects.equals(customer.getFirstRechargeDone(), Constants.ONE)) {
+ return;
+ }
+ List<Integer> electricalIds = ywCustomerElectricalMapper.selectList(new QueryWrapper<YwCustomerElectrical>().lambda()
+ .eq(YwCustomerElectrical::getCustomerId, customerId)
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO))
+ .stream().map(YwCustomerElectrical::getElectricalId).collect(Collectors.toList());
+ for (Integer eid : electricalIds) {
+ YwCustomerRechargeElectricalDTO dto = new YwCustomerRechargeElectricalDTO();
+ dto.setCustomerId(customerId);
+ dto.setElectricalId(eid);
+ dto.setResetAction("resetPrepay");
+ try {
+ ywCustomerRechargeBizService.resetElectricalAccount(dto, user);
+ } catch (Exception e) {
+ // 宸插紑鎴峰垯璺宠繃
+ }
+ }
+ try {
+ ywCustomerRechargeBizService.cleanConditionerAccount(customerId, user);
+ } catch (Exception ignored) {
+ }
+ ywCustomerMapper.update(null, new UpdateWrapper<YwCustomer>().lambda()
+ .set(YwCustomer::getFirstRechargeDone, Constants.ONE)
+ .set(YwCustomer::getEditDate, new Date())
+ .eq(YwCustomer::getId, customerId));
+ }
+
+ private List<CustomerDeviceH5VO> buildElectricalDevices(Integer customerId) {
+ List<Integer> ids = ywCustomerElectricalMapper.selectList(new QueryWrapper<YwCustomerElectrical>().lambda()
+ .eq(YwCustomerElectrical::getCustomerId, customerId)
+ .eq(YwCustomerElectrical::getIsdeleted, Constants.ZERO))
+ .stream().map(YwCustomerElectrical::getElectricalId).collect(Collectors.toList());
+ if (ids.isEmpty()) {
+ return Collections.emptyList();
+ }
+ List<YwElectrical> list = ywElectricalMapper.selectBatchIds(ids);
+ ywElectricalBizService.enrichList(list);
+ List<CustomerDeviceH5VO> vos = new ArrayList<>();
+ for (YwElectrical e : list) {
+ if (e == null || Objects.equals(e.getIsdeleted(), Constants.ONE)) continue;
+ CustomerDeviceH5VO vo = new CustomerDeviceH5VO();
+ vo.setDeviceType(0);
+ vo.setDeviceId(e.getId());
+ vo.setDeviceName(e.getName());
+ vo.setMeterAccountNo(e.getParamId());
+ vo.setRoomInfo(e.getRoomNames());
+ vo.setBalance(e.getBalance());
+ vo.setBalanceLow(e.getBalance() != null && e.getBalance().compareTo(new BigDecimal("50")) < 0);
+ vo.setUpdateTime(e.getEditDate());
+ boolean online = Objects.equals(e.getOnline(), Constants.ONE);
+ vo.setStatusCode(online ? 1 : 2);
+ vo.setStatusText(online ? "姝e父" : "鏂數");
+ List<String> alarms = new ArrayList<>();
+ if (!online) alarms.add("鏂數鎶ヨ");
+ if (Boolean.TRUE.equals(vo.getBalanceLow())) alarms.add("浣欓涓嶈冻浜岀骇鎶ヨ");
+ vo.setAlarmTags(alarms);
+ vos.add(vo);
+ }
+ return vos;
+ }
+
+ private List<CustomerDeviceH5VO> buildConditionerDevices(Integer customerId) {
+ List<YwConditioner> list = ywCustomerRechargeBizService.listCustomerConditioner(new PageWrap<>(), customerId).getRecords();
+ if (CollectionUtils.isEmpty(list)) {
+ return Collections.emptyList();
+ }
+ YwCustomerGs gs = ywCustomerRechargeBizService.getCustomerGsConfig(customerId);
+ List<CustomerDeviceH5VO> vos = new ArrayList<>();
+ for (YwConditioner c : list) {
+ CustomerDeviceH5VO vo = new CustomerDeviceH5VO();
+ vo.setDeviceType(1);
+ vo.setDeviceId(c.getId());
+ vo.setDeviceName(StringUtils.defaultIfBlank(c.getName(), c.getCode()));
+ vo.setRoomInfo(c.getRoomName());
+ if (StringUtils.isNotBlank(c.getRoomName())) {
+ vo.setRoomList(Collections.singletonList(c.getRoomName()));
+ }
+ vo.setBalance(gs != null ? gs.getLeftMoney() : null);
+ boolean online = "鍦ㄧ嚎".equals(c.getOnline());
+ vo.setStatusCode(online ? 1 : 2);
+ vo.setStatusText(online ? "姝e父" : "绂荤嚎");
+ vo.setUpdateTime(c.getLastSyncDate());
+ vos.add(vo);
+ }
+ return vos;
+ }
+
+ private boolean matchDeviceStatus(CustomerDeviceH5VO d, Integer filter) {
+ if (filter == 1) return Objects.equals(d.getStatusCode(), 1);
+ if (filter == 2) return Objects.equals(d.getStatusCode(), 2);
+ return true;
+ }
+
+ private YwCustomer requireCustomer(Integer customerId) {
+ YwCustomer c = ywCustomerMapper.selectById(customerId);
+ if (c == null || Objects.equals(c.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "鍟嗘埛涓嶅瓨鍦�");
+ }
+ return c;
+ }
+
+ private YwContract requireCustomerContract(Integer contractId, Integer customerId) {
+ YwContract c = ywContractMapper.selectById(contractId);
+ if (c == null || !Objects.equals(c.getRenterId(), customerId)) {
+ throw new BusinessException(ResponseStatus.NOT_ALLOWED);
+ }
+ return c;
+ }
+
+ private List<Integer> listCustomerContractIds(Integer customerId) {
+ return ywContractMapper.selectList(new QueryWrapper<YwContract>().lambda()
+ .eq(YwContract::getRenterId, customerId)
+ .eq(YwContract::getIsdeleted, Constants.ZERO))
+ .stream().map(YwContract::getId).collect(Collectors.toList());
+ }
+
+ private void enrichContractListForH5(List<YwContract> contracts) {
+ if (CollectionUtils.isEmpty(contracts)) {
+ return;
+ }
+ List<Integer> contractIds = contracts.stream().map(YwContract::getId).collect(Collectors.toList());
+ Map<Integer, List<YwRoom>> roomMap = loadContractRoomMap(contractIds);
+ Map<Integer, List<YwContractBill>> billMap = loadContractBillMap(contractIds);
+ for (YwContract contract : contracts) {
+ List<YwRoom> rooms = roomMap.getOrDefault(contract.getId(), Collections.emptyList());
+ applyRoomSummary(contract, rooms);
+ contract.setPayTypeText(resolvePayTypeText(contract));
+ fillBillStatusTip(contract, billMap.getOrDefault(contract.getId(), Collections.emptyList()));
+ }
+ }
+
+ private void enrichContractForH5(YwContract contract, boolean withFiles) {
+ if (contract == null) {
+ return;
+ }
+ Map<Integer, List<YwRoom>> roomMap = loadContractRoomMap(Collections.singletonList(contract.getId()));
+ applyRoomSummary(contract, roomMap.getOrDefault(contract.getId(), Collections.emptyList()));
+ contract.setPayTypeText(resolvePayTypeText(contract));
+ contract.setFreeRentPeriod(resolveFreeRentPeriod(contract));
+ fillBillStatusTip(contract, loadContractBillMap(Collections.singletonList(contract.getId()))
+ .getOrDefault(contract.getId(), Collections.emptyList()));
+ if (withFiles) {
+ initContractFiles(contract);
+ }
+ }
+
+ private Map<Integer, List<YwRoom>> loadContractRoomMap(List<Integer> contractIds) {
+ if (CollectionUtils.isEmpty(contractIds)) {
+ return Collections.emptyMap();
+ }
+ List<YwContractRoom> contractRooms = ywContractRoomMapper.selectList(new QueryWrapper<YwContractRoom>().lambda()
+ .in(YwContractRoom::getContractId, contractIds)
+ .eq(YwContractRoom::getType, Constants.ZERO)
+ .eq(YwContractRoom::getIsdeleted, Constants.ZERO));
+ if (contractRooms.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ List<Integer> roomIds = contractRooms.stream().map(YwContractRoom::getRoomId).distinct().collect(Collectors.toList());
+ Map<Integer, YwRoom> roomById = loadRoomsWithNames(roomIds).stream()
+ .collect(Collectors.toMap(YwRoom::getId, r -> r, (a, b) -> a));
+ Map<Integer, List<YwRoom>> result = new HashMap<>();
+ for (YwContractRoom cr : contractRooms) {
+ YwRoom room = roomById.get(cr.getRoomId());
+ if (room != null) {
+ result.computeIfAbsent(cr.getContractId(), k -> new ArrayList<>()).add(room);
+ }
+ }
+ return result;
+ }
+
+ private List<YwRoom> loadRoomsWithNames(List<Integer> roomIds) {
+ if (CollectionUtils.isEmpty(roomIds)) {
+ return Collections.emptyList();
+ }
+ MPJLambdaWrapper<YwRoom> wrapper = new MPJLambdaWrapper<YwRoom>()
+ .selectAll(YwRoom.class)
+ .selectAs(YwProject::getName, YwRoom::getProjectName)
+ .selectAs(YwFloor::getName, YwRoom::getFloorName)
+ .selectAs(YwBuilding::getName, YwRoom::getBuildingName)
+ .leftJoin(YwProject.class, YwProject::getId, YwRoom::getProjectId)
+ .leftJoin(YwBuilding.class, YwBuilding::getId, YwRoom::getBuildingId)
+ .leftJoin(YwFloor.class, YwFloor::getId, YwRoom::getFloor)
+ .in(YwRoom::getId, roomIds)
+ .eq(YwRoom::getIsdeleted, Constants.ZERO);
+ return ywRoomMapper.selectJoinList(YwRoom.class, wrapper);
+ }
+
+ private void applyRoomSummary(YwContract contract, List<YwRoom> rooms) {
+ contract.setRoomList(rooms);
+ contract.setRoomInfo(buildRoomInfo(rooms));
+ BigDecimal totalArea = BigDecimal.ZERO;
+ if (!CollectionUtils.isEmpty(rooms)) {
+ for (YwRoom room : rooms) {
+ totalArea = totalArea.add(Constants.formatBigdecimal(room.getRentArea()));
+ }
+ }
+ contract.setTotalArea(totalArea);
+ }
+
+ private String buildRoomInfo(List<YwRoom> rooms) {
+ if (CollectionUtils.isEmpty(rooms)) {
+ return "";
+ }
+ return rooms.stream().map(this::formatRoomLine).filter(StringUtils::isNotBlank).collect(Collectors.joining("銆�"));
+ }
+
+ private String formatRoomLine(YwRoom room) {
+ if (room == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ if (StringUtils.isNotBlank(room.getProjectName())) {
+ sb.append(room.getProjectName());
+ }
+ if (StringUtils.isNotBlank(room.getBuildingName())) {
+ if (sb.length() > 0) {
+ sb.append("/");
+ }
+ sb.append(room.getBuildingName());
+ }
+ if (StringUtils.isNotBlank(room.getFloorName()) || StringUtils.isNotBlank(room.getRoomNum())) {
+ if (sb.length() > 0) {
+ sb.append("/");
+ }
+ sb.append(StringUtils.defaultString(room.getFloorName())).append("/").append(StringUtils.defaultString(room.getRoomNum()));
+ }
+ return sb.toString();
+ }
+
+ private String resolvePayTypeText(YwContract contract) {
+ Integer type = contract.getType() == null ? Constants.ZERO : contract.getType();
+ if (Objects.equals(type, Constants.ONE)) {
+ return formatPayType(contract.getWyPayType());
+ }
+ if (Objects.equals(type, Constants.TWO)) {
+ return formatPayType(contract.getZlPayType());
+ }
+ String zl = formatPayType(contract.getZlPayType());
+ String wy = formatPayType(contract.getWyPayType());
+ if (StringUtils.isBlank(wy) || StringUtils.equals(zl, wy)) {
+ return zl;
+ }
+ return "绉熻祦" + zl + "锛涚墿涓�" + wy;
+ }
+
+ private String formatPayType(Integer payType) {
+ if (payType == null) {
+ return "-";
+ }
+ if (Objects.equals(payType, Constants.ONE)) {
+ return "姣忎笁涓湀涓�浠�";
+ }
+ if (Objects.equals(payType, Constants.TWO)) {
+ return "鍏釜鏈堜竴浠�";
+ }
+ if (Objects.equals(payType, Constants.THREE)) {
+ return "涓�骞翠竴浠�";
+ }
+ return "涓�娆℃�т粯娆�";
+ }
+
+ private String resolveFreeRentPeriod(YwContract contract) {
+ Date start;
+ Date end;
+ if (Objects.equals(contract.getType(), Constants.ONE)) {
+ start = contract.getWyFreeStartDate();
+ end = contract.getWyFreeEndDate();
+ } else {
+ start = contract.getZlFreeStartDate();
+ end = contract.getZlFreeEndDate();
+ }
+ if (start == null && end == null) {
+ return "-";
+ }
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ String startText = start != null ? sdf.format(start) : "-";
+ String endText = end != null ? sdf.format(end) : "-";
+ return startText + " ~ " + endText;
+ }
+
+ private Map<Integer, List<YwContractBill>> loadContractBillMap(List<Integer> contractIds) {
+ if (CollectionUtils.isEmpty(contractIds)) {
+ return Collections.emptyMap();
+ }
+ List<YwContractBill> bills = ywContractBillMapper.selectList(new QueryWrapper<YwContractBill>().lambda()
+ .in(YwContractBill::getContractId, contractIds)
+ .eq(YwContractBill::getIsdeleted, Constants.ZERO)
+ .orderByDesc(YwContractBill::getPlanPayDate));
+ return bills.stream().collect(Collectors.groupingBy(YwContractBill::getContractId));
+ }
+
+ private void fillBillStatusTip(YwContract contract, List<YwContractBill> bills) {
+ int overdue = 0;
+ int dueSoon = 0;
+ long now = System.currentTimeMillis();
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_MONTH, BILL_DUE_SOON_DAYS);
+ long dueSoonLimit = Utils.Date.getDayEnd(cal.getTime()).getTime();
+ for (YwContractBill bill : bills) {
+ if (!isBillCountable(bill) || !isBillUnpaid(bill) || bill.getPlanPayDate() == null) {
+ continue;
+ }
+ long planEnd = Utils.Date.getEnd(bill.getPlanPayDate()).getTime();
+ if (planEnd < now) {
+ overdue++;
+ } else if (planEnd <= dueSoonLimit) {
+ dueSoon++;
+ }
+ }
+ if (overdue > 0) {
+ contract.setBillStatusTip(overdue + "涓处鍗曞凡閫炬湡锛岃灏藉揩鏀粯");
+ contract.setBillStatusType("danger");
+ } else if (dueSoon > 0) {
+ contract.setBillStatusTip(dueSoon + "涓处鍗曞嵆灏嗗埌鏈燂紝璇峰強鏃舵敮浠�");
+ contract.setBillStatusType("warn");
+ } else {
+ contract.setBillStatusTip("璐﹀崟閮芥寜鏃剁即璐逛簡锛屽緢妫掞紒");
+ contract.setBillStatusType("ok");
+ }
+ }
+
+ private boolean isBillCountable(YwContractBill bill) {
+ if (bill == null || Objects.equals(bill.getIsdeleted(), Constants.ONE)) {
+ return false;
+ }
+ if (!Constants.equalsInteger(bill.getStatus(), Constants.ZERO)) {
+ return false;
+ }
+ return bill.getReceivableFee() != null && bill.getReceivableFee().compareTo(BigDecimal.ZERO) > 0;
+ }
+
+ private boolean isBillUnpaid(YwContractBill bill) {
+ Integer payStatus = bill.getPayStatus();
+ if (Constants.equalsInteger(payStatus, Constants.ONE) || Constants.equalsInteger(payStatus, Constants.FIVE)) {
+ return false;
+ }
+ return Constants.equalsInteger(payStatus, Constants.ZERO)
+ || Constants.equalsInteger(payStatus, Constants.TWO)
+ || Constants.equalsInteger(payStatus, Constants.THREE)
+ || Constants.equalsInteger(payStatus, Constants.FOUR);
+ }
+
+ private void initContractFiles(YwContract contract) {
+ List<Multifile> multifiles = multifileMapper.selectJoinList(Multifile.class, new MPJLambdaWrapper<Multifile>()
+ .selectAll(Multifile.class)
+ .eq(Multifile::getObjId, contract.getId())
+ .eq(Multifile::getObjType, Constants.MultiFile.YW_CONTRACT_FILE.getKey())
+ .eq(Multifile::getIsdeleted, Constants.ZERO)
+ .orderByAsc(Multifile::getSortnum));
+ if (CollectionUtils.isEmpty(multifiles)) {
+ contract.setFileList(Collections.emptyList());
+ return;
+ }
+ String path = systemDictDataBiz.queryByCode(Constants.FTP, Constants.FTP_RESOURCE_PATH).getCode()
+ + systemDictDataBiz.queryByCode(Constants.FTP, Constants.YW_CONTRACT_FILE).getCode();
+ List<Multifile> fileList = new ArrayList<>();
+ for (Multifile file : multifiles) {
+ if (StringUtils.isBlank(file.getFileurl())) {
+ continue;
+ }
+ file.setFileurlFull(path + file.getFileurl());
+ fileList.add(file);
+ }
+ contract.setFileList(fileList);
+ }
+
+ private void enrichBillsWithContract(List<YwContractBill> bills) {
+ if (CollectionUtils.isEmpty(bills)) {
+ return;
+ }
+ List<Integer> contractIds = bills.stream()
+ .map(YwContractBill::getContractId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (contractIds.isEmpty()) {
+ return;
+ }
+ Map<Integer, YwContract> contractMap = ywContractMapper.selectBatchIds(contractIds).stream()
+ .filter(Objects::nonNull)
+ .collect(Collectors.toMap(YwContract::getId, c -> c, (a, b) -> a));
+ for (YwContractBill bill : bills) {
+ applyContractToBill(bill, contractMap.get(bill.getContractId()));
+ }
+ }
+
+ private void enrichBillWithContract(YwContractBill bill) {
+ if (bill == null || bill.getContractId() == null) {
+ return;
+ }
+ YwContract contract = ywContractMapper.selectById(bill.getContractId());
+ applyContractToBill(bill, contract);
+ if (contract != null && contract.getRenterId() != null) {
+ YwCustomer customer = ywCustomerMapper.selectById(contract.getRenterId());
+ if (customer != null) {
+ bill.setCustomerName(customer.getName());
+ }
+ }
+ }
+
+ private void enrichBillWithRooms(YwContractBill bill) {
+ if (bill == null || bill.getContractId() == null) {
+ return;
+ }
+ List<YwRoom> rooms = loadContractRoomMap(Collections.singletonList(bill.getContractId()))
+ .getOrDefault(bill.getContractId(), Collections.emptyList());
+ bill.setRoomList(rooms);
+ bill.setRoomInfo(buildRoomInfo(rooms));
+ }
+
+ private void applyContractToBill(YwContractBill bill, YwContract contract) {
+ if (bill == null || contract == null) {
+ return;
+ }
+ bill.setContractCode(contract.getCode());
+ bill.setContractStartDate(contract.getStartDate());
+ bill.setContractEndDate(contract.getEndDate());
+ }
+
+ private List<YwContractBill> listContractBillsForH5(Integer contractId, Integer billType) {
+ List<YwContractBill> bills = ywContractBillMapper.selectJoinList(YwContractBill.class,
+ new MPJLambdaWrapper<YwContractBill>()
+ .selectAll(YwContractBill.class)
+ .select(" ( select ifnull( sum( CASE WHEN t.bill_type = 0 and yw.REVENUE_TYPE = 0 THEN yw.ACT_RECEIVABLE_FEE when t.bill_type = 0 and yw.REVENUE_TYPE = 1 then -yw.ACT_RECEIVABLE_FEE when t.bill_type = 1 and yw.REVENUE_TYPE = 0 then -yw.ACT_RECEIVABLE_FEE else yw.ACT_RECEIVABLE_FEE END),0) from yw_contract_revenue yw where yw.bill_id = t.id and yw.status = 0 and yw.isdeleted = 0 ) as actReceivableFee ")
+ .eq(YwContractBill::getIsdeleted, Constants.ZERO)
+ .eq(YwContractBill::getStatus, Constants.ZERO)
+ .eq(YwContractBill::getContractId, contractId)
+ .eq(YwContractBill::getBillType, billType != null ? billType : Constants.ZERO)
+ .orderByDesc(YwContractBill::getId));
+ enrichContractBillsForH5(bills);
+ return bills;
+ }
+
+ private void enrichContractBillsForH5(List<YwContractBill> bills) {
+ if (CollectionUtils.isEmpty(bills)) {
+ return;
+ }
+ for (YwContractBill bill : bills) {
+ if (bill.getReceivableFee() != null && bill.getActReceivableFee() != null) {
+ bill.setNeedReceivableFee(bill.getReceivableFee().subtract(bill.getActReceivableFee()));
+ }
+ if (Constants.equalsInteger(bill.getStatus(), Constants.ZERO)
+ && (Constants.equalsInteger(bill.getPayStatus(), Constants.ZERO)
+ || Constants.equalsInteger(bill.getPayStatus(), Constants.TWO)
+ || Constants.equalsInteger(bill.getPayStatus(), Constants.THREE)
+ || Constants.equalsInteger(bill.getPayStatus(), Constants.FOUR))
+ && bill.getPlanPayDate() != null
+ && Utils.Date.getEnd(bill.getPlanPayDate()).getTime() < System.currentTimeMillis()) {
+ bill.setIsOverdue(Constants.ONE);
+ } else {
+ bill.setIsOverdue(Constants.ZERO);
+ }
+ }
+ }
+
+ /**
+ * 涓� PC 绔敹鏀祦姘翠竴鑷达細缁忚处鍗曞叧鑱斿悎鍚屾壙绉熶汉锛屽~鍏呭鏂瑰崟浣嶅悕绉�
+ */
+ private List<YwContractRevenue> listBillRevenues(Integer billId, YwContractBill bill) {
+ List<YwContractRevenue> revenues = ywContractRevenueMapper.selectJoinList(YwContractRevenue.class, new MPJLambdaWrapper<YwContractRevenue>()
+ .selectAll(YwContractRevenue.class)
+ .selectAs(YwCustomer::getName, YwContractRevenue::getCustomerName)
+ .leftJoin(YwContractBill.class, YwContractBill::getId, YwContractRevenue::getBillId)
+ .leftJoin(YwContract.class, YwContract::getId, YwContractBill::getContractId)
+ .leftJoin(YwCustomer.class, YwCustomer::getId, YwContract::getRenterId)
+ .eq(YwContractRevenue::getStatus, Constants.ZERO)
+ .eq(YwContractRevenue::getBillId, billId)
+ .orderByDesc(YwContractRevenue::getId));
+ fillRevenueCustomerNames(revenues, bill);
+ return revenues;
+ }
+
+ private void fillRevenueCustomerNames(List<YwContractRevenue> revenues, YwContractBill bill) {
+ if (CollectionUtils.isEmpty(revenues) || bill == null || StringUtils.isBlank(bill.getCustomerName())) {
+ return;
+ }
+ for (YwContractRevenue revenue : revenues) {
+ if (StringUtils.isBlank(revenue.getCustomerName())) {
+ revenue.setCustomerName(bill.getCustomerName());
+ }
+ }
+ }
+
+ private BigDecimal sumPaid(List<YwContractRevenue> revenues, Integer billType) {
+ if (CollectionUtils.isEmpty(revenues)) return BigDecimal.ZERO;
+ BigDecimal total = BigDecimal.ZERO;
+ for (YwContractRevenue r : revenues) {
+ if (r.getActReceivableFee() == null) continue;
+ int sign = Constants.equalsInteger(r.getRevenueType(), Constants.ZERO) ? 1 : -1;
+ if (Constants.equalsInteger(billType, Constants.ONE)) sign = -sign;
+ total = total.add(r.getActReceivableFee().multiply(BigDecimal.valueOf(sign)));
+ }
+ return total;
+ }
+
+ private PageData<YwContractBill> emptyBillPage(PageWrap<?> pageWrap) {
+ PageData<YwContractBill> data = new PageData<>();
+ data.setRecords(Collections.emptyList());
+ data.setTotal(0);
+ data.setPage(pageWrap.getPage());
+ data.setCapacity(pageWrap.getCapacity());
+ return data;
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
index 66dc3c4..c1136e4 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerRechargeBizServiceImpl.java
@@ -751,6 +751,7 @@
.eq(YwElectricalCharge::getIsdeleted, Constants.ZERO)
.eq(query.getType() != null, YwElectricalCharge::getType, query.getType())
.eq(query.getStatus() != null, YwElectricalCharge::getStatus, query.getStatus())
+ .eq(query.getCustomerId() != null, YwElectricalCharge::getCustomerId, query.getCustomerId())
.like(StringUtils.isNotBlank(query.getCustomerName()), YwCustomer::getName, query.getCustomerName())
.ge(query.getCreateTimeBegin() != null, YwElectricalCharge::getCreateDate, query.getCreateTimeBegin())
.le(query.getCreateTimeEnd() != null, YwElectricalCharge::getCreateDate, query.getCreateTimeEnd())
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerServiceImpl.java
index f36d94a..e9cdf1a 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerServiceImpl.java
@@ -78,6 +78,7 @@
memberMapper.insert(member);
ywCustomer.setMemberId(member.getId());
+ ywCustomer.setPhone(member.getPhone());
ywCustomerMapper.updateById(ywCustomer);
return ywCustomer.getId();
@@ -94,12 +95,14 @@
if(StringUtils.isNotBlank(member.getIdcardNo() ) && Constants.equalsInteger(member.getIdcardType(),Constants.ZERO) ){
if(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
.eq(Member::getIdcardNo, DESUtil.encrypt(Constants.EDS_PWD, member.getIdcardNo()))
+ .eq(Member::getType, Constants.memberType.customer)
.eq(Member::getIsdeleted,Constants.ZERO)) >0){
throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "韬唤璇佸彿銆�"+member.getIdcardNo()+"銆戝凡琚娇鐢紝涓嶈兘閲嶅");
}
}
if(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
.eq(Member::getPhone, member.getPhone())
+ .eq(Member::getType, Constants.memberType.customer)
.eq(Member::getIsdeleted,Constants.ZERO) ) >0){
throw new BusinessException(ResponseStatus.DATA_EXISTS.getCode(), "鎵嬫満鍙枫��"+member.getPhone()+"銆戝凡琚娇鐢紝涓嶈兘閲嶅");
}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerWxPayServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerWxPayServiceImpl.java
new file mode 100644
index 0000000..35e1b09
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwCustomerWxPayServiceImpl.java
@@ -0,0 +1,257 @@
+package com.doumee.service.business.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+import com.doumee.core.model.LoginUserInfo;
+import com.doumee.core.utils.Constants;
+import com.doumee.core.wx.WxJsapiPayUtil;
+import com.doumee.dao.business.*;
+import com.doumee.dao.business.dto.YwCustomerRechargeConditionerDTO;
+import com.doumee.dao.business.dto.YwCustomerRechargeElectricalDTO;
+import com.doumee.dao.business.dto.h5.CustomerPayCreateDTO;
+import com.doumee.dao.business.model.*;
+import com.doumee.service.business.YwContractRevenueService;
+import com.doumee.service.business.YwCustomerH5BizService;
+import com.doumee.service.business.YwCustomerRechargeBizService;
+import com.doumee.service.business.YwCustomerWxPayService;
+import lombok.extern.slf4j.Slf4j;
+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.util.*;
+
+@Service
+@Slf4j
+public class YwCustomerWxPayServiceImpl implements YwCustomerWxPayService {
+
+ @Autowired
+ private YwWxPayOrderMapper ywWxPayOrderMapper;
+ @Autowired
+ private YwContractBillMapper ywContractBillMapper;
+ @Autowired
+ private YwContractMapper ywContractMapper;
+ @Autowired
+ private YwAccountMapper ywAccountMapper;
+ @Autowired
+ private YwElectricalChargeMapper ywElectricalChargeMapper;
+ @Autowired
+ private WxJsapiPayUtil wxJsapiPayUtil;
+ @Autowired
+ private YwCustomerRechargeBizService ywCustomerRechargeBizService;
+ @Autowired
+ private YwContractRevenueService ywContractRevenueService;
+ @Autowired
+ private YwCustomerH5BizService ywCustomerH5BizService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Map<String, String> createOrder(CustomerPayCreateDTO dto, LoginUserInfo user, String clientIp) {
+ if (user == null || user.getCustomerId() == null) {
+ throw new BusinessException(ResponseStatus.NO_ALLOW_LOGIN);
+ }
+ if (dto.getAmount() == null || dto.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "閲戦椤诲ぇ浜�0");
+ }
+ if (StringUtils.isBlank(dto.getOpenid())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "openid涓嶈兘涓虹┖");
+ }
+ Integer orderType = dto.getOrderType();
+ Integer bizRefId = resolveBizRefId(dto, user.getCustomerId());
+ validateBiz(orderType, bizRefId, user.getCustomerId(), dto.getAmount());
+
+ String orderNo = "WX" + System.currentTimeMillis() + user.getCustomerId() + new Random().nextInt(1000);
+ YwWxPayOrder order = new YwWxPayOrder();
+ order.setCreator(user.getId());
+ order.setCreateDate(new Date());
+ order.setEditor(user.getId());
+ order.setEditDate(new Date());
+ order.setIsdeleted(Constants.ZERO);
+ order.setOrderNo(orderNo);
+ order.setCustomerId(user.getCustomerId());
+ order.setOrderType(orderType);
+ order.setBizRefId(bizRefId);
+ order.setAmount(dto.getAmount());
+ order.setStatus(YwWxPayOrder.STATUS_WAIT);
+ order.setOpenid(dto.getOpenid());
+ order.setRemark(dto.getRemark());
+ order.setRequestSnapshot(JSON.toJSONString(dto));
+ ywWxPayOrderMapper.insert(order);
+
+ String body = orderType == YwWxPayOrder.TYPE_BILL ? "璐﹀崟缂磋垂" :
+ (orderType == YwWxPayOrder.TYPE_CONDITIONER ? "绌鸿皟鍏呭��" : "鐢佃垂鍏呭��");
+ Map<String, String> payParams = wxJsapiPayUtil.createJsapiOrder(orderNo, dto.getOpenid(), body, dto.getAmount(), clientIp);
+ payParams.put("orderNo", orderNo);
+ return payParams;
+ }
+
+ @Override
+ public YwWxPayOrder queryOrder(String orderNo, Integer customerId) {
+ return ywWxPayOrderMapper.selectOne(new QueryWrapper<YwWxPayOrder>().lambda()
+ .eq(YwWxPayOrder::getOrderNo, orderNo)
+ .eq(YwWxPayOrder::getCustomerId, customerId)
+ .eq(YwWxPayOrder::getIsdeleted, Constants.ZERO)
+ .last("limit 1"));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public String handleNotify(String xmlBody) {
+ Map<String, String> data = wxJsapiPayUtil.parseNotifyXml(xmlBody);
+ if (!wxJsapiPayUtil.verifyNotifySign(data)) {
+ return failXml("绛惧悕澶辫触");
+ }
+ if (!"SUCCESS".equals(data.get("return_code")) || !"SUCCESS".equals(data.get("result_code"))) {
+ return successXml();
+ }
+ String orderNo = data.get("out_trade_no");
+ YwWxPayOrder order = ywWxPayOrderMapper.selectOne(new QueryWrapper<YwWxPayOrder>().lambda()
+ .eq(YwWxPayOrder::getOrderNo, orderNo)
+ .eq(YwWxPayOrder::getIsdeleted, Constants.ZERO)
+ .last("limit 1"));
+ if (order == null) {
+ return successXml();
+ }
+ if (Objects.equals(order.getStatus(), YwWxPayOrder.STATUS_SUCCESS)) {
+ return successXml();
+ }
+ LoginUserInfo user = buildCustomerUser(order.getCustomerId());
+ try {
+ Integer bizRecordId = fulfillBiz(order, user);
+ order.setStatus(YwWxPayOrder.STATUS_SUCCESS);
+ order.setWxTransactionId(data.get("transaction_id"));
+ order.setPayTime(new Date());
+ order.setBizRecordId(bizRecordId);
+ order.setEditDate(new Date());
+ ywWxPayOrderMapper.updateById(order);
+ } catch (Exception e) {
+ log.error("wx pay fulfill failed orderNo={}", orderNo, e);
+ order.setStatus(YwWxPayOrder.STATUS_FAIL);
+ order.setStatusInfo(e.getMessage());
+ order.setEditDate(new Date());
+ ywWxPayOrderMapper.updateById(order);
+ }
+ return successXml();
+ }
+
+ private Integer fulfillBiz(YwWxPayOrder order, LoginUserInfo user) {
+ CustomerPayCreateDTO dto = JSON.parseObject(order.getRequestSnapshot(), CustomerPayCreateDTO.class);
+ if (order.getOrderType() == YwWxPayOrder.TYPE_BILL) {
+ return createBillRevenue(order, user);
+ }
+ ywCustomerH5BizService.applyFirstRechargeIfNeeded(order.getCustomerId(), user);
+ if (order.getOrderType() == YwWxPayOrder.TYPE_ELECTRICAL) {
+ YwCustomerRechargeElectricalDTO req = new YwCustomerRechargeElectricalDTO();
+ req.setCustomerId(order.getCustomerId());
+ req.setElectricalId(order.getBizRefId());
+ req.setMoney(order.getAmount());
+ req.setRemark(StringUtils.defaultIfBlank(dto != null ? dto.getRemark() : null, "寰俊H5鍏呭��"));
+ ywCustomerRechargeBizService.rechargeElectrical(req, user);
+ return findLatestChargeId(order.getCustomerId(), Constants.ZERO, order.getBizRefId(), order.getOrderNo());
+ }
+ YwCustomerRechargeConditionerDTO req = new YwCustomerRechargeConditionerDTO();
+ req.setCustomerId(order.getCustomerId());
+ req.setMoney(order.getAmount());
+ req.setRemark(StringUtils.defaultIfBlank(dto != null ? dto.getRemark() : null, "寰俊H5鍏呭��"));
+ ywCustomerRechargeBizService.rechargeConditioner(req, user);
+ return findLatestChargeId(order.getCustomerId(), Constants.ONE, order.getCustomerId(), order.getOrderNo());
+ }
+
+ private Integer createBillRevenue(YwWxPayOrder order, LoginUserInfo user) {
+ YwContractBill bill = ywContractBillMapper.selectById(order.getBizRefId());
+ if (bill == null) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "璐﹀崟涓嶅瓨鍦�");
+ }
+ YwAccount account = ywAccountMapper.selectOne(new QueryWrapper<YwAccount>().lambda()
+ .eq(YwAccount::getCompanyId, bill.getCompanyId())
+ .eq(YwAccount::getIsdeleted, Constants.ZERO)
+ .eq(YwAccount::getStatus, Constants.ZERO)
+ .orderByAsc(YwAccount::getId)
+ .last("limit 1"));
+ if (account == null) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "鏈厤缃敹鏀处鎴�");
+ }
+ YwContractRevenue revenue = new YwContractRevenue();
+ revenue.setBillId(bill.getId());
+ revenue.setCompanyId(bill.getCompanyId());
+ revenue.setAccountId(account.getId());
+ revenue.setActReceivableFee(order.getAmount());
+ revenue.setActPayDate(new Date());
+ revenue.setPayType(4);
+ revenue.setWxOrderNo(order.getOrderNo());
+ revenue.setRemark("寰俊H5鏀粯 orderNo=" + order.getOrderNo());
+ revenue.setLoginUserInfo(user);
+ return ywContractRevenueService.create(revenue);
+ }
+
+ private Integer findLatestChargeId(Integer customerId, int type, Integer objId, String orderNo) {
+ YwElectricalCharge charge = ywElectricalChargeMapper.selectOne(new QueryWrapper<YwElectricalCharge>().lambda()
+ .eq(YwElectricalCharge::getCustomerId, customerId)
+ .eq(YwElectricalCharge::getType, type)
+ .eq(type == Constants.ZERO, YwElectricalCharge::getObjId, objId)
+ .eq(YwElectricalCharge::getIsdeleted, Constants.ZERO)
+ .orderByDesc(YwElectricalCharge::getCreateDate)
+ .last("limit 1"));
+ if (charge != null && StringUtils.isBlank(charge.getWxOrderNo())) {
+ ywElectricalChargeMapper.update(null, new UpdateWrapper<YwElectricalCharge>().lambda()
+ .set(YwElectricalCharge::getWxOrderNo, orderNo)
+ .eq(YwElectricalCharge::getId, charge.getId()));
+ return charge.getId();
+ }
+ return charge != null ? charge.getId() : null;
+ }
+
+ private void validateBiz(Integer orderType, Integer bizRefId, Integer customerId, BigDecimal amount) {
+ if (orderType == YwWxPayOrder.TYPE_BILL) {
+ YwContractBill bill = ywContractBillMapper.selectById(bizRefId);
+ if (bill == null || Objects.equals(bill.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY.getCode(), "璐﹀崟涓嶅瓨鍦�");
+ }
+ YwContract contract = ywContractMapper.selectById(bill.getContractId());
+ if (contract == null || !Objects.equals(contract.getRenterId(), customerId)) {
+ throw new BusinessException(ResponseStatus.NOT_ALLOWED);
+ }
+ }
+ }
+
+ private Integer resolveBizRefId(CustomerPayCreateDTO dto, Integer customerId) {
+ if (dto.getOrderType() == YwWxPayOrder.TYPE_ELECTRICAL) {
+ if (dto.getElectricalId() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "鐢佃〃ID涓嶈兘涓虹┖");
+ }
+ return dto.getElectricalId();
+ }
+ if (dto.getOrderType() == YwWxPayOrder.TYPE_CONDITIONER) {
+ return customerId;
+ }
+ if (dto.getOrderType() == YwWxPayOrder.TYPE_BILL) {
+ if (dto.getBillId() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璐﹀崟ID涓嶈兘涓虹┖");
+ }
+ return dto.getBillId();
+ }
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璁㈠崟绫诲瀷鏃犳晥");
+ }
+
+ private LoginUserInfo buildCustomerUser(Integer customerId) {
+ LoginUserInfo u = new LoginUserInfo();
+ u.setId(customerId);
+ u.setCustomerId(customerId);
+ u.setH5UserType(LoginUserInfo.H5_USER_CUSTOMER);
+ u.setRealname("鍟嗘埛H5");
+ return u;
+ }
+
+ private String successXml() {
+ return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
+ }
+
+ private String failXml(String msg) {
+ return "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[" + msg + "]]></return_msg></xml>";
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwH5BannerServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwH5BannerServiceImpl.java
new file mode 100644
index 0000000..9f17e6c
--- /dev/null
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwH5BannerServiceImpl.java
@@ -0,0 +1,169 @@
+package com.doumee.service.business.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.doumee.core.constants.ResponseStatus;
+import com.doumee.core.exception.BusinessException;
+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.YwH5BannerMapper;
+import com.doumee.dao.business.model.YwH5Banner;
+import com.doumee.service.business.YwH5BannerService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+@Service
+public class YwH5BannerServiceImpl implements YwH5BannerService {
+
+ @Autowired
+ private YwH5BannerMapper ywH5BannerMapper;
+
+ @Override
+ public Integer create(YwH5Banner model) {
+ validateBanner(model, true);
+ LoginUserInfo user = resolveUser(model);
+ Date now = new Date();
+ YwH5Banner insert = new YwH5Banner();
+ insert.setCreator(user.getId());
+ insert.setCreateDate(now);
+ insert.setEditor(user.getId());
+ insert.setEditDate(now);
+ insert.setIsdeleted(Constants.ZERO);
+ insert.setTitle(StringUtils.trimToNull(model.getTitle()));
+ insert.setImageUrl(StringUtils.trim(model.getImageUrl()));
+ insert.setLinkUrl(StringUtils.trimToNull(model.getLinkUrl()));
+ insert.setSortnum(model.getSortnum() == null ? Constants.ZERO : model.getSortnum());
+ insert.setStatus(model.getStatus() == null ? Constants.ZERO : model.getStatus());
+ insert.setScope(Constants.ONE);
+ insert.setRemark(StringUtils.trimToNull(model.getRemark()));
+ ywH5BannerMapper.insert(insert);
+ return insert.getId();
+ }
+
+ @Override
+ public void deleteById(Integer id, LoginUserInfo user) {
+ YwH5Banner row = ywH5BannerMapper.selectById(id);
+ if (row == null || Constants.equalsInteger(row.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY);
+ }
+ ywH5BannerMapper.update(null, new UpdateWrapper<YwH5Banner>().lambda()
+ .set(YwH5Banner::getIsdeleted, Constants.ONE)
+ .set(YwH5Banner::getEditor, user.getId())
+ .set(YwH5Banner::getEditDate, new Date())
+ .eq(YwH5Banner::getId, id));
+ }
+
+ @Override
+ public void deleteByIdInBatch(List<Integer> ids, LoginUserInfo user) {
+ if (CollectionUtils.isEmpty(ids)) {
+ return;
+ }
+ for (Integer id : ids) {
+ deleteById(id, user);
+ }
+ }
+
+ @Override
+ public void updateById(YwH5Banner model) {
+ if (model.getId() == null) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST);
+ }
+ YwH5Banner existing = ywH5BannerMapper.selectById(model.getId());
+ if (existing == null || Constants.equalsInteger(existing.getIsdeleted(), Constants.ONE)) {
+ throw new BusinessException(ResponseStatus.DATA_EMPTY);
+ }
+ if (StringUtils.isNotBlank(model.getImageUrl())) {
+ validateBanner(model, false);
+ }
+ LoginUserInfo user = resolveUser(model);
+ YwH5Banner update = new YwH5Banner();
+ update.setId(model.getId());
+ update.setEditor(user.getId());
+ update.setEditDate(new Date());
+ if (model.getTitle() != null) {
+ update.setTitle(StringUtils.trimToNull(model.getTitle()));
+ }
+ if (StringUtils.isNotBlank(model.getImageUrl())) {
+ update.setImageUrl(StringUtils.trim(model.getImageUrl()));
+ }
+ if (model.getLinkUrl() != null) {
+ update.setLinkUrl(StringUtils.trimToNull(model.getLinkUrl()));
+ }
+ if (model.getSortnum() != null) {
+ update.setSortnum(model.getSortnum());
+ }
+ if (model.getStatus() != null) {
+ update.setStatus(model.getStatus());
+ }
+ if (model.getRemark() != null) {
+ update.setRemark(StringUtils.trimToNull(model.getRemark()));
+ }
+ ywH5BannerMapper.updateById(update);
+ }
+
+ @Override
+ public YwH5Banner findById(Integer id) {
+ YwH5Banner row = ywH5BannerMapper.selectById(id);
+ if (row == null || Constants.equalsInteger(row.getIsdeleted(), Constants.ONE)) {
+ return null;
+ }
+ return row;
+ }
+
+ @Override
+ public PageData<YwH5Banner> findPage(PageWrap<YwH5Banner> pageWrap) {
+ IPage<YwH5Banner> page = new Page<>(pageWrap.getPage(), pageWrap.getCapacity());
+ QueryWrapper<YwH5Banner> qw = new QueryWrapper<>();
+ YwH5Banner model = pageWrap.getModel();
+ if (model != null) {
+ Utils.MP.blankToNull(model);
+ if (StringUtils.isNotBlank(model.getTitle())) {
+ qw.lambda().like(YwH5Banner::getTitle, model.getTitle());
+ }
+ if (model.getStatus() != null) {
+ qw.lambda().eq(YwH5Banner::getStatus, model.getStatus());
+ }
+ }
+ qw.lambda()
+ .eq(YwH5Banner::getIsdeleted, Constants.ZERO)
+ .eq(YwH5Banner::getScope, Constants.ONE)
+ .orderByAsc(YwH5Banner::getSortnum)
+ .orderByDesc(YwH5Banner::getCreateDate);
+ return PageData.from(ywH5BannerMapper.selectPage(page, qw));
+ }
+
+ @Override
+ public List<YwH5Banner> listEnabledForCustomerWorkbench() {
+ return ywH5BannerMapper.selectList(new QueryWrapper<YwH5Banner>().lambda()
+ .eq(YwH5Banner::getIsdeleted, Constants.ZERO)
+ .eq(YwH5Banner::getStatus, Constants.ZERO)
+ .eq(YwH5Banner::getScope, Constants.ONE)
+ .orderByAsc(YwH5Banner::getSortnum)
+ .orderByDesc(YwH5Banner::getCreateDate));
+ }
+
+ private void validateBanner(YwH5Banner model, boolean requireImage) {
+ if (requireImage && StringUtils.isBlank(model.getImageUrl())) {
+ throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "璇蜂笂浼犺疆鎾浘鐗�");
+ }
+ }
+
+ private LoginUserInfo resolveUser(YwH5Banner model) {
+ LoginUserInfo user = model.getLoginUserInfo();
+ if (user == null) {
+ throw new BusinessException(ResponseStatus.NO_LOGIN);
+ }
+ return user;
+ }
+}
diff --git a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwMaterialServiceImpl.java b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwMaterialServiceImpl.java
index ea498da..29d83f8 100644
--- a/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwMaterialServiceImpl.java
+++ b/server/visits/dmvisit_service/src/main/java/com/doumee/service/business/impl/YwMaterialServiceImpl.java
@@ -27,7 +27,6 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.base.MPJBaseMapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
-import javafx.scene.paint.Material;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.BeanUtils;
--
Gitblit v1.9.3