Files
uni-fans-score/pages/device/detail.vue
T

749 lines
18 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 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">{{ depositAmount }}</text>
</view>
<button class="action-btn" :class="hasActiveOrder ? 'return' : 'rent'" @click="handleRent">
{{ hasActiveOrder ? '归还设备' : '立即租借' }}
</button>
</view>
</view>
</template>
<script>
import {
getDeviceInfo,
rentPowerBank,
updateOrderPackage
} from '@/config/user.js'
export default {
data() {
return {
deviceInfo: {},
deviceId: '',
deviceLocation: '一号教学楼大厅',
batteryLevel: 95,
hasActiveOrder: false,
deviceStatus: {
text: '可使用',
class: 'available'
},
selectedPackage: 1,
packages: [],
depositAmount: "99.00", // 默认押金金额
isLoggedIn: true,
phoneNumber: ''
}
},
onLoad(options) {
// console.log(options);
this.deviceId = options.deviceNo
// 如果URL中包含了feeConfig参数,直接使用它
if (options.feeConfig) {
try {
console.log('从URL获取到feeConfig:', options.feeConfig)
const feeConfigStr = decodeURIComponent(options.feeConfig)
// 存储到设备信息中,这样后续处理逻辑可以保持不变
this.deviceInfo = { ...this.deviceInfo, feeConfig: feeConfigStr }
// 马上解析feeConfig并生成套餐选项
this.parseFeeConfig()
} catch (e) {
console.error('解析URL中的feeConfig失败:', e)
// 如果解析失败,继续正常流程获取设备信息
this.checkOrderStatus()
this.getDeviceInfo()
}
} else {
// 正常流程
this.checkOrderStatus() // 新增状态检查
console.log(options.deviceNo);
this.getDeviceInfo()
}
},
methods: {
// 检查登录状态和订单
async getDeviceInfo() {
const res = await getDeviceInfo(this.deviceId);
if (res.code == 200) {
this.deviceInfo = res.data.device || {};
// 更新设备位置信息
if (this.deviceInfo.deviceLocation) {
this.deviceLocation = this.deviceInfo.deviceLocation;
} else if (res.data.position && res.data.position.name) {
this.deviceLocation = res.data.position.name;
}
// 获取押金金额
if (this.deviceInfo.depositAmount) {
this.depositAmount = this.deviceInfo.depositAmount;
}
// 更新设备状态
if (this.deviceInfo.status) {
if (this.deviceInfo.status === 'online') {
this.deviceStatus = {
text: '可使用',
class: 'available'
};
} else if (this.deviceInfo.status === 'offline') {
this.deviceStatus = {
text: '离线',
class: 'offline'
};
}
}
// 使用抽取的方法解析feeConfig并生成套餐选项
this.parseFeeConfig();
}
},
// 显示登录提示
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) {
const order = result.order; // 假设后端返回 order 对象
// 检查订单状态
if (order.status === 'waiting_for_payment') {
// 跳转支付页面,带上订单ID
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${this.deviceId}`
})
}else if(order.status === 'in_used'){
// 如果有正在进行的订单,跳转到归还页面,带上设备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
}
// 直接提交订单,不显示确认对话框
this.submitRentOrder()
},
// 提交租借订单
async submitRentOrder() {
try {
uni.showLoading({
title: '处理中'
})
// 获取选中的套餐信息
const selectedPkg = this.packages[this.selectedPackage]
// 调用设备租借接口
const rentResult = await rentPowerBank(this.deviceId, this.phoneNumber)
if (rentResult.code !== 200) {
throw new Error(rentResult.msg || '设备租借失败')
}
// 获取后端返回的订单信息
const order = rentResult.data
// --- 新增:立即更新订单套餐信息 ---
try {
let packageTimeMinutes = 0;
if (selectedPkg.time.includes('小时')) {
packageTimeMinutes = parseInt(selectedPkg.time) * 60;
} else if (selectedPkg.time.includes('分钟')) {
packageTimeMinutes = parseInt(selectedPkg.time);
} else {
packageTimeMinutes = parseInt(selectedPkg.time) * 60; // 默认按小时处理
}
const updateRes = await updateOrderPackage({
orderId: order.orderId,
packageTime: packageTimeMinutes,
packagePrice: parseFloat(selectedPkg.price)
});
if (updateRes.code !== 200) {
console.warn("更新订单套餐信息失败:", updateRes.msg);
// 这里可以选择是否提示用户或阻止流程,当前不阻止
} else {
console.log("订单套餐信息已提前更新");
}
} catch (updateError) {
console.error("更新订单套餐信息时出错:", updateError);
// 即使更新失败,也继续尝试跳转支付,让用户完成支付
}
// --- 更新结束 ---
// --- 新增:计算总金额 ---
const deposit = parseFloat(this.depositAmount);
const packagePrice = parseFloat(selectedPkg.price);
const totalAmount = (deposit + packagePrice).toFixed(2);
// --- 计算结束 ---
uni.hideLoading()
// 跳转到订单支付页面,传递订单ID、套餐信息和总金额
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&packageTimeHours=${selectedPkg.time.replace('小时', '')}&packagePrice=${selectedPkg.price}&totalAmount=${totalAmount}&depositAmount=${this.depositAmount}${this.deviceInfo && this.deviceInfo.feeConfig ? '&feeConfig=' + encodeURIComponent(this.deviceInfo.feeConfig) : ''}`
})
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || '租借失败,请重试',
icon: 'none'
})
}
},
// 单独抽取解析feeConfig的逻辑
parseFeeConfig() {
if (this.deviceInfo.feeConfig) {
try {
const feeConfig = JSON.parse(this.deviceInfo.feeConfig);
// 检查是否为新格式 [{"hour":1,"timesPrice":4},{"hour":3,"timesPrice":10},{"hour":5,"timesPrice":15}]
if (feeConfig.length > 0 && 'hour' in feeConfig[0] && 'timesPrice' in feeConfig[0]) {
// 新格式处理 - 直接使用所有套餐
this.packages = feeConfig.map(pkg => {
const hour = pkg.hour;
const price = pkg.timesPrice;
const unitPrice = (price / hour).toFixed(2);
return {
time: `${hour}小时`,
price: price.toFixed(2),
unitPrice: unitPrice,
hour: hour // 添加小时信息,用于后续处理
};
});
// 按小时排序
this.packages.sort((a, b) => a.hour - b.hour);
} else {
// 旧格式处理
// 通常使用common规格的配置
const commonConfig = feeConfig.find(item => item.specCode === 'common') || feeConfig[0];
if (commonConfig) {
// 根据收费类型生成套餐
if (this.deviceInfo.feeType === 'hour') {
// 按小时收费
const hourPrice = commonConfig.hourPrice > 0 ? commonConfig.hourPrice : commonConfig.timesPrice / 6;
this.packages = [
{
time: '1小时',
price: hourPrice.toFixed(2),
unitPrice: hourPrice.toFixed(2),
hour: 1
},
{
time: '6小时',
price: (hourPrice * 6).toFixed(2),
unitPrice: hourPrice.toFixed(2),
hour: 6
},
{
time: '12小时',
price: (hourPrice * 12).toFixed(2),
unitPrice: hourPrice.toFixed(2),
hour: 12
}
];
} else if (this.deviceInfo.feeType === 'times') {
// 按次收费
const timesPrice = commonConfig.timesPrice;
this.packages = [
{
time: '1次',
price: timesPrice.toFixed(2),
unitPrice: timesPrice.toFixed(2),
hour: 1
}
];
} else {
// 默认套餐
this.packages = [
{
time: '1小时',
price: '2.00',
unitPrice: '2.00',
hour: 1
},
{
time: '6小时',
price: '10.00',
unitPrice: '1.67',
hour: 6
},
{
time: '12小时',
price: '15.00',
unitPrice: '1.25',
hour: 12
}
];
}
}
}
// 默认选中中间套餐
this.selectedPackage = Math.min(1, this.packages.length - 1);
} catch (e) {
console.error('解析设备费用配置失败:', e);
// 使用默认套餐
this.packages = [
{
time: '1小时',
price: '2.00',
unitPrice: '2.00',
hour: 1
},
{
time: '6小时',
price: '10.00',
unitPrice: '1.67',
hour: 6
},
{
time: '12小时',
price: '15.00',
unitPrice: '1.25',
hour: 12
}
];
// 默认选中中间套餐
this.selectedPackage = 1;
}
} else {
// 如果没有feeConfig,使用默认套餐
this.packages = [
{
time: '1小时',
price: '2.00',
unitPrice: '2.00',
hour: 1
},
{
time: '6小时',
price: '10.00',
unitPrice: '1.67',
hour: 6
},
{
time: '12小时',
price: '15.00',
unitPrice: '1.25',
hour: 12
}
];
// 默认选中中间套餐
this.selectedPackage = 1;
}
}
}
}
</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>