HomeLease/components/renew-modal/renew-modal.vue
2025-08-22 09:41:56 +08:00

426 lines
8.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view v-if="visible" class="renew-modal-overlay" @click="handleOverlayClick">
<view class="renew-modal" @click.stop>
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">设备续费</text>
<view class="close-btn" @click="handleClose">
<text class="close-icon">×</text>
</view>
</view>
<!-- 设备信息 -->
<view v-if="device" class="device-info">
<view class="device-item">
<image :src="device.image" class="device-image" mode="aspectFill" />
<view class="device-details">
<text class="device-name">{{ device.name }}</text>
<text :class="device.status" class="device-status">
{{ getStatusText(device.status) }}
</text>
<text v-if="device.endTime" class="device-time">
到期时间: {{ formatTime(device.endTime) }}
</text>
</view>
</view>
</view>
<!-- 套餐列表 -->
<view class="package-section">
<text class="section-title">选择续费套餐</text>
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<text class="loading-text">加载中...</text>
</view>
<!-- 套餐列表 -->
<view v-else-if="packageList.length > 0" class="package-list">
<view
v-for="(item, index) in packageList"
:key="index"
:class="{ active: selectedPackage && selectedPackage.id === item.id }"
class="package-item"
@click="handleSelectPackage(item)"
>
<view class="package-info">
<text class="package-name">{{ item.name || `套餐${index + 1}` }}</text>
<text class="package-desc">{{ item.description || item.period || '暂无描述' }}</text>
</view>
<view class="package-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ item.price || item.amount || '0.00' }}</text>
</view>
<view v-if="selectedPackage && selectedPackage.id === item.id" class="select-icon">
<text class="check-icon">✓</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-state">
<text class="empty-text">暂无可用套餐</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="modal-footer">
<button class="cancel-btn" @click="handleClose">取消</button>
<button :disabled="!selectedPackage" class="confirm-btn" @click="handleConfirm">
确认续费
</button>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'RenewModal',
props: {
visible: {
type: Boolean,
default: false,
},
device: {
type: Object,
default: null,
},
packageList: {
type: Array,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedPackage: null,
}
},
watch: {
visible(newVal) {
if (!newVal) {
// 当弹窗关闭时,重置选中的套餐
this.selectedPackage = null
}
},
},
methods: {
// 处理遮罩层点击
handleOverlayClick() {
this.handleClose()
},
// 关闭弹窗
handleClose() {
this.$emit('close')
},
// 选择套餐
handleSelectPackage(combo) {
this.selectedPackage = combo
this.$emit('select-package', combo)
},
// 确认续费
handleConfirm() {
if (!this.selectedPackage) {
uni.showToast({
title: '请选择套餐',
icon: 'none',
})
return
}
this.$emit('confirm-renew', this.selectedPackage)
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
available: '可租用',
rented: '已出租',
maintenance: '维护中',
scrapped: '报废',
normal: '正常',
}
return statusMap[status] || '未知状态'
},
// 格式化时间
formatTime(timeStr) {
if (!timeStr) return ''
const date = new Date(timeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
},
},
}
</script>
<style lang="scss" scoped>
.renew-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.renew-modal {
width: 90%;
max-width: 600rpx;
max-height: 80vh;
background-color: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: #f5f5f5;
}
.close-icon {
font-size: 40rpx;
color: #999;
line-height: 1;
}
.device-info {
padding: 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.device-item {
display: flex;
align-items: center;
}
.device-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 20rpx;
}
.device-details {
flex: 1;
display: flex;
flex-direction: column;
}
.device-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.device-status {
font-size: 24rpx;
margin-bottom: 8rpx;
&.available {
color: #52c41a;
}
&.rented {
color: #1890ff;
}
&.maintenance {
color: #faad14;
}
&.scrapped {
color: #ff4d4f;
}
&.normal {
color: #52c41a;
}
}
.device-time {
font-size: 24rpx;
color: #666;
}
.package-section {
flex: 1;
padding: 30rpx;
overflow-y: auto;
}
.section-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.loading-text {
font-size: 28rpx;
color: #999;
}
.package-list {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.package-item {
display: flex;
align-items: center;
padding: 24rpx;
border: 2rpx solid #f0f0f0;
border-radius: 12rpx;
position: relative;
transition: all 0.3s ease;
&.active {
border-color: #1890ff;
background-color: #f6ffed;
}
&:active {
transform: scale(0.98);
}
}
.package-info {
flex: 1;
display: flex;
flex-direction: column;
}
.package-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.package-desc {
font-size: 24rpx;
color: #666;
}
.package-price {
display: flex;
align-items: baseline;
margin-right: 20rpx;
}
.price-symbol {
font-size: 24rpx;
color: #ff4d4f;
margin-right: 4rpx;
}
.price-value {
font-size: 32rpx;
font-weight: bold;
color: #ff4d4f;
}
.select-icon {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: #f15a04;
display: flex;
align-items: center;
justify-content: center;
}
.check-icon {
font-size: 24rpx;
color: #fff;
line-height: 1;
}
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 60rpx 0;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
.modal-footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1rpx solid #f0f0f0;
}
.cancel-btn,
.confirm-btn {
flex: 1;
height: 80rpx;
border-radius: 40rpx;
font-size: 28rpx;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.cancel-btn {
background-color: #f5f5f5;
color: #666;
}
.confirm-btn {
background-color: #f15a04;
color: #fff;
&:disabled {
background-color: #d9d9d9;
color: #999;
}
}
</style>