rentease-app/pages/rental-add/rental-add.vue

1373 lines
36 KiB
Vue

<template>
<view class="rental-add-page">
<!-- 顶部导航 -->
<view class="custom-nav safe-area-top">
<view class="nav-content">
<view class="nav-btn back-btn" @click="goBack">
<uni-icons type="left" size="22" color="#1E293B"></uni-icons>
</view>
<text class="nav-title">{{isEdit ? '编辑租赁' : '办理入住'}}</text>
<view class="nav-btn placeholder"></view>
</view>
</view>
<!-- 表单内容 -->
<scroll-view scroll-y class="form-scroll" :scroll-into-view="scrollIntoView" scroll-with-animation>
<view class="form-container">
<!-- 房间信息卡片 -->
<view class="section-card" id="section-room">
<view class="section-header">
<view class="section-icon room-icon">
<uni-icons type="home-filled" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="section-title">房间信息</text>
<text class="section-badge" v-if="form.roomId">已选择</text>
</view>
<!-- 公寓选择 -->
<view class="form-group">
<text class="group-label">选择公寓 <text class="required">*</text></text>
<view class="apartment-selector">
<scroll-view scroll-x class="apartment-scroll" show-scrollbar="false">
<view class="apartment-list">
<view
v-for="apt in apartments"
:key="apt.id"
class="apartment-item"
:class="{ active: selectedApartmentId === apt.id }"
@click="selectApartment(apt.id)"
>
<view class="apt-content">
<text class="apt-name">{{apt.name}}</text>
</view>
<view class="apt-check" v-if="selectedApartmentId === apt.id">
<uni-icons type="checkmarkempty" size="14" color="#FFFFFF"></uni-icons>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 房间选择 -->
<view class="form-group" v-if="selectedApartmentId">
<text class="group-label">选择房间 <text class="required">*</text></text>
<view class="room-grid">
<view
v-for="room in availableRooms"
:key="room.id"
class="room-card"
:class="{ active: form.roomId === room.id }"
@click="selectRoom(room)"
>
<view class="room-header">
<text class="room-number">{{room.roomNumber}}</text>
<view class="room-check" v-if="form.roomId === room.id">
<uni-icons type="checkmarkempty" size="12" color="#FFFFFF"></uni-icons>
</view>
</view>
<view class="room-info">
<text class="room-price">¥{{room.rent || 0}}<text class="price-unit">/月</text></text>
<text class="room-area" v-if="room.area">{{room.area}}m²</text>
</view>
</view>
<view v-if="availableRooms.length === 0" class="empty-rooms">
<uni-icons type="info-filled" size="32" color="#CBD5E1"></uni-icons>
<text>该公寓暂无空房</text>
</view>
</view>
</view>
</view>
<!-- 租客信息卡片 -->
<view class="section-card" id="section-renter">
<view class="section-header">
<view class="section-icon renter-icon">
<uni-icons type="person-filled" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="section-title">租客信息</text>
</view>
<view class="form-group">
<text class="group-label">租客姓名 <text class="required">*</text></text>
<view class="input-wrapper">
<uni-icons type="person" size="18" color="#94A3B8" class="input-icon"></uni-icons>
<input
type="text"
v-model="form.renterName"
placeholder="请输入租客姓名"
class="form-input"
maxlength="20"
/>
</view>
</view>
<view class="form-row">
<view class="form-group half">
<text class="group-label">联系电话</text>
<view class="input-wrapper">
<uni-icons type="phone-filled" size="18" color="#94A3B8" class="input-icon"></uni-icons>
<input
type="number"
v-model="form.renterPhone"
placeholder="选填"
class="form-input"
maxlength="11"
/>
</view>
</view>
<view class="form-group half">
<text class="group-label">身份证号</text>
<view class="input-wrapper">
<uni-icons type="wallet-filled" size="18" color="#94A3B8" class="input-icon"></uni-icons>
<input
type="idcard"
v-model="form.renterIdCard"
placeholder="选填"
class="form-input"
maxlength="18"
/>
</view>
</view>
</view>
</view>
<!-- 合同信息卡片 -->
<view class="section-card" id="section-contract">
<view class="section-header">
<view class="section-icon contract-icon">
<uni-icons type="compose" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="section-title">合同信息</text>
</view>
<!-- 付租方式 -->
<view class="form-group">
<text class="group-label">付租方式 <text class="required">*</text></text>
<view class="payment-grid">
<view
v-for="type in paymentTypes"
:key="type.value"
class="payment-item"
:class="{ active: form.paymentType === type.value }"
@click="selectPaymentType(type.value)"
>
<text class="payment-label">{{type.label}}</text>
<text class="payment-desc">{{type.desc}}</text>
<view class="payment-indicator" v-if="form.paymentType === type.value">
<uni-icons type="checkmarkempty" size="12" color="#FFFFFF"></uni-icons>
</view>
</view>
</view>
</view>
<!-- 租期 -->
<view class="form-group" v-if="!isEdit">
<text class="group-label">租期 <text class="required">*</text></text>
<view class="period-scroll">
<scroll-view scroll-x show-scrollbar="false">
<view class="period-list">
<view
v-for="period in periodOptions"
:key="period.value"
class="period-tag"
:class="{ active: leasePeriod === period.value }"
@click="selectPeriod(period.value)"
>
{{period.label}}
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 日期选择 -->
<view class="form-row">
<view class="form-group half">
<text class="group-label">开始日期 <text class="required">*</text></text>
<picker mode="date" :value="form.startDate" @change="onStartDateChange">
<view class="picker-box">
<text class="picker-text" :class="{ placeholder: !form.startDate }">{{form.startDate || '请选择'}}</text>
<uni-icons type="calendar" size="18" color="#64748B"></uni-icons>
</view>
</picker>
</view>
<view class="form-group half">
<text class="group-label">结束日期 <text class="required">*</text></text>
<picker mode="date" :value="form.endDate" @change="onEndDateChange" v-if="isEdit">
<view class="picker-box">
<text class="picker-text" :class="{ placeholder: !form.endDate }">{{form.endDate || '请选择'}}</text>
<uni-icons type="calendar" size="18" color="#64748B"></uni-icons>
</view>
</picker>
<view class="static-box" v-else>
<text class="static-text">{{form.endDate || '自动计算'}}</text>
<uni-icons type="calendar-filled" size="18" color="#CBD5E1"></uni-icons>
</view>
</view>
</view>
<!-- 金额信息 -->
<view class="form-row">
<view class="form-group half">
<text class="group-label">{{form.paymentType === 'yearly' ? '年租金' : '月租金'}} <text class="required">*</text></text>
<view class="input-wrapper money">
<text class="currency">¥</text>
<input
type="digit"
v-model="form.rent"
:placeholder="form.paymentType === 'yearly' ? '年租金' : '月租金'"
class="form-input"
/>
</view>
</view>
<view class="form-group half">
<text class="group-label">押金</text>
<view class="input-wrapper money">
<text class="currency">¥</text>
<input
type="digit"
v-model="form.deposit"
placeholder="押金金额"
class="form-input"
/>
</view>
</view>
</view>
<!-- 经办人 -->
<view class="form-group">
<text class="group-label">经办人</text>
<view class="input-wrapper">
<uni-icons type="personadd-filled" size="18" color="#94A3B8" class="input-icon"></uni-icons>
<input
type="text"
v-model="form.operator"
placeholder="请输入经办人"
class="form-input"
maxlength="20"
/>
</view>
</view>
</view>
<!-- 水电表读数卡片 -->
<view class="section-card" id="section-meter">
<view class="section-header">
<view class="section-icon meter-icon">
<uni-icons type="settings-filled" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="section-title">水电表读数</text>
<text class="section-hint">选填</text>
</view>
<view class="meter-grid">
<view class="meter-item">
<view class="meter-icon-box water">
<uni-icons type="info-filled" size="24" color="#3B82F6"></uni-icons>
</view>
<view class="meter-content">
<text class="meter-label">水表起始读数</text>
<view class="meter-input-box">
<input
type="digit"
v-model="form.waterStartReading"
placeholder="0"
class="meter-input"
/>
<text class="meter-unit">吨</text>
</view>
</view>
</view>
<view class="meter-item">
<view class="meter-icon-box electric">
<uni-icons type="info-filled" size="24" color="#F59E0B"></uni-icons>
</view>
<view class="meter-content">
<text class="meter-label">电表起始读数</text>
<view class="meter-input-box">
<input
type="digit"
v-model="form.electricStartReading"
placeholder="0"
class="meter-input"
/>
<text class="meter-unit">度</text>
</view>
</view>
</view>
</view>
</view>
<!-- 备注卡片 -->
<view class="section-card" id="section-remark">
<view class="section-header">
<view class="section-icon remark-icon">
<uni-icons type="chatbubble-filled" size="20" color="#FFFFFF"></uni-icons>
</view>
<text class="section-title">备注信息</text>
<text class="section-hint">选填</text>
</view>
<view class="textarea-wrapper">
<textarea
v-model="form.remark"
placeholder="添加备注信息..."
class="form-textarea"
maxlength="200"
/>
<text class="char-count">{{form.remark.length}}/200</text>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<button
class="submit-btn"
:class="{ active: canSubmit, loading: submitLoading }"
:disabled="!canSubmit || submitLoading"
@click="submitForm"
>
<text v-if="!submitLoading">{{isEdit ? '保存修改' : '确认办理'}}</text>
<view v-else class="btn-loading">
<view class="loading-dot"></view>
<view class="loading-dot"></view>
<view class="loading-dot"></view>
</view>
</button>
</view>
<!-- 底部安全区域 -->
<view class="safe-area-bottom" style="height: 160rpx;"></view>
</view>
</scroll-view>
<!-- 成功弹窗 -->
<view class="success-modal" v-if="showSuccess" @click="closeSuccess">
<view class="success-content" @click.stop>
<view class="success-icon">
<view class="success-circle">
<uni-icons type="checkmarkempty" size="48" color="#FFFFFF"></uni-icons>
</view>
</view>
<text class="success-title">{{isEdit ? '修改成功' : '办理成功'}}</text>
<text class="success-desc">{{isEdit ? '租赁信息已更新' : '租客入住登记已完成'}}</text>
<button class="success-btn" @click="closeSuccess">确定</button>
</view>
</view>
</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 {
isEdit: false,
rentalId: null,
submitLoading: false,
showSuccess: false,
scrollIntoView: '',
apartments: [],
rooms: [],
selectedApartmentId: null,
leasePeriod: 1,
form: {
roomId: '',
renterName: '',
renterPhone: '',
renterIdCard: '',
paymentType: 'monthly',
startDate: '',
endDate: '',
rent: '',
deposit: '',
operator: '',
waterStartReading: '',
electricStartReading: '',
remark: ''
},
paymentTypes: [
{ label: '月租', value: 'monthly', desc: '每月支付' },
{ label: '年租', value: 'yearly', desc: '每年支付' }
]
}
},
computed: {
availableRooms() {
// 编辑模式下显示所有房间,新增模式下只显示空置房间
if (this.isEdit) {
return this.rooms
}
return this.rooms.filter(room => room.status === 'empty' || room.status === 'vacant')
},
periodOptions() {
if (this.form.paymentType === 'monthly') {
return Array.from({ length: 24 }, (_, i) => ({
label: `${i + 1}个月`,
value: i + 1
}))
} else if (this.form.paymentType === 'quarterly') {
return Array.from({ length: 8 }, (_, i) => ({
label: `${i + 1}个季度`,
value: i + 1
}))
} else if (this.form.paymentType === 'half_year') {
return Array.from({ length: 6 }, (_, i) => ({
label: `${i + 1}个半年`,
value: i + 1
}))
} else {
return Array.from({ length: 10 }, (_, i) => ({
label: `${i + 1}`,
value: i + 1
}))
}
},
canSubmit() {
return this.form.roomId && this.form.renterName.trim() && this.form.startDate && this.form.endDate && this.form.rent
}
},
onLoad(options) {
this.loadApartments()
this.form.startDate = this.formatDate(new Date())
if (options.id) {
// 编辑模式
this.isEdit = true
this.rentalId = options.id
this.loadRentalDetail(options.id)
} else if (options.roomId) {
// 从房间预选
this.preSelectRoom(options.roomId)
}
},
watch: {
'form.paymentType': function() {
if (!this.isEdit) {
this.leasePeriod = 1
this.updateEndDate()
}
},
leasePeriod: function() {
if (!this.isEdit) {
this.updateEndDate()
}
},
'form.startDate': function() {
if (!this.isEdit) {
this.updateEndDate()
}
}
},
methods: {
async loadApartments() {
try {
const res = await apartmentApi.list()
if (res.data) {
this.apartments = res.data
}
} catch (error) {
console.error('加载公寓列表失败:', error)
}
},
async loadRooms(apartmentId) {
try {
const res = await roomApi.list({ apartmentId })
if (res.data) {
this.rooms = res.data
}
} catch (error) {
console.error('加载房间列表失败:', error)
}
},
async loadRentalDetail(id) {
try {
const res = await rentalApi.getDetail(id)
if (res.data) {
const data = res.data
this.selectedApartmentId = data.Room?.Apartment?.id
await this.loadRooms(this.selectedApartmentId)
this.form = {
roomId: data.roomId,
renterName: data.Renter?.name || '',
renterPhone: data.Renter?.phone || '',
renterIdCard: data.Renter?.idCard || '',
paymentType: data.paymentType || 'monthly',
startDate: data.startDate,
endDate: data.endDate,
rent: String(data.rent || ''),
deposit: String(data.deposit || ''),
operator: data.operator || '',
waterStartReading: String(data.waterMeterStart || ''),
electricStartReading: String(data.electricityMeterStart || ''),
remark: data.remark || ''
}
}
} catch (error) {
console.error('加载租赁详情失败:', error)
uni.showToast({ title: '加载详情失败', icon: 'none' })
}
},
async preSelectRoom(roomId) {
try {
const res = await roomApi.getDetail(roomId)
if (res.data) {
const room = res.data
this.selectedApartmentId = room.apartmentId
await this.loadRooms(room.apartmentId)
this.form.roomId = roomId
this.form.rent = room.rent || ''
}
} catch (error) {
console.error('加载房间详情失败:', error)
}
},
selectApartment(id) {
this.selectedApartmentId = id
this.form.roomId = ''
this.loadRooms(id)
},
selectRoom(room) {
// 新增模式下检查房间是否已出租
if (!this.isEdit && room.status === 'rented') {
uni.showToast({ title: '该房间已出租', icon: 'none' })
return
}
this.form.roomId = room.id
this.form.rent = room.rent || ''
},
selectPaymentType(type) {
this.form.paymentType = type
},
selectPeriod(period) {
this.leasePeriod = period
},
onStartDateChange(e) {
this.form.startDate = e.detail.value
},
onEndDateChange(e) {
this.form.endDate = e.detail.value
},
updateEndDate() {
if (!this.form.startDate) return
const startDate = new Date(this.form.startDate)
const endDate = new Date(startDate)
if (this.form.paymentType === 'monthly') {
endDate.setMonth(endDate.getMonth() + this.leasePeriod)
} else if (this.form.paymentType === 'quarterly') {
endDate.setMonth(endDate.getMonth() + this.leasePeriod * 3)
} else if (this.form.paymentType === 'half_year') {
endDate.setMonth(endDate.getMonth() + this.leasePeriod * 6)
} else {
endDate.setFullYear(endDate.getFullYear() + this.leasePeriod)
}
this.form.endDate = this.formatDate(endDate)
},
async submitForm() {
if (!this.canSubmit) return
this.submitLoading = true
try {
const submitData = {
roomId: this.form.roomId,
renterName: this.form.renterName.trim(),
renterPhone: this.form.renterPhone.trim() || null,
renterIdCard: this.form.renterIdCard.trim() || null,
paymentType: this.form.paymentType,
startDate: this.form.startDate,
endDate: this.form.endDate,
rent: parseFloat(this.form.rent),
deposit: parseFloat(this.form.deposit) || 0,
operator: this.form.operator.trim() || null,
waterStartReading: this.form.waterStartReading ? parseFloat(this.form.waterStartReading) : null,
electricStartReading: this.form.electricStartReading ? parseFloat(this.form.electricStartReading) : null,
remark: this.form.remark.trim() || null
}
if (this.isEdit) {
await rentalApi.update(this.rentalId, submitData)
} else {
await rentalApi.createWithRenter(submitData)
}
this.showSuccess = true
} catch (error) {
console.error(this.isEdit ? '修改失败:' : '办理入住失败:', error)
uni.showToast({
title: this.isEdit ? '修改失败' : '办理失败',
icon: 'none'
})
} finally {
this.submitLoading = false
}
},
closeSuccess() {
this.showSuccess = false
uni.navigateBack()
},
formatDate(date) {
const d = new Date(date)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
},
goBack() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.rental-add-page {
min-height: 100vh;
background: linear-gradient(180deg, #F0F7FF 0%, #F8FAFC 200rpx);
display: flex;
flex-direction: column;
}
/* 导航栏 */
.custom-nav {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20rpx);
border-bottom: 1rpx solid rgba(226, 232, 240, 0.6);
position: sticky;
top: 0;
z-index: 100;
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 24rpx;
height: 96rpx;
}
.nav-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.back-btn:active {
background: rgba(37, 99, 235, 0.1);
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
color: #1E293B;
}
.placeholder {
width: 72rpx;
}
/* 表单滚动区 */
.form-scroll {
flex: 1;
}
.form-container {
padding: 32rpx;
}
/* 页面标题 */
.page-header {
margin-bottom: 40rpx;
}
.header-title {
display: block;
font-size: 44rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
}
.header-subtitle {
font-size: 28rpx;
color: #64748B;
}
/* 卡片样式 */
.section-card {
background: #FFFFFF;
border-radius: 28rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
border: 1rpx solid rgba(226, 232, 240, 0.5);
}
.section-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 28rpx;
}
.section-icon {
width: 48rpx;
height: 48rpx;
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
}
.room-icon { background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%); }
.renter-icon { background: linear-gradient(135deg, #10B981 0%, #059669 100%); }
.contract-icon { background: linear-gradient(135deg, #8B5CF6 0%, #7C3AED 100%); }
.meter-icon { background: linear-gradient(135deg, #F59E0B 0%, #D97706 100%); }
.remark-icon { background: linear-gradient(135deg, #EC4899 0%, #DB2777 100%); }
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #1E293B;
flex: 1;
}
.section-badge {
font-size: 22rpx;
color: #10B981;
background: rgba(16, 185, 129, 0.1);
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.section-hint {
font-size: 24rpx;
color: #94A3B8;
}
/* 表单组 */
.form-group {
margin-bottom: 28rpx;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-row {
display: flex;
gap: 24rpx;
}
.form-group.half {
flex: 1;
margin-bottom: 0;
}
.group-label {
display: block;
font-size: 26rpx;
color: #475569;
margin-bottom: 16rpx;
font-weight: 500;
}
.required {
color: #EF4444;
}
/* 输入框 */
.input-wrapper {
display: flex;
align-items: center;
background: #F8FAFC;
border-radius: 16rpx;
padding: 0 24rpx;
height: 88rpx;
border: 2rpx solid transparent;
transition: all 0.2s ease;
}
.input-wrapper:focus-within {
background: #FFFFFF;
border-color: #3B82F6;
box-shadow: 0 0 0 4rpx rgba(59, 130, 246, 0.1);
}
.input-icon {
margin-right: 16rpx;
}
.form-input {
flex: 1;
height: 100%;
font-size: 30rpx;
color: #1E293B;
background: transparent;
}
.input-wrapper.money {
padding-left: 32rpx;
}
.currency {
font-size: 32rpx;
color: #3B82F6;
font-weight: 600;
margin-right: 12rpx;
}
/* 公寓选择器 */
.apartment-selector {
margin: 0 -32rpx;
padding: 0 32rpx;
}
.apartment-scroll {
width: 100%;
}
.apartment-list {
display: flex;
gap: 16rpx;
padding-bottom: 8rpx;
}
.apartment-item {
flex-shrink: 0;
min-width: 200rpx;
background: #F8FAFC;
border-radius: 20rpx;
padding: 20rpx 24rpx;
border: 2rpx solid transparent;
position: relative;
transition: all 0.2s ease;
}
.apartment-item.active {
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
border-color: #3B82F6;
}
.apt-content {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.apt-name {
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
}
.apt-count {
font-size: 22rpx;
color: #64748B;
}
.apt-check {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 32rpx;
height: 32rpx;
background: #3B82F6;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(59, 130, 246, 0.4);
}
/* 房间网格 */
.room-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.room-card {
background: #F8FAFC;
border-radius: 20rpx;
padding: 20rpx;
border: 2rpx solid transparent;
transition: all 0.2s ease;
}
.room-card.active {
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
border-color: #3B82F6;
}
.room-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12rpx;
}
.room-number {
font-size: 32rpx;
font-weight: 700;
color: #1E293B;
}
.room-check {
width: 28rpx;
height: 28rpx;
background: #3B82F6;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.room-info {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.room-price {
font-size: 26rpx;
color: #EF4444;
font-weight: 600;
}
.price-unit {
font-size: 20rpx;
font-weight: 400;
}
.room-area {
font-size: 22rpx;
color: #94A3B8;
}
.empty-rooms {
grid-column: span 3;
display: flex;
flex-direction: column;
align-items: center;
padding: 48rpx;
color: #94A3B8;
font-size: 26rpx;
gap: 16rpx;
}
/* 付租方式 */
.payment-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
}
.payment-item {
background: #F8FAFC;
border-radius: 16rpx;
padding: 20rpx 12rpx;
text-align: center;
border: 2rpx solid transparent;
position: relative;
transition: all 0.2s ease;
}
.payment-item.active {
background: linear-gradient(135deg, #EFF6FF 0%, #DBEAFE 100%);
border-color: #3B82F6;
}
.payment-label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #1E293B;
margin-bottom: 4rpx;
}
.payment-desc {
display: block;
font-size: 20rpx;
color: #64748B;
}
.payment-item.active .payment-label {
color: #3B82F6;
}
.payment-indicator {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 24rpx;
height: 24rpx;
background: #3B82F6;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
/* 租期选择 */
.period-scroll {
margin: 0 -32rpx;
padding: 0 32rpx;
}
.period-list {
display: flex;
gap: 12rpx;
padding-bottom: 8rpx;
}
.period-tag {
flex-shrink: 0;
padding: 16rpx 28rpx;
background: #F8FAFC;
border-radius: 12rpx;
font-size: 26rpx;
color: #475569;
border: 2rpx solid transparent;
transition: all 0.2s ease;
}
.period-tag.active {
background: #3B82F6;
color: #FFFFFF;
border-color: #3B82F6;
}
/* 日期选择 */
.picker-box, .static-box {
display: flex;
align-items: center;
justify-content: space-between;
background: #F8FAFC;
border-radius: 16rpx;
padding: 0 24rpx;
height: 88rpx;
}
.picker-text {
font-size: 30rpx;
color: #1E293B;
}
.picker-text.placeholder {
color: #94A3B8;
}
.static-text {
font-size: 30rpx;
color: #64748B;
}
/* 水电表 */
.meter-grid {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.meter-item {
display: flex;
align-items: center;
gap: 20rpx;
background: #F8FAFC;
border-radius: 20rpx;
padding: 20rpx 24rpx;
}
.meter-icon-box {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.meter-icon-box.water { background: rgba(59, 130, 246, 0.1); }
.meter-icon-box.electric { background: rgba(245, 158, 11, 0.1); }
.meter-content {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
}
.meter-label {
font-size: 28rpx;
color: #475569;
}
.meter-input-box {
display: flex;
align-items: center;
gap: 12rpx;
}
.meter-input {
width: 120rpx;
height: 56rpx;
background: #FFFFFF;
border-radius: 12rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #1E293B;
border: 2rpx solid #E2E8F0;
}
.meter-unit {
font-size: 24rpx;
color: #64748B;
}
/* 备注 */
.textarea-wrapper {
position: relative;
}
.form-textarea {
width: 100%;
height: 180rpx;
background: #F8FAFC;
border-radius: 16rpx;
padding: 24rpx;
font-size: 30rpx;
color: #1E293B;
border: 2rpx solid transparent;
transition: all 0.2s ease;
}
.form-textarea:focus {
background: #FFFFFF;
border-color: #3B82F6;
}
.char-count {
position: absolute;
bottom: 16rpx;
right: 20rpx;
font-size: 22rpx;
color: #94A3B8;
}
/* 提交按钮区域 */
.submit-section {
margin-top: 32rpx;
margin-bottom: 32rpx;
}
.submit-section .submit-btn {
width: 100%;
height: 96rpx;
background: #CBD5E1;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
border: none;
transition: all 0.3s ease;
}
.submit-section .submit-btn.active {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
box-shadow: 0 8rpx 24rpx rgba(59, 130, 246, 0.35);
}
.submit-section .submit-btn.active:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
}
/* 底部提交栏 */
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(20rpx);
border-top: 1rpx solid rgba(226, 232, 240, 0.6);
padding: 20rpx 32rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
z-index: 100;
}
.submit-content {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
}
.submit-info {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.info-label {
font-size: 22rpx;
color: #94A3B8;
}
.info-value {
font-size: 28rpx;
font-weight: 600;
color: #10B981;
}
.info-value.incomplete {
color: #F59E0B;
}
.submit-btn {
min-width: 240rpx;
height: 88rpx;
background: #CBD5E1;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 600;
color: #FFFFFF;
border: none;
transition: all 0.3s ease;
}
.submit-btn.active {
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
box-shadow: 0 8rpx 24rpx rgba(59, 130, 246, 0.35);
}
.submit-btn.active:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
}
.btn-loading {
display: flex;
gap: 12rpx;
align-items: center;
}
.loading-dot {
width: 12rpx;
height: 12rpx;
background: #FFFFFF;
border-radius: 50%;
animation: bounce 1.4s ease-in-out infinite both;
}
.loading-dot:nth-child(1) { animation-delay: -0.32s; }
.loading-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
/* 成功弹窗 */
.success-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
animation: fadeIn 0.3s ease;
}
.success-content {
background: #FFFFFF;
border-radius: 32rpx;
padding: 56rpx 64rpx;
display: flex;
flex-direction: column;
align-items: center;
animation: scaleIn 0.3s ease;
}
.success-icon {
margin-bottom: 32rpx;
}
.success-circle {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #10B981 0%, #059669 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(16, 185, 129, 0.35);
}
.success-title {
font-size: 40rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
}
.success-desc {
font-size: 28rpx;
color: #64748B;
margin-bottom: 40rpx;
}
.success-btn {
width: 320rpx;
height: 88rpx;
background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
border-radius: 44rpx;
font-size: 30rpx;
font-weight: 600;
color: #FFFFFF;
border: none;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
</style>