Files
property-resident-side/property-uniapp-project/pages/index/index.vue
2026-07-21 09:41:54 +08:00

809 lines
19 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="header-fixed">
<view class="nar-top"></view>
<view class="header-content">
<!-- 状态栏占位 -->
<view class="status_bar" :style="{ height: statusBarHeight + 'px' }"></view>
<!-- 导航栏 -->
<view class="nav-bar">
<text class="nav-title">首页</text>
</view>
<!-- 小区选择 -->
<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>
<!-- 滚动内容区 -->
<scroll-view class="main-scroll" scroll-y>
<!-- banner -->
<view class="bannarClass">
<up-swiper :key="bannerSwiperKey" :list="bannerList" indicator indicatorMode="dot" radius="10">
<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">
<view class="demo-layout bg-purple openClass" @click="openDoorClick">
<view class="iconClass">
<image src="https://wuyeadmin.bugtc.com/images/home/openDoor.png" class="iconClass-img">
</image>
</view>
<view class="title-dome-calss">一键开门</view>
</view>
</up-col>
<up-col span="3">
<view class="demo-layout bg-purple-light openClass" @click="payMentClick">
<view class="iconClass">
<image src="https://wuyeadmin.bugtc.com/images/home/lifePay.png" class="iconClass-img">
</image>
</view>
<view class="title-dome-calss">生活缴费</view>
</view>
</up-col>
<up-col span="3">
<view class="demo-layout bg-purple openClass" @click="onlineRepairClick">
<view class="iconClass">
<image src="https://wuyeadmin.bugtc.com/images/home/repair.png" class="iconClass-img">
</image>
</view>
<view class="title-dome-calss">在线报修</view>
</view>
</up-col>
<up-col span="3">
<view class="demo-layout bg-purple-light openClass" @click="visitorClick">
<view class="iconClass">
<image src="https://wuyeadmin.bugtc.com/images/home/visitorInvite.png"
class="iconClass-img"></image>
</view>
<view class="title-dome-calss">访客邀请</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>
<!-- 活动列表内容去掉内层 scroll-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>
</scroll-view>
</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"
// 列表数据状态
const isDropdownOpen = ref(false)
const scrollTop = ref(0)
const activityList = ref([]) // 活动列表数据
const currentCommunity = ref('请绑定房屋')
const currentVillageId = ref('')
const statusBarHeight = ref(0)
const bannerList = ref([])
const bannerRawData = ref([]) // 保留原始数据含link
const bannerSwiperKey = ref(0)
const noticeText = ref('暂无公告')
const noticeKey = ref(0)
const noticeId = ref('')
const lastUserId = ref('') // 用于检测是否切换账号
// =======方法=======
// 格式化时间
const formatDate = (date) => {
if (!date) return ''
return moment(date).format('YYYY-MM-DD HH:mm')
}
// 公告跳转
const getNoticeMore = () => {
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({
url: "/pageSubPack/notice-list/noticeList"
})
}
// 获取bannar列表
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)
// 只有 banner 数据真正变化时才更新,避免 swiper 反复重建
if (JSON.stringify(newList) !== JSON.stringify(bannerList.value)) {
bannerList.value = newList
await nextTick()
bannerSwiperKey.value++
}
} else {
uni.showToast({
title: '获取失败',
icon: 'error'
})
}
} catch (err) {
const msg = err?.msg || (typeof err === 'string' ? err : '获取失败')
uni.showToast({
title: msg,
icon: 'error'
})
}
}
// banner点击跳转
const bannerClick = (index) => {
const item = bannerRawData.value[index]
if (item && item.link) {
uni.navigateTo({
url: item.link
})
}
}
// 接收选择小区返回的参数
const slelctHomeData = (data) => {
// console.log('收到B页面返回的数据', 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 => {
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 = ''
}
} else {
uni.showToast({
title: res.msg || '获取通知失败',
icon: 'none',
duration: 2000
})
}
} catch (err) {
uni.showToast({
title: err.msg || '网络异常',
icon: 'none',
duration: 1000
})
noticeText.value = '暂无公告'
noticeId.value = ''
}
}
// 刷新所有数据(提取公共方法)
const refreshAllData = async () => {
const token = uni.getStorageSync('token') || '';
const needReload = uni.getStorageSync('needReloadUserInfo');
// 只有从绑定房屋返回首页时才重新加载用户信息获取最新小区ID
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()
}
// 切换账号时重置页面数据
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(() => {
const info = uni.getSystemInfoSync()
statusBarHeight.value = info.statusBarHeight || 0
// 绑定全局事件监听
uni.$on('confirm', slelctHomeData)
// 记录当前用户ID用于 onShow 中检测是否切换账号
lastUserId.value = uni.getStorageSync('userId') || ''
// 数据刷新统一放到 onShow避免 onLoad + onShow 首次重复请求
})
// 页面销毁时移除监听(防止内存泄漏)
onUnload(() => {
uni.$off('confirm', slelctHomeData)
})
// 方法逻辑
const openDoorClick = () => {
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({
url: "/pageSubPack/notice-list/oneClickOpen"
})
}
// 生活缴费
const payMentClick = () => {
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({
url: "/pageSubPack/payment-list/paymentIndex"
})
}
// 点击小区选择
const navigateToPage = () => {
// const token = uni.getStorageSync('token') || '';
const cachedVillageId = uni.getStorageSync('currentVillageId');
// 未登录 或 未绑定小区 → 去我的页面认证
if (!cachedVillageId || cachedVillageId === '0' || cachedVillageId === 0) {
currentCommunity.value = '请先绑定房屋';
uni.setStorageSync('currentCommunity', '请先绑定房屋');
uni.showToast({
title: '请先绑定小区',
icon: 'none',
duration: 1500
});
setTimeout(() => {
uni.switchTab({
url: '/pages/myIndex/myIndex',
});
}, 1500);
} else {
// 已登录且已绑定小区 → 去选择小区
uni.navigateTo({
url: `/pageSubPack/notice-list/selectHome?villageId=${cachedVillageId}`
});
}
};
// 获取首页最新活动
const getNewActivityList = async () => {
try {
let params = {
villageId: uni.getStorageSync('currentVillageId')
}
const res = await getActivitylatest(params)
if (res.code === 200) {
if (res.data) {
activityList.value = res.data || []
} else {
activityList.value = []
}
} else {
activityList.value = []
uni.showToast({
title: res.msg,
icon: 'error',
duration: 1000
})
}
} catch (err) {
uni.showToast({
title: err.msg || '网络异常',
icon: 'error',
duration: 1000
})
}
}
const onlineRepairClick = () => {
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({
url: '/pageSubPack/online-repair/onlineRepair'
})
}
// 访客邀请
const visitorClick = () => {
uni.setStorageSync('sourcePage', "index")
uni.navigateTo({
url: "/pageSubPack/visitor/visitor-list"
})
}
// 跳转到活动详情页
const goToActivityDetail = (activity) => {
uni.navigateTo({
url: `/pageSubPack/activity-list/activityListDetails?id=${activity.id}&title=${activity.title}&applied=${activity.applied || ''}&statusText=${activity.statusText || ''}`
})
}
// 活动列表的查看更多
const moreClick = () => {
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;
/* 固定头部 */
.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;
}
.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;
}
.status_bar {
width: 100%;
flex-shrink: 0;
position: relative;
z-index: 1;
}
/* 导航栏 */
.nav-bar {
height: 44px;
line-height: 44px;
text-align: center;
position: relative;
z-index: 1;
}
.nav-title {
font-size: 32rpx;
font-weight: 500;
}
.header-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4rpx 30rpx;
box-sizing: border-box;
// border-bottom: 1rpx solid #f0f0f0;
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;
// margin-bottom: 20rpx;
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;
}
.notice-bar::after {
color: #3E8EFF;
}
.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;
// opacity: 0.4;
}
.iconClass-img {
width: 100%;
height: 100%;
border-radius: 8px
}
.title-dome-calss {
margin-top: 10rpx;
font-size: 28rpx;
color: #000;
}
}
.adCardClass {
// width: 100%;
height: 200rpx;
margin: 0 30rpx 20rpx;
background-color: #3E8EFF;
// margin-top: 30rpx;
border-radius: 28rpx;
.titleLeft {
text-align: left;
line-height: 2;
// margin-top: 32rpx;
// margin-left: 30rpx;
}
.iconRightClass {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
// margin-top: 20rpx;
margin: 36rpx;
}
}
.activity-card {}
.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 {
// min-height:200rpx ;
position: relative;
margin: 30rpx 0rpx;
// overflow: hidden;
.activity-img {
width: 170rpx;
height: 230rpx;
position: relative;
// background-color: #333333;
}
.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>