rentease-app/pages/properties/properties.vue

860 lines
21 KiB
Vue
Raw Normal View History

2026-04-20 06:23:11 +00:00
<template>
<view class="properties-page">
<!-- 自定义导航栏 -->
<view class="custom-nav safe-area-top">
<view class="nav-content">
2026-04-22 06:47:04 +00:00
<text class="nav-title">房态图</text>
2026-04-20 06:23:11 +00:00
<view class="nav-actions">
<view class="nav-btn primary" @click="addProperty">
<uni-icons type="plus" size="22" color="#FFFFFF"></uni-icons>
</view>
</view>
</view>
</view>
<!-- 公寓选择器 -->
2026-04-22 06:47:04 +00:00
<view class="apartment-section-header" v-if="apartments.length > 0">
<scroll-view scroll-x class="apartment-tabs" show-scrollbar="false">
2026-04-20 06:23:11 +00:00
<view
v-for="(apt, index) in apartments"
:key="index"
class="tab-item"
:class="{ active: selectedApartmentId === apt.id }"
@click="selectApartment(apt.id)"
>
<text class="tab-name">{{apt.name}}</text>
<text class="tab-count">{{apt.roomCount}}</text>
</view>
</scroll-view>
</view>
2026-04-22 06:47:04 +00:00
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input-wrapper">
<uni-icons type="search" size="18" color="#909399"></uni-icons>
<input
class="search-input"
type="text"
v-model="searchKeyword"
placeholder="搜索房间号"
confirm-type="search"
@confirm="onSearch"
/>
<uni-icons v-if="searchKeyword" type="clear" size="16" color="#909399" @click="clearSearch"></uni-icons>
</view>
</view>
<!-- 状态图例 -->
<view class="status-legend">
<view class="legend-scroll">
<view
v-for="(filter, index) in statusFilters"
:key="index"
class="legend-item"
:class="{ active: currentFilter === filter.value }"
@click="setFilter(filter.value)"
>
<view class="legend-dot" :style="{ background: filter.color }"></view>
<text class="legend-text">{{filter.label}}</text>
<text class="legend-count">{{getStatusCount(filter.value)}}</text>
</view>
2026-04-20 06:23:11 +00:00
</view>
</view>
2026-04-22 06:47:04 +00:00
<!-- 房间列表 -->
<scroll-view
scroll-y
class="room-list"
@scrolltolower="loadMore"
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="onRefresh"
>
<!-- 空状态 -->
2026-04-20 06:23:11 +00:00
<view v-if="filteredRooms.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>
2026-04-22 06:47:04 +00:00
<!-- 房间卡片网格 -->
<view v-else class="room-grid">
<view
v-for="(room, index) in filteredRooms"
2026-04-20 06:23:11 +00:00
:key="index"
class="room-card"
2026-04-22 06:47:04 +00:00
:class="getRoomStatusClass(room)"
2026-04-20 06:23:11 +00:00
@click="handleRoomClick(room)"
>
2026-04-22 06:47:04 +00:00
<text class="room-number">{{room.roomNumber}}</text>
2026-04-20 06:23:11 +00:00
<view class="room-info">
2026-04-22 06:47:04 +00:00
<text class="room-price" v-if="room.monthlyPrice">¥{{room.monthlyPrice}}/</text>
<text class="room-tenant" v-if="room.renterName">{{room.renterName}}</text>
<text class="room-date" v-if="room.endDate">{{formatDate(room.endDate)}}到期</text>
2026-04-20 06:23:11 +00:00
</view>
2026-04-22 06:47:04 +00:00
<view class="room-status-tag">{{getRoomStatusText(room)}}</view>
</view>
</view>
2026-04-20 06:23:11 +00:00
2026-04-22 06:47:04 +00:00
<uni-load-more v-if="filteredRooms.length > 0" :status="loadStatus"></uni-load-more>
2026-04-20 06:23:11 +00:00
2026-04-22 06:47:04 +00:00
<!-- 底部统计栏 -->
<view class="bottom-stats">
<view class="stats-content">
<view class="stat-item">
<text class="stat-value">{{currentApartmentStats.total}}</text>
<text class="stat-label">总房间</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value" style="color: #67C23A;">{{currentApartmentStats.rented}}</text>
<text class="stat-label">在租</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-value" style="color: #909399;">{{currentApartmentStats.empty}}</text>
<text class="stat-label">空房</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item highlight">
<text class="stat-value" style="color: #409EFF;">{{currentApartmentStats.occupancyRate}}%</text>
<text class="stat-label">出租率</text>
2026-04-20 06:23:11 +00:00
</view>
</view>
</view>
2026-04-22 06:47:04 +00:00
<view class="safe-area-bottom" style="height: 120rpx;"></view>
2026-04-20 06:23:11 +00:00
</scroll-view>
</view>
</template>
<script>
import apartmentApi from '@/api/apartment.js'
import roomApi from '@/api/room.js'
2026-04-22 06:47:04 +00:00
import statisticsApi from '@/api/statistics.js'
2026-04-20 06:23:11 +00:00
export default {
data() {
return {
isLoading: false,
isRefreshing: false,
apartments: [],
selectedApartmentId: null,
rooms: [],
2026-04-22 06:47:04 +00:00
apartmentStats: {}, // 公寓统计数据
2026-04-20 06:23:11 +00:00
currentFilter: 'all',
loadStatus: 'more',
page: 1,
pageSize: 20,
2026-04-22 06:47:04 +00:00
searchKeyword: '',
2026-04-20 06:23:11 +00:00
statusFilters: [
{ label: '全部', value: 'all', color: '#409EFF' },
2026-04-22 06:47:04 +00:00
{ label: '空置', value: 'empty', color: '#67C23A' },
{ label: '已租', value: 'rented', color: '#409EFF' },
{ label: '即将到期', value: 'soon_expire', color: '#E6A23C' },
{ label: '已到期', value: 'expired', color: '#F56C6C' },
{ label: '预定', value: 'reserved', color: '#909399' }
2026-04-20 06:23:11 +00:00
]
}
},
computed: {
filteredRooms() {
let list = this.rooms
2026-04-22 06:47:04 +00:00
// 按状态筛选
2026-04-20 06:23:11 +00:00
if (this.currentFilter !== 'all') {
list = list.filter(room => room.status === this.currentFilter)
}
2026-04-22 06:47:04 +00:00
// 按关键词搜索
if (this.searchKeyword.trim()) {
const keyword = this.searchKeyword.trim().toLowerCase()
list = list.filter(room => room.roomNumber.toLowerCase().includes(keyword))
}
2026-04-20 06:23:11 +00:00
return list
},
currentApartmentStats() {
const total = this.rooms.length
const rented = this.rooms.filter(r => r.status === 'rented').length
const empty = this.rooms.filter(r => r.status === 'empty').length
2026-04-22 06:47:04 +00:00
const soonExpire = this.rooms.filter(r => r.status === 'soon_expire').length
2026-04-20 06:23:11 +00:00
return {
total,
rented,
empty,
2026-04-22 06:47:04 +00:00
soonExpire,
2026-04-20 06:23:11 +00:00
occupancyRate: total > 0 ? Math.round((rented / total) * 100) : 0
}
}
},
onLoad(options) {
this.loadApartments()
},
onShow() {
if (this.selectedApartmentId) {
this.loadRooms()
}
},
methods: {
async loadApartments() {
try {
2026-04-22 06:47:04 +00:00
// 使用统计接口获取公寓列表及统计数据
const res = await statisticsApi.getApartmentRoomStatusStats()
2026-04-20 06:23:11 +00:00
if (res.data) {
this.apartments = res.data.map(apt => ({
2026-04-22 06:47:04 +00:00
id: apt.apartmentId,
name: apt.apartment,
roomCount: apt.total || 0
2026-04-20 06:23:11 +00:00
}))
2026-04-22 06:47:04 +00:00
// 保存统计数据
this.apartmentStats = {}
res.data.forEach(apt => {
this.apartmentStats[apt.apartmentId] = {
total: apt.total || 0,
empty: apt.empty || 0,
rented: apt.rented || 0,
soonExpire: apt.soon_expire || 0,
expired: apt.expired || 0,
reserved: apt.reserved || 0
}
})
2026-04-20 06:23:11 +00:00
if (this.apartments.length > 0 && !this.selectedApartmentId) {
this.selectedApartmentId = this.apartments[0].id
this.loadRooms()
}
}
} catch (error) {
console.error('加载公寓列表失败:', error)
}
},
async loadRooms() {
if (!this.selectedApartmentId) return
this.isLoading = true
try {
2026-04-22 06:47:04 +00:00
const res = await roomApi.list({
apartmentId: this.selectedApartmentId
2026-04-20 06:23:11 +00:00
})
if (res.data) {
const list = res.data.map(room => ({
id: room.id,
roomNumber: room.roomNumber,
area: room.area,
2026-04-22 06:47:04 +00:00
monthlyPrice: room.rent || room.monthlyPrice,
2026-04-20 06:23:11 +00:00
status: this.getRoomStatus(room),
2026-04-22 06:47:04 +00:00
rentalStatus: room.rentalStatus,
2026-04-20 06:23:11 +00:00
renterName: room.Renter?.name,
renterPhone: room.Renter?.phone,
rentalId: room.Rental?.id,
2026-04-22 06:47:04 +00:00
endDate: room.Rental?.endDate
2026-04-20 06:23:11 +00:00
}))
if (this.page === 1) {
this.rooms = list
} else {
this.rooms = [...this.rooms, ...list]
}
this.loadStatus = this.rooms.length < (res.total || 0) ? 'more' : 'noMore'
}
} catch (error) {
console.error('加载房间列表失败:', error)
} finally {
this.isLoading = false
this.isRefreshing = false
}
},
getRoomStatus(room) {
2026-04-22 06:47:04 +00:00
// 与Web端Status.vue保持一致直接使用room.status
return room.status || 'empty'
2026-04-20 06:23:11 +00:00
},
2026-04-22 06:47:04 +00:00
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 ''
2026-04-20 06:23:11 +00:00
},
2026-04-22 06:47:04 +00:00
formatDate(date) {
if (!date) return '-'
const d = new Date(date)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
2026-04-20 06:23:11 +00:00
},
selectApartment(id) {
this.selectedApartmentId = id
this.page = 1
this.loadRooms()
},
setFilter(value) {
this.currentFilter = value
},
getStatusCount(status) {
2026-04-22 06:47:04 +00:00
// 使用接口返回的统计数据
const stats = this.apartmentStats[this.selectedApartmentId]
if (!stats) return 0
if (status === 'all') return stats.total
const keyMap = {
'empty': 'empty',
'rented': 'rented',
'soon_expire': 'soonExpire',
'expired': 'expired',
'reserved': 'reserved'
}
return stats[keyMap[status]] || 0
2026-04-20 06:23:11 +00:00
},
getStatusColor(status) {
const colors = {
2026-04-22 06:47:04 +00:00
empty: '#67C23A',
rented: '#409EFF',
soon_expire: '#E6A23C',
expired: '#F56C6C',
reserved: '#909399'
2026-04-20 06:23:11 +00:00
}
return colors[status] || '#909399'
},
getStatusText(status) {
2026-04-22 06:47:04 +00:00
// 保持与Web端一致的文本映射
2026-04-20 06:23:11 +00:00
const texts = {
2026-04-22 06:47:04 +00:00
empty: '空置',
rented: '已租',
2026-04-20 06:23:11 +00:00
soon_expire: '即将到期',
2026-04-22 06:47:04 +00:00
expired: '已到期',
reserved: '预定'
2026-04-20 06:23:11 +00:00
}
return texts[status] || status
},
2026-04-22 06:47:04 +00:00
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
},
2026-04-20 06:23:11 +00:00
handleRoomClick(room) {
uni.navigateTo({
url: `/pages/room-detail/room-detail?id=${room.id}`
})
},
rentRoom(room) {
uni.navigateTo({
url: `/pages/rental-add/rental-add?roomId=${room.id}`
})
},
reserveRoom(room) {
uni.showModal({
title: '预订房间',
content: `确定要预订房间 ${room.roomNumber} 吗?`,
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '预订功能开发中', icon: 'none' })
}
}
})
},
confirmRent(room) {
uni.navigateTo({
url: `/pages/rental-add/rental-add?roomId=${room.id}`
})
},
createBill(room) {
uni.navigateTo({
url: `/pages/bill-add/bill-add?roomId=${room.id}&renterId=${room.renterId}`
})
},
addProperty() {
2026-04-22 06:47:04 +00:00
// 直接跳转到添加房间页面
this.addRoom()
2026-04-20 06:23:11 +00:00
},
addRoom() {
if (!this.selectedApartmentId) {
uni.showToast({ title: '请先选择公寓', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages/room-add/room-add?apartmentId=${this.selectedApartmentId}`
})
},
onRefresh() {
this.isRefreshing = true
this.page = 1
this.loadRooms()
},
loadMore() {
if (this.loadStatus === 'noMore') return
this.loadStatus = 'loading'
this.page++
this.loadRooms()
},
showFilter() {
uni.showToast({ title: '筛选功能', icon: 'none' })
2026-04-22 06:47:04 +00:00
},
onSearch() {
// 搜索时不需要重新加载数据computed 会自动过滤
uni.showToast({ title: `搜索: ${this.searchKeyword}`, icon: 'none', duration: 1000 })
},
clearSearch() {
this.searchKeyword = ''
2026-04-20 06:23:11 +00:00
}
}
}
</script>
<style scoped>
.properties-page {
min-height: 100vh;
2026-04-22 06:47:04 +00:00
background: #F5F7FA;
2026-04-20 06:23:11 +00:00
display: flex;
flex-direction: column;
}
/* 导航栏 */
.custom-nav {
background: #FFFFFF;
2026-04-22 06:47:04 +00:00
border-bottom: 1rpx solid #EBEEF5;
2026-04-20 06:23:11 +00:00
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 32rpx;
}
.nav-title {
font-size: 36rpx;
font-weight: 700;
2026-04-22 06:47:04 +00:00
color: #303133;
2026-04-20 06:23:11 +00:00
}
.nav-actions {
display: flex;
gap: 16rpx;
}
.nav-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
2026-04-22 06:47:04 +00:00
background: #F5F7FA;
transition: all 0.2s;
}
.nav-btn:active {
transform: scale(0.95);
background: #EBEEF5;
2026-04-20 06:23:11 +00:00
}
.nav-btn.primary {
2026-04-22 06:47:04 +00:00
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
box-shadow: 0 4rpx 16rpx rgba(64, 158, 255, 0.3);
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
/* 统计概览卡片 */
.statistics-overview {
padding: 24rpx 32rpx;
2026-04-20 06:23:11 +00:00
background: #FFFFFF;
}
2026-04-22 06:47:04 +00:00
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
}
.stat-card {
border-radius: 16rpx;
padding: 20rpx 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0.1;
background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, transparent 100%);
}
.stat-card.total {
background: linear-gradient(135deg, #667EEA 0%, #764BA2 100%);
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
}
.stat-card.empty {
background: linear-gradient(135deg, #11998E 0%, #38EF7D 100%);
box-shadow: 0 4rpx 16rpx rgba(17, 153, 142, 0.3);
}
.stat-card.rented {
background: linear-gradient(135deg, #4FACFE 0%, #00F2FE 100%);
box-shadow: 0 4rpx 16rpx rgba(79, 172, 254, 0.3);
}
.stat-card.soon-expire {
background: linear-gradient(135deg, #FA709A 0%, #FEE140 100%);
box-shadow: 0 4rpx 16rpx rgba(250, 112, 154, 0.3);
}
.stat-icon {
margin-bottom: 8rpx;
opacity: 0.9;
}
.stat-content {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-number {
font-size: 36rpx;
font-weight: 700;
color: #FFFFFF;
line-height: 1.2;
}
.stat-label {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.9);
margin-top: 4rpx;
}
/* 搜索栏 */
.search-bar {
background: #FFFFFF;
padding: 20rpx 32rpx;
border-bottom: 1rpx solid #EBEEF5;
}
.search-input-wrapper {
display: flex;
align-items: center;
background: #F5F7FA;
border-radius: 32rpx;
padding: 16rpx 24rpx;
gap: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #303133;
height: 40rpx;
}
.search-input::placeholder {
color: #909399;
}
/* 公寓选择器 */
.apartment-section-header {
background: #FFFFFF;
border-bottom: 1rpx solid #EBEEF5;
padding: 16rpx 0;
}
.apartment-tabs {
2026-04-20 06:23:11 +00:00
white-space: nowrap;
padding: 0 32rpx;
}
.tab-item {
display: inline-flex;
flex-direction: column;
align-items: center;
padding: 16rpx 32rpx;
margin-right: 16rpx;
2026-04-22 06:47:04 +00:00
border-radius: 12rpx;
background: #F5F7FA;
border: 2rpx solid transparent;
transition: all 0.2s;
2026-04-20 06:23:11 +00:00
}
.tab-item.active {
2026-04-22 06:47:04 +00:00
background: #ECF5FF;
border-color: #409EFF;
2026-04-20 06:23:11 +00:00
}
.tab-name {
font-size: 28rpx;
font-weight: 600;
2026-04-22 06:47:04 +00:00
color: #303133;
2026-04-20 06:23:11 +00:00
}
.tab-count {
font-size: 22rpx;
2026-04-22 06:47:04 +00:00
color: #909399;
2026-04-20 06:23:11 +00:00
margin-top: 4rpx;
}
2026-04-22 06:47:04 +00:00
/* 状态图例 */
.status-legend {
background: #FFFFFF;
padding: 20rpx 0;
border-bottom: 1rpx solid #EBEEF5;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.legend-scroll {
2026-04-20 06:23:11 +00:00
display: flex;
2026-04-22 06:47:04 +00:00
padding: 0 32rpx;
2026-04-20 06:23:11 +00:00
gap: 16rpx;
2026-04-22 06:47:04 +00:00
flex-wrap: wrap;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.legend-item {
2026-04-20 06:23:11 +00:00
display: flex;
align-items: center;
gap: 8rpx;
padding: 12rpx 20rpx;
2026-04-22 06:47:04 +00:00
background: #F5F7FA;
border-radius: 24rpx;
border: 2rpx solid transparent;
transition: all 0.2s;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.legend-item.active {
background: #ECF5FF;
border-color: #409EFF;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.legend-dot {
width: 16rpx;
height: 16rpx;
2026-04-20 06:23:11 +00:00
border-radius: 50%;
}
2026-04-22 06:47:04 +00:00
.legend-text {
font-size: 24rpx;
color: #606266;
}
.legend-count {
2026-04-20 06:23:11 +00:00
font-size: 22rpx;
2026-04-22 06:47:04 +00:00
color: #909399;
background: #FFFFFF;
padding: 2rpx 10rpx;
border-radius: 10rpx;
margin-left: 4rpx;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
/* 房间列表 */
.room-list {
2026-04-20 06:23:11 +00:00
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;
2026-04-22 06:47:04 +00:00
color: #303133;
2026-04-20 06:23:11 +00:00
margin-bottom: 12rpx;
}
.empty-desc {
font-size: 26rpx;
2026-04-22 06:47:04 +00:00
color: #909399;
2026-04-20 06:23:11 +00:00
margin-bottom: 40rpx;
}
.empty-btn {
2026-04-22 06:47:04 +00:00
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
2026-04-20 06:23:11 +00:00
color: #FFFFFF;
2026-04-22 06:47:04 +00:00
font-size: 28rpx;
font-weight: 500;
padding: 16rpx 40rpx;
border-radius: 12rpx;
2026-04-20 06:23:11 +00:00
border: none;
2026-04-22 06:47:04 +00:00
box-shadow: 0 4rpx 12rpx rgba(64, 158, 255, 0.3);
line-height: 1.5;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
/* 房间卡片网格 */
.room-grid {
2026-04-20 06:23:11 +00:00
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.room-card {
background: #FFFFFF;
2026-04-22 06:47:04 +00:00
border: 2rpx solid #EBEEF5;
border-radius: 16rpx;
2026-04-20 06:23:11 +00:00
padding: 24rpx;
2026-04-22 06:47:04 +00:00
transition: all 0.2s;
position: relative;
overflow: hidden;
min-height: 160rpx;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-card:active {
transform: scale(0.98);
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
/* 状态样式 */
.room-card.status-empty {
border-color: #67C23A;
background: #F0F9FF;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-card.status-rented {
border-color: #409EFF;
background: #ECF5FF;
}
.room-card.status-soon-expire {
border-color: #E6A23C;
background: #FDF6EC;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-card.status-expired {
border-color: #F56C6C;
2026-04-20 06:23:11 +00:00
background: #FEF0F0;
}
2026-04-22 06:47:04 +00:00
.room-card.status-reserved {
border-color: #909399;
background: #F4F4F5;
2026-04-20 06:23:11 +00:00
}
.room-number {
2026-04-22 06:47:04 +00:00
font-size: 32rpx;
2026-04-20 06:23:11 +00:00
font-weight: 700;
2026-04-22 06:47:04 +00:00
color: #303133;
margin-bottom: 12rpx;
display: block;
2026-04-20 06:23:11 +00:00
}
.room-info {
font-size: 24rpx;
2026-04-22 06:47:04 +00:00
color: #606266;
line-height: 1.6;
2026-04-20 06:23:11 +00:00
display: flex;
2026-04-22 06:47:04 +00:00
flex-direction: column;
gap: 4rpx;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-price {
2026-04-20 06:23:11 +00:00
color: #F56C6C;
2026-04-22 06:47:04 +00:00
font-weight: 600;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-tenant {
color: #409EFF;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-date {
color: #909399;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.room-status-tag {
position: absolute;
top: 20rpx;
right: 20rpx;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.9);
color: #606266;
2026-04-20 06:23:11 +00:00
font-weight: 500;
}
2026-04-22 06:47:04 +00:00
/* 底部统计栏 */
.bottom-stats {
background: #FFFFFF;
border-top: 1rpx solid #EBEEF5;
border-radius: 16rpx;
margin: 24rpx 0;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.stats-content {
2026-04-20 06:23:11 +00:00
display: flex;
justify-content: space-around;
2026-04-22 06:47:04 +00:00
align-items: center;
2026-04-20 06:23:11 +00:00
padding: 20rpx 32rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
2026-04-22 06:47:04 +00:00
flex: 1;
}
.stat-item.highlight {
background: linear-gradient(135deg, #ECF5FF 0%, #F5F7FA 100%);
border-radius: 12rpx;
padding: 12rpx 0;
margin: -12rpx 0;
2026-04-20 06:23:11 +00:00
}
2026-04-22 06:47:04 +00:00
.stat-divider {
width: 2rpx;
height: 40rpx;
background: #EBEEF5;
}
.bottom-stats .stat-value {
2026-04-20 06:23:11 +00:00
font-size: 32rpx;
font-weight: 700;
2026-04-22 06:47:04 +00:00
color: #303133;
2026-04-20 06:23:11 +00:00
margin-bottom: 4rpx;
}
2026-04-22 06:47:04 +00:00
.bottom-stats .stat-label {
2026-04-20 06:23:11 +00:00
font-size: 22rpx;
color: #909399;
}
</style>