From 5b23e48e2c1f4da93d478f0b9d8fdb565f19b9c0 Mon Sep 17 00:00:00 2001 From: zhouyanli <593579392@qq.com> Date: Fri, 7 Aug 2026 16:36:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E8=B4=B9=E6=80=BB=E8=A7=88=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wuye-manager-uniapp/App.vue | 24 +++++-- wuye-manager-uniapp/api/request.js | 1 + wuye-manager-uniapp/api/statisticsApi.js | 8 +++ wuye-manager-uniapp/manifest.json | 2 +- .../pageSubPack/setting/setting.vue | 6 +- .../statistics/components/DepositCard.vue | 40 +++++++---- .../pageSubPack/statistics/statistics.vue | 71 +++++++++++++------ wuye-manager-uniapp/pages.json | 3 +- wuye-manager-uniapp/pages/index/index.vue | 47 +++++++++--- wuye-manager-uniapp/pages/my/my.vue | 6 +- .../qiun-data-charts/qiun-data-charts.vue | 4 +- .../js_sdk/u-charts/u-charts.js | 12 +++- 12 files changed, 163 insertions(+), 61 deletions(-) diff --git a/wuye-manager-uniapp/App.vue b/wuye-manager-uniapp/App.vue index 29a0351..89f7bc6 100644 --- a/wuye-manager-uniapp/App.vue +++ b/wuye-manager-uniapp/App.vue @@ -4,6 +4,7 @@ import { onLaunch, onShow, onHide } from '@dcloudio/uni-app' let pushCIDRetryCount = 0 const MAX_RETRY = 5 let retryTimer = null +let pushCIDFetching = false // 防止并发请求 // 应用启动时执行 onLaunch(() => { @@ -13,27 +14,32 @@ onLaunch(() => { }) // #ifdef APP-PLUS - // 只有 App 平台才获取 CID,延迟 1 秒等推送服务就绪 + // 延迟 2 秒等推送服务完全就绪后再获取 CID setTimeout(() => { getPushCID() - }, 1000) + }, 2000) // #endif }) onShow(() => { console.log('App Show') // #ifdef APP-PLUS - // 每次回到前台都重新获取 CID,防止 CID 过期失效 + // 从后台恢复时,如果 CID 已经获取过且未过期,不再重复请求 + const existingCid = uni.getStorageSync('push_cid') + if (existingCid) { + console.log('已有缓存 CID,跳过重复获取') + return + } pushCIDRetryCount = 0 setTimeout(() => { refreshPushCID() - }, 500) + }, 1000) // #endif }) onHide(() => { console.log('App Hide') - // 清除重试定时器,避免后台无效重试 + pushCIDFetching = false if (retryTimer) { clearTimeout(retryTimer) retryTimer = null @@ -43,9 +49,12 @@ onHide(() => { // 刷新 CID(每次 onShow 调用,比对更新) function refreshPushCID() { + if (pushCIDFetching) return const oldCid = uni.getStorageSync('push_cid') || '' + pushCIDFetching = true uni.getPushClientId({ success: res => { + pushCIDFetching = false const newCid = res.cid if (!newCid) { console.warn('获取到的 CID 为空,视为失败') @@ -62,6 +71,7 @@ function refreshPushCID() { } }, fail: err => { + pushCIDFetching = false console.error('获取推送 CID 失败', err) scheduleRetry() } @@ -70,8 +80,11 @@ function refreshPushCID() { // 启动时首次获取 CID(重试更积极) function getPushCID() { + if (pushCIDFetching) return + pushCIDFetching = true uni.getPushClientId({ success: res => { + pushCIDFetching = false const cid = res.cid if (!cid) { console.warn('获取到的 CID 为空,视为失败') @@ -84,6 +97,7 @@ function getPushCID() { // TODO: 将 CID 上报到服务器 }, fail: err => { + pushCIDFetching = false console.error('获取推送 CID 失败', err) scheduleRetry() } diff --git a/wuye-manager-uniapp/api/request.js b/wuye-manager-uniapp/api/request.js index d7c70eb..c142941 100644 --- a/wuye-manager-uniapp/api/request.js +++ b/wuye-manager-uniapp/api/request.js @@ -11,6 +11,7 @@ export default function request(url, data = {}, method = 'GET') { url: baseUrl + url, data: data, method: method, + timeout: 15000, header: { Authorization: 'Bearer ' + (uni.getStorageSync('token') || ''), clientid: 'butler', diff --git a/wuye-manager-uniapp/api/statisticsApi.js b/wuye-manager-uniapp/api/statisticsApi.js index ebced34..daad328 100644 --- a/wuye-manager-uniapp/api/statisticsApi.js +++ b/wuye-manager-uniapp/api/statisticsApi.js @@ -24,3 +24,11 @@ export const getFeeSummary = data => { export const getParkingFee = data => { return get('/api/butler/feeOverview/parking', data) } +// 押金余额统计 +export const getDepositBalance = data => { + return get('/api/butler/feeOverview/depositBalance', data) +} +// 巡检总览 +export const getInspection = data => { + return get('/api/butler/feeOverview/inspection', data) +} diff --git a/wuye-manager-uniapp/manifest.json b/wuye-manager-uniapp/manifest.json index 8c5c1fa..1e0e92d 100644 --- a/wuye-manager-uniapp/manifest.json +++ b/wuye-manager-uniapp/manifest.json @@ -10,7 +10,7 @@ "nvueStyleCompiler" : "uni-app", "compilerVersion" : 3, "splashscreen" : { - "alwaysShowBeforeRender" : true, + "alwaysShowBeforeRender" : false, "waiting" : true, "autoclose" : true, "delay" : 0, diff --git a/wuye-manager-uniapp/pageSubPack/setting/setting.vue b/wuye-manager-uniapp/pageSubPack/setting/setting.vue index 7326437..4a5be0e 100644 --- a/wuye-manager-uniapp/pageSubPack/setting/setting.vue +++ b/wuye-manager-uniapp/pageSubPack/setting/setting.vue @@ -1,14 +1,14 @@ @@ -15,16 +18,17 @@ import { computed } from 'vue' const props = defineProps({ depositBalance: { type: String, default: '0' }, - depositRenovation: { type: Number, default: 0 }, - depositRental: { type: Number, default: 0 } + depositItems: { type: Array, default: () => [] } }) -const depositColumnData = computed(() => ({ - categories: ['装修押金', '租赁押金'], - series: [ - { name: '余额', data: [props.depositRenovation, props.depositRental] } - ] -})) +const depositColumnData = computed(() => { + const categories = props.depositItems.map(item => item.typeName) + const data = props.depositItems.map(item => (item.balance / 10000)) + return { + categories, + series: [{ name: '余额', data }] + } +}) const depositColumnOpts = computed(() => ({ color: ['#4D5DFA'], @@ -34,22 +38,24 @@ const depositColumnOpts = computed(() => ({ xAxis: { disableGrid: true, axisLineColor: '#EAEAEA', + dashLength: 4, fontColor: '#666', fontSize: 12 }, yAxis: { + showTitle: true, data: [{ - title: '万元', + title: '金额(万)', min: 0, - max: 80, axisLineColor: '#EAEAEA', fontColor: '#666', fontSize: 12, titleFontSize: 12, titleFontColor: '#666' }], - splitLine: { show: true, lineStyle: { color: '#F2F2F2' } }, - calibration: { step: 0.5 } + gridType: 'dash', + dashLength: 4, + gridColor: '#F2F2F2', }, extra: { column: { @@ -88,6 +94,16 @@ const depositColumnOpts = computed(() => ({ .deposit-chart { width: 100%; height: 420rpx; + position: relative; + .deposit-empty-mask { + position: absolute; + top: 0; left: 0; right: 0; bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: #fff; + .empty-text { font-size: 26rpx; color: #999; } + } } } diff --git a/wuye-manager-uniapp/pageSubPack/statistics/statistics.vue b/wuye-manager-uniapp/pageSubPack/statistics/statistics.vue index 0336d13..7f44688 100644 --- a/wuye-manager-uniapp/pageSubPack/statistics/statistics.vue +++ b/wuye-manager-uniapp/pageSubPack/statistics/statistics.vue @@ -58,8 +58,7 @@ @@ -77,7 +76,7 @@ import FeeOverviewCard from './components/FeeOverviewCard.vue' import ParkingFeeCard from './components/ParkingFeeCard.vue' import DepositCard from './components/DepositCard.vue' import InspectionOverview from './components/InspectionOverview.vue' -import { getManagedArea, getRate, getFeeSummary, getParkingFee } from '@/api/statisticsApi.js' +import { getManagedArea, getRate, getFeeSummary, getParkingFee, getDepositBalance, getInspection } from '@/api/statisticsApi.js' @@ -109,16 +108,15 @@ const parkingYearReceived = ref(0) const parkingArcbarPercent = ref(0) // 押金余额统计 -const depositBalance = ref('33322') -const depositRenovation = ref(18) -const depositRental = ref(23) +const depositBalance = ref('0') +const depositItems = ref([]) // 巡检总览 const inspectionList = ref([ - { label: '巡检任务', value: 124, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' }, - { label: '已完成', value: 98, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' }, - { label: '已过期', value: 12, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' }, - { label: '待处理', value: 14, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' } + { label: '巡检任务', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' }, + { label: '已完成', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' }, + { label: '已过期', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' }, + { label: '待处理', value: 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' } ]) const switchTab = (tab) => { @@ -134,6 +132,8 @@ const onProjectChange = (e) => { fetchRate(opt.value) fetchFeeSummary() fetchParkingFee(opt.value) + fetchDepositBalance(opt.value) + fetchInspection(opt.value) } const onFeeTypeChange = (opt) => { @@ -169,19 +169,20 @@ const fetchRate = (villageId) => { } }).catch(() => {}) } - +// 车位收费总览 const fetchParkingFee = (villageId) => { getParkingFee({ villageId: villageId || '' }).then(res => { if (res.code === 200 && res.data) { const d = res.data - parkingCount.value = d.parkingCount || 0 - parkingYearReceivable.value = d.receivableAmount || 0 - parkingYearReceived.value = d.receivedAmount || 0 - parkingArcbarPercent.value = d.receivedPercent != null ? d.receivedPercent / 100 : 0 + parkingCount.value = Number(d.parkingCount) || 0 + parkingYearReceivable.value = Number(d.receivableAmount) || 0 + parkingYearReceived.value = Number(d.receivedAmount) || 0 + parkingArcbarPercent.value = d.receivedPercent != null ? Number(d.receivedPercent) / 100 : 0 } }).catch(() => {}) } +// 收费总览 const fetchFeeSummary = () => { getFeeSummary({ villageId: selectedProject.value || '', @@ -190,19 +191,21 @@ const fetchFeeSummary = () => { }).then(res => { if (res.code === 200 && res.data) { const d = res.data - arcbarPercent.value = d.receivedPercent != null ? d.receivedPercent / 100 : 0 + arcbarPercent.value = Number(d.receivedPercent) || 0 metricList.value = [ - { label: '应收总额', value: d.receivableAmount != null ? d.receivableAmount.toFixed(2) : '0.00', yoy: formatRate(d.receivableYoyRate), mom: formatRate(d.receivableMomRate) }, - { label: '实收总额', value: d.receivedAmount != null ? d.receivedAmount.toFixed(2) : '0.00', yoy: formatRate(d.receivedYoyRate), mom: formatRate(d.receivedMomRate) }, - { label: '欠收总额', value: d.unpaidAmount != null ? d.unpaidAmount.toFixed(2) : '0.00', yoy: formatRate(d.unpaidYoyRate), mom: formatRate(d.unpaidMomRate) } + { label: '应收总额', value: (Number(d.receivableAmount) || 0).toFixed(2), yoy: formatRate(d.receivableYoyRate), mom: formatRate(d.receivableMomRate) }, + { label: '实收总额', value: (Number(d.receivedAmount) || 0).toFixed(2), yoy: formatRate(d.receivedYoyRate), mom: formatRate(d.receivedMomRate) }, + { label: '欠收总额', value: (Number(d.unpaidAmount) || 0).toFixed(2), yoy: formatRate(d.unpaidYoyRate), mom: formatRate(d.unpaidMomRate) } ] } }).catch(() => {}) } const formatRate = (val) => { - if (val == null) return '-' - return (val >= 0 ? '+' : '') + val.toFixed(2) + '%' + if (val == null || val === '') return '-' + const num = Number(val) + if (isNaN(num)) return '-' + return (num >= 0 ? '+' : '') + num.toFixed(2) + '%' } const fetchFeeTypes = () => { @@ -214,6 +217,30 @@ const fetchFeeTypes = () => { } }).catch(() => {}) } +// 押金余额统计 +const fetchDepositBalance = (villageId) => { + getDepositBalance({ villageId: villageId}).then(res => { + if (res.code === 200 && res.data) { + const d = res.data + depositBalance.value = d.totalBalance + depositItems.value = d.items || [] + } + }).catch(() => {}) +} +// 巡检总览 +const fetchInspection = (villageId) => { + getInspection({ villageId: villageId || '' }).then(res => { + if (res.code === 200 && res.data) { + const d = res.data + inspectionList.value = [ + { label: '巡检任务', value: d.taskCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-task.png' }, + { label: '已完成', value: d.completedCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-done.png' }, + { label: '已过期', value: d.expiredCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-expire.png' }, + { label: '待处理', value: d.pendingCount || 0, icon: 'https://wuye.ckdzkj.com/images/imgs/inspection-pending.png' } + ] + } + }).catch(() => {}) +} onShow(() => { Promise.all([fetchVillageList(), fetchFeeTypes()]).then(() => { @@ -221,6 +248,8 @@ onShow(() => { fetchRate(selectedProject.value) fetchFeeSummary() fetchParkingFee(selectedProject.value) + fetchDepositBalance(selectedProject.value) + fetchInspection(selectedProject.value) }) }) diff --git a/wuye-manager-uniapp/pages.json b/wuye-manager-uniapp/pages.json index 2544162..6a261c0 100644 --- a/wuye-manager-uniapp/pages.json +++ b/wuye-manager-uniapp/pages.json @@ -262,8 +262,7 @@ "pages": [{ "path": "setting", "style": { - "navigationBarTitleText": "设置", - "navigationStyle": "custom" + "navigationBarTitleText": "设置" } }, { diff --git a/wuye-manager-uniapp/pages/index/index.vue b/wuye-manager-uniapp/pages/index/index.vue index aed6cb8..3b9c44b 100644 --- a/wuye-manager-uniapp/pages/index/index.vue +++ b/wuye-manager-uniapp/pages/index/index.vue @@ -136,6 +136,13 @@ import { usePermStore } from '@/stores/usePermStore' const permStore = usePermStore() +// 首次加载标记:onLoad 和 onShow 在首次进入时都会触发,用此标记避免重复请求 +const hasInit = ref(false) + +// banner 缓存 key 和缓存时长(30分钟) +const BANNER_CACHE_KEY = 'home_banner_cache' +const BANNER_CACHE_TIME = 30 * 60 * 1000 + // 列表数据状态 const currentCommunity = ref('请先绑定房屋') const bannerList = ref([]) @@ -342,17 +349,34 @@ const getNoticeData = () => { }) .catch(() => {}) } -// 获取bannar图 -function getbannarData() { +// 获取bannar图(带缓存:30分钟内优先用缓存,请求成功后更新缓存) +function getbannarData(forceRefresh = false) { + // 非强制刷新时,优先读缓存 + if (!forceRefresh) { + try { + const cached = uni.getStorageSync(BANNER_CACHE_KEY) + if (cached) { + const { data, time } = JSON.parse(cached) + if (data && data.length > 0 && Date.now() - time < BANNER_CACHE_TIME) { + bannerList.value = data + return // 缓存有效,不发起请求 + } + } + } catch (e) {} + } getBannarList() .then(res => { if (res.code === 200 && res.data) { - // 只有数据真正变化时才更新,避免 swiper 重新渲染导致闪动 const newData = res.data const oldData = bannerList.value if (JSON.stringify(newData) !== JSON.stringify(oldData)) { bannerList.value = newData } + // 写入缓存 + uni.setStorageSync(BANNER_CACHE_KEY, JSON.stringify({ + data: newData, + time: Date.now() + })) } else { bannerList.value = [] } @@ -386,21 +410,26 @@ const slelctHomeData = data => { getInspectionData() } -const permissionlist = ref([]) onShow(() => { + if (!hasInit.value) { + // 首次加载:onLoad 已触发过核心数据请求,这里只补 onLoad 没调的和需要每次都刷新的 + hasInit.value = true + getbannarData() // onLoad 没调 banner + getMyOrderNum() // onLoad 没调工单数 + permStore.initPerm() // 刷新权限缓存 + // currentVillageData 和 getInspectionData 已在 onLoad 中调用,不再重复 + return + } + // 从其他页面返回 / 从后台恢复:只刷新可能变化的数据 getbannarData() - getInspectionData() - currentVillageData() getMyOrderNum() - permStore.initPerm() + // 小区和巡检数据不频繁变化,从后台恢复时用缓存/旧数据,减少请求 }) onLoad(() => { uni.$on('confirm', slelctHomeData) - // 优先调用接口获取当前小区,失败时再读缓存 currentVillageData() getInspectionData() - // getbannarData() }) onUnload(() => { diff --git a/wuye-manager-uniapp/pages/my/my.vue b/wuye-manager-uniapp/pages/my/my.vue index a21f4e9..e661c53 100644 --- a/wuye-manager-uniapp/pages/my/my.vue +++ b/wuye-manager-uniapp/pages/my/my.vue @@ -76,11 +76,7 @@ const onItemClick = item => { } const url = routes[item.title] if (!url) return - if (item.title === '设置') { - uni.redirectTo({ url }) - } else { - uni.navigateTo({ url }) - } + uni.navigateTo({ url }) } diff --git a/wuye-manager-uniapp/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue b/wuye-manager-uniapp/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue index 5678f7d..f15eaac 100644 --- a/wuye-manager-uniapp/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue +++ b/wuye-manager-uniapp/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue @@ -869,7 +869,9 @@ export default { const ctx = cfu.option[cid].context; if(typeof ctx === "object" && !!!cfu.option[cid].update){ ctx.clearRect(0, 0, this.cWidth*this.pixel, this.cHeight*this.pixel); - ctx.draw(); + if (typeof ctx.draw === 'function') { + ctx.draw(); + } } } }, diff --git a/wuye-manager-uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/u-charts.js b/wuye-manager-uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/u-charts.js index f78bde5..e903442 100644 --- a/wuye-manager-uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/u-charts.js +++ b/wuye-manager-uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/u-charts.js @@ -4643,9 +4643,13 @@ function drawXAxis(categories, opts, config, context) { context.beginPath(); context.setStrokeStyle(opts.xAxis.axisLineColor); context.setLineWidth(1 * opts.pix); + if (opts.xAxis.dashLength) { + context.setLineDash([opts.xAxis.dashLength * opts.pix, opts.xAxis.dashLength * opts.pix]); + } context.moveTo(startX, opts.height - opts.area[2]); context.lineTo(endX, opts.height - opts.area[2]); context.stroke(); + context.setLineDash([]); } } @@ -5834,7 +5838,9 @@ function drawMapDataPoints(series, opts, config, context) { mercator: mapOption.mercator } drawToolTipBridge(opts, config, context, 1); - context.draw(); + if (typeof context.draw === 'function') { + context.draw(); + } } function normalInt(min, max, iter) { @@ -6305,7 +6311,9 @@ function drawCanvas(opts, context) { context.save(); context.translate(0, 0.5); context.restore(); - context.draw(); + if (typeof context.draw === 'function') { + context.draw(); + } } var Timing = {