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

764 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="container">
<!-- 顶部设备信息 -->
<view class="device-header">
<view class="device-status-card" :class="deviceStatus.class">
<view class="status-indicator"></view>
<text class="status-text">{{ deviceStatus.text }}</text>
</view>
<view class="device-title">
<text class="name">共享风扇</text>
<view class="device-meta">
<text class="id-label">设备号</text>
<text class="id-value">{{ deviceId }}</text>
</view>
</view>
</view>
<!-- 设备信息卡片 -->
<view class="card device-info-card">
<view class="card-row">
<view class="card-item">
<view class="item-icon location-icon">
<!-- <uni-icons type="location" size="24" color="#fff"></uni-icons> -->
<image src="/static/images/location-map.svg" mode="aspectFill"
style="width: 45rpx;height: 45rpx;"></image>
</view>
<view class="item-content">
<text class="item-label">当前位置</text>
<text class="item-value">{{ deviceLocation }}</text>
</view>
</view>
<view class="card-item">
<view class="item-icon battery-icon" :class="{ 'battery-low': batteryLevel < 20 }">
<image src="/static/images/Electricity.svg" mode="aspectFill"
style="width: 45rpx;height: 45rpx;"></image>
</view>
<view class="item-content">
<text class="item-label">电池电量</text>
<text class="item-value">{{ batteryLevel }}%</text>
</view>
</view>
</view>
</view>
<!-- 计费规则 -->
<view class="card pricing-card">
<view class="card-header">
<text class="card-title">计费规则</text>
</view>
<view class="pricing-banner">
<view class="pricing-main">
<text class="price">¥5.00</text>
<text class="unit">/小时</text>
</view>
<text class="cap-price">封顶 ¥99</text>
</view>
<view class="pricing-rules">
<view class="rule-item">
<view class="rule-dot"></view>
<text class="rule-text">前15分钟内归还<text class="highlight">免费</text></text>
</view>
<view class="rule-item">
<view class="rule-dot"></view>
<text class="rule-text">不足60分钟按60分钟计费</text>
</view>
<view class="rule-item">
<view class="rule-dot"></view>
<text class="rule-text">持续计费至99元视为买断</text>
</view>
</view>
</view>
<!-- 手机号输入 -->
<!-- <view class="card phone-card" v-if="!hasActiveOrder">
<view class="card-header">
<text class="card-title">联系方式</text>
</view>
<view class="phone-input-container">
<view class="input-wrapper">
<text class="prefix">+86</text>
<input type="number" class="phone-input" maxlength="11" placeholder="请输入手机号码"
v-model="phoneNumber" />
</view>
</view>
</view> -->
<!-- 使用须知 -->
<view class="card notice-card">
<view class="card-header">
<text class="card-title">使用须知</text>
</view>
<view class="notice-items">
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">请在使用前检查设备是否完好</text>
</view>
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">请在指定区域内使用设备</text>
</view>
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">归还时请确保设备完好避免损坏</text>
</view>
</view>
</view>
<!-- 底部操作区 -->
<view class="footer">
<view class="wechat-credit">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon"></image>
<view class="credit-text">
<text>微信支付分</text>
<text class="credit-divider">|</text>
<text class="credit-score">支付分200分及以上优享</text>
</view>
</view>
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
@click="handleRent('wx-score-pay')">
<text>{{ hasActiveOrder ? '归还设备' : '免押金租借' }}</text>
</button>
<view class="" style="align-items: center;align-content: center;text-align: center;line-height: 50rpx;"
@click="handleRent('wx-pay')">
无法免押点这里></view>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
getDeviceInfo,
rentPowerBank,
getOrderByOrderNoScore,
getOrderByOrderNoScorePayStatus,
getOrderByOrderNo,
updateOrderPackage,
cancelOrder
} from '@/config/user.js'
import {
URL
} from "@/config/url.js"
import {
initiateWeChatScorePayment
} from '@/util/index.js'
// 响应式状态
const deviceInfo = ref({})
const deviceId = ref('')
const deviceLocation = ref('一号教学楼大厅')
const batteryLevel = ref(95)
const hasActiveOrder = ref(false)
const deviceStatus = reactive({
text: '可使用',
class: 'available'
})
const isLoggedIn = ref(true)
const phoneNumber = ref('')
// 生命周期 onLoad 钩子
onLoad((options) => {
deviceId.value = options.deviceNo
checkOrderStatus()
console.log(options.deviceNo)
fetchDeviceInfo()
})
// 检查登录状态和订单
const fetchDeviceInfo = async () => {
const res = await getDeviceInfo(deviceId.value)
if (res.code == 200) {
deviceInfo.value = res.data.device || {}
// 更新设备位置信息
if (deviceInfo.value.deviceLocation) {
deviceLocation.value = deviceInfo.value.deviceLocation
} else if (res.data.position && res.data.position.name) {
deviceLocation.value = res.data.position.name
}
// 更新设备状态
if (deviceInfo.value.status) {
if (deviceInfo.value.status === 'online') {
deviceStatus.text = '可使用'
deviceStatus.class = 'available'
} else if (deviceInfo.value.status === 'offline') {
deviceStatus.text = '离线'
deviceStatus.class = 'offline'
}
}
}
}
// 显示登录提示
const showLoginTip = () => {
uni.showModal({
title: '提示',
content: '请先登录后再操作',
confirmText: '去登录',
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/pages/login/index'
})
}
}
})
}
// 检查订单状态
const checkOrderStatus = async () => {
try {
// 调用接口检查是否有进行中的订单
const result = await uni.$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=${deviceId.value}`
})
} else if (order.status === 'in_used') {
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
uni.redirectTo({
url: `/pages/device/return?deviceId=${deviceId.value}`
})
}
}
} catch (error) {
uni.showToast({
title: '订单状态查询失败',
icon: 'none'
})
}
}
// 处理租借操作
const handleRent = (payWay) => {
if (!isLoggedIn.value) {
showLoginTip()
return
}
// 直接提交订单,不显示确认对话框
submitRentOrder(payWay);
}
const selectedPkg = reactive({
time: '1小时',
price: '5.00'
})
const depositAmount = ref('99.00')
// 提交租借订单
const submitRentOrder = async (payWay) => {
try {
uni.showLoading({
title: '处理中'
})
// 调用设备租借接口
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value)
if (rentResult.code !== 200) {
throw new Error(rentResult.msg || '设备租借失败')
}
// 获取后端返回的订单信息
const order = rentResult.data
//当支付方式为押金支付时
if (payWay == 'wx-pay') {
//当支付方式为押金支付时
uni.hideLoading()
const res = await getOrderByOrderNo(order.orderNo);
console.log(res);
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(deviceInfo.value.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=${depositAmount.value}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
})
} else if (payWay == 'wx-score-pay') {
// 当支付方式为支付分支付时
uni.hideLoading()
// 获取支付分所需参数
const res = await getOrderByOrderNoScore(order.orderNo);
uni.hideLoading()
if (res && res.code === 200) {
try {
// 调用微信支付分小程序
const payResult = await initiateWeChatScorePayment(res);
// 支付成功后的逻辑处理 - 可以根据业务需求决定是否跳转或刷新页面
if (payResult.errCode == '0') {
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
console.log(res.data.orderStatus);
if (res.data.orderStatus == 'in_used') {
// 用户完成了支付流程,可以查询订单状态或跳转到订单页
uni.showToast({
title: '设备租借成功',
icon: 'success'
});
setTimeout(() => {
// 延迟跳转到租用中页面或订单页
uni.redirectTo({
url: '/pages/order/index'
});
}, 1500);
} else if (res.data.orderStatus == 'waiting_for_payment') {
uni.showToast({
title: '设备租借失败,订单已取消',
icon: 'error'
});
await cancelOrder({
orderId: order.orderNo
});
// 延迟跳转到租用中页面或订单页
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 1500)
}
// 用户取消等其他情况,不做特殊处理
}
// 用户取消等其他情况,不做特殊处理
} catch (payError) {
uni.showToast({
title: '支付分调用失败,请重试',
icon: 'none'
});
}
} else {
uni.showToast({
title: res?.msg || '获取支付参数失败',
icon: 'none'
});
}
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || '租借失败,请重试',
icon: 'none'
})
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background-color: #f5f7fa;
padding: 30rpx 30rpx 300rpx;
box-sizing: border-box;
}
// 顶部设备信息
.device-header {
display: flex;
flex-direction: column;
margin-bottom: 30rpx;
.device-status-card {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.status-indicator {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
margin-right: 10rpx;
}
&.available {
.status-indicator {
background-color: #10c469;
box-shadow: 0 0 10rpx rgba(16, 196, 105, 0.5);
}
.status-text {
color: #10c469;
}
}
&.offline {
.status-indicator {
background-color: #9a9a9a;
}
.status-text {
color: #9a9a9a;
}
}
.status-text {
font-size: 28rpx;
font-weight: 500;
}
}
.device-title {
.name {
font-size: 48rpx;
font-weight: bold;
color: #333;
}
.device-meta {
margin-top: 10rpx;
display: flex;
align-items: center;
.id-label {
font-size: 26rpx;
color: #999;
}
.id-value {
font-size: 26rpx;
color: #666;
}
}
}
}
// 卡片通用样式
.card {
background-color: #fff;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.03);
padding: 30rpx;
margin-bottom: 30rpx;
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
}
// 设备信息卡
.device-info-card {
.card-row {
display: flex;
justify-content: space-between;
}
.card-item {
display: flex;
align-items: center;
flex: 1;
.item-icon {
width: 60rpx;
height: 60rpx;
border-radius: 12rpx;
margin-right: 20rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
color: #fff;
&.location-icon {
background: linear-gradient(135deg, #40c9ff, #32a5ff);
// &::before {
// content: "\e900"; // 使用字体图标,需要自行替换
// }
}
&.battery-icon {
background: linear-gradient(135deg, #33db92, #10c469);
// &::before {
// content: "\e901"; // 使用字体图标,需要自行替换
// }
&.battery-low {
background: linear-gradient(135deg, #ff7676, #f54f4f);
}
}
}
.item-content {
display: flex;
flex-direction: column;
.item-label {
font-size: 26rpx;
color: #999;
margin-bottom: 4rpx;
}
.item-value {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
}
}
}
// 计费规则
.pricing-card {
.pricing-banner {
background: linear-gradient(to right, #f8f9ff, #e8f0ff);
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 30rpx;
display: flex;
flex-direction: column;
align-items: center;
.pricing-main {
display: flex;
align-items: flex-end;
.price {
font-size: 60rpx;
font-weight: bold;
color: #ff6b6b;
}
.unit {
font-size: 28rpx;
color: #999;
margin-left: 4rpx;
margin-bottom: 10rpx;
}
}
.cap-price {
margin-top: 10rpx;
font-size: 26rpx;
color: #666;
background-color: rgba(255, 107, 107, 0.1);
padding: 6rpx 20rpx;
border-radius: 20rpx;
}
}
.pricing-rules {
.rule-item {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.rule-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background-color: #ff6b6b;
margin-right: 16rpx;
}
.rule-text {
font-size: 28rpx;
color: #666;
.highlight {
color: #ff6b6b;
font-weight: bold;
}
}
}
}
}
// 手机号输入
.phone-card {
.phone-input-container {
.input-wrapper {
display: flex;
align-items: center;
height: 88rpx;
background-color: #f5f7fa;
border-radius: 16rpx;
padding: 0 24rpx;
.prefix {
font-size: 28rpx;
color: #666;
margin-right: 16rpx;
padding-right: 16rpx;
border-right: 1px solid #e0e0e0;
}
.phone-input {
flex: 1;
height: 88rpx;
font-size: 28rpx;
color: #333;
padding-left: 10rpx;
}
}
.phone-tip {
font-size: 24rpx;
color: #999;
margin-top: 16rpx;
display: block;
}
}
}
// 使用须知
.notice-card {
.notice-items {
.notice-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.notice-dot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
background-color: #32a5ff;
margin-right: 16rpx;
margin-top: 12rpx;
}
.notice-text {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
}
}
// 底部操作区
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 20rpx 30rpx;
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
box-shadow: 0 -2rpx 20rpx rgba(0, 0, 0, 0.05);
z-index: 100;
display: flex;
flex-direction: column;
// 添加一个变量来保存footer高度,方便管理和确保一致性
--footer-height: 180rpx;
.wechat-credit {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
.wx-icon {
width: 50rpx;
height: 40rpx;
margin-right: 10rpx;
}
.credit-text {
font-size: 24rpx;
color: #07c160;
display: flex;
align-items: center;
.credit-divider {
margin: 0 10rpx;
}
.credit-score {
font-weight: 500;
}
}
}
.rent-button {
height: 92rpx;
border-radius: 46rpx;
background: linear-gradient(135deg, #07c160, #10d673);
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border: none;
width: 90%;
&.return-button {
background: linear-gradient(135deg, #FF9800, #FFB74D);
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
}
</style>