1373 lines
36 KiB
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>
|