530 lines
10 KiB
Vue
530 lines
10 KiB
Vue
<template>
|
||
<view class="detail-container">
|
||
<!-- 设备状态卡片 -->
|
||
<view class="device-card">
|
||
<view class="device-header">
|
||
<view class="device-title">
|
||
<text class="name">共享风扇</text>
|
||
<text class="id">设备号:{{ deviceId }}</text>
|
||
</view>
|
||
<view class="status" :class="deviceStatus.class">{{ deviceStatus.text }}</view>
|
||
</view>
|
||
<view class="device-info">
|
||
<view class="info-item">
|
||
<text class="label">设备位置</text>
|
||
<text class="value">{{ deviceLocation }}</text>
|
||
</view>
|
||
<view class="info-item">
|
||
<text class="label">电池电量</text>
|
||
<text class="value">{{ batteryLevel }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 套餐选择 -->
|
||
<view class="package-section" v-if="!hasActiveOrder">
|
||
<view class="section-title">选择套餐</view>
|
||
<view class="package-list">
|
||
<view v-for="(pkg, index) in packages" :key="index" class="package-item"
|
||
:class="{ active: selectedPackage === index }" @click="selectPackage(index)">
|
||
<view class="package-content">
|
||
<text class="time">{{ pkg.time }}</text>
|
||
<text class="price">¥{{ pkg.price }}</text>
|
||
</view>
|
||
<text class="unit-price">约{{ pkg.unitPrice }}元/小时</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 手机号输入 -->
|
||
<view class="phone-section" v-if="!hasActiveOrder">
|
||
<view class="section-title">联系方式</view>
|
||
<view class="phone-input-wrap">
|
||
<input
|
||
type="number"
|
||
class="phone-input"
|
||
maxlength="11"
|
||
placeholder="请输入手机号码"
|
||
v-model="phoneNumber"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 使用说明 -->
|
||
<view class="notice-section">
|
||
<view class="section-title">使用说明</view>
|
||
<view class="notice-list">
|
||
<view class="notice-item">
|
||
<view class="dot"></view>
|
||
<text>请在使用前检查设备是否完好</text>
|
||
</view>
|
||
<view class="notice-item">
|
||
<view class="dot"></view>
|
||
<text>超出使用时间将自动按小时计费</text>
|
||
</view>
|
||
<view class="notice-item">
|
||
<view class="dot"></view>
|
||
<text>请在指定区域内使用设备</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar">
|
||
<view class="price-info" v-if="!hasActiveOrder">
|
||
<text class="deposit-text">押金:</text>
|
||
<text class="deposit-amount">¥99</text>
|
||
</view>
|
||
<button class="action-btn" :class="hasActiveOrder ? 'return' : 'rent'" @click="handleRent">
|
||
{{ hasActiveOrder ? '归还设备' : '立即租借' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getDeviceInfo
|
||
} from '@/config/user.js'
|
||
export default {
|
||
data() {
|
||
return {
|
||
deviceInfo: {},
|
||
deviceId: '',
|
||
deviceLocation: '一号教学楼大厅',
|
||
batteryLevel: 95,
|
||
hasActiveOrder: false,
|
||
deviceStatus: {
|
||
text: '可使用',
|
||
class: 'available'
|
||
},
|
||
selectedPackage: 1,
|
||
packages: [{
|
||
time: '1小时',
|
||
price: '2.00',
|
||
unitPrice: '2.00'
|
||
},
|
||
{
|
||
time: '4小时',
|
||
price: '6.00',
|
||
unitPrice: '1.50'
|
||
},
|
||
{
|
||
time: '12小时',
|
||
price: '15.00',
|
||
unitPrice: '1.25'
|
||
}
|
||
],
|
||
isLoggedIn: true,
|
||
phoneNumber: ''
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
// console.log(options);
|
||
this.deviceId = options.deviceNo
|
||
console.log(options.deviceNo);
|
||
this.getDeviceInfo()
|
||
},
|
||
methods: {
|
||
// 检查登录状态和订单
|
||
async getDeviceInfo() {
|
||
const res = await getDeviceInfo(this.deviceId);
|
||
if (res.code == 200) {
|
||
this.deviceInfo = res.data
|
||
}
|
||
},
|
||
|
||
// 显示登录提示
|
||
showLoginTip() {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '请先登录后再操作',
|
||
confirmText: '去登录',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
uni.navigateTo({
|
||
url: '/pages/login/index'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
selectPackage(index) {
|
||
this.selectedPackage = index;
|
||
},
|
||
|
||
// 检查订单状态
|
||
async checkOrderStatus() {
|
||
try {
|
||
// 调用接口检查是否有进行中的订单
|
||
const result = await this.$api.checkActiveOrder()
|
||
|
||
if (result.hasOrder) {
|
||
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
|
||
uni.redirectTo({
|
||
url: `/pages/device/return?deviceId=${this.deviceId}`
|
||
})
|
||
}
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '订单状态查询失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
|
||
// 处理租借操作
|
||
handleRent() {
|
||
if (!this.isLoggedIn) {
|
||
this.showLoginTip()
|
||
return
|
||
}
|
||
|
||
// 添加手机号验证
|
||
if (!this.phoneNumber) {
|
||
uni.showToast({
|
||
title: '请输入手机号码',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
// 验证手机号格式
|
||
if (!/^1[3-9]\d{9}$/.test(this.phoneNumber)) {
|
||
uni.showToast({
|
||
title: '请输入正确的手机号码',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
|
||
const selectedPkg = this.packages[this.selectedPackage]
|
||
uni.showModal({
|
||
title: '确认租借',
|
||
content: `确认支付押金¥99.00及${selectedPkg.time}套餐费用¥${selectedPkg.price}?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
this.submitRentOrder()
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
// 提交租借订单
|
||
async submitRentOrder() {
|
||
try {
|
||
uni.showLoading({
|
||
title: '处理中'
|
||
})
|
||
|
||
const selectedPkg = this.packages[this.selectedPackage]
|
||
// 添加手机号到请求参数
|
||
const result = await this.$api.createOrder({
|
||
deviceId: this.deviceId,
|
||
packageId: this.selectedPackage,
|
||
duration: selectedPkg.time,
|
||
amount: selectedPkg.price,
|
||
phone: this.phoneNumber
|
||
})
|
||
|
||
uni.hideLoading()
|
||
|
||
if (result.success) {
|
||
uni.showToast({
|
||
title: '租借成功',
|
||
icon: 'success'
|
||
})
|
||
// 跳转到使用中页面
|
||
setTimeout(() => {
|
||
uni.redirectTo({
|
||
url: `/pages/return/index?deviceId=${this.deviceId}&orderId=${result.orderId}`
|
||
})
|
||
}, 1500)
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '租借失败,请重试',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.detail-container {
|
||
min-height: 100vh;
|
||
background: #f8f8f8;
|
||
padding: 30rpx;
|
||
padding-bottom: 180rpx;
|
||
box-sizing: border-box;
|
||
|
||
.device-card {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.device-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
|
||
.device-title {
|
||
.name {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.id {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.status {
|
||
padding: 8rpx 24rpx;
|
||
border-radius: 24rpx;
|
||
font-size: 24rpx;
|
||
|
||
&.available {
|
||
background: #E8F5E9;
|
||
color: #4CAF50;
|
||
}
|
||
|
||
&.in-use {
|
||
background: #E3F2FD;
|
||
color: #1976D2;
|
||
}
|
||
}
|
||
}
|
||
|
||
.device-info {
|
||
.info-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.value {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.package-section {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.package-list {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
|
||
.package-item {
|
||
flex: 1;
|
||
margin: 0 10rpx;
|
||
padding: 24rpx;
|
||
background: #F5F5F5;
|
||
border-radius: 16rpx;
|
||
text-align: center;
|
||
transition: all 0.3s;
|
||
|
||
&:first-child {
|
||
margin-left: 0;
|
||
}
|
||
|
||
&:last-child {
|
||
margin-right: 0;
|
||
}
|
||
|
||
&.active {
|
||
background: #E3F2FD;
|
||
border: 2rpx solid #1976D2;
|
||
|
||
.package-content {
|
||
|
||
.time,
|
||
.price {
|
||
color: #1976D2;
|
||
}
|
||
}
|
||
}
|
||
|
||
.package-content {
|
||
margin-bottom: 8rpx;
|
||
|
||
.time {
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
display: block;
|
||
}
|
||
|
||
.price {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.unit-price {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.phone-section {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.phone-input-wrap {
|
||
.phone-input {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: #F5F5F5;
|
||
border-radius: 16rpx;
|
||
padding: 0 30rpx;
|
||
font-size: 28rpx;
|
||
box-sizing: border-box;
|
||
|
||
&::placeholder {
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.notice-section {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.notice-list {
|
||
.notice-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.dot {
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
background: #1976D2;
|
||
border-radius: 50%;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
text {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.bottom-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: #fff;
|
||
padding: 20rpx 30rpx;
|
||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||
|
||
.price-info {
|
||
.deposit-text {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.deposit-amount {
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #FF9800;
|
||
}
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
margin-left: 30rpx;
|
||
height: 88rpx;
|
||
border-radius: 44rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
|
||
&.rent {
|
||
background: linear-gradient(135deg, #1976D2, #42A5F5);
|
||
color: #fff;
|
||
}
|
||
|
||
&.return {
|
||
background: linear-gradient(135deg, #FF9800, #FFB74D);
|
||
color: #fff;
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.98);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |