rentease-app/pages/rooms/rooms.vue

367 lines
10 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="rooms-page">
<view class="custom-nav safe-area-top">
<view class="nav-content">
<view class="nav-btn" @click="goBack">
<uni-icons type="left" size="22" color="#1E293B"></uni-icons>
</view>
<text class="nav-title">房间管理</text>
<view class="nav-btn" @click="addRoom">
<uni-icons type="plus" size="22" color="#2563EB"></uni-icons>
</view>
</view>
</view>
<view class="apartment-filter" v-if="apartments.length > 0">
<picker mode="selector" :range="apartmentOptions" :value="selectedApartmentIndex" @change="onApartmentChange">
<view class="picker-value">
<text>{{selectedApartmentLabel}}</text>
<uni-icons type="arrowdown" size="14" color="#64748B"></uni-icons>
</view>
</picker>
</view>
<scroll-view scroll-y class="room-list" @scrolltolower="loadMore" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
<view v-if="rooms.length === 0 && !isLoading" class="empty-state">
<view class="empty-icon">
<uni-icons type="home-filled" size="80" color="#CBD5E1"></uni-icons>
</view>
<text class="empty-title">暂无房间</text>
<text class="empty-desc">点击右上角添加房间</text>
<button class="empty-btn" @click="addRoom">添加房间</button>
</view>
<view v-else class="room-grid">
<view v-for="(item, index) in rooms" :key="index" class="room-card" :class="getRoomStatusClass(item)" @click="viewRoomDetail(item)">
<view class="room-header">
<text class="room-number">{{item.roomNumber}}</text>
<view class="room-status">{{item.statusText}}</view>
</view>
<view class="room-info">
<text class="info-item">面积: {{item.area || '--'}} m²</text>
<text class="info-item">朝向: {{item.orientation || '--'}}</text>
</view>
<view class="room-price" v-if="item.rent">¥{{item.rent}}/月</view>
<view class="room-tenant" v-if="item.renterName">
<uni-icons type="person" size="14" color="#64748B"></uni-icons>
<text>{{item.renterName}}</text>
</view>
</view>
</view>
<uni-load-more v-if="rooms.length > 0" :status="loadStatus"></uni-load-more>
<view class="safe-area-bottom" style="height: 40rpx;"></view>
</scroll-view>
</view>
</template>
<script>
import roomApi from '@/api/room.js'
import apartmentApi from '@/api/apartment.js'
export default {
data() {
return {
isLoading: false,
isRefreshing: false,
rooms: [],
apartments: [],
selectedApartmentId: '',
loadStatus: 'more',
page: 1,
pageSize: 20
}
},
computed: {
apartmentOptions() {
return this.apartments.map(a => a.name)
},
selectedApartmentIndex() {
const index = this.apartments.findIndex(a => a.id === this.selectedApartmentId)
return index >= 0 ? index : 0
},
selectedApartmentLabel() {
const apartment = this.apartments.find(a => a.id === this.selectedApartmentId)
return apartment ? apartment.name : '选择公寓'
}
},
onLoad(options) {
if (options.apartmentId) {
this.selectedApartmentId = parseInt(options.apartmentId)
}
this.loadApartments()
},
onShow() {
this.loadRooms()
},
methods: {
async loadApartments() {
try {
const res = await apartmentApi.list()
if (res.data) {
this.apartments = res.data
if (!this.selectedApartmentId && this.apartments.length > 0) {
this.selectedApartmentId = this.apartments[0].id
}
// 注意loadRooms 由 onShow 触发,避免重复请求
}
} catch (error) {
console.error('加载公寓列表失败:', error)
}
},
async loadRooms() {
if (!this.selectedApartmentId) return
this.isLoading = true
try {
const params = {
page: this.page,
pageSize: this.pageSize,
apartmentId: this.selectedApartmentId
}
const res = await roomApi.getList(params)
if (res.data && res.data.list) {
const list = res.data.list.map(item => ({
id: item.id,
roomNumber: item.roomNumber,
area: item.area,
orientation: item.orientation,
rent: item.rent,
status: this.getRoomStatus(item),
rentalStatus: item.rentalStatus,
statusText: this.getRoomStatusText(item),
renterName: item.Renter && item.Renter.name
}))
if (this.page === 1) {
this.rooms = list
} else {
this.rooms = [...this.rooms, ...list]
}
this.loadStatus = this.rooms.length < (res.data.total || 0) ? 'more' : 'noMore'
}
} catch (error) {
console.error('加载房间列表失败:', error)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.isLoading = false
this.isRefreshing = false
}
},
onRefresh() {
this.isRefreshing = true
this.page = 1
this.loadRooms()
},
onApartmentChange(e) {
this.selectedApartmentId = this.apartments[e.detail.value].id
this.page = 1
this.loadRooms()
},
addRoom() {
if (!this.selectedApartmentId) {
uni.showToast({ title: '请先选择公寓', icon: 'none' })
return
}
uni.navigateTo({ url: `/pages/room-add/room-add?apartmentId=${this.selectedApartmentId}` })
},
viewRoomDetail(item) {
uni.navigateTo({ url: `/pages/room-detail/room-detail?id=${item.id}` })
},
loadMore() {
if (this.loadStatus === 'noMore') return
this.loadStatus = 'loading'
this.page++
this.loadRooms()
},
goBack() {
uni.navigateBack()
},
getRoomStatus(room) {
// 与Web端Status.vue保持一致直接使用room.status
return room.status || 'empty'
},
getRoomStatusText(room) {
// 与Web端Status.vue#getRoomStatusText保持一致
if (room.status === 'empty') return '空置'
if (room.status === 'rented') {
if (room.rentalStatus === 'expired') return '已到期'
if (room.rentalStatus === 'soon_expire') return '即将到期'
return '已租'
}
if (room.status === 'reserved') return '预定'
return room.status
},
getRoomStatusClass(room) {
// 根据status和rentalStatus确定样式类与Web端保持一致
if (room.status === 'empty') return 'status-empty'
if (room.status === 'rented') {
if (room.rentalStatus === 'expired') return 'status-expired'
if (room.rentalStatus === 'soon_expire') return 'status-soon-expire'
return 'status-rented'
}
if (room.status === 'reserved') return 'status-reserved'
return ''
}
}
}
</script>
<style scoped>
.rooms-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-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.nav-title {
font-size: 36rpx;
font-weight: 700;
color: #1E293B;
}
.apartment-filter {
background: #FFFFFF;
padding: 20rpx 32rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.picker-value {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 28rpx;
color: #1E293B;
font-weight: 500;
}
.room-list {
flex: 1;
padding: 24rpx 32rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 60rpx;
}
.empty-icon {
margin-bottom: 32rpx;
}
.empty-title {
font-size: 32rpx;
font-weight: 600;
color: #1E293B;
margin-bottom: 12rpx;
}
.empty-desc {
font-size: 26rpx;
color: #94A3B8;
margin-bottom: 40rpx;
}
.empty-btn {
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
color: #FFFFFF;
font-size: 28rpx;
font-weight: 500;
padding: 16rpx 40rpx;
border-radius: 12rpx;
border: none;
line-height: 1.5;
}
.room-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.room-card {
background: #FFFFFF;
border: 2rpx solid #EBEEF5;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.room-card:active {
transform: scale(0.98);
}
/* 状态样式 */
.room-card.status-empty {
border-color: #67C23A;
background: #F0F9FF;
}
.room-card.status-rented {
border-color: #409EFF;
background: #ECF5FF;
}
.room-card.status-soon-expire {
border-color: #E6A23C;
background: #FDF6EC;
}
.room-card.status-expired {
border-color: #F56C6C;
background: #FEF0F0;
}
.room-card.status-reserved {
border-color: #909399;
background: #F4F4F5;
}
.room-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.room-number {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
}
.room-status {
padding: 4rpx 12rpx;
border-radius: 20rpx;
font-size: 20rpx;
background: rgba(255, 255, 255, 0.9);
color: #606266;
font-weight: 500;
}
.room-info {
margin-bottom: 12rpx;
}
.info-item {
display: block;
font-size: 24rpx;
color: #64748B;
margin-bottom: 4rpx;
}
.room-price {
font-size: 28rpx;
color: #EF4444;
font-weight: 600;
margin-bottom: 8rpx;
}
.room-tenant {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 24rpx;
color: #64748B;
}
</style>