Files
zhouyanli 0661687bc5 修复bug
2026-08-28 17:25:55 +08:00

540 lines
13 KiB
Vue
Raw Permalink 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="container">
<!-- 搜索区域 -->
<view class="search-container">
<view class="search-input-wrapper">
<uni-icons class="search-icon" type="search" size="20" color="#999"></uni-icons>
<input
class="search-input"
placeholder="请输入关键字进行搜索"
placeholder-class="placeholder"
:value="searchText"
@input="handleSearchInput"
/>
<view v-if="searchText" class="clear-icon" @tap="clearSearch">
<uni-icons type="clear" size="18" color="#999"></uni-icons>
</view>
</view>
<!-- <button class="search-btn" @tap="handleSearch">搜索</button> -->
</view>
<!-- 小区列表 -->
<view class="community-list">
<view
v-for="item in displayedCommunities"
:key="item.villageId"
class="community-item"
:class="{ 'active-item': selectedItem && selectedItem.villageId === item.villageId }"
:id="'item-' + item.villageId"
@tap="selectCommunity(item)"
>
<view class="community-item-left">
<image src="https://wuye.ckdzkj.com/images/home/community.png" style="width: 60rpx;height: 60rpx;"></image>
<view class="community-info">
<text class="community-name">{{ item.villageName }}</text>
</view>
</view>
<view v-if="selectedItem && selectedItem.villageId === item.villageId" class="selected-icon">
<uni-icons type="checkbox-filled" size="24" color="#007AFF"></uni-icons>
</view>
</view>
<!-- 加载更多 -->
<view v-if="hasMoreData" class="load-more-container">
<view v-if="!isLoading" class="load-more-btn" @tap="loadMoreData">
<text>加载更多</text>
<uni-icons type="arrow-down" size="16" color="#007AFF"></uni-icons>
</view>
<view v-else class="loading-container">
<uni-icons type="spinner-cycle" size="20" color="#007AFF" class="loading-icon"></uni-icons>
<text>正在加载...</text>
</view>
</view>
<!-- 没有更多数据 -->
<view v-if="!hasMoreData && displayedCommunities.length > 0" class="no-more-data">
<text>没有更多数据了</text>
</view>
<!-- 空状态 -->
<view v-if="displayedCommunities.length === 0 && !isSearching && !isLoading" class="empty-state">
<uni-icons type="info" size="30" color="#ccc"></uni-icons>
<text class="empty-text">暂无小区数据</text>
</view>
<!-- 搜索无结果 -->
<view v-if="displayedCommunities.length === 0 && isSearching && !isLoading" class="empty-state">
<uni-icons type="search" size="30" color="#ccc"></uni-icons>
<text class="empty-text">未找到相关小区</text>
<text class="empty-tip">请尝试其他关键词</text>
</view>
</view>
<!-- 确认按钮 -->
<view class="confirm-area">
<button
class="confirm-btn"
:disabled="!selectedItem"
:class="{ 'disabled-btn': !selectedItem }"
@tap="handleConfirm"
>
确认选择
</button>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { onLoad, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
import { getVillageList, getVillageSwitch } from "../api/api.js"
// 响应式数据
const searchText = ref('')
const selectedItem = ref(null)
const scrollIntoViewId = ref('')
const currentVillageIdFromHome = ref('') // 首页传入的当前选中小区ID
const sourcePage = ref('') // 来源页面payment 为缴费页独立切换,其他走首页切换
// 分页相关
const currentPage = ref(1)
const pageSize = 10
const totalCount = ref(0)
const displayedCommunities = ref([])
const hasMoreData = ref(true)
const isLoading = ref(false)
// 搜索状态
const isSearching = ref(false)
let searchTimeout = null
// =====方法======
// 查询已认证小区列表
const getCommunityList = async (pageNum = 1, keyword = '') => {
isLoading.value = true
try {
// keyword 来自用户输入时是普通文本,无需 decodeURIComponent
// 仅当 keyword 来自 URL 参数时才需要解码(已在外部处理)
const params = {
pageNum,
pageSize,
keyword: keyword.trim()
}
const res = await getVillageList(params)
if (res.code === 200) {
// 兼容两种返回格式:数组 或 { rows, total }
const resData = res.data || {}
const listData = Array.isArray(resData) ? resData : (resData.rows || [])
const total = Array.isArray(resData) ? resData.length : (resData.total || listData.length)
if (pageNum === 1) {
displayedCommunities.value = listData
} else {
displayedCommunities.value = [...displayedCommunities.value, ...listData]
}
totalCount.value = total
hasMoreData.value = displayedCommunities.value.length < totalCount.value
// 默认选中:优先匹配传入的 villageId否则选第一条仅第一页时
// 搜索场景下不覆盖用户已手动选择的项
if (pageNum === 1 && displayedCommunities.value.length > 0) {
const userSelectedInCurrentList = selectedItem.value &&
displayedCommunities.value.some(
item => String(item.villageId) === String(selectedItem.value.villageId)
)
// 如果用户已选中且该项仍在当前列表中,保留用户选择
if (!isSearching.value || !userSelectedInCurrentList) {
const targetId = currentVillageIdFromHome.value || uni.getStorageSync('currentVillageId') || ''
if (targetId) {
const matched = displayedCommunities.value.find(
item => String(item.villageId) === String(targetId)
)
selectedItem.value = matched || displayedCommunities.value[0]
} else {
selectedItem.value = displayedCommunities.value[0]
}
}
} else if (pageNum === 1) {
// 第一页返回空列表(搜索无结果等场景),清除选中项使确认按钮禁用
selectedItem.value = null
}
} else {
displayedCommunities.value = []
hasMoreData.value = false
selectedItem.value = null
uni.showToast({
title: res.msg || "查询失败",
icon: 'error'
})
}
} catch (err) {
// console.error('获取小区列表失败:', err)
displayedCommunities.value = []
hasMoreData.value = false
selectedItem.value = null
uni.showToast({
title: err.msg || "网络异常",
icon: 'error'
})
} finally {
isLoading.value = false
uni.stopPullDownRefresh()
}
}
// 页面加载时初始化
onLoad((options) => {
// 接收来源页面及选中小区id
sourcePage.value = options?.source || ''
currentVillageIdFromHome.value = options?.villageId || ''
getCommunityList(1)
})
// 加载更多数据
const loadMoreData = () => {
if (isLoading.value || !hasMoreData.value) return
getCommunityList(currentPage.value + 1, searchText.value)
currentPage.value += 1
}
// 下拉刷新
onPullDownRefresh(() => {
currentPage.value = 1
getCommunityList(1, searchText.value)
})
// 上拉加载更多
onReachBottom(() => {
loadMoreData()
})
// 处理搜索输入(防抖)
const handleSearchInput = (e) => {
let val = e.detail.value || ''
searchText.value = val
if (searchTimeout) {
clearTimeout(searchTimeout)
}
searchTimeout = setTimeout(() => {
currentPage.value = 1
isSearching.value = !!val.trim()
getCommunityList(1, val.trim())
}, 300)
}
// 清空搜索
const clearSearch = () => {
searchText.value = ''
isSearching.value = false
currentPage.value = 1
getCommunityList(1)
}
// 选择小区
const selectCommunity = (item) => {
selectedItem.value = item
}
// 确认选择
const handleConfirm = async () => {
if (!selectedItem.value) {
uni.showToast({
title: '请先选择小区',
icon: 'none',
duration: 2000
})
return
}
// 缴费页为独立切换:不调用后端切换小区接口
const isPayment = sourcePage.value === 'payment'
try {
uni.showLoading({
title: isPayment ? '加载中...' : '切换小区中...',
mask: true
})
if (!isPayment) {
const res = await getVillageSwitch({
villageId: selectedItem.value.villageId,
})
uni.hideLoading()
if (res.code !== 200) {
uni.showToast({
title: res.msg || '切换小区失败',
icon: 'none',
duration: 2000
})
return
}
if (res.data?.villageId) {
selectedItem.value.villageId = res.data.villageId
}
} else {
uni.hideLoading()
}
uni.showToast({
title: '选择成功',
icon: 'success',
duration: 1500
})
if (isPayment) {
// 缴费页独立缓存 + 独立事件,通知缴费页刷新(按账号隔离)
const userId = uni.getStorageSync('userId') || ''
uni.setStorageSync('paymentVillageId_' + userId, selectedItem.value.villageId)
uni.setStorageSync('paymentVillageName_' + userId, selectedItem.value.villageName)
uni.$emit('confirmPayment', {
name: selectedItem.value.villageName,
villageId: selectedItem.value.villageId
})
} else {
// 更新缓存并通知首页
uni.setStorageSync('currentVillageId', selectedItem.value.villageId)
uni.$emit('confirm', {
name: selectedItem.value.villageName,
villageId: selectedItem.value.villageId
})
}
setTimeout(() => {
uni.navigateBack()
}, 1000)
} catch (err) {
uni.hideLoading()
uni.showToast({
title: err.msg || '网络异常,请重试',
icon: 'error',
duration: 2000
})
}
}
// 初始化
onMounted(() => {
if (displayedCommunities.value.length === 0 && !isLoading.value) {
getCommunityList(1)
}
})
</script>
<style scoped>
.container {
min-height: 100vh;
background-color: #f5f5f5;
display: flex;
flex-direction: column;
padding: 40rpx;
}
.search-container {
padding: 20rpx 30rpx;
background-color: #ffffff;
display: flex;
align-items: center;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
z-index: 10;
position: sticky;
top: 0;
}
.search-input-wrapper {
flex: 1;
background-color: #f8f8f8;
border-radius: 10rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
height: 80rpx;
margin-right: 20rpx;
}
.search-icon {
margin-right: 15rpx;
}
.search-input {
flex: 1;
height: 100%;
font-size: 28rpx;
color: #333;
}
.placeholder {
color: #999;
font-size: 28rpx;
}
.clear-icon {
padding: 10rpx;
display: flex;
align-items: center;
justify-content: center;
}
.search-btn {
background-color: #007AFF;
color: #fff;
font-size: 28rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 10rpx;
padding: 0 30rpx;
margin: 0;
white-space: nowrap;
}
.community-list {
margin-top: 20rpx;
margin-bottom: 180rpx;
}
.community-item {
background-color: #fff;
border-radius: 12rpx;
padding: 30rpx;
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.2s;
}
.community-item:active,
.community-item.active-item {
/* background-color: #e6f2ff; */
}
.community-item-left {
display: flex;
align-items: center;
flex: 1;
}
.community-info {
display: flex;
flex-direction: column;
flex: 1;
margin-left: 20rpx;
}
.community-name {
font-size: 32rpx;
color: #333;
font-weight: 500;
}
.selected-icon {
margin-left: 20rpx;
}
.load-more-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx 0;
color: #999;
}
.load-more-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 40rpx;
background-color: #f8f8f8;
border-radius: 50rpx;
color: #007AFF;
font-size: 28rpx;
margin-bottom: 20rpx;
}
.load-more-btn text {
margin-right: 10rpx;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 40rpx;
background-color: #f8f8f8;
border-radius: 50rpx;
color: #007AFF;
font-size: 28rpx;
margin-bottom: 20rpx;
}
.loading-icon {
margin-right: 10rpx;
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.no-more-data {
text-align: center;
padding: 40rpx 0;
color: #999;
font-size: 28rpx;
}
.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;
}
.empty-tip {
font-size: 24rpx;
color: #ccc;
}
.confirm-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx 30rpx 40rpx;
background-color: #fff;
box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.05);
}
.confirm-btn {
background-color: #007AFF;
color: #fff;
height: 100rpx;
line-height: 100rpx;
border-radius: 12rpx;
font-size: 34rpx;
width: 100%;
margin: 0;
}
.disabled-btn {
background-color: #ccc;
color: #fff;
}
@media (min-width: 768px) {
.container {
max-width: 500px;
margin: 0 auto;
}
}
</style>