Files
property-resident-side/property-uniapp-project/pages/index/index.vue
zhouyanli f74fb33e11 修复bug
2026-08-01 11:56:28 +08:00

685 lines
17 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="header-fixed" v-if="pageReady">
<view class="nar-top"></view>
<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://wuyeadmin.bugtc.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>
<!-- 滚动内容区 -->
<pull-refresh-scroll
custom-class="main-scroll"
v-if="pageReady"
:refreshing="refreshing"
@refresh="onRefresh">
<!-- 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://wuyeadmin.bugtc.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://wuyeadmin.bugtc.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.createName}}</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 class="scroll-bottom-space"></view>
</pull-refresh-scroll>
</view>
</template>
<script setup>
import {
ref,
nextTick
} from 'vue'
import {
onLoad,
onUnload,
onShow
} 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 refreshing = ref(false)
const activityList = ref([])
const serviceItems = [
{ title: '一键开门', icon: 'https://wuyeadmin.bugtc.com/images/home/openDoor.png', url: '/pageSubPack/notice-list/oneClickOpen' },
{ title: '生活缴费', icon: 'https://wuyeadmin.bugtc.com/images/home/lifePay.png', url: '/pageSubPack/payment-list/paymentIndex' },
{ title: '在线报修', icon: 'https://wuyeadmin.bugtc.com/images/home/repair.png', url: '/pageSubPack/online-repair/onlineRepair' },
{ title: '访客邀请', icon: 'https://wuyeadmin.bugtc.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 pageReady = ref(false)
// =======方法=======
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/noticeList" })
}
const getbannarData = async () => {
try {
const res = await getBannarList({ villageId: uni.getStorageSync('currentVillageId') })
if (res.code === 200) {
bannerRawData.value = res.data || []
const newList = (res.data || []).map(item => item.url)
if (JSON.stringify(newList) !== JSON.stringify(bannerList.value)) {
bannerList.value = newList
await nextTick()
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
getCurrentCommunityData()
getNoticeData()
noticeKey.value++
getNewActivityList()
}
const getCurrentCommunityData = () => {
getCurrentVillage().then(res => {
if (res.code === 200 && res.data && res.data.villageId) {
currentCommunity.value = res.data.villageName || '未命名小区'
currentVillageId.value = res.data.villageId
uni.setStorageSync('currentVillageId', res.data.villageId)
} else {
currentCommunity.value = '请绑定房屋'
currentVillageId.value = ''
}
}).catch(err => {
// 有 token 才提示错误(无 token 用户正常浏览首页API 401 属于预期行为)
const token = uni.getStorageSync('token')
if (token) {
uni.showToast({ title: err.msg || '网络异常', icon: 'error' })
}
})
}
const getNoticeData = async () => {
try {
const res = await getNoticeLatest()
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 = ''
}
}
const refreshAllData = async () => {
const token = uni.getStorageSync('token') || ''
const needReload = uni.getStorageSync('needReloadUserInfo')
if (token && needReload) {
try {
const res = await getReloadUserInfo()
if (res.code === 200 && res.data) {
if (res.data.userId) uni.setStorageSync('userId', res.data.userId)
const reloadedVillageId = res.data.currentVillageId || 0
uni.setStorageSync('currentVillageId', reloadedVillageId)
}
} catch (err) {
console.error('重新加载用户信息失败', err)
} finally {
uni.removeStorageSync('needReloadUserInfo')
}
}
getCurrentCommunityData()
getNoticeData()
noticeKey.value++
getNewActivityList()
getbannarData()
pageReady.value = true
refreshing.value = false
}
const onRefresh = async () => {
refreshing.value = true
await nextTick()
await refreshAllData()
await nextTick()
refreshing.value = false
}
const resetPageData = () => {
currentCommunity.value = '请先绑定房屋'
currentVillageId.value = ''
activityList.value = []
bannerList.value = []
bannerRawData.value = []
noticeText.value = '暂无公告'
noticeId.value = ''
}
onShow(() => {
const currentUserId = uni.getStorageSync('userId') || ''
if (lastUserId.value && lastUserId.value !== currentUserId) {
resetPageData()
refreshAllData()
} else {
refreshAllData()
}
lastUserId.value = currentUserId
})
onLoad(() => {
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 || []
} 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;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
/* 加载遮罩 */
.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);
}
/* 固定头部 */
.header-fixed {
position: relative;
flex-shrink: 0;
.nar-top {
height: 400rpx;
width: 100%;
background: repeating-linear-gradient(to right, #EDF4FA, #C0E4FB);
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
.status-bar {
height: var(--status-bar-height);
width: 100%;
flex-shrink: 0;
}
.header-content {
position: relative;
z-index: 1;
}
}
/* 滚动区域 */
.main-scroll {
flex: 1;
min-height: 0;
overflow: hidden;
position: relative;
z-index: 1;
}
.scroll-bottom-space {
height: 40rpx;
}
.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;
}
.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>