rentease-app/pages/rentals/rentals.vue

1321 lines
28 KiB
Vue

<template>
<view class="rentals-page">
<!-- 自定义导航栏 -->
<view class="custom-nav safe-area-top">
<view class="nav-content">
<view class="nav-left">
<view class="nav-btn back-btn" @click="goBack">
<uni-icons type="left" size="22" color="#1E293B"></uni-icons>
</view>
<text class="nav-title">租赁记录</text>
</view>
<view class="nav-actions">
<view class="nav-btn" @click="showFilter">
<uni-icons type="settings-filled" size="22" color="#1E293B"></uni-icons>
</view>
<view class="nav-btn" @click="addRental">
<uni-icons type="plus" size="22" color="#2563EB"></uni-icons>
</view>
</view>
</view>
</view>
<!-- 搜索栏 -->
<view class="search-section">
<view class="search-bar">
<uni-icons type="search" size="18" color="#94A3B8"></uni-icons>
<input
class="search-input"
type="text"
placeholder="搜索租客姓名..."
placeholder-class="search-placeholder"
v-model="searchKeyword"
@confirm="handleSearch"
/>
<uni-icons v-if="searchKeyword" type="clear" size="18" color="#94A3B8" @click="clearSearch"></uni-icons>
</view>
</view>
<!-- 筛选标签 -->
<scroll-view scroll-x class="filter-tabs" show-scrollbar="false">
<view
v-for="(tab, index) in filterTabs"
:key="index"
class="filter-tab"
:class="{ active: currentTab === tab.value }"
@click="switchTab(tab.value)"
>
<text>{{tab.label}}</text>
<view v-if="currentTab === tab.value" class="tab-indicator"></view>
</view>
</scroll-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="rental-list" @scrolltolower="loadMore" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh">
<view v-if="filteredRentals.length === 0 && !isLoading" class="empty-state">
<view class="empty-icon">
<uni-icons type="folder-add-filled" size="80" color="#CBD5E1"></uni-icons>
</view>
<text class="empty-title">暂无租赁记录</text>
<text class="empty-desc">点击右上角添加您的第一条租赁记录</text>
<button class="empty-btn" @click="addRental">添加租赁</button>
</view>
<view v-else class="rental-list-content">
<view
v-for="(item, index) in filteredRentals"
:key="index"
class="rental-card"
@click="viewRentalDetail(item)"
:style="{ animationDelay: index * 50 + 'ms' }"
>
<view class="card-header">
<view class="room-info">
<text class="apartment-name">{{item.apartmentName}}</text>
<text class="room-number">{{item.roomNumber}}</text>
</view>
<view class="status-tag" :class="item.status">
{{item.statusText}}
</view>
</view>
<view class="renter-info">
<view class="renter-avatar">
<text>{{item.renterName.charAt(0)}}</text>
</view>
<view class="renter-detail">
<text class="renter-name">{{item.renterName}}</text>
<text class="renter-phone">{{item.renterPhone}}</text>
</view>
</view>
<view class="rental-info">
<view class="info-row">
<view class="info-item">
<text class="label">租期</text>
<text class="value">{{item.startDate}} ~ {{item.endDate}}</text>
</view>
</view>
<view class="info-row">
<view class="info-item">
<text class="label">付租方式</text>
<view class="payment-tag" :class="item.paymentType">{{item.paymentTypeText}}</view>
</view>
<view class="info-item">
<text class="label">月租金</text>
<text class="value price">¥{{item.rent}}</text>
</view>
</view>
</view>
<view v-if="item.isExpiringSoon" class="expiring-warning">
<uni-icons type="info-filled" size="14" color="#EF4444"></uni-icons>
<text>即将到期(剩余{{item.remainingDays}}天)</text>
</view>
<view class="card-actions" v-if="item.status === 'active'">
<view class="action-btn renew" @click.stop="handleRenew(item)">
<text>续租</text>
</view>
<view class="action-btn terminate" @click.stop="handleTerminate(item)">
<text>退租</text>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<uni-load-more v-if="filteredRentals.length > 0" :status="loadStatus"></uni-load-more>
<view class="safe-area-bottom" style="height: 40rpx;"></view>
</scroll-view>
<!-- 退租弹窗 -->
<uni-popup ref="terminatePopup" type="center">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">办理退租</text>
<uni-icons type="close" size="20" color="#94A3B8" @click="closeTerminatePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="form-item">
<text class="form-label">租客</text>
<text class="form-static">{{selectedRental?.renterName}}</text>
</view>
<view class="form-item">
<text class="form-label">押金金额</text>
<text class="form-static price">¥{{selectedRental?.deposit || 0}}</text>
</view>
<view class="form-item">
<text class="form-label">水表最终读数</text>
<input
type="digit"
v-model="terminateForm.waterMeterEnd"
placeholder="请输入水表读数"
class="form-input"
/>
</view>
<view class="form-item">
<text class="form-label">电表最终读数</text>
<input
type="digit"
v-model="terminateForm.electricityMeterEnd"
placeholder="请输入电表读数"
class="form-input"
/>
</view>
<view class="form-item">
<text class="form-label">退还押金</text>
<input
type="digit"
v-model="terminateForm.refundDeposit"
placeholder="请输入退还金额"
class="form-input"
/>
</view>
<view class="form-item">
<text class="form-label">退租备注</text>
<textarea
v-model="terminateForm.remark"
placeholder="请输入退租备注(选填)"
class="form-textarea"
/>
</view>
</view>
<view class="popup-footer">
<button class="btn-cancel" @click="closeTerminatePopup">取消</button>
<button class="btn-confirm" :loading="submitLoading" @click="confirmTerminate">确认退租</button>
</view>
</view>
</uni-popup>
<!-- 筛选弹窗 -->
<uni-popup ref="filterPopup" type="right">
<view class="filter-drawer">
<view class="filter-header">
<text class="filter-title">筛选条件</text>
<uni-icons type="close" size="20" color="#94A3B8" @click="closeFilterPopup"></uni-icons>
</view>
<scroll-view scroll-y class="filter-body">
<!-- 公寓筛选 -->
<view class="filter-section">
<text class="filter-label">公寓</text>
<view class="filter-options">
<view
v-for="apt in apartmentFilterOptions"
:key="apt.value"
class="filter-option"
:class="{ active: filterForm.apartmentId === apt.value }"
@click="selectApartment(apt.value)"
>
<text>{{apt.label}}</text>
</view>
</view>
</view>
<!-- 房间筛选 -->
<view class="filter-section" v-if="filterForm.apartmentId">
<text class="filter-label">房间</text>
<view class="filter-options">
<view
v-for="room in roomFilterOptions"
:key="room.value"
class="filter-option"
:class="{ active: filterForm.roomId === room.value }"
@click="selectRoom(room.value)"
>
<text>{{room.label}}</text>
</view>
</view>
</view>
<!-- 状态筛选 -->
<view class="filter-section">
<text class="filter-label">租赁状态</text>
<view class="filter-options">
<view
v-for="status in statusOptions"
:key="status.value"
class="filter-option"
:class="{ active: filterForm.status === status.value }"
@click="selectStatus(status.value)"
>
<text>{{status.label}}</text>
</view>
</view>
</view>
<!-- 付租方式筛选 -->
<view class="filter-section">
<text class="filter-label">付租方式</text>
<view class="filter-options">
<view
v-for="type in paymentTypeOptions"
:key="type.value"
class="filter-option"
:class="{ active: filterForm.paymentType === type.value }"
@click="selectPaymentType(type.value)"
>
<text>{{type.label}}</text>
</view>
</view>
</view>
<!-- 租客姓名 -->
<view class="filter-section">
<text class="filter-label">租客姓名</text>
<input
class="filter-input"
type="text"
placeholder="请输入租客姓名"
v-model="filterForm.renterName"
/>
</view>
</scroll-view>
<view class="filter-footer">
<button class="btn-reset" @click="resetFilter">重置</button>
<button class="btn-confirm" @click="confirmFilter">确定</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import rentalApi from '@/api/rental.js'
import apartmentApi from '@/api/apartment.js'
import roomApi from '@/api/room.js'
export default {
data() {
return {
searchKeyword: '',
currentTab: 'all',
isLoading: false,
isRefreshing: false,
submitLoading: false,
filterTabs: [
{ label: '全部', value: 'all' },
{ label: '在租', value: 'active' },
{ label: '已到期', value: 'expired' },
{ label: '已终止', value: 'terminated' }
],
rentals: [],
apartments: [],
rooms: [],
selectedApartmentId: '',
loadStatus: 'more',
page: 1,
pageSize: 10,
total: 0,
selectedRental: null,
terminateForm: {
waterMeterEnd: '',
electricityMeterEnd: '',
refundDeposit: '',
remark: ''
},
// 筛选表单
filterForm: {
apartmentId: '',
roomId: '',
status: '',
paymentType: '',
renterName: ''
},
// 筛选选项
statusOptions: [
{ label: '全部', value: '' },
{ label: '在租', value: 'active' },
{ label: '已到期', value: 'expired' },
{ label: '已终止', value: 'terminated' }
],
paymentTypeOptions: [
{ label: '全部', value: '' },
{ label: '月租', value: 'monthly' },
{ label: '季租', value: 'quarterly' },
{ label: '半年租', value: 'half_year' },
{ label: '年租', value: 'yearly' }
]
}
},
computed: {
filteredRentals() {
let list = this.rentals
// 根据状态筛选
if (this.currentTab !== 'all') {
list = list.filter(r => r.status === this.currentTab)
}
// 根据公寓筛选
if (this.selectedApartmentId) {
list = list.filter(r => r.apartmentId === this.selectedApartmentId)
}
return list
},
apartmentOptions() {
return ['全部公寓', ...this.apartments.map(a => a.name)]
},
selectedApartmentIndex() {
if (!this.selectedApartmentId) return 0
const index = this.apartments.findIndex(a => a.id === this.selectedApartmentId)
return index + 1
},
selectedApartmentLabel() {
if (!this.selectedApartmentId) return '全部公寓'
const apartment = this.apartments.find(a => a.id === this.selectedApartmentId)
return apartment ? apartment.name : '全部公寓'
},
// 筛选弹窗用的公寓选项
apartmentFilterOptions() {
return [{ label: '全部', value: '' }, ...this.apartments.map(a => ({ label: a.name, value: a.id }))]
},
// 筛选弹窗用的房间选项
roomFilterOptions() {
const filteredRooms = this.filterForm.apartmentId
? this.rooms.filter(r => r.apartmentId === this.filterForm.apartmentId)
: this.rooms
return [{ label: '全部', value: '' }, ...filteredRooms.map(r => ({ label: r.roomNumber, value: r.id }))]
}
},
onLoad() {
this.loadApartments()
this.loadRooms()
this.loadRentals()
},
onShow() {
this.loadRentals()
},
methods: {
async loadApartments() {
try {
const res = await apartmentApi.list()
if (res.data) {
this.apartments = res.data
}
} catch (error) {
console.error('加载公寓列表失败:', error)
}
},
async loadRooms() {
try {
const res = await roomApi.list()
if (res.data) {
this.rooms = res.data
}
} catch (error) {
console.error('加载房间列表失败:', error)
}
},
async loadRentals() {
// 防止重复请求
if (this.isLoading) return
this.isLoading = true
try {
const params = {
page: this.page,
pageSize: this.pageSize,
renterName: this.searchKeyword || this.filterForm.renterName,
apartmentId: this.filterForm.apartmentId,
roomId: this.filterForm.roomId,
status: this.filterForm.status,
paymentType: this.filterForm.paymentType
}
const res = await rentalApi.getList(params)
// 后端返回格式: { data: { list: [], total: n, page: n, pageSize: n } }
const pageData = res.data || {}
const rawList = pageData.list || []
if (rawList.length > 0 || this.page === 1) {
const list = rawList.map(item => ({
id: item.id,
apartmentId: item.Room?.Apartment?.id,
apartmentName: item.Room?.Apartment?.name || '--',
roomId: item.roomId,
roomNumber: item.Room?.roomNumber || '--',
renterId: item.renterId,
renterName: item.Renter?.name || '--',
renterPhone: item.Renter?.phone || '--',
startDate: item.startDate,
endDate: item.endDate,
paymentType: item.paymentType,
paymentTypeText: this.getPaymentTypeText(item.paymentType),
rent: item.rent,
deposit: item.deposit || 0,
status: item.status,
statusText: this.getStatusText(item.status),
isExpiringSoon: this.isExpiringSoon(item.endDate) && item.status === 'active',
remainingDays: this.getRemainingDays(item.endDate)
}))
if (this.page === 1) {
this.rentals = list
} else {
this.rentals = [...this.rentals, ...list]
}
this.total = pageData.total || 0
this.loadStatus = this.rentals.length < this.total ? '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.loadRentals()
},
switchTab(value) {
this.currentTab = value
},
onApartmentChange(e) {
const index = e.detail.value
if (index === 0) {
this.selectedApartmentId = ''
} else {
this.selectedApartmentId = this.apartments[index - 1].id
}
},
handleSearch() {
this.page = 1
this.loadRentals()
},
clearSearch() {
this.searchKeyword = ''
this.page = 1
this.loadRentals()
},
addRental() {
uni.switchTab({
url: '/pages/add-record/add-record'
})
},
goBack() {
uni.navigateBack()
},
viewRentalDetail(item) {
uni.navigateTo({
url: `/pages/rental-detail/rental-detail?id=${item.id}`
})
},
handleRenew(item) {
uni.navigateTo({
url: `/pages/rental-renew/rental-renew?id=${item.id}`
})
},
handleTerminate(item) {
this.selectedRental = item
this.terminateForm = {
waterMeterEnd: '',
electricityMeterEnd: '',
refundDeposit: item.deposit || '',
remark: ''
}
this.$refs.terminatePopup.open()
},
closeTerminatePopup() {
this.$refs.terminatePopup.close()
},
async confirmTerminate() {
if (!this.selectedRental) return
// 表单验证
if (!this.terminateForm.waterMeterEnd) {
uni.showToast({ title: '请输入水表读数', icon: 'none' })
return
}
if (!this.terminateForm.electricityMeterEnd) {
uni.showToast({ title: '请输入电表读数', icon: 'none' })
return
}
this.submitLoading = true
try {
await rentalApi.terminate(this.selectedRental.id, {
waterMeterEnd: parseFloat(this.terminateForm.waterMeterEnd),
electricityMeterEnd: parseFloat(this.terminateForm.electricityMeterEnd),
refundDeposit: parseFloat(this.terminateForm.refundDeposit) || 0,
remark: this.terminateForm.remark
})
uni.showToast({
title: '退租办理成功',
icon: 'success'
})
this.closeTerminatePopup()
this.loadRentals()
} catch (error) {
console.error('退租失败:', error)
uni.showToast({
title: '退租办理失败',
icon: 'none'
})
} finally {
this.submitLoading = false
}
},
// 筛选弹窗相关方法
showFilter() {
// 打开筛选弹窗前加载房间数据
this.loadRooms()
this.$refs.filterPopup.open()
},
closeFilterPopup() {
this.$refs.filterPopup.close()
},
selectApartment(apartmentId) {
this.filterForm.apartmentId = apartmentId
// 切换公寓时重置房间选择
this.filterForm.roomId = ''
},
selectRoom(roomId) {
this.filterForm.roomId = roomId
},
selectStatus(status) {
this.filterForm.status = status
},
selectPaymentType(type) {
this.filterForm.paymentType = type
},
resetFilter() {
this.filterForm = {
apartmentId: '',
roomId: '',
status: '',
paymentType: '',
renterName: ''
}
this.searchKeyword = ''
this.page = 1
this.loadRentals()
this.closeFilterPopup()
},
confirmFilter() {
this.page = 1
this.loadRentals()
this.closeFilterPopup()
},
loadMore() {
if (this.loadStatus === 'noMore') return
this.loadStatus = 'loading'
this.page++
this.loadRentals()
},
getPaymentTypeText(type) {
const map = {
monthly: '月租',
quarterly: '季租',
half_year: '半年租',
yearly: '年租'
}
return map[type] || type
},
getStatusText(status) {
const map = {
active: '在租',
expired: '已到期',
terminated: '已终止'
}
return map[status] || status
},
isExpiringSoon(endDate) {
if (!endDate) return false
const end = new Date(endDate)
const now = new Date()
const diffDays = Math.ceil((end - now) / (1000 * 60 * 60 * 24))
return diffDays <= 7 && diffDays >= 0
},
getRemainingDays(endDate) {
if (!endDate) return 0
const end = new Date(endDate)
const now = new Date()
return Math.ceil((end - now) / (1000 * 60 * 60 * 24))
}
}
}
</script>
<style scoped>
.rentals-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-left {
display: flex;
align-items: center;
gap: 16rpx;
}
.nav-title {
font-size: 36rpx;
font-weight: 700;
color: #1E293B;
}
.nav-actions {
display: flex;
gap: 16rpx;
}
.back-btn {
margin-left: -8rpx;
}
.nav-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #F8FAFC;
}
.nav-btn:active {
background: #F1F5F9;
}
/* 搜索栏 */
.search-section {
padding: 24rpx 32rpx;
background: #FFFFFF;
}
.search-bar {
display: flex;
align-items: center;
background: #F1F5F9;
border-radius: 16rpx;
padding: 16rpx 24rpx;
gap: 16rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #1E293B;
}
.search-placeholder {
color: #94A3B8;
}
/* 统计栏 */
.stats-bar {
display: flex;
align-items: center;
justify-content: space-around;
padding: 24rpx 32rpx;
background: #FFFFFF;
margin-bottom: 2rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-num {
font-size: 40rpx;
font-weight: 700;
color: #2563EB;
margin-bottom: 8rpx;
}
.stat-num.warning {
color: #F59E0B;
}
.stat-label {
font-size: 24rpx;
color: #64748B;
}
.stat-divider {
width: 2rpx;
height: 60rpx;
background: #E2E8F0;
}
/* 筛选标签 */
.filter-tabs {
background: #FFFFFF;
padding: 0 32rpx;
white-space: nowrap;
}
.filter-tab {
display: inline-flex;
flex-direction: column;
align-items: center;
padding: 24rpx 32rpx;
font-size: 28rpx;
color: #64748B;
position: relative;
}
.filter-tab.active {
color: #2563EB;
font-weight: 600;
}
.tab-indicator {
position: absolute;
bottom: 0;
width: 40rpx;
height: 4rpx;
background: #2563EB;
border-radius: 2rpx;
}
/* 公寓筛选 */
.apartment-filter {
background: #FFFFFF;
padding: 16rpx 32rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.picker-value {
display: flex;
align-items: center;
gap: 8rpx;
font-size: 28rpx;
color: #1E293B;
font-weight: 500;
}
/* 租赁列表 */
.rental-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: 30rpx;
font-weight: 600;
padding: 24rpx 60rpx;
border-radius: 16rpx;
border: none;
box-shadow: 0 8rpx 24rpx rgba(37, 99, 235, 0.3);
}
/* 租赁卡片 */
.rental-card {
background: #FFFFFF;
border-radius: 24rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
animation: fadeInUp 0.4s ease-out both;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.room-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.apartment-name {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
}
.room-number {
font-size: 24rpx;
color: #64748B;
background: #F1F5F9;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
.status-tag {
padding: 8rpx 20rpx;
border-radius: 8rpx;
font-size: 22rpx;
font-weight: 500;
}
.status-tag.active {
background: #D1FAE5;
color: #059669;
}
.status-tag.expired {
background: #F3F4F6;
color: #6B7280;
}
.status-tag.terminated {
background: #FEE2E2;
color: #DC2626;
}
/* 租客信息 */
.renter-info {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.renter-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
display: flex;
align-items: center;
justify-content: center;
}
.renter-avatar text {
font-size: 28rpx;
font-weight: 600;
color: #FFFFFF;
}
.renter-detail {
display: flex;
flex-direction: column;
}
.renter-name {
font-size: 30rpx;
font-weight: 600;
color: #1E293B;
}
.renter-phone {
font-size: 24rpx;
color: #64748B;
}
/* 租赁信息 */
.rental-info {
margin-bottom: 16rpx;
}
.info-row {
display: flex;
justify-content: space-between;
margin-bottom: 12rpx;
}
.info-item {
display: flex;
align-items: center;
gap: 12rpx;
}
.label {
font-size: 24rpx;
color: #94A3B8;
}
.value {
font-size: 26rpx;
color: #1E293B;
}
.value.price {
color: #EF4444;
font-weight: 700;
font-size: 32rpx;
}
.payment-tag {
padding: 4rpx 12rpx;
border-radius: 6rpx;
font-size: 22rpx;
}
.payment-tag.monthly {
background: #DBEAFE;
color: #2563EB;
}
.payment-tag.quarterly {
background: #FEF3C7;
color: #D97706;
}
.payment-tag.half_year {
background: #F3F4F6;
color: #4B5563;
}
.payment-tag.yearly {
background: #D1FAE5;
color: #059669;
}
/* 到期警告 */
.expiring-warning {
display: flex;
align-items: center;
gap: 8rpx;
background: #FEF2F2;
padding: 12rpx 16rpx;
border-radius: 12rpx;
margin-bottom: 16rpx;
}
.expiring-warning text {
font-size: 24rpx;
color: #DC2626;
}
/* 操作按钮 */
.card-actions {
display: flex;
gap: 16rpx;
padding-top: 16rpx;
border-top: 2rpx solid #F1F5F9;
}
.action-btn {
flex: 1;
height: 72rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.action-btn text {
font-size: 28rpx;
font-weight: 600;
}
.action-btn.renew {
background: #DBEAFE;
}
.action-btn.renew text {
color: #2563EB;
}
.action-btn.terminate {
background: #FEE2E2;
}
.action-btn.terminate text {
color: #DC2626;
}
/* 弹窗样式 */
.popup-content {
background: #FFFFFF;
border-radius: 24rpx;
width: 600rpx;
max-height: 800rpx;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.popup-title {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
}
.popup-body {
padding: 32rpx;
max-height: 560rpx;
overflow-y: auto;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 26rpx;
color: #64748B;
margin-bottom: 12rpx;
}
.form-static {
font-size: 30rpx;
color: #1E293B;
font-weight: 500;
}
.form-static.price {
color: #EF4444;
font-weight: 700;
}
.form-input {
width: 100%;
height: 80rpx;
background: #F8FAFC;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #1E293B;
}
.form-textarea {
width: 100%;
height: 160rpx;
background: #F8FAFC;
border-radius: 12rpx;
padding: 20rpx 24rpx;
font-size: 28rpx;
color: #1E293B;
}
.popup-footer {
display: flex;
gap: 20rpx;
padding: 24rpx 32rpx;
border-top: 2rpx solid #F1F5F9;
}
.btn-cancel {
flex: 1;
height: 80rpx;
background: #F3F4F6;
border-radius: 12rpx;
font-size: 28rpx;
color: #64748B;
border: none;
}
.btn-confirm {
flex: 1;
height: 80rpx;
background: linear-gradient(135deg, #EF4444 0%, #DC2626 100%);
border-radius: 12rpx;
font-size: 28rpx;
color: #FFFFFF;
font-weight: 600;
border: none;
}
/* 筛选抽屉样式 */
.filter-drawer {
width: 600rpx;
height: 100vh;
background: #FFFFFF;
display: flex;
flex-direction: column;
}
.filter-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 2rpx solid #F1F5F9;
}
.filter-title {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
}
.filter-body {
flex: 1;
padding: 32rpx;
}
.filter-section {
margin-bottom: 40rpx;
}
.filter-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
margin-bottom: 20rpx;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.filter-option {
padding: 16rpx 32rpx;
background: #F1F5F9;
border-radius: 12rpx;
font-size: 26rpx;
color: #64748B;
transition: all 0.2s;
}
.filter-option.active {
background: #DBEAFE;
color: #2563EB;
font-weight: 500;
}
.filter-input {
width: 100%;
height: 80rpx;
background: #F8FAFC;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #1E293B;
border: 2rpx solid #E2E8F0;
}
.filter-footer {
display: flex;
gap: 20rpx;
padding: 24rpx 32rpx;
border-top: 2rpx solid #F1F5F9;
background: #FFFFFF;
}
.filter-footer .btn-reset {
flex: 1;
height: 80rpx;
background: #F3F4F6;
border-radius: 12rpx;
font-size: 28rpx;
color: #64748B;
border: none;
}
.filter-footer .btn-confirm {
flex: 1;
height: 80rpx;
background: linear-gradient(135deg, #2563EB 0%, #1D4ED8 100%);
border-radius: 12rpx;
font-size: 28rpx;
color: #FFFFFF;
font-weight: 600;
border: none;
}
</style>