rentease-app/pages/apartments/apartments.vue

591 lines
14 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="apartments-page">
<!-- 自定义导航栏 -->
<view class="custom-nav safe-area-top">
<view class="nav-content">
<view class="nav-back" @click="goBack">
<uni-icons type="left" size="20" color="#1E293B"></uni-icons>
</view>
<text class="nav-title">公寓管理</text>
<view class="nav-actions">
<view class="nav-btn" @click="addApartment">
<uni-icons type="plus" size="20" color="#2563EB"></uni-icons>
</view>
</view>
</view>
</view>
<scroll-view scroll-y class="page-content" @scrolltolower="loadMore" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-box">
<uni-icons type="search" size="18" color="#94A3B8"></uni-icons>
<input
type="text"
v-model="searchKeyword"
placeholder="搜索公寓名称或地址"
@confirm="search"
/>
</view>
</view>
<!-- 统计概览 -->
<view class="stats-section">
<view class="stats-card">
<view class="stat-item">
<text class="stat-num">{{stats.total}}</text>
<text class="stat-label">公寓总数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-num">{{stats.rooms}}</text>
<text class="stat-label">房源总数</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-num">{{stats.rented}}</text>
<text class="stat-label">在租房间</text>
</view>
</view>
</view>
<!-- 公寓列表 -->
<view class="apartment-list">
<view
v-for="(item, index) in apartmentList"
:key="index"
class="apartment-card"
@click="viewDetail(item)"
>
<view class="card-header">
<view class="apartment-info">
<text class="apartment-name">{{item.name}}</text>
<view class="apartment-address">
<uni-icons type="location" size="14" color="#94A3B8"></uni-icons>
<text>{{item.address || '暂无地址'}}</text>
</view>
</view>
<view class="apartment-status" :class="item.status">
<text>{{getStatusText(item.status)}}</text>
</view>
</view>
<view class="room-stats">
<view class="room-stat">
<text class="stat-label">总房间</text>
<text class="stat-value">{{item.roomCount || 0}}</text>
</view>
<view class="room-stat">
<text class="stat-label">空房</text>
<text class="stat-value empty">{{item.emptyCount || 0}}</text>
</view>
<view class="room-stat">
<text class="stat-label">在租</text>
<text class="stat-value rented">{{item.rentedCount || 0}}</text>
</view>
<view class="room-stat">
<text class="stat-label">预订</text>
<text class="stat-value">{{item.bookedCount || 0}}</text>
</view>
</view>
<view class="card-footer">
<text class="create-time">创建时间: {{formatDate(item.createTime)}}</text>
<view class="action-btns">
<view class="action-btn" @click.stop="editApartment(item)">
<uni-icons type="compose" size="16" color="#64748B"></uni-icons>
<text>编辑</text>
</view>
<view class="action-btn" @click.stop="manageRooms(item)">
<uni-icons type="home-filled" size="16" color="#64748B"></uni-icons>
<text>房源</text>
</view>
<view class="action-btn delete" @click.stop="deleteApartment(item)">
<uni-icons type="trash" size="16" color="#EF4444"></uni-icons>
<text class="delete-text">删除</text>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="apartmentList.length === 0 && !isLoading" class="empty-state">
<image src="/static/empty.png" mode="aspectFit"></image>
<text class="empty-text">暂无公寓数据</text>
<button class="add-btn" @click="addApartment">添加公寓</button>
</view>
<!-- 加载更多 -->
<view v-if="hasMore && apartmentList.length > 0" class="load-more" @click="loadMore">
<text>加载更多</text>
</view>
<view class="safe-area-bottom" style="height: 40rpx;"></view>
</scroll-view>
</view>
</template>
<script>
import { apartmentApi, statisticsApi } from '../../api/index.js'
export default {
data() {
return {
searchKeyword: '',
apartmentList: [],
isLoading: false,
isRefreshing: false,
page: 1,
pageSize: 10,
hasMore: true,
stats: {
total: 0,
rooms: 0,
rented: 0
}
}
},
onLoad() {
this.loadData()
},
onShow() {
this.loadData()
},
methods: {
async loadData() {
if (this.isLoading) return
this.isLoading = true
try {
// 加载公寓列表 - 与web端保持一致使用getAll即getList
const res = await apartmentApi.getList({
name: this.searchKeyword,
page: this.page,
pageSize: this.pageSize
})
// 适配分页数据格式: { data: { list: [], total: n, page: n, pageSize: n } }
const apartments = res.data.list || []
const total = res.data.total || 0
// 加载公寓房间状态统计
try {
const statsRes = await statisticsApi.getApartmentRoomStatusStats()
const apartmentStats = statsRes.data || []
// 将统计数据合并到公寓列表中
for (let apt of apartments) {
const stat = apartmentStats.find(s => s.apartmentId === apt.id)
if (stat) {
apt.roomCount = stat.total || 0
apt.emptyCount = stat.empty || 0
apt.rentedCount = stat.rented || 0
apt.bookedCount = stat.reserved || 0
} else {
apt.roomCount = 0
apt.emptyCount = 0
apt.rentedCount = 0
apt.bookedCount = 0
}
}
} catch (e) {
console.error('加载房间统计失败:', e)
// 统计接口失败时,设置默认值
for (let apt of apartments) {
apt.roomCount = 0
apt.emptyCount = 0
apt.rentedCount = 0
apt.bookedCount = 0
}
}
if (this.page === 1) {
this.apartmentList = apartments
} else {
this.apartmentList = [...this.apartmentList, ...apartments]
}
// 根据total判断是否还有更多数据
this.hasMore = this.apartmentList.length < total
// 更新统计
this.updateStats()
} catch (error) {
console.error('加载公寓列表失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.isLoading = false
this.isRefreshing = false
}
},
updateStats() {
this.stats.total = this.apartmentList.length
this.stats.rooms = this.apartmentList.reduce((sum, apt) => sum + (apt.roomCount || 0), 0)
this.stats.rented = this.apartmentList.reduce((sum, apt) => sum + (apt.rentedCount || 0), 0)
},
onRefresh() {
this.isRefreshing = true
this.page = 1
this.loadData()
},
loadMore() {
if (this.hasMore && !this.isLoading) {
this.page++
this.loadData()
}
},
search() {
this.page = 1
this.loadData()
},
addApartment() {
uni.navigateTo({
url: '/pages/apartment-add/apartment-add'
})
},
viewDetail(item) {
uni.navigateTo({
url: `/pages/apartment-detail/apartment-detail?id=${item.id}`
})
},
editApartment(item) {
uni.navigateTo({
url: `/pages/apartment-add/apartment-add?id=${item.id}&mode=edit`
})
},
manageRooms(item) {
uni.navigateTo({
url: `/pages/rooms/rooms?apartmentId=${item.id}&apartmentName=${encodeURIComponent(item.name)}`
})
},
deleteApartment(item) {
uni.showModal({
title: '确认删除',
content: `确定要删除公寓"${item.name}"吗?`,
confirmColor: '#EF4444',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({ title: '删除中...' })
await apartmentApi.delete(item.id)
uni.showToast({ title: '删除成功', icon: 'success' })
// 刷新列表
this.page = 1
this.loadData()
} catch (error) {
const msg = error.data && error.data.message
uni.showToast({ title: msg || '删除失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
}
})
},
goBack() {
uni.navigateBack()
},
getStatusText(status) {
const texts = {
'active': '运营中',
'inactive': '已停用',
'renovating': '装修中'
}
return texts[status] || '未知'
},
formatDate(date) {
if (!date) return '-'
return date.split(' ')[0]
}
}
}
</script>
<style scoped>
.apartments-page {
min-height: 100vh;
background: #F8FAFC;
display: flex;
flex-direction: column;
}
/* 导航栏 */
.custom-nav {
background: #FFFFFF;
border-bottom: 2rpx solid #F1F5F9;
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 32rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 700;
color: #1E293B;
}
.nav-actions {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 页面内容 */
.page-content {
flex: 1;
padding: 24rpx 32rpx;
}
/* 搜索栏 */
.search-section {
margin-bottom: 24rpx;
}
.search-box {
display: flex;
align-items: center;
background: #FFFFFF;
border-radius: 16rpx;
padding: 20rpx 24rpx;
gap: 16rpx;
}
.search-box input {
flex: 1;
font-size: 28rpx;
color: #1E293B;
}
/* 统计区域 */
.stats-section {
margin-bottom: 24rpx;
}
.stats-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
align-items: center;
justify-content: space-around;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-num {
font-size: 40rpx;
font-weight: 700;
color: #2563EB;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #64748B;
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background: #E2E8F0;
}
/* 公寓列表 */
.apartment-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.apartment-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 32rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24rpx;
}
.apartment-info {
flex: 1;
}
.apartment-name {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
display: block;
}
.apartment-address {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 26rpx;
color: #64748B;
}
.apartment-status {
padding: 8rpx 20rpx;
border-radius: 8rpx;
font-size: 24rpx;
}
.apartment-status.active {
background: #DCFCE7;
color: #16A34A;
}
.apartment-status.inactive {
background: #F1F5F9;
color: #64748B;
}
.apartment-status.renovating {
background: #FEF3C7;
color: #D97706;
}
.room-stats {
display: flex;
justify-content: space-around;
padding: 24rpx 0;
border-top: 2rpx solid #F1F5F9;
border-bottom: 2rpx solid #F1F5F9;
margin-bottom: 24rpx;
}
.room-stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.room-stat .stat-label {
font-size: 24rpx;
color: #94A3B8;
}
.room-stat .stat-value {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
}
.room-stat .stat-value.empty {
color: #94A3B8;
}
.room-stat .stat-value.rented {
color: #10B981;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.create-time {
font-size: 24rpx;
color: #94A3B8;
}
.action-btns {
display: flex;
gap: 24rpx;
}
.action-btn {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 26rpx;
color: #64748B;
}
.action-btn.delete {
color: #EF4444;
}
.action-btn.delete .delete-text {
color: #EF4444;
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 0;
}
.empty-state image {
width: 240rpx;
height: 240rpx;
margin-bottom: 32rpx;
}
.empty-text {
font-size: 28rpx;
color: #94A3B8;
margin-bottom: 32rpx;
}
.add-btn {
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
color: #FFFFFF;
font-size: 28rpx;
padding: 24rpx 64rpx;
border-radius: 16rpx;
border: none;
}
/* 加载更多 */
.load-more {
text-align: center;
padding: 32rpx;
color: #64748B;
font-size: 26rpx;
}
</style>