Files
property-resident-side/property-uniapp-project/pages/index/index.vue
zhouyanli 0661687bc5 修复bug
2026-08-28 17:25:55 +08:00

752 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="content-class">
<!-- 首次加载遮罩 -->
<view class="loading-mask" v-if="!pageReady">
<view class="loading-bg"></view>
</view>
<!-- 顶部渐变背景 -->
<view class="nar-top" v-if="pageReady"></view>
<!-- 固定头部 -->
<view class="header-fixed" v-if="pageReady">
<view class="status-bar"></view>
<view class="header-content">
<!-- 小区选择 -->
<view class="header-container" @click="navigateToPage">
<view class="location-area">
<view class="location-icon">
<image src="https://wuye.ckdzkj.com/images/home/adress.png"
style="height: 100%;width: 100%;"></image>
</view>
<view class="community-selector">
<text class="community-name">{{ currentCommunity }}</text>
<u-icon name="arrow-down-fill" size="16" :color="isDropdownOpen ? '#2979ff' : '#222222'"
:class="['dropdown-arrow', { 'rotate': isDropdownOpen }]"></u-icon>
</view>
</view>
</view>
</view>
</view>
<!-- 滚动内容区 -->
<view
class="main-scroll"
v-if="pageReady">
<!-- banner -->
<view class="bannarClass">
<up-swiper :key="bannerSwiperKey" :list="bannerList" indicator indicatorMode="dot" radius="10" circular>
<template #default="{ item, index }">
<image :src="item" mode="aspectFill" class="banner-img" @tap="bannerClick(index)"></image>
</template>
</up-swiper>
</view>
<!-- 公告 -->
<view class="noticeClass" @click="getNoticeMore">
<uni-notice-bar show-icon :text="noticeText" type="line" :key="noticeKey" show-get-more scrollable
color="#000" background-color="#fff" moreColor="#000">
<template #noticebarIcon>
<image src="https://wuye.ckdzkj.com/images/home/notice.png"
style="width: 38px;height: 38px;"></image>
</template>
</uni-notice-bar>
</view>
<!-- 快捷服务 -->
<view class="u-demo-block__content bg-card-open">
<up-row justify="space-between" gutter="10">
<up-col span="3" v-for="item in serviceItems" :key="item.title">
<view class="demo-layout bg-purple openClass" @click="serviceClick(item)">
<view class="iconClass">
<image :src="item.icon" class="iconClass-img"></image>
</view>
<view class="title-dome-calss">{{ item.title }}</view>
</view>
</up-col>
</up-row>
</view>
<!-- 广告卡片 -->
<view class="adCardClass">
<view class="u-demo-block__content">
<up-row justify="space-between" customStyle="margin-bottom: 10px">
<up-col span="3">
<view class="demo-layout bg-purple iconRightClass">
<image src="https://wuye.ckdzkj.com/images/home/ad1.png" mode=""
style="width: 100%;height: 100%;"></image>
</view>
</up-col>
<up-col span="8">
<view class="demo-layout bg-purple-light titleLeft">
<text style="color: #fff;font-size: 40rpx;font-weight: 700;">便民服务</text>
<view style="color: #fff;font-size: 28rpx;">邻里便民服务 即将上线</view>
</view>
</up-col>
</up-row>
</view>
</view>
<!-- 活动列表 -->
<uni-card class="activity-card">
<view class="eventListClass">
<view>
<text class="activity-name">活动列表</text>
</view>
<view class="nav-more">
<text class="look-more-title" @click="moreClick">查看更多</text>
</view>
</view>
<view class="activity-list">
<view class="eventListCard" v-for="(activity, index) in activityList" :key="activity.id"
@click="goToActivityDetail(activity)">
<view class="u-demo-block__content ">
<up-row justify="space-between" customStyle="margin-bottom: 10px">
<up-col span="4">
<view class="demo-layout bg-purple-light activity-img">
<view class="status-tag">
{{ activity.statusText }}
</view>
<image :src="activity.coverImage"
style="width: 100%;height: 100%;border-radius: 16rpx;" mode="aspectFill">
</image>
</view>
</up-col>
<up-col span="8">
<view class="demo-layout bg-purple activity-info">
<view class="activity-title">{{activity.title}}</view>
<view class="activity-time">
{{formatDate(activity.startTime)}}-{{formatDate(activity.endTime)}}</view>
<view class="activity-organizer">
<text>{{activity.address}}</text>
</view>
<view class="activity-enrollment">
<text>
<text v-if="activity.quota !== 0">活动名额{{ activity.quota }},</text>
已报名{{activity.appliedCount}}
</text>
</view>
</view>
</up-col>
</up-row>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="activityList.length === 0" class="empty-state">
<uni-icons type="info" size="30" color="#ccc"></uni-icons>
<text class="empty-text">暂无数据</text>
</view>
</uni-card>
</view>
</view>
</template>
<script setup>
import {
ref,
nextTick
} from 'vue'
import {
onLoad,
onUnload,
onShow,
onPullDownRefresh
} from '@dcloudio/uni-app'
import moment from 'moment'
import {
getNoticeLatest,
getCurrentVillage,
getActivitylatest,
getBannarList,
getReloadUserInfo
} from "/pageSubPack/api/api.js"
import { requireLogin } from '@/common/checkLogin.js'
// 列表数据状态
const isDropdownOpen = ref(false)
const activityList = ref([])
const serviceItems = [
{ title: '一键开门', icon: 'https://wuye.ckdzkj.com/images/home/openDoor.png', url: '/pageSubPack/notice-list/oneClickOpen' },
{ title: '生活缴费', icon: 'https://wuye.ckdzkj.com/images/home/lifePay.png', url: '/pageSubPack/payment-list/paymentIndex' },
{ title: '在线报修', icon: 'https://wuye.ckdzkj.com/images/home/repair.png', url: '/pageSubPack/online-repair/onlineRepair' },
{ title: '访客邀请', icon: 'https://wuye.ckdzkj.com/images/home/visitorInvite.png', url: '/pageSubPack/visitor/visitor-list' }
]
const currentCommunity = ref('请绑定房屋')
const currentVillageId = ref('')
const bannerList = ref([])
const bannerRawData = ref([])
const bannerSwiperKey = ref(0)
const noticeText = ref('暂无公告')
const noticeKey = ref(0)
const noticeId = ref('')
const lastUserId = ref('')
const dataLoaded = ref(false)
const pageReady = ref(false)
// ======= 首页数据缓存(冷启动秒开) =======
const getHomeCacheKey = () => 'home_cache_' + (uni.getStorageSync('userId') || '')
function loadHomeCache() {
try {
const raw = uni.getStorageSync(getHomeCacheKey())
if (!raw) return false
const c = JSON.parse(raw)
if (c.community && c.community !== '请绑定房屋') currentCommunity.value = c.community
if (c.villageId) {
currentVillageId.value = c.villageId
uni.setStorageSync('currentVillageId', c.villageId)
}
if (c.bannerList && c.bannerList.length) bannerList.value = c.bannerList
if (c.bannerRaw && c.bannerRaw.length) bannerRawData.value = c.bannerRaw
if (c.notice && c.notice !== '暂无公告') noticeText.value = c.notice
if (c.noticeId) noticeId.value = c.noticeId
if (c.activityList && c.activityList.length) activityList.value = c.activityList
bannerSwiperKey.value++
return true
} catch (e) { return false }
}
function saveHomeCache() {
try {
uni.setStorageSync(getHomeCacheKey(), JSON.stringify({
community: currentCommunity.value,
villageId: currentVillageId.value,
bannerList: bannerList.value,
bannerRaw: bannerRawData.value,
notice: noticeText.value,
noticeId: noticeId.value,
activityList: activityList.value
}))
} catch (e) {}
}
// =======方法=======
const formatDate = (date) => {
if (!date) return ''
return moment(date).format('YYYY-MM-DD HH:mm')
}
const getNoticeMore = () => {
if (!requireLogin()) return
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({ url: `/pageSubPack/notice-list/noticeListDetails?id=${noticeId.value}` })
}
const getbannarData = async () => {
try {
const res = await getBannarList({ villageId: uni.getStorageSync('currentVillageId') })
if (res.code === 200 && Array.isArray(res.data)) {
bannerRawData.value = res.data
const newList = res.data.map(item => item.url).filter(url => typeof url === 'string' && url)
if (JSON.stringify(newList) !== JSON.stringify(bannerList.value)) {
bannerList.value = newList
await nextTick()
bannerSwiperKey.value++
saveHomeCache()
}
} else if (res.code === 200) {
bannerRawData.value = []
bannerList.value = []
bannerSwiperKey.value++
}
} catch (err) {}
}
const bannerClick = (index) => {
if (!requireLogin()) return
const item = bannerRawData.value[index]
if (item && item.link) {
uni.navigateTo({ url: item.link })
}
}
const slelctHomeData = (data) => {
if (data.name) currentCommunity.value = data.name
if (data.villageId) {
currentVillageId.value = data.villageId
uni.setStorageSync('currentVillageId', data.villageId)
}
// 标记需要刷新,回到首页后 onShow 会触发 refreshAllData
dataLoaded.value = false
}
const getCurrentCommunityData = (reloadedVillageId) => {
// 删除房屋后 reloadUserInfo 会返回 currentVillageId=0
// 此时直接清空,避免再请求可能返回过期数据的 /village/current 导致小区名残留
if (reloadedVillageId === 0 || reloadedVillageId === '0') {
currentCommunity.value = '请绑定房屋'
currentVillageId.value = ''
uni.removeStorageSync('currentVillageId')
saveHomeCache()
return Promise.resolve()
}
return getCurrentVillage().then(res => {
if (res.code === 200 && res.data && res.data.villageId) {
currentCommunity.value = res.data.villageName
saveHomeCache()
currentVillageId.value = res.data.villageId
uni.setStorageSync('currentVillageId', res.data.villageId)
} else {
currentCommunity.value = '请绑定房屋'
currentVillageId.value = ''
uni.removeStorageSync('currentVillageId')
saveHomeCache()
}
}).catch(err => {
currentCommunity.value = '请绑定房屋'
currentVillageId.value = ''
uni.removeStorageSync('currentVillageId')
saveHomeCache()
const token = uni.getStorageSync('token')
if (token) {
uni.showToast({ title: err.msg || '网络异常', icon: 'none' })
}
})
}
const getNoticeData = async () => {
try {
const res = await getNoticeLatest({ villageId: uni.getStorageSync('currentVillageId') })
if (res.code === 200) {
if (res.data) {
noticeText.value = res.data.noticeTitle || ''
noticeId.value = res.data.noticeId || ''
} else {
noticeText.value = '暂无公告'
noticeId.value = ''
}
}
} catch (err) {
noticeText.value = '暂无公告'
noticeId.value = ''
}
saveHomeCache()
await nextTick()
noticeKey.value++
}
const refreshAllData = async () => {
// 立即展示页面骨架,避免首屏长时间停留在渐变加载页;数据在后台逐步填充
pageReady.value = true
const token = uni.getStorageSync('token') || ''
dataLoaded.value = true
const needReload = uni.getStorageSync('needReloadUserInfo')
// 重新加载用户信息时,拿到权威的 currentVillageId删除房屋后会变成 0
let reloadedVillageId = null
if (token && needReload) {
uni.removeStorageSync('needReloadUserInfo')
try {
const res = await getReloadUserInfo()
if (res.code === 200 && res.data) {
if (res.data.userId) uni.setStorageSync('userId', res.data.userId)
reloadedVillageId = res.data.currentVillageId || 0
uni.setStorageSync('currentVillageId', reloadedVillageId)
}
} catch (err) {
console.error('重新加载用户信息失败', err)
}
}
// 先获取小区信息,确保 villageId 已更新,再请求依赖 villageId 的接口
await getCurrentCommunityData(reloadedVillageId)
getNoticeData()
getNewActivityList()
getbannarData()
uni.stopPullDownRefresh()
}
onPullDownRefresh(() => {
refreshAllData()
})
const resetPageData = () => {
currentCommunity.value = '请绑定房屋'
currentVillageId.value = ''
uni.removeStorageSync('currentVillageId')
activityList.value = []
bannerList.value = []
bannerRawData.value = []
noticeText.value = '暂无公告'
noticeId.value = ''
uni.removeStorageSync(getHomeCacheKey())
}
onShow(() => {
const currentUserId = uni.getStorageSync('userId') || ''
const userIdChanged = lastUserId.value !== currentUserId
const needReload = uni.getStorageSync('needReloadUserInfo')
if (userIdChanged) {
resetPageData()
refreshAllData()
} else if (!dataLoaded.value || needReload) {
// 首次进入且之前因无 token 跳过了 API现在有 token 则补拉
// 或房屋/小区信息有变更(如删除房屋后),需要重新拉取
refreshAllData()
}
lastUserId.value = currentUserId
})
onLoad(() => {
// 新用户(未看过引导页且未登录)跳转引导页
var hasShowGuide = uni.getStorageSync('hasShowGuide')
var token = uni.getStorageSync('token')
if (!hasShowGuide && !token) {
uni.reLaunch({ url: '/pages/navigation/navigation' })
return
}
// 关闭原生 splash
// #ifdef APP-PLUS
try { plus.navigator.closeSplashscreen() } catch (e) {}
// #endif
// 优先展示缓存数据,实现冷启动秒开
const hasCache = loadHomeCache()
if (hasCache) {
pageReady.value = true
}
refreshAllData()
uni.$on('confirm', slelctHomeData)
lastUserId.value = uni.getStorageSync('userId') || ''
})
onUnload(() => {
uni.$off('confirm', slelctHomeData)
})
const navigateToPage = () => {
const cachedVillageId = uni.getStorageSync('currentVillageId')
if (!cachedVillageId || cachedVillageId === '0' || cachedVillageId === 0) {
uni.navigateTo({ url: '/pageSubPack/my/myHouseIndex' })
} else {
uni.navigateTo({ url: `/pageSubPack/notice-list/selectHome?villageId=${cachedVillageId}` })
}
}
const serviceClick = (item) => {
if (!requireLogin()) return
const cachedVillageId = uni.getStorageSync('currentVillageId')
if (!cachedVillageId || cachedVillageId === '0' || cachedVillageId === 0) {
uni.showToast({ title: '未绑定房屋,没有权限', icon: 'none', duration: 2000 })
return
}
uni.setStorageSync('sourcePage', 'index')
uni.navigateTo({ url: item.url })
}
const getNewActivityList = async () => {
try {
const res = await getActivitylatest({ villageId: uni.getStorageSync('currentVillageId') })
if (res.code === 200) {
activityList.value = res.data || [];
saveHomeCache()
} else {
activityList.value = []
}
} catch (err) {
activityList.value = []
}
}
const goToActivityDetail = (activity) => {
if (!requireLogin()) return
uni.navigateTo({
url: `/pageSubPack/activity-list/activityListDetails?id=${activity.id}&title=${activity.title}&applied=${activity.applied || ''}&statusText=${activity.statusText || ''}`
})
}
const moreClick = () => {
if (!requireLogin()) return
const cachedVillageId = uni.getStorageSync('currentVillageId')
if (!cachedVillageId || cachedVillageId === '0' || cachedVillageId === 0) {
uni.showToast({ title: '未绑定房屋,没有权限', icon: 'none', duration: 2000 })
return
}
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({ url: "/pageSubPack/activity-list/activityList" })
}
</script>
<style lang="scss" scoped>
.content-class {
background-color: #f9f9f9;
min-height: 100vh;
/* 加载遮罩 */
.loading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
}
.loading-bg {
width: 100%;
height: 100%;
background: repeating-linear-gradient(to right, #EDF4FA, #C0E4FB);
}
/* 顶部渐变背景(固定在内容下方) */
.nar-top {
height: 400rpx;
width: 100%;
background: repeating-linear-gradient(to right, #EDF4FA, #C0E4FB);
position: fixed;
top: 0;
left: 0;
z-index: 0;
}
/* 固定头部 */
.header-fixed {
position: sticky;
top: 0;
z-index: 10;
background: repeating-linear-gradient(to right, #EDF4FA, #C0E4FB);
.status-bar {
height: var(--status-bar-height);
width: 100%;
flex-shrink: 0;
}
.header-content {
position: relative;
z-index: 1;
}
}
/* 滚动区域 */
.main-scroll {
position: relative;
z-index: 1;
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
box-sizing: border-box;
position: relative;
z-index: 10;
}
// 左侧区域
.location-area {
display: flex;
align-items: center;
position: relative;
flex: 1;
min-width: 0;
.location-icon {
width: 45rpx;
height: 45rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.community-selector {
display: flex;
align-items: center;
max-width: 300rpx;
cursor: pointer;
.community-name {
font-size: 26rpx;
font-weight: 500;
color: #000;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
}
}
.bannarClass {
position: relative;
padding: 20rpx 40rpx;
height: 300rpx;
::v-deep .u-swiper {
height: 300rpx !important;
}
::v-deep .u-swiper__wrapper {
height: 300rpx !important;
}
::v-deep .u-swiper__wrapper__item__wrapper__image {
height: 300rpx !important;
}
::v-deep .banner-img {
height: 300rpx !important;
width: 100%;
border-radius: 10rpx;
}
}
.noticeClass {
padding: 0rpx 36rpx;
margin-bottom: 20rpx;
}
::v-deep .uni-noticebar__content-wrapper--scrollable {
margin-right: 20px;
}
::v-deep .uni-noticebar {
padding: 4px 12px;
}
.bg-card-open {
padding: 20rpx;
background-color: #fff;
border-radius: 8rpx;
margin: 0 18px 20rpx;
}
.openClass {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20rpx;
.iconClass {
width: 88rpx;
height: 88rpx;
border-radius: 10rpx;
}
.iconClass-img {
width: 100%;
height: 100%;
border-radius: 8px
}
.title-dome-calss {
margin-top: 10rpx;
font-size: 28rpx;
color: #000;
}
}
.adCardClass {
height: 200rpx;
margin: 0 30rpx 20rpx;
background-color: #3E8EFF;
border-radius: 28rpx;
.titleLeft {
text-align: left;
line-height: 2;
}
.iconRightClass {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
margin: 36rpx;
}
}
.uni-card {
margin: 0 18px 20rpx !important;
}
.eventListClass {
margin: 10rpx 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.activity-name {
font-size: 32rpx;
font-weight: 600;
color: #222;
}
.eventListCard {
position: relative;
margin: 30rpx 0rpx;
.activity-img {
width: 170rpx;
height: 230rpx;
position: relative;
}
.status-tag {
position: absolute;
top: 0;
left: 0;
padding: 0px 7px 10px 11px;
border-radius: 10px 0px 10px 0px;
font-size: 12px;
color: #fff;
font-weight: 500;
width: 80rpx;
height: 20rpx;
background: repeating-linear-gradient(to right, #FD7373, #FB3F3F);
z-index: 5;
}
.activity-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 12px;
line-height: 1.4;
white-space: normal;
word-break: break-all;
overflow-wrap: break-word;
}
.activity-time {
display: flex;
align-items: center;
font-size: 22rpx;
color: #999999;
margin-bottom: 8px;
}
.activity-organizer {
font-size: 28rpx;
display: flex;
align-items: center;
color: #999999;
margin-bottom: 8px;
}
.activity-enrollment {
display: flex;
align-items: center;
font-size: 28rpx;
margin-top: 20rpx;
color: #666666;
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
margin-top: 20rpx;
margin-bottom: 10rpx;
}
.look-more-title {
color: #3E8EFF;
font-size: 28rpx;
}
}
</style>