Files
uni-fans-score/pages/device/detail.vue
T
2026-03-09 09:07:58 +08:00

1161 lines
28 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">
<!-- 骨架屏 -->
<DeviceDetailSkeleton v-if="loading&&!deviceInfo" />
<!-- 实际内容 -->
<view v-else>
<!-- 设备信息卡片 -->
<view class="card device-info-card">
<view class="device-location">
<view class="location-left">
<image src="/static/device_location.png" mode="aspectFit" class="location-icon" lazy-load="true"></image>
<text class="location-name">{{ deviceLocation }}</text>
</view>
<view class="device-status" :class="deviceStatus.class">
<text class="status-text">{{ deviceStatus.text }}</text>
</view>
</view>
<view class="device-id">
<text class="id-label">{{ $t('order.deviceName') }}</text>
<text class="id-value">{{ deviceInfo.name }}</text>
</view>
<view class="device-id">
<text class="id-label">{{ $t('device.deviceNo') }}</text>
<text class="id-value">{{ deviceId }}</text>
</view>
</view>
<!-- 计费规则 -->
<view class="card pricing-card">
<view class="card-header">
<text class="card-title">{{ $t('device.pricingRules') }}</text>
</view>
<view class="pricing-banner">
<view class="pricing-main">
<text class="price-symbol">¥</text>
<text class="price">{{ deviceFeeConfig.maxHourPrice || '5.00' }}</text>
<text class="unit">/{{ getPriceUnit() }}</text>
</view>
<view class="cap-badge">
<text class="cap-text">{{ deviceInfo.depositAmount || '99' }}{{ $t('device.capLimit') }}</text>
</view>
</view>
<view class="pricing-info">
<view class="info-icon">
<text class="icon-text"></text>
</view>
<text class="info-text">{{ getPricingInfoText() }}</text>
</view>
<view class="pricing-info">
<view class="info-icon">
<text class="icon-text"></text>
</view>
<text class="info-text">{{ getDetailInfoText() }}</text>
</view>
</view>
<!-- 使用说明 -->
<view class="card notice-card">
<view class="card-header">
<text class="card-title">{{ $t('device.usageInstructions') }}</text>
</view>
<view class="notice-items">
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">{{ $t('device.checkBeforeUse') }}</text>
</view>
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">{{ $t('device.autoChargeOvertime') }}</text>
</view>
<view class="notice-item">
<view class="notice-dot"></view>
<text class="notice-text">{{ $t('device.useInDesignatedArea') }}</text>
</view>
</view>
</view>
<view class="promotion-tip" @click="goToPurchase">
<view class="tip-left">
<text class="tip-text">{{ $t('device.canUsePromotion') }}</text>
</view>
<view class="tip-right">
<text class="buy-text">{{ $t('device.goToBuy') }}</text>
<image src="/static/gotoBuy.png" mode="aspectFit" class="arrow-icon"></image>
</view>
</view>
<!-- 底部操作区 -->
<view class="footer">
<view class="rent-button" :class="{ 'return-button': hasActiveOrder }"
@click="handleRent">
<text>{{ hasActiveOrder ? $t('order.returnDevice') : getRentButtonText() }}</text>
</view>
<!-- 微信支付分标识仅在微信小程序环境显示 -->
<view class="wechat-credit" v-if="isWechatMiniProgram">
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon" lazy-load="true"></image>
<text class="credit-text">{{ $t('device.wxPayScoreDesc') }}</text>
</view>
</view>
<!-- 手机号授权弹窗 -->
<view class="phone-auth-popup" v-if="showPhoneAuthPopup">
<view class="popup-mask" @click.stop></view>
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ $t('auth.authTitle') }}</text>
</view>
<view class="popup-body">
<view class="auth-desc">
<text>{{ $t('auth.authDescShort') }}</text>
</view>
<button class="auth-btn" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
{{ $t('auth.getPhoneNumber') }}
</button>
<view class="auth-cancel" @click="showPhoneAuthPopup = false">
<text>{{ $t('auth.notNow') }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted
} from 'vue'
import {
onLoad,
onUnload
} from '@dcloudio/uni-app'
import {
getDeviceInfo,
rentPowerBank
} from '@/config/api/device.js'
import {
getOrderByOrderNoScore,
getOrderByOrderNo,
cancelOrder,
getInUseOrder,
getUnpaidOrder
} from '@/config/api/order.js'
import {
initiateWeChatScorePayment,
getUserInfo,
getUserPhoneNumber
} from '@/util/index.js'
import {
useI18n
} from '@/utils/i18n.js'
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
const {
t
} = useI18n()
// 响应式状态
const loading = ref(true)
const deviceInfo = ref({})
const deviceId = ref('')
const deviceFeeConfig = ref({})
const positionInfo = ref({})
const deviceLocation = ref('一号教学楼大厅')
const hasActiveOrder = ref(false)
const deviceStatus = reactive({
text: t('device.available'),
class: 'available'
})
const isLoggedIn = ref(true)
const phoneNumber = ref('')
const showPhoneAuthPopup = ref(false)
const isWechatMiniProgram = ref(false)
const isAlipayMiniProgram = ref(false)
const isH5 = ref(false)
// 生命周期 onLoad 钩子
onLoad(async (options) => {
if (options.deviceNo != uni.getStorageSync('deviceId') || !uni.getStorageSync('deviceId')) {
deviceId.value = options.deviceNo
uni.setStorageSync('deviceId', options.deviceNo)
} else {
deviceId.value = uni.getStorageSync('deviceId')
}
await checkOrderStatus()
await fetchDeviceInfo()
})
onMounted(async () => {
uni.setNavigationBarTitle({
title: t('device.deviceInfo')
})
// 检测当前运行环境:微信小程序 / 支付宝小程序 / H5
// #ifdef MP-WEIXIN
isWechatMiniProgram.value = true
isAlipayMiniProgram.value = false
isH5.value = false
// #endif
// #ifdef MP-ALIPAY
isWechatMiniProgram.value = false
isAlipayMiniProgram.value = true
isH5.value = false
// #endif
// #ifdef H5
isWechatMiniProgram.value = false
isAlipayMiniProgram.value = false
isH5.value = true
// #endif
console.log('当前运行环境:', {
isWechatMiniProgram: isWechatMiniProgram.value,
isAlipayMiniProgram: isAlipayMiniProgram.value,
isH5: isH5.value
})
await checkUserPhone()
await fetchDeviceInfo()
})
// 页面卸载时设置默认启动路径为首页(仅在非下单流程时生效)
onUnload(() => {
// 如果是下单流程跳转(在提交订单时设置了标记),则本次不设置启动路径
const skipSetLaunchPathOnce = uni.getStorageSync('skipSetLaunchPathOnce')
if (skipSetLaunchPathOnce) {
console.log('下单流程离开设备详情页,本次不设置启动路径')
uni.removeStorageSync('skipSetLaunchPathOnce')
return
}
// 正常离开设备详情页(比如返回、关闭小程序)时,记录启动路径为首页
uni.setStorageSync('launchPath', '/pages/index/index')
console.log('设备详情页卸载,已设置启动路径为首页')
})
const checkUserPhone = async () => {
try {
const userInfoRes = await getUserInfo()
console.log(userInfoRes.data.phone, 'getUserInfoPhone')
if (userInfoRes.code == 200 && userInfoRes.data && userInfoRes.data.phone) {
phoneNumber.value = userInfoRes.data.phone
} else {
// 如果没有手机号,显示授权弹窗
showPhoneAuthPopup.value = true
}
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
// 处理获取手机号
const onGetPhoneNumber = (e) => {
console.log('getPhoneNumber event:', e.detail)
// 用户拒绝授权的情况
if (e.detail.errMsg && e.detail.errMsg.includes('deny')) {
uni.showToast({
title: t('auth.phoneRequired'),
icon: 'none'
})
return
}
// 获取到授权code
if (e.detail.code) {
// uni.showLoading({
// title: t('auth.getting')
// })
console.log('获取到的授权code:', e.detail.code)
// 添加 try-catch 以捕获任何 Promise 外部的错误
try {
getUserPhoneNumber(e.detail.code)
.then(res => {
console.log('获取手机号API响应原始数据:', JSON.stringify(res))
// uni.hideLoading()
// 不立即抛出错误,而是记录问题并继续处理
if (!res) {
console.error('API返回数据为空')
uni.showModal({
title: '数据异常',
content: 'API返回为空',
showCancel: false
})
return
}
// 检查响应格式
console.log('响应code:', res.code, '响应类型:', typeof res.code)
console.log('是否有data:', !!res.data, '是否有phone:', res.data && !!res.data.phone)
if (res.code == 200 && res.data && res.data.phoneNumber) {
phoneNumber.value = res.data.phoneNumber
showPhoneAuthPopup.value = false
uni.showToast({
title: t('auth.phoneSuccess'),
icon: 'success'
})
} else {
// 记录详细信息,不抛出错误
console.warn('获取手机号响应异常:', res.msg || '未知错误')
uni.showModal({
title: t('auth.phoneError'),
content: `${t('common.statusCode')}: ${res.code}, ${t('common.message')}: ${res.msg || t('common.none')}`,
showCancel: false
})
}
})
.catch(err => {
// uni.hideLoading()
console.error('获取手机号码失败(catch):', err)
// 显示更详细的错误信息
let errMsg = err.message || err.toString()
uni.showModal({
title: t('auth.phoneGetFailed'),
content: t('common.errorInfo') + ': ' + errMsg,
showCancel: false
})
})
} catch (outerError) {
// uni.hideLoading()
console.error('获取手机号外部错误:', outerError)
uni.showModal({
title: t('common.unexpectedError'),
content: t('common.processException') + ': ' + (outerError.message || outerError),
showCancel: false
})
}
} else {
uni.showToast({
title: t('auth.authCodeFailed'),
icon: 'none'
})
}
}
// 检查登录状态和订单
const fetchDeviceInfo = async () => {
try {
loading.value = true
console.log(deviceId.value);
const res = await getDeviceInfo(deviceId.value)
if (res.code == 200) {
deviceInfo.value = res.data.device || {}
// 保存 position 信息
if (res.data.position) {
positionInfo.value = res.data.position
}
// 更新设备位置信息
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 = t('device.available')
deviceStatus.class = 'available'
} else if (deviceInfo.value.status === 'offline') {
deviceStatus.text = t('device.offline')
deviceStatus.class = 'offline'
}
}
if (deviceInfo.value.feeConfig) {
deviceFeeConfig.value = JSON.parse(deviceInfo.value.feeConfig)[0] || {}
console.log('deviceFeeConfig', deviceFeeConfig.value);
} else {
deviceFeeConfig.value = {
maxHourPrice: '5.00',
}
}
}else{
uni.reLaunch({
url:'/pages/index/index'
})
}
}catch(error){
console.error('获取设备信息失败:', error)
}
finally {
}
}
// 显示登录提示
const showLoginTip = () => {
uni.showModal({
title: t('common.tips'),
content: t('common.loginRequired'),
confirmText: t('auth.goToLogin'),
success: (res) => {
if (res.confirm) {
uni.navigateTo({
url: '/subPackages/user/login/index'
})
}
}
})
}
// 跳转到优惠专区
const goToPurchase = () => {
const positionId = positionInfo.value?.positionId || positionInfo.value?.id
uni.navigateTo({
url: `/subPackages/business/purchase/index?positionId=${positionId}`
})
}
// 检查订单状态
const checkOrderStatus = async () => {
try {
// 调用接口检查是否有进行中的订单
const inUseRes = await getInUseOrder()
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
const order = inUseRes.data
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
console.log('1111111111');
uni.redirectTo({
url: `/subPackages/service/return/index?deviceId=${deviceId.value}`
})
return
}
// 检查是否有待支付的订单
const unpaidRes = await getUnpaidOrder()
if (unpaidRes && unpaidRes.code === 200 && unpaidRes.data) {
const order = unpaidRes.data
// 跳转支付页面,带上订单ID
uni.redirectTo({
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${deviceId.value}`
})
}
} catch (error) {
console.error('检查订单状态失败:', error)
uni.showToast({
title: t('order.getOrderStatusFailed'),
icon: 'none'
})
}
}
// 处理租借操作
const handleRent = () => {
if (!isLoggedIn.value) {
showLoginTip()
return
}
// 检查是否有手机号,如果没有则提示授权
if (!phoneNumber.value) {
showPhoneAuthPopup.value = true
return
}
// 根据运行环境选择不同的租借/支付流程
// 微信小程序:走微信支付分免押租借
if (isWechatMiniProgram.value) {
submitRentOrder('wx-score-pay')
return
}
// 支付宝小程序:走押金租借,后续在支付页内调起支付宝支付
if (isAlipayMiniProgram.value) {
submitRentOrder('wx-pay')
return
}
// H5 等其他环境:统一走押金租借,支付页内根据平台选择支付方式(Antom 等)
submitRentOrder('wx-pay')
}
// 获取价格单位文本
const getPriceUnit = () => {
console.log(deviceInfo.value);
// 按分钟计费
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
return '分钟'
} else if (deviceInfo.value && deviceFeeConfig.value.hourPrice == '0.5') {
return '30分钟'
}
// 按小时计费(默认)
return t('time.hour')
}
// 计算计费单位时间(分钟)
const getBillingUnitMinutes = () => {
// 按分钟计费时,单位为1分钟
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
return 1
}
// 按小时计费
if (!deviceFeeConfig.value || !deviceFeeConfig.value.hourPrice) return 60
const hourPrice = parseFloat(deviceFeeConfig.value.hourPrice)
// hourPrice 为 0.5 时表示 30 分钟,为 1 时表示 60 分钟
return hourPrice === 0.5 ? 30 : 60
}
// 计算每个计费单位的价格
const getBillingUnitPrice = () => {
if (!deviceFeeConfig.value || !deviceFeeConfig.value.maxHourPrice) return '5'
const maxHourPrice = parseFloat(deviceFeeConfig.value.maxHourPrice)
// 按分钟计费时,直接返回每分钟价格
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
return maxHourPrice.toFixed(2)
}
// 按小时计费
const hourPrice = parseFloat(deviceFeeConfig.value.hourPrice || 1)
const unitPrice = maxHourPrice
return unitPrice.toFixed(2)
}
// 获取免费时间(分钟)
const getFreeMinutes = () => {
if (!positionInfo.value || !positionInfo.value.freeRentTime) return 15
return parseInt(positionInfo.value.freeRentTime)
}
// 生成计费说明文本
const getPricingInfoText = () => {
const unitPrice = getBillingUnitPrice()
const maxHourPrice = deviceFeeConfig.value.maxHourPrice || '5'
// 按分钟计费
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
return `${unitPrice}元/分钟`
}
// 按小时计费
const unitMinutes = getBillingUnitMinutes()
return deviceInfo.value.remark;
}
// 生成详细说明文本
const getDetailInfoText = () => {
const freeMinutes = getFreeMinutes()
const unitMinutes = getBillingUnitMinutes()
const depositAmount = deviceInfo.value.depositAmount || '99'
// 按分钟计费
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
}
// 按小时计费
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
}
// 获取租借按钮文本
const getRentButtonText = () => {
if (isWechatMiniProgram.value) {
return t('device.rentDepositFree')
} else {
return '立即租借'
}
}
// 提交租借订单
const submitRentOrder = async (payWay) => {
try {
uni.showLoading({
title: t('common.processing')
})
// --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)---
if (payWay === 'wx-score-pay') {
console.log('准备请求订阅消息(在异步操作之前),时间:', new Date().toLocaleTimeString());
try {
await new Promise((resolve, reject) => {
uni.requestSubscribeMessage({
tmplIds: ['o7OMTIcHnFBR7mvsggxFtdt8FfIgSl-v0swVUefGx6w'],
success: (subscribeRes) => {
console.log('订阅消息success回调,时间:', new Date()
.toLocaleTimeString(), subscribeRes);
resolve(subscribeRes);
},
fail: (subscribeErr) => {
console.log('订阅消息fail回调,时间:', new Date().toLocaleTimeString(),
subscribeErr);
// 订阅失败不影响主流程
resolve(subscribeErr);
}
});
});
console.log('订阅消息完成,时间:', new Date().toLocaleTimeString());
} catch (subscribeError) {
console.log('订阅消息异常', subscribeError);
}
}
// --- 订阅消息请求完成 ---
console.log(deviceId.value);
// 调用设备租借接口
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value)
if (rentResult.code !== 200) {
throw new Error(rentResult.msg || t('device.rentFailed'))
}
// 获取后端返回的订单信息
const order = rentResult.data
console.log('订单信息', order);
// 标记:本次是从设备详情页发起的下单流程,离开页面时不设置启动路径
try {
uni.setStorageSync('skipSetLaunchPathOnce', true)
} catch (e) {
console.warn('设置 skipSetLaunchPathOnce 失败:', e)
}
if (payWay == 'wx-pay') {
// 当支付方式为押金支付时
uni.hideLoading()
const res = await getOrderByOrderNo(order.orderNo);
console.log(res);
const deposit = parseFloat(order.depositAmount);
const packagePrice = parseFloat(order.unitPrice);
const totalAmount = deposit.toFixed(2);
// 跳转到订单支付页面
uni.redirectTo({
url: `/subPackages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${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);
console.log('支付分调用结果', payResult);
// 成功则跳转到等待页面
if (payResult.errCode == '0' && payResult.extraData && Object.keys(payResult.extraData)
.length > 0) {
console.log('支付分授权成功,准备跳转到等待页,时间:', new Date().toLocaleTimeString());
// 跳转到等待页面(订阅消息已经在前面完成了)
uni.redirectTo({
url: `/pages/waiting/index?orderNo=${order.orderNo}&orderId=${order.orderId}&deviceId=${deviceId.value}`
});
return;
} else {
console.log('支付分未完成授权或用户取消,extraData:', payResult.extraData);
// 用户取消授权,需要取消订单
try {
uni.showLoading({
title: t('order.cancelling')
});
const cancelRes = await cancelOrder({
orderId: order.orderNo
});
console.log('订单取消结果:', cancelRes);
uni.hideLoading();
uni.showToast({
title: t('order.orderCancelled'),
icon: 'none',
duration: 2000
});
// 延迟返回首页
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 2000);
} catch (cancelError) {
console.error('取消订单失败:', cancelError);
uni.hideLoading();
uni.showToast({
title: t('order.cancelFailedContactService'),
icon: 'none'
});
}
return;
}
} catch (payError) {
console.error('支付分调用异常:', payError);
// 支付分调用异常,也需要取消订单
try {
uni.showLoading({
title: t('order.cancelling')
});
const cancelRes = await cancelOrder({
orderId: order.orderNo
});
console.log('订单取消结果:', cancelRes);
uni.hideLoading();
} catch (cancelError) {
console.error('取消订单失败:', cancelError);
uni.hideLoading();
}
uni.showToast({
title: t('device.payScoreFailedCancelled'),
icon: 'none'
});
setTimeout(() => {
uni.switchTab({
url: '/pages/index/index'
});
}, 2000);
}
} else {
uni.showToast({
title: res?.msg || t('device.getPayParamsFailed'),
icon: 'none'
});
}
}
} catch (error) {
uni.hideLoading()
uni.showToast({
title: error.message || t('device.rentFailedRetry'),
icon: 'none'
})
}
}
</script>
<style lang="scss" scoped>
.container {
min-height: 100vh;
background-color: #f5f7fa;
padding: 30rpx 30rpx 300rpx;
box-sizing: border-box;
}
// 卡片通用样式
.card {
background-color: #fff;
border-radius: 24rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
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 {
.device-location {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
.location-left {
display: flex;
align-items: center;
.location-icon {
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
// background-color: #10d673;
border-radius: 50%;
}
.location-name {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
}
.device-status {
padding: 8rpx 24rpx;
border-radius: 30rpx;
font-size: 22rpx;
&.available {
background-color: #d4f4dd;
.status-text {
color: #07c160;
}
}
&.offline {
background-color: #f0f0f0;
.status-text {
color: #999;
}
}
.status-text {
font-size: 24rpx;
}
}
}
.device-id {
display: flex;
align-items: center;
margin-bottom: 5rpx;
.id-label {
font-size: 26rpx;
color: #999;
}
.id-value {
font-size: 28rpx;
color: #666;
}
}
}
// 计费规则卡片
.pricing-card {
.pricing-banner {
background: #E6F7EC;
border-radius: 20rpx;
padding: 40rpx 30rpx;
margin-bottom: 24rpx;
display: flex;
flex-direction: column;
align-items: center;
.pricing-main {
display: flex;
align-items: baseline;
margin-bottom: 16rpx;
.price-symbol {
font-size: 36rpx;
font-weight: bold;
color: #07c160;
margin-right: 4rpx;
}
.price {
font-size: 64rpx;
font-weight: bold;
color: #07c160;
line-height: 1;
}
.unit {
font-size: 28rpx;
color: #07c160;
margin-left: 8rpx;
}
}
.cap-badge {
background-color: #07c160;
padding: 10rpx 28rpx;
border-radius: 30rpx;
line-height: 1;
.cap-text {
font-size: 24rpx;
color: #fff;
font-weight: 500;
}
}
}
.pricing-info {
display: flex;
align-items: flex-start;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
.info-icon {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
margin-top: 2rpx;
.icon-text {
font-size: 28rpx;
color: #999;
}
}
.info-text {
flex: 1;
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
}
}
// 使用说明卡片
.notice-card {
.notice-items {
.notice-item {
display: flex;
align-items: flex-start;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.notice-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: #07c160;
margin-right: 16rpx;
margin-top: 10rpx;
flex-shrink: 0;
}
.notice-text {
flex: 1;
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
}
}
}
// 促销提示框
.promotion-tip {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 30rpx;
background: rgba(255, 244, 227, 1);
border-radius: 22rpx;
margin-bottom: 30rpx;
transition: all 0.3s;
&:active {
opacity: 0.8;
transform: scale(0.98);
}
.tip-left {
display: flex;
align-items: center;
.tip-text {
font-size: 26rpx;
color: #A16300;
font-weight: 400;
}
}
.tip-right {
display: flex;
align-items: center;
gap: 12rpx;
.buy-text {
font-size: 26rpx;
color: #A16300;
font-weight: 500;
}
.arrow-icon {
width: 20rpx;
height: 20rpx;
}
}
}
// 底部操作区
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 24rpx 30rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
.rent-button {
width: 100%;
height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(135deg, #07c160, #10d673);
color: #fff;
font-size: 34rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border: none;
box-shadow: 0 8rpx 20rpx rgba(7, 193, 96, 0.3);
&.return-button {
background: linear-gradient(135deg, #FF9800, #FFB74D);
box-shadow: 0 8rpx 20rpx rgba(255, 152, 0, 0.3);
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.wechat-credit {
display: flex;
align-items: center;
justify-content: center;
margin-top: 16rpx;
.wx-icon {
width: 48rpx;
height: 38rpx;
margin-right: 8rpx;
}
.credit-text {
font-size: 24rpx;
color: #999;
}
}
}
/* 手机号授权弹窗样式 */
.phone-auth-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
}
.popup-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.popup-content {
background-color: #fff;
border-radius: 24rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
width: 90%;
max-width: 500rpx;
padding: 40rpx 30rpx;
position: relative;
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;
}
.popup-header {
margin-bottom: 30rpx;
text-align: center;
}
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.popup-body {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.auth-desc {
font-size: 28rpx;
color: #666;
text-align: center;
margin-bottom: 30rpx;
line-height: 1.6;
}
.auth-btn {
width: 100%;
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;
margin-bottom: 20rpx;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.auth-cancel {
width: 100%;
height: 92rpx;
border-radius: 46rpx;
background-color: #f5f7fa;
color: #333;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border: none;
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
</style>