fix:修复bug
This commit is contained in:
@@ -82,7 +82,7 @@
|
||||
<!-- 底部操作区 -->
|
||||
<view class="footer">
|
||||
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
||||
@click="handleRent('wx-score-pay')">
|
||||
@click="handleRent('wx-pay')">
|
||||
<text>{{ hasActiveOrder ? $t('order.returnDevice') : $t('device.rentDepositFree') }}</text>
|
||||
</button>
|
||||
<view class="wechat-credit">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,710 @@
|
||||
<template>
|
||||
<view class="order-detail-container">
|
||||
<!-- 状态标题 -->
|
||||
<!-- <view class="status-header">
|
||||
<view class="status-icon">
|
||||
<uv-icon name="checkbox-mark" size="40" color="#07c160"></uv-icon>
|
||||
</view>
|
||||
<text class="status-text">{{ statusText }}</text>
|
||||
</view> -->
|
||||
|
||||
<!-- 产品信息卡片 -->
|
||||
<view class="product-card">
|
||||
<image
|
||||
:src="orderDetail.pictureUrl || orderDetail.productImage || '/static/default-product.png'"
|
||||
mode="aspectFill"
|
||||
class="product-image"
|
||||
></image>
|
||||
<view class="product-info">
|
||||
<view class="product-name">{{ orderDetail.productName || orderDetail.deviceName || '风电者2026新款风扇、充电宝、暖手宝三合一' }}</view>
|
||||
<view class="product-style">款式:{{ orderDetail.optionName || orderDetail.style || '标准' }}</view>
|
||||
<view class="product-price">¥{{ orderDetail.price || orderDetail.totalAmount }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">订单信息</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">订单号</text>
|
||||
<text class="value">{{ orderDetail.orderNo || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="orderDetail.outTradeNo">
|
||||
<text class="label">交易单号</text>
|
||||
<text class="value">{{ orderDetail.outTradeNo }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">订单状态</text>
|
||||
<text class="value" :style="{color: statusColor}">{{ statusText }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">创建时间</text>
|
||||
<text class="value">{{ orderDetail.createTime || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">支付方式</text>
|
||||
<text class="value">{{ paymentMethodText }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">支付时间</text>
|
||||
<text class="value">{{ orderDetail.payTime || '未支付' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">收货人</text>
|
||||
<text class="value">{{ receiverInfo }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">收货地址</text>
|
||||
<text class="value address">{{ orderDetail.receiverAddress || '-' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="orderDetail.expressageNo">
|
||||
<text class="label">快递单号</text>
|
||||
<text class="value">{{ orderDetail.expressageNo }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item" v-if="orderDetail.remark">
|
||||
<text class="label">备注</text>
|
||||
<text class="value address">{{ orderDetail.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 费用信息 -->
|
||||
<view class="info-section">
|
||||
<view class="section-title">费用信息</view>
|
||||
|
||||
<view class="info-item" v-if="orderDetail.quantity">
|
||||
<text class="label">数量</text>
|
||||
<text class="value">x{{ orderDetail.quantity }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item">
|
||||
<text class="label">单价</text>
|
||||
<text class="value">¥ {{ orderDetail.price || orderDetail.totalAmount }}</text>
|
||||
</view>
|
||||
|
||||
<view class="info-item total">
|
||||
<text class="label">合计</text>
|
||||
<text class="value highlight">¥ {{ totalAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<!-- 待付款状态:显示取消订单和立即支付 -->
|
||||
<template v-if="orderDetail.status === 0 || orderDetail.status === '0'">
|
||||
<view class="action-btn secondary" @click="onCancelOrder">
|
||||
取消订单
|
||||
</view>
|
||||
<view class="action-btn primary" @click="onPayNow">
|
||||
立即支付
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 已完成/已取消状态:显示删除订单和再次定制 -->
|
||||
<template v-else-if="orderDetail.status === 3 || orderDetail.status === '3' || orderDetail.status === 4 || orderDetail.status === '4'">
|
||||
<view class="action-btn secondary" @click="onDeleteOrder">
|
||||
删除订单
|
||||
</view>
|
||||
<view class="action-btn primary" @click="onReorder">
|
||||
再次定制
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 其他状态:显示联系客服和再次定制 -->
|
||||
<template v-else>
|
||||
<view class="action-btn secondary" @click="onContactService">
|
||||
联系客服
|
||||
</view>
|
||||
<view class="action-btn primary" @click="onReorder">
|
||||
再次定制
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import {
|
||||
queryById,
|
||||
getProductOrderDetail,
|
||||
deleteProductOrder,
|
||||
cancelProductOrder,
|
||||
createWxPayment
|
||||
} from '../../config/api/order.js';
|
||||
// import { getSystemParamByKey } from '../../config/api/system.js';
|
||||
import { useI18n } from '@/utils/i18n.js'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const orderDetail = ref({});
|
||||
const orderId = ref('');
|
||||
|
||||
// 订单状态文本
|
||||
const statusText = computed(() => {
|
||||
const status = orderDetail.value.status;
|
||||
if (status === 0 || status === '0') {
|
||||
return '待付款';
|
||||
}
|
||||
if (status === 1 || status === '1') {
|
||||
return '待发货';
|
||||
}
|
||||
if (status === 2 || status === '2') {
|
||||
return '待收货';
|
||||
}
|
||||
if (status === 3 || status === '3') {
|
||||
return '已完成';
|
||||
}
|
||||
if (status === 4 || status === '4') {
|
||||
return '已取消';
|
||||
}
|
||||
if (status === 5 || status === '5') {
|
||||
return '退款中';
|
||||
}
|
||||
return '待付款';
|
||||
});
|
||||
|
||||
// 订单状态颜色
|
||||
const statusColor = computed(() => {
|
||||
const status = orderDetail.value.status;
|
||||
if (status === 0 || status === '0') {
|
||||
return '#ff976a'; // 待付款 - 橙色
|
||||
}
|
||||
if (status === 1 || status === '1') {
|
||||
return '#1989fa'; // 待发货 - 蓝色
|
||||
}
|
||||
if (status === 2 || status === '2') {
|
||||
return '#1989fa'; // 待收货 - 蓝色
|
||||
}
|
||||
if (status === 3 || status === '3') {
|
||||
return '#07c160'; // 已完成 - 绿色
|
||||
}
|
||||
if (status === 4 || status === '4') {
|
||||
return '#999'; // 已取消 - 灰色
|
||||
}
|
||||
if (status === 5 || status === '5') {
|
||||
return '#ff6b6b'; // 退款中 - 红色
|
||||
}
|
||||
return '#ff976a';
|
||||
});
|
||||
|
||||
// 支付方式文本
|
||||
const paymentMethodText = computed(() => {
|
||||
const payWay = orderDetail.value.payWay;
|
||||
if (payWay === 'wx_score_pay') return '微信支付';
|
||||
if (payWay === 'wx_global_pay') return '微信支付';
|
||||
if (payWay === 'wx_member_pay') return '微信支付';
|
||||
return '微信支付';
|
||||
});
|
||||
|
||||
// 收货人信息
|
||||
const receiverInfo = computed(() => {
|
||||
const name = orderDetail.value.receiverName || '-';
|
||||
const phone = orderDetail.value.receiverPhone || '-';
|
||||
return `${name} ${phone}`;
|
||||
});
|
||||
|
||||
// 总金额
|
||||
const totalAmount = computed(() => {
|
||||
return orderDetail.value.totalAmount || orderDetail.value.amount || orderDetail.value.payAmount || '99.0';
|
||||
});
|
||||
|
||||
// 页面加载
|
||||
onLoad(async (options) => {
|
||||
if (options && options.orderId) {
|
||||
orderId.value = options.orderId;
|
||||
await loadOrderDetail();
|
||||
}
|
||||
});
|
||||
|
||||
// 根据订单状态设置页面标题
|
||||
const updatePageTitle = () => {
|
||||
const status = orderDetail.value.status;
|
||||
let title = '订单详情';
|
||||
|
||||
if (status === 0 || status === '0') {
|
||||
title = '待付款';
|
||||
} else if (status === 1 || status === '1') {
|
||||
title = '待发货';
|
||||
} else if (status === 2 || status === '2') {
|
||||
title = '待收货';
|
||||
} else if (status === 3 || status === '3') {
|
||||
title = '已完成';
|
||||
} else if (status === 4 || status === '4') {
|
||||
title = '已取消';
|
||||
} else if (status === 5 || status === '5') {
|
||||
title = '退款中';
|
||||
}
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: title
|
||||
});
|
||||
};
|
||||
|
||||
// 加载订单详情
|
||||
const loadOrderDetail = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
const res = await getProductOrderDetail(orderId.value);
|
||||
|
||||
if (res.code === 200 && res.data) {
|
||||
const data = res.data;
|
||||
orderDetail.value = {
|
||||
// 基础信息
|
||||
id: data.id,
|
||||
orderNo: data.orderNo,
|
||||
outTradeNo: data.outTradeNo,
|
||||
userId: data.userId,
|
||||
|
||||
// 状态信息
|
||||
status: data.status,
|
||||
payStatus: data.payStatus,
|
||||
|
||||
// 金额信息
|
||||
totalAmount: data.totalAmount,
|
||||
payAmount: data.payAmount,
|
||||
price: data.price,
|
||||
|
||||
// 时间信息
|
||||
createTime: data.createTime,
|
||||
updateTime: data.updateTime,
|
||||
payTime: data.payTime,
|
||||
|
||||
// 收货信息
|
||||
receiverName: data.receiverName,
|
||||
receiverPhone: data.receiverPhone,
|
||||
receiverAddress: data.receiverAddress,
|
||||
|
||||
// 物流信息
|
||||
expressageNo: data.expressageNo,
|
||||
|
||||
// 备注
|
||||
remark: data.remark,
|
||||
|
||||
// 商品信息
|
||||
productName: data.productName,
|
||||
optionName: data.optionName,
|
||||
pictureUrl: data.pictureUrl,
|
||||
color: data.color,
|
||||
quantity: data.quantity,
|
||||
|
||||
// 兼容旧字段
|
||||
orderId: data.id,
|
||||
deviceId: data.deviceNo || '',
|
||||
deviceName: data.deviceName || '',
|
||||
style: data.optionName || data.style || data.deviceStyle || '',
|
||||
payWay: data.payWay || 'wx_global_pay',
|
||||
phone: data.receiverPhone,
|
||||
address: data.receiverAddress,
|
||||
deposit: data.deposit || '0',
|
||||
package: data.package || '',
|
||||
amount: data.payAmount,
|
||||
productImage: data.pictureUrl || data.productImage || ''
|
||||
};
|
||||
|
||||
// 根据订单状态更新页面标题
|
||||
updatePageTitle();
|
||||
}
|
||||
|
||||
uni.hideLoading();
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('加载订单详情失败:', error);
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 退款/售后
|
||||
const onRefund = () => {
|
||||
uni.showToast({
|
||||
title: '退款/售后功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
const onCancelOrder = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要取消这个订单吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '取消中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
const result = await cancelProductOrder(orderDetail.value.id);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (result && result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '订单已取消',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 重新加载订单详情
|
||||
await loadOrderDetail();
|
||||
} else {
|
||||
throw new Error(result?.msg || '取消失败');
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('取消订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '取消失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除订单
|
||||
const onDeleteOrder = () => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除这个订单吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '删除中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
const result = await deleteProductOrder(orderDetail.value.id);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (result && result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 返回订单列表
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
throw new Error(result?.msg || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('删除订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 立即支付
|
||||
const onPayNow = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '正在创建支付...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
const res = await createWxPayment(orderDetail.value.orderNo);
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
uni.hideLoading();
|
||||
|
||||
const payParams = res.data;
|
||||
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType,
|
||||
paySign: payParams.paySign,
|
||||
success: async (payRes) => {
|
||||
console.log('支付成功:', payRes);
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 重新加载订单详情
|
||||
await loadOrderDetail();
|
||||
},
|
||||
fail: async (err) => {
|
||||
console.error('支付失败:', err);
|
||||
|
||||
// 判断是用户取消还是支付失败
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,调用取消订单接口
|
||||
try {
|
||||
await cancelProductOrder(orderDetail.value.id);
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
});
|
||||
|
||||
// 重新加载订单详情
|
||||
await loadOrderDetail();
|
||||
} catch (cancelError) {
|
||||
console.error('取消订单失败:', cancelError);
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 支付失败
|
||||
uni.showToast({
|
||||
title: '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: res?.msg || '创建支付订单失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('支付异常:', error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: error.message || '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 联系客服
|
||||
const onContactService = async () => {
|
||||
|
||||
|
||||
const phoneNumber = uni.getStorageSync('customerPhone');
|
||||
|
||||
// 拨打客服电话
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: phoneNumber,
|
||||
success: () => {
|
||||
console.log('拨打电话成功');
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('拨打电话失败:', err);
|
||||
uni.showToast({
|
||||
title: '拨打电话失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
// 再次定制
|
||||
const onReorder = (order) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/goods?productId=${order.productId}`
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-detail-container {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding-bottom: 120rpx;
|
||||
|
||||
// 状态头部
|
||||
.status-header {
|
||||
background: #fff;
|
||||
padding: 60rpx 0 40rpx;
|
||||
text-align: center;
|
||||
|
||||
.status-icon {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// 产品卡片
|
||||
.product-card {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.product-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f5f5f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.product-style {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 32rpx;
|
||||
color: #07c160;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 信息区块
|
||||
.info-section {
|
||||
background: #fff;
|
||||
margin: 20rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 26rpx;
|
||||
height: 40rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
word-break: break-all;
|
||||
|
||||
&.address {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.total {
|
||||
margin-top: 12rpx;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx dashed #e5e5e5;
|
||||
|
||||
.label {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value.highlight {
|
||||
color: #07c160;
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部按钮
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 20rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20rpx;
|
||||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
z-index: 100;
|
||||
|
||||
.action-btn {
|
||||
width: 180rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.secondary {
|
||||
background: #fff;
|
||||
color: #07c160;
|
||||
border: 2rpx solid #07c160;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,799 @@
|
||||
<template>
|
||||
<view class="order-container">
|
||||
<!-- 状态切换 -->
|
||||
<view class="status-tabs">
|
||||
<view v-for="(tab, index) in orderStatusTabs" :key="index" class="tab-item"
|
||||
:class="{ active: currentTab === index }" @click="switchTab(index)">
|
||||
{{ tab.text }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="order-list">
|
||||
<view class="empty-state" v-if="orderList.length === 0">
|
||||
<view class="empty-icon">
|
||||
<image src="/static/orderList.png" mode="aspectFill" class="empty-icon"></image>
|
||||
</view>
|
||||
<text class="empty-text">{{ $t('order.noOrderRecord') }}</text>
|
||||
</view>
|
||||
|
||||
<DeviceOrderItemCard
|
||||
v-for="(order, index) in orderList"
|
||||
:key="index"
|
||||
:order="order"
|
||||
@click="navigateToDeviceOrderDetail"
|
||||
@delete="handleDeleteOrder"
|
||||
@pay="handlePayment"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
onUnmounted
|
||||
} from 'vue';
|
||||
import DeviceOrderItemCard from '../../components/DeviceOrderItemCard.vue';
|
||||
import {
|
||||
onLoad,
|
||||
onShow
|
||||
} from '@dcloudio/uni-app';
|
||||
import {
|
||||
getOrderList,
|
||||
getProductOrderList,
|
||||
queryById,
|
||||
getOrderByOrderNoScorePayStatus,
|
||||
cancelOrder,
|
||||
createWxPayment,
|
||||
deleteProductOrder,
|
||||
cancelProductOrder,
|
||||
} from '../../config/api/order.js';
|
||||
import{
|
||||
createProductOrder
|
||||
}from "@/config/api/product.js"
|
||||
import {
|
||||
confirmPaymentAndRent
|
||||
} from '../../config/api/device.js';
|
||||
import {
|
||||
updateUserBalance
|
||||
} from '../../config/api/user.js';
|
||||
import {
|
||||
URL
|
||||
} from '../../config/url.js';
|
||||
import { useI18n } from '@/utils/i18n.js'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// 初始化状态
|
||||
const currentTab = ref(0);
|
||||
const orderList = ref([]);
|
||||
|
||||
// 订单状态映射
|
||||
const orderStatusMap = reactive({
|
||||
'0': {
|
||||
text: '待付款',
|
||||
class: 'status-waiting'
|
||||
},
|
||||
'1': {
|
||||
text: '待发货',
|
||||
class: 'status-shipping'
|
||||
},
|
||||
'2': {
|
||||
text: '待收货',
|
||||
class: 'status-receiving'
|
||||
},
|
||||
'3': {
|
||||
text: '已完成',
|
||||
class: 'status-finished'
|
||||
},
|
||||
'4': {
|
||||
text: '已取消',
|
||||
class: 'status-cancelled'
|
||||
},
|
||||
'5': {
|
||||
text: '退款中',
|
||||
class: 'status-refunding'
|
||||
}
|
||||
});
|
||||
|
||||
// 订单状态标签
|
||||
const orderStatusTabs = reactive([
|
||||
{
|
||||
text: '全部',
|
||||
status: []
|
||||
},
|
||||
{
|
||||
text: '待付款',
|
||||
status: [0]
|
||||
},
|
||||
{
|
||||
text: '待发货',
|
||||
status: [1]
|
||||
},
|
||||
{
|
||||
text: '待收货',
|
||||
status: [2]
|
||||
},
|
||||
{
|
||||
text: '已完成',
|
||||
status: [3]
|
||||
},
|
||||
{
|
||||
text: '已取消',
|
||||
status: [4]
|
||||
},
|
||||
// {
|
||||
// text: '退款中',
|
||||
// status: [5]
|
||||
// }
|
||||
]);
|
||||
|
||||
// 页面加载
|
||||
onLoad(async (options) => {
|
||||
// 如果有传入orderId参数,说明是从设备租借页面跳转过来的
|
||||
if (options && options.orderId) {
|
||||
try {
|
||||
// 获取特定订单信息
|
||||
const res = await queryById(options.orderId);
|
||||
if (res.code === 200 && res.data) {
|
||||
// 获取到的订单数据
|
||||
const orderData = res.data;
|
||||
|
||||
// 使用实际的startTime字段,如果没有则尝试使用createTime
|
||||
const orderStartTime = orderData.startTime || orderData.createTime || '';
|
||||
|
||||
// 格式化订单数据
|
||||
const formattedOrder = {
|
||||
orderNo: orderData.orderId,
|
||||
status: orderData.orderStatus,
|
||||
deviceId: orderData.deviceNo,
|
||||
payWay: orderData.payWay,
|
||||
startTime: orderStartTime,
|
||||
endTime: orderData.endTime || '',
|
||||
positionName: orderData.positionName || orderData.positionLocation || '',
|
||||
deviceName: orderData.deviceName || '',
|
||||
amount: orderData.payAmount || orderData.actualDeviceAmount || orderData.currentFee || orderData.residueAmount || '0.00'
|
||||
};
|
||||
|
||||
// 将订单添加到列表开头
|
||||
orderList.value = [formattedOrder, ...orderList.value];
|
||||
|
||||
// 根据订单状态切换到对应标签
|
||||
const tabIndex = orderStatusTabs.findIndex(tab =>
|
||||
tab.status.includes(orderData.orderStatus)
|
||||
);
|
||||
|
||||
if (tabIndex !== -1) {
|
||||
switchTab(tabIndex);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取订单详情失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取订单列表
|
||||
await loadOrderList();
|
||||
});
|
||||
|
||||
// 页面显示时刷新订单列表
|
||||
onShow(async () => {
|
||||
// 根据当前选中的标签刷新对应状态的订单
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
});
|
||||
|
||||
// 切换标签
|
||||
const switchTab = async (index) => {
|
||||
currentTab.value = index;
|
||||
// 根据状态获取订单列表
|
||||
const statusArray = orderStatusTabs[index].status;
|
||||
// 如果是全部,传undefined;否则传第一个数字状态值
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
};
|
||||
|
||||
// 加载订单列表
|
||||
const loadOrderList = async (statusList) => {
|
||||
try {
|
||||
let params = {};
|
||||
if(statusList !== undefined){
|
||||
params = {
|
||||
status: statusList
|
||||
}
|
||||
}
|
||||
const res = await getProductOrderList(params);
|
||||
|
||||
// 根据实际接口返回结构处理数据
|
||||
if (res.code === 200 && res.data) {
|
||||
// 支持两种数据结构:res.data.rows 或 res.data.records
|
||||
const dataList = res.data.rows || res.data.records || [];
|
||||
|
||||
// 处理订单列表数据
|
||||
orderList.value = dataList.map(item => {
|
||||
return {
|
||||
// 基础信息
|
||||
id: item.id,
|
||||
orderNo: item.orderNo,
|
||||
orderId: item.id,
|
||||
userId: item.userId,
|
||||
|
||||
// 状态信息
|
||||
status: item.status,
|
||||
orderStatus: item.status,
|
||||
payStatus: item.payStatus,
|
||||
|
||||
// 金额信息
|
||||
totalAmount: item.totalAmount,
|
||||
payAmount: item.payAmount,
|
||||
price: item.price,
|
||||
|
||||
// 时间信息
|
||||
createTime: item.createTime,
|
||||
payTime: item.payTime,
|
||||
|
||||
// 收货信息
|
||||
receiverName: item.receiverName,
|
||||
receiverPhone: item.receiverPhone,
|
||||
receiverAddress: item.receiverAddress,
|
||||
|
||||
// 物流信息
|
||||
expressageNo: item.expressageNo,
|
||||
|
||||
// 备注
|
||||
remark: item.remark,
|
||||
|
||||
// 商品信息
|
||||
productName: item.productName,
|
||||
optionName: item.optionName,
|
||||
pictureUrl: item.pictureUrl,
|
||||
color: item.color,
|
||||
quantity: item.quantity,
|
||||
|
||||
// 兼容旧字段
|
||||
deviceId: item.deviceNo || '',
|
||||
deviceName: item.productName || item.deviceName || '',
|
||||
productImage: item.pictureUrl || '',
|
||||
style: item.optionName || item.style || item.deviceStyle || '',
|
||||
payWay: item.payWay || '',
|
||||
startTime: item.createTime || item.startTime || '',
|
||||
endTime: item.endTime || '',
|
||||
positionName: item.positionName || item.positionLocation || '',
|
||||
amount: item.payAmount || item.totalAmount || '0.00'
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取订单列表失败:', error);
|
||||
uni.showToast({
|
||||
title: t('order.getOrderListFailed'),
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 处理订单完成事件
|
||||
const handleOrderCompleted = (orderData) => {
|
||||
console.log('订单列表页收到订单完成事件:', orderData)
|
||||
// 刷新订单列表,根据当前选中的标签刷新对应状态的订单
|
||||
const statusArray = orderStatusTabs[currentTab.value].status
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0]
|
||||
loadOrderList(statusValue)
|
||||
}
|
||||
|
||||
// 设置页面标题并监听订单完成事件
|
||||
onMounted(() => {
|
||||
uni.setNavigationBarTitle({
|
||||
title: t('order.myOrders')
|
||||
})
|
||||
|
||||
// 监听订单完成事件
|
||||
uni.$on('orderCompleted', handleOrderCompleted)
|
||||
})
|
||||
|
||||
// 页面卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
uni.$off('orderCompleted', handleOrderCompleted)
|
||||
})
|
||||
|
||||
// 同步订单状态
|
||||
const getOrderStatus = async (order) => {
|
||||
try {
|
||||
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: t('order.syncSuccess'),
|
||||
icon: 'success'
|
||||
});
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
}
|
||||
} catch (error) {
|
||||
uni.showToast({
|
||||
title: t('order.syncFailed'),
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到订单详情页面(统一入口)
|
||||
const navigateToOrderDetail = (order) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/detail?orderId=${order.orderId || order.orderNo}&deviceId=${order.deviceId}`
|
||||
});
|
||||
};
|
||||
|
||||
// 组件事件:归还设备(实际跳转到订单详情页)
|
||||
const onReturnDevice = (order) => {
|
||||
navigateToOrderDetail(order);
|
||||
};
|
||||
|
||||
// 跳转到订单详情页
|
||||
const navigateToDetails = (order) => {
|
||||
navigateToOrderDetail(order);
|
||||
};
|
||||
|
||||
// 立即支付
|
||||
const handlePayment = async (order) => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '正在创建支付...',
|
||||
mask: true
|
||||
});
|
||||
console.log(order);
|
||||
|
||||
// 调用后端创建微信支付订单接口(使用订单号)
|
||||
// const res = await createWxPayment(order.orderNo);
|
||||
const res = await createProductOrder({orderNo:order.orderNo});
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
uni.hideLoading();
|
||||
|
||||
const payParams = res.data;
|
||||
|
||||
// 调用微信支付
|
||||
uni.requestPayment({
|
||||
timeStamp: payParams.timeStamp,
|
||||
nonceStr: payParams.nonceStr,
|
||||
package: payParams.package,
|
||||
signType: payParams.signType,
|
||||
paySign: payParams.paySign,
|
||||
success: async (payRes) => {
|
||||
console.log('支付成功:', payRes);
|
||||
uni.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// 刷新订单列表
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
},
|
||||
fail: async (err) => {
|
||||
console.error('支付失败:', err);
|
||||
|
||||
// 判断是用户取消还是支付失败
|
||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
||||
// 用户取消支付,调用取消订单接口
|
||||
try {
|
||||
// await cancelProductOrder(order.id || order.orderId);
|
||||
// uni.showToast({
|
||||
// title: '支付已取消',
|
||||
// icon: 'none'
|
||||
// });
|
||||
|
||||
// 刷新订单列表
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
} catch (cancelError) {
|
||||
console.error('取消订单失败:', cancelError);
|
||||
uni.showToast({
|
||||
title: '支付已取消',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 支付失败
|
||||
uni.showToast({
|
||||
title: '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: res?.msg || '创建支付订单失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('支付异常:', error);
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: error.message || '支付失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
const handleCancelOrder = async (order) => {
|
||||
try {
|
||||
uni.showModal({
|
||||
title: t('order.confirmCancel'),
|
||||
content: t('order.confirmCancelContent'),
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({
|
||||
title: t('common.processing')
|
||||
});
|
||||
|
||||
const result = await cancelOrder({
|
||||
orderId: order.orderNo
|
||||
});
|
||||
|
||||
if (result) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: t('order.cancelSuccess'),
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 刷新订单列表
|
||||
await loadOrderList();
|
||||
} else {
|
||||
throw new Error(result.msg || t('order.cancelFailed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: error.message || t('order.cancelFailed'),
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 跳转到设备订单详情页
|
||||
const navigateToDeviceOrderDetail = (order) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/orderDetail?orderId=${order.orderId || order.orderNo}`
|
||||
});
|
||||
};
|
||||
|
||||
// 处理删除订单
|
||||
const handleDeleteOrder = (order) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除这个订单吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: '删除中...',
|
||||
mask: true
|
||||
});
|
||||
|
||||
// 调用删除订单接口
|
||||
const result = await deleteProductOrder(order.id || order.orderId);
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (result && result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 刷新订单列表
|
||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
||||
await loadOrderList(statusValue);
|
||||
} else {
|
||||
throw new Error(result?.msg || '删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('删除订单失败:', error);
|
||||
uni.showToast({
|
||||
title: error.message || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.order-container {
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
padding-bottom: 30rpx;
|
||||
|
||||
// 状态标签栏
|
||||
.status-tabs {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 0 20rpx;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #07c160;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background: #07c160;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 订单列表
|
||||
.order-list {
|
||||
padding: 20rpx;
|
||||
|
||||
// 订单项
|
||||
.order-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
// 订单头部
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.order-id {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.order-status {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
|
||||
&.status-waiting {
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
&.status-shipping {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
&.status-receiving {
|
||||
color: #1989fa;
|
||||
}
|
||||
|
||||
&.status-finished {
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
&.status-cancelled {
|
||||
color: #9E9E9E;
|
||||
}
|
||||
|
||||
&.status-refunding {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
&.status-express-return {
|
||||
color: #FF9800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 订单内容
|
||||
.order-body {
|
||||
padding: 24rpx;
|
||||
|
||||
.device-info {
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
.device-left {
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.device-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.device-id {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.device-right {
|
||||
|
||||
// 支付分标识
|
||||
.payment-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
white-space: nowrap;
|
||||
|
||||
&.wx-score {
|
||||
background: rgba(7, 193, 96, 0.08);
|
||||
|
||||
.badge-icon {
|
||||
width: 32rpx;
|
||||
height: 26rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 22rpx;
|
||||
color: #07c160;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.divider {
|
||||
margin: 0 6rpx;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.member {
|
||||
background: rgba(25, 118, 210, 0.08);
|
||||
|
||||
.badge-text {
|
||||
font-size: 22rpx;
|
||||
color: #1976D2;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&.deposit {
|
||||
background: #f5f5f5;
|
||||
|
||||
.badge-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.order-times {
|
||||
.time-row {
|
||||
display: flex;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.time-label {
|
||||
color: #999;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 订单底部
|
||||
.order-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fafafa;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.price {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.action-item {
|
||||
font-size: 26rpx;
|
||||
padding: 10rpx 30rpx;
|
||||
border-radius: 30rpx;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.primary {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态
|
||||
.empty-state {
|
||||
padding: 100rpx 0;
|
||||
text-align: center;
|
||||
|
||||
.empty-icon {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
margin: 0 auto 30rpx;
|
||||
background: #f5f5f5;
|
||||
// border-radius: 50%;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+120
-40
@@ -24,7 +24,7 @@
|
||||
:enableMarkers="true" :bannerImages="bannerImages"
|
||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" />
|
||||
@mapCenterChange="onMapCenterChange" @bannerClick="handleBannerClick" @guide="openGuidePopup" />
|
||||
|
||||
<!-- 地图加载状态 -->
|
||||
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
||||
@@ -44,11 +44,11 @@
|
||||
<text class="action-label">{{ $t('home.nearbyDevices') }}</text>
|
||||
</view> -->
|
||||
|
||||
<view class="action-btn secondary small btn-nearby" @click="openPopup">
|
||||
<view class="action-btn secondary small btn-nearby" @click="goToBuy">
|
||||
<view class="icon-wrap">
|
||||
<image src="/static/use_help.png" class="action-icon" mode="aspectFit"></image>
|
||||
<image src="/static/shop_icon.png" class="action-icon" mode="scaleToFill"></image>
|
||||
</view>
|
||||
<text class="action-label">{{ $t('home.useGuide') }}</text>
|
||||
<text class="action-label">{{ $t('home.buyDevice') }}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-btn primary btn-scan" @click="handleScan">
|
||||
@@ -206,6 +206,9 @@
|
||||
getCurrentAnnouncement,
|
||||
getCurrentAdvertisement
|
||||
} from '../../config/api/system.js'
|
||||
import {
|
||||
getProductList
|
||||
} from '../../config/api/product.js'
|
||||
// 导入地图工具函数
|
||||
import {
|
||||
getUserLocation,
|
||||
@@ -524,10 +527,20 @@
|
||||
onMounted(() => {
|
||||
initNavBarHeight()
|
||||
init()
|
||||
|
||||
// #ifdef H5
|
||||
// 监听 H5 扫码结果
|
||||
uni.$on('h5ScanSuccess', (data) => {
|
||||
console.log('接收到 H5 扫码结果:', data);
|
||||
processScanResult(data);
|
||||
});
|
||||
// #endif
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理工作在子组件中处理
|
||||
// #ifdef H5
|
||||
uni.$off('h5ScanSuccess');
|
||||
// #endif
|
||||
})
|
||||
|
||||
// 方法
|
||||
@@ -564,10 +577,10 @@
|
||||
|
||||
} catch (error) {
|
||||
console.error('初始化失败:', error)
|
||||
uni.showToast({
|
||||
title: t('home.getLocationFailed'),
|
||||
icon: 'none'
|
||||
})
|
||||
// uni.showToast({
|
||||
// title: t('home.getLocationFailed'),
|
||||
// icon: 'none'
|
||||
// })
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
@@ -760,10 +773,10 @@
|
||||
try {
|
||||
isRelocating.value = true
|
||||
|
||||
uni.showLoading({
|
||||
title: t('home.relocating'),
|
||||
mask: true
|
||||
})
|
||||
// uni.showLoading({
|
||||
// title: t('home.relocating'),
|
||||
// mask: true
|
||||
// })
|
||||
|
||||
// 重新获取用户真实位置
|
||||
const loc = await getUserLocation()
|
||||
@@ -804,20 +817,20 @@
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
uni.showToast({
|
||||
title: t('home.locateSuccess'),
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
// uni.showToast({
|
||||
// title: t('home.locateSuccess'),
|
||||
// icon: 'success',
|
||||
// duration: 1500
|
||||
// })
|
||||
} catch (e) {
|
||||
console.error('定位失败:', e)
|
||||
uni.hideLoading()
|
||||
|
||||
uni.showToast({
|
||||
title: e.errMsg || t('home.locateFailed'),
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
// uni.showToast({
|
||||
// title: e.errMsg || t('home.locateFailed'),
|
||||
// icon: 'none',
|
||||
// duration: 2000
|
||||
// })
|
||||
} finally {
|
||||
// 1秒后解除防抖锁定
|
||||
setTimeout(() => {
|
||||
@@ -866,6 +879,53 @@
|
||||
})
|
||||
}
|
||||
|
||||
const goToBuy = async () => {
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: t('common.loading'),
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 查询商品列表
|
||||
const res = await getProductList({
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
console.log('商品列表查询结果:', res)
|
||||
|
||||
// 根据实际返回格式:data.rows 是商品列表数组
|
||||
if (res && res.code === 200 && res.data && res.data.rows && res.data.rows.length > 0) {
|
||||
// 获取第一个商品的ID
|
||||
const firstProduct = res.data.rows[0]
|
||||
const productId = firstProduct.id
|
||||
|
||||
console.log('获取到商品ID:', productId)
|
||||
|
||||
// 跳转到商品详情页面,传递商品ID
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/goods?productId=${productId}`
|
||||
})
|
||||
} else {
|
||||
console.warn('没有查询到商品数据')
|
||||
// 如果没有商品数据,仍然跳转到商品页面(显示空状态)
|
||||
uni.navigateTo({
|
||||
url: '/pages/device/goods'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询商品列表失败:', error)
|
||||
uni.hideLoading()
|
||||
|
||||
// 即使查询失败,也跳转到商品页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/device/goods'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const selectPositionFromPopup = (position) => {
|
||||
// 先关闭弹窗
|
||||
hideLocationList()
|
||||
@@ -905,6 +965,13 @@
|
||||
}
|
||||
|
||||
const handleScan = async () => {
|
||||
// #ifdef H5
|
||||
uni.navigateTo({
|
||||
url: '/pages/scan/index'
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
|
||||
try {
|
||||
const scanResult = await new Promise((resolve, reject) => {
|
||||
uni.scanCode({
|
||||
@@ -914,16 +981,23 @@
|
||||
})
|
||||
|
||||
console.log(scanResult);
|
||||
let deviceNo;
|
||||
if (scanResult.scanType=='"QR_CODE"') {
|
||||
processScanResult(scanResult);
|
||||
} catch (error) {
|
||||
console.error('扫码处理失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const processScanResult = async (scanResult) => {
|
||||
try {
|
||||
let deviceNo;
|
||||
if (scanResult.scanType == 'MANUAL') {
|
||||
deviceNo = scanResult.result;
|
||||
} else if (scanResult.scanType == '"QR_CODE"') {
|
||||
deviceNo = getQueryString(scanResult.result, 'deviceNo')
|
||||
} else {
|
||||
deviceNo = getQueryString(scanResult.path, 'deviceNo')
|
||||
deviceNo = getQueryString(scanResult.path || scanResult.result, 'deviceNo')
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!deviceNo) {
|
||||
uni.showToast({
|
||||
title: t('home.invalidQRCode'),
|
||||
@@ -975,10 +1049,10 @@
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: t('device.getDeviceInfoFailed'),
|
||||
icon: 'none'
|
||||
})
|
||||
// uni.showToast({
|
||||
// title: t('device.getDeviceInfoFailed'),
|
||||
// icon: 'none'
|
||||
// })
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
||||
})
|
||||
@@ -991,11 +1065,7 @@
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('扫码处理失败:', error)
|
||||
// uni.showToast({
|
||||
// title: '扫码失败',
|
||||
// icon: 'none'
|
||||
// })
|
||||
console.error('处理扫码结果失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1015,6 +1085,16 @@
|
||||
|
||||
// 使用指南弹窗控制
|
||||
const openPopup = () => {
|
||||
uni.navigateTo({
|
||||
url:'/pages/device/goods'
|
||||
})
|
||||
// try {
|
||||
// showGuidePopup.value = true
|
||||
// guidePopup.value && typeof guidePopup.value.open === 'function' && guidePopup.value.open()
|
||||
// } catch (e) {}
|
||||
}
|
||||
|
||||
const openGuidePopup = () => {
|
||||
try {
|
||||
showGuidePopup.value = true
|
||||
guidePopup.value && typeof guidePopup.value.open === 'function' && guidePopup.value.open()
|
||||
@@ -1494,8 +1574,8 @@
|
||||
// box-shadow: 0 8rpx 24rpx rgba(62, 171, 100, 0.3);
|
||||
|
||||
.icon-wrap {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1745,7 +1825,7 @@
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
/* 使用指南弹窗样式 */
|
||||
/* 弹窗样式 */
|
||||
.guide-popup {
|
||||
width: 640rpx;
|
||||
max-width: 86vw;
|
||||
|
||||
+13
-10
@@ -3,7 +3,7 @@
|
||||
<!-- 会员卡列表 -->
|
||||
<view class="card-list" v-if="cardList.length > 0">
|
||||
<view v-for="card in cardList" :key="card.id" style="position: relative;background-color: #f5f5f5;"
|
||||
@click="viewCardDetail(card)" :style="card.cardType==='COUNT'?'height: 240rpx;':'height: 200rpx;'">
|
||||
@click="viewCardDetail(card)" :style="card.cardType==='COUNT'?'height: 240rpx;':'height: 240rpx;'">
|
||||
<view
|
||||
style="height: 120rpx;background-color: #ffffff;z-index: 999;border-radius: 25rpx;padding: 32rpx;position: absolute;top: 0;left: 0;right: 0;">
|
||||
<!-- 卡片头部:标题和日期 -->
|
||||
@@ -22,9 +22,9 @@
|
||||
class="region-text">{{ $t('myCard.onlyForRegionBefore') }}{{ card.positionName }}{{ $t('myCard.onlyForRegionAfter') }}</text>
|
||||
<!-- 状态标签 / 去使用按钮 -->
|
||||
<view v-if="card.status !== 'expired'" class="status-tag active"
|
||||
style="display: flex; align-items: center; gap: 4rpx; background-color: #FFE2B8; border-radius: 26rpx; padding: 6rpx 20rpx;"
|
||||
style="display: flex; align-items: center; gap: 4rpx; background-color: #FFE2B8; border-radius: 20rpx; padding: 6rpx 20rpx;"
|
||||
@click.stop="handleUseCard(card)">
|
||||
<text class="status-text" style="color: #D4A574;">{{ $t('myCard.toUse') }}</text>
|
||||
<text class="status-text" style="color: #A16300;">{{ $t('myCard.toUse') }}</text>
|
||||
<!-- <uv-icon name="scan" size="12" color="#D4A574"></uv-icon> -->
|
||||
</view>
|
||||
<view v-else class="status-tag" :class="getStatusClass(card.status)">
|
||||
@@ -33,8 +33,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<!-- 使用情况和操作按钮 -->
|
||||
<view style="position: absolute; bottom: -10rpx; left: 0; right: 0; padding: 20rpx;z-index:1;"
|
||||
v-if="card.cardType==='COUNT'">
|
||||
<view style="position: absolute; bottom: -20rpx; left: 0; right: 0; padding: 20rpx;z-index:1;">
|
||||
<view class="card-footer">
|
||||
<!-- 次卡信息 -->
|
||||
<view v-if="card.cardType === 'COUNT'" class="card-usage-info">
|
||||
@@ -42,8 +41,8 @@
|
||||
$t('myCard.times') }}</text>
|
||||
</view>
|
||||
<!-- 时长卡信息 -->
|
||||
<view v-if="card.cardType === 'TIME'" class="card-usage-info">
|
||||
<text class="usage-text">{{ $t('myCard.durationCard') }}</text>
|
||||
<view v-else class="card-usage-info">
|
||||
<text class="usage-text">每日限用次数:{{card.dailyLimitCount}}次</text>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
@@ -52,7 +51,10 @@
|
||||
<view v-if="card.cardType === 'COUNT'" class="renew-btn" @click.stop="renewCard(card)">
|
||||
<text class="renew-text">{{ $t('myCard.renew') }}</text>
|
||||
<uv-icon name="arrow-right" size="14" color="#D4A574"></uv-icon>
|
||||
<!-- <text class="arrow">{{ '>' }}</text> -->
|
||||
|
||||
</view>
|
||||
<view v-else class="renew-btn">
|
||||
<text class="renew-text">单次限时:{{card.singleLimitMinutes}}分钟</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -356,7 +358,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* padding-top: 24rpx ; */
|
||||
padding: 20rpx;
|
||||
padding-top: 50rpx;
|
||||
position: absolute;
|
||||
background: rgba(255, 244, 227, 1);
|
||||
z-index: 1;
|
||||
@@ -364,7 +367,7 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-radius: 0 0 25rpx 25rpx;
|
||||
padding: 20rpx;
|
||||
|
||||
/* text-align: 30rpx ; */
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -40,7 +40,7 @@
|
||||
style="font-size: 22rpx; color: #999; margin-top: 4rpx;">
|
||||
{{ $t('myCoupon.onlyForRegionBefore') }}{{ coupon.positionName }}{{ $t('myCoupon.onlyForRegionAfter') }}
|
||||
</text> -->
|
||||
<view class="use-btn" v-if="coupon.status === 'available'" @click="useCoupon(coupon)">
|
||||
<view class="use-btn" v-if="coupon.status == 'unused'" @click="useCoupon(coupon)">
|
||||
<text class="use-text">{{ $t('myCoupon.useNow') }}</text>
|
||||
</view>
|
||||
<text class="coupon-status" v-else>{{ getStatusText(coupon.status) }}</text>
|
||||
@@ -85,7 +85,7 @@ const couponList = ref([])
|
||||
|
||||
// 过滤后的优惠券
|
||||
const filteredCoupons = computed(() => {
|
||||
return couponList.value.filter(coupon => coupon.status === currentTab.value)
|
||||
return couponList.value;
|
||||
})
|
||||
|
||||
// 获取优惠券列表
|
||||
@@ -114,6 +114,7 @@ const getCouponList = async () => {
|
||||
} else if (item.couponEndTime) {
|
||||
validity = `于 ${item.couponEndTime.split(' ')[0]} 过期`
|
||||
}
|
||||
console.log(item.status);
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
@@ -123,7 +124,7 @@ const getCouponList = async () => {
|
||||
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
|
||||
condition: condition,
|
||||
validity: validity,
|
||||
status: currentTab.value,
|
||||
status: item.status,
|
||||
positionName: item.positionName
|
||||
}
|
||||
})
|
||||
@@ -148,15 +149,17 @@ const switchTab = (tab) => {
|
||||
|
||||
// 获取优惠券样式类名
|
||||
const getCouponClass = (status) => {
|
||||
return status === 'available' ? '' : 'disabled'
|
||||
return status === 'unused' ? '' : 'disabled'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status) => {
|
||||
const statusMap = {
|
||||
'used': t('myCoupon.usedStatus'),
|
||||
'expired': t('myCoupon.expiredStatus')
|
||||
'expired': t('myCoupon.expiredStatus'),
|
||||
'refunded':t('myCoupon.refundedStatus')
|
||||
}
|
||||
console.log("获取状态文本:"+statusMap[status]);
|
||||
return statusMap[status] || ''
|
||||
}
|
||||
|
||||
|
||||
+106
-78
@@ -89,7 +89,8 @@
|
||||
<view class="rent-label">{{ $t('order.usedPromotion') }}</view>
|
||||
<view class="rent-value promotion-value">
|
||||
<image src="/static/promotion-icon.png" class="promotion-icon" mode="aspectFit"></image>
|
||||
{{ orderInfo.discountTypeName }}
|
||||
{{ orderInfo.discountTypeName }}<text
|
||||
v-if="orderInfo.discountAmount">{{'-¥'+orderInfo.discountAmount||''}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
|
||||
@@ -108,7 +109,8 @@
|
||||
</view>
|
||||
<view class="">
|
||||
<view class="" style="font-size: 24rpx;text-align: center;">
|
||||
{{ $t('order.returnProblemTip') }}<text @click="contactService" style="color:#07c160 ;">{{ $t('user.customerService') }}</text>{{ $t('order.contactStaff') }}
|
||||
{{ $t('order.returnProblemTip') }}<text @click="contactService"
|
||||
style="color:#07c160 ;">{{ $t('user.customerService') }}</text>{{ $t('order.contactStaff') }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -161,7 +163,8 @@
|
||||
|
||||
<!-- 已完成状态 -->
|
||||
<template v-if="isOrderCompleted()">
|
||||
<view class="bottom-icon-btn" @click="handleWithdraw" v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">
|
||||
<view class="bottom-icon-btn" @click="handleWithdraw"
|
||||
v-if="!orderInfo.isWithdrawn && orderInfo.refundAmount > 0">
|
||||
<image src="/static/suggess.png" class="icon" mode="aspectFit"></image>
|
||||
<text>{{ $t('order.feeAppeal') }}</text>
|
||||
</view>
|
||||
@@ -187,14 +190,8 @@
|
||||
</view>
|
||||
|
||||
<!-- 转为自用确认弹窗 -->
|
||||
<uv-popup
|
||||
ref="convertToOwnPopup"
|
||||
mode="center"
|
||||
round="20"
|
||||
:overlay="true"
|
||||
:closeOnClickOverlay="false"
|
||||
:safeAreaInsetBottom="false"
|
||||
>
|
||||
<uv-popup ref="convertToOwnPopup" mode="center" round="20" :overlay="true" :closeOnClickOverlay="false"
|
||||
:safeAreaInsetBottom="false">
|
||||
<view class="convert-to-own-popup">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">{{ $t('order.convertToOwnWithMaxFeeTitle') }}</text>
|
||||
@@ -216,8 +213,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'
|
||||
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
getCurrentInstance
|
||||
} from 'vue'
|
||||
import {
|
||||
onLoad,
|
||||
onShow,
|
||||
onHide,
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app'
|
||||
import {
|
||||
queryById,
|
||||
cancelOrder,
|
||||
@@ -238,9 +246,13 @@
|
||||
import {
|
||||
URL
|
||||
} from "@/config/url.js"
|
||||
import { useI18n } from '@/utils/i18n.js'
|
||||
import {
|
||||
useI18n
|
||||
} from '@/utils/i18n.js'
|
||||
|
||||
const { t } = useI18n()
|
||||
const {
|
||||
t
|
||||
} = useI18n()
|
||||
const instance = getCurrentInstance()
|
||||
const $orderMonitor = instance?.proxy?.$orderMonitor || null
|
||||
|
||||
@@ -274,7 +286,9 @@
|
||||
canUseCoupon: false,
|
||||
userMemberCardId: '',
|
||||
userPurchaseId: '',
|
||||
discountTypeName: ''
|
||||
discountTypeName: '',
|
||||
originalFee:'',
|
||||
discountAmount:''
|
||||
})
|
||||
const timer = ref(null)
|
||||
const statusCheckTimer = ref(null)
|
||||
@@ -289,12 +303,12 @@
|
||||
const countdownTimer = ref(null)
|
||||
const feeRuleText = ref('')
|
||||
const convertToOwnPopup = ref(null)
|
||||
|
||||
|
||||
// 计算属性:是否显示优惠券/会员卡可用提示
|
||||
const canUsePromotionTag = computed(() => {
|
||||
return orderInfo.value.canUseMember === true || orderInfo.value.canUseCoupon === true
|
||||
})
|
||||
|
||||
|
||||
// 计算属性:是否使用了优惠
|
||||
const hasUsedPromotion = computed(() => {
|
||||
return !!(orderInfo.value.userMemberCardId || orderInfo.value.userPurchaseId)
|
||||
@@ -308,11 +322,11 @@
|
||||
}
|
||||
return '-'
|
||||
}
|
||||
|
||||
|
||||
// 判断订单是否已完成
|
||||
const isOrderCompleted = () => {
|
||||
return orderInfo.value.orderStatus === 'used_done' ||
|
||||
orderInfo.value.orderStatus === 'used_down'
|
||||
return orderInfo.value.orderStatus === 'used_done' ||
|
||||
orderInfo.value.orderStatus === 'used_down'
|
||||
}
|
||||
|
||||
// 获取订单状态文字
|
||||
@@ -362,13 +376,13 @@
|
||||
// 获取计费规则显示
|
||||
const getPricingRuleDisplay = () => {
|
||||
if (!orderInfo.value.unitPrice || !orderInfo.value.orderType) return '-'
|
||||
|
||||
|
||||
const orderTypeMap = {
|
||||
'hours': t('time.hours'),
|
||||
'minutes': t('time.minutes'),
|
||||
'halfhours': t('time.halfHours')
|
||||
}
|
||||
|
||||
|
||||
const orderTypeText = orderTypeMap[orderInfo.value.orderType] || orderInfo.value.orderType
|
||||
return `${orderInfo.value.unitPrice}${t('unit.yuan')}/${orderTypeText}`
|
||||
}
|
||||
@@ -407,39 +421,40 @@
|
||||
// 获取使用时长显示信息
|
||||
const getUsedTimeDisplay = () => {
|
||||
let usedTime = orderInfo.value.usedTime
|
||||
|
||||
|
||||
// 如果 usedTime 为空,通过开始时间和结束时间计算
|
||||
if (usedTime === '0分钟' && orderInfo.value.startTime && orderInfo.value.endTime) {
|
||||
const startMs = parseStartTimeToMs(orderInfo.value.startTime)
|
||||
const endMs = parseStartTimeToMs(orderInfo.value.endTime)
|
||||
|
||||
|
||||
if (!isNaN(startMs) && !isNaN(endMs)) {
|
||||
const diffMs = endMs - startMs
|
||||
const totalMinutes = Math.floor(diffMs / (1000 * 60))
|
||||
|
||||
|
||||
// 格式化为 "X小时X分钟" 或 "X分钟"
|
||||
if (totalMinutes >= 60) {
|
||||
const hours = Math.floor(totalMinutes / 60)
|
||||
const minutes = totalMinutes % 60
|
||||
usedTime = minutes > 0 ? `${hours}${t('time.hour')}${minutes}${t('time.minute')}` : `${hours}${t('time.hour')}`
|
||||
usedTime = minutes > 0 ? `${hours}${t('time.hour')}${minutes}${t('time.minute')}` :
|
||||
`${hours}${t('time.hour')}`
|
||||
} else {
|
||||
usedTime = `${totalMinutes}${t('time.minute')}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果还是没有值,使用默认值
|
||||
if (!usedTime) {
|
||||
usedTime = `0${t('time.minute')}`
|
||||
}
|
||||
|
||||
|
||||
// 解析时长字符串,例如 "1小时5分钟" 或 "5分钟"
|
||||
const hourMatch = usedTime.match(new RegExp(`(\\d+)${t('time.hour')}`))
|
||||
const minuteMatch = usedTime.match(new RegExp(`(\\d+)${t('time.minute')}`))
|
||||
|
||||
|
||||
let displayNumber = ''
|
||||
let displayUnit = ''
|
||||
|
||||
|
||||
if (hourMatch && minuteMatch) {
|
||||
// 有小时也有分钟,如 "1小时5分钟"
|
||||
displayNumber = `${hourMatch[1]}`
|
||||
@@ -457,7 +472,7 @@
|
||||
displayNumber = '0'
|
||||
displayUnit = t('time.minute')
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
number: displayNumber,
|
||||
unit: displayUnit
|
||||
@@ -472,7 +487,8 @@
|
||||
|
||||
// 获取订单费用(不含单位)
|
||||
const getOrderFee = () => {
|
||||
const fee = orderInfo.value.currentFee || orderInfo.value.payAmount || '0'
|
||||
|
||||
const fee = orderInfo.value.originalFee || orderInfo.value.originalFee || '0'
|
||||
// 移除可能的"元"字符
|
||||
return String(fee).replace(/[元¥]/g, '')
|
||||
}
|
||||
@@ -526,7 +542,7 @@
|
||||
countdownRemaining.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (orderInfo.value.orderStatus !== 'in_used') {
|
||||
showExpressAction.value = false
|
||||
countdownRemaining.value = 0
|
||||
@@ -559,7 +575,7 @@
|
||||
countdownRemaining.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
clearExpressCountdown()
|
||||
recomputeExpressCountdownFromStartTime()
|
||||
if (showExpressAction.value) return
|
||||
@@ -590,13 +606,16 @@
|
||||
const loadSystemConfig = async () => {
|
||||
try {
|
||||
// 优先使用订单数据中的 expressReturnStart(小时转秒)
|
||||
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' && orderInfo.value.expressReturnStart > 0) {
|
||||
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' &&
|
||||
orderInfo.value.expressReturnStart > 0) {
|
||||
expressThresholdSeconds.value = orderInfo.value.expressReturnStart * 3600
|
||||
console.log('使用订单配置的快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value, '秒')
|
||||
console.log('使用订单配置的快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds
|
||||
.value, '秒')
|
||||
} else {
|
||||
// 如果订单数据中没有,则使用系统配置
|
||||
const res = await getSystemConfig()
|
||||
if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds === 'number') {
|
||||
if (res && res.code === 200 && res.data && typeof res.data.expressReturnCountdownSeconds ===
|
||||
'number') {
|
||||
const seconds = res.data.expressReturnCountdownSeconds
|
||||
if (seconds > 0) {
|
||||
expressThresholdSeconds.value = seconds
|
||||
@@ -604,7 +623,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (orderInfo.value.orderStatus === 'in_used' && orderInfo.value.startTime) {
|
||||
recomputeExpressCountdownFromStartTime()
|
||||
}
|
||||
@@ -692,13 +711,13 @@
|
||||
clearStatusCheckTimer()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (orderInfo.value.orderStatus !== 'in_used') {
|
||||
console.log('订单状态已变更,停止状态检查计时器')
|
||||
clearStatusCheckTimer()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
currentStatusChecks.value++
|
||||
console.log(`执行归还状态检查 (${currentStatusChecks.value}/${maxStatusChecks.value})`)
|
||||
checkReturnStatus()
|
||||
@@ -730,9 +749,10 @@
|
||||
// 更新订单信息
|
||||
const updateOrderInfo = (orderData) => {
|
||||
const oldStatus = orderInfo.value.orderStatus
|
||||
|
||||
|
||||
orderInfo.value.usedTime = orderData.usedTime || '0分钟'
|
||||
orderInfo.value.currentFee = orderData.currentFee || orderData.actualDeviceAmount || orderData.payAmount || '0.00'
|
||||
orderInfo.value.currentFee = orderData.currentFee || orderData.actualDeviceAmount || orderData.payAmount ||
|
||||
'0.00'
|
||||
orderInfo.value.orderStatus = orderData.orderStatus || ''
|
||||
orderInfo.value.payWay = orderData.payWay || ''
|
||||
orderInfo.value.startTime = orderData.startTime || orderData.createTime || ''
|
||||
@@ -747,47 +767,52 @@
|
||||
orderInfo.value.withdrawStatus = orderData.withdrawStatus || 'waiting'
|
||||
orderInfo.value.isWithdrawn = orderData.withdrawStatus === 'success'
|
||||
orderInfo.value.positionName = orderData.positionName || orderData.positionLocation || ''
|
||||
orderInfo.value.returnPosition = orderData.returnPosition || orderData.positionName || orderData.positionLocation || ''
|
||||
orderInfo.value.returnPosition = orderData.returnPosition || orderData.positionName || orderData
|
||||
.positionLocation || ''
|
||||
orderInfo.value.freeRentTime = orderData.freeRentTime || ''
|
||||
orderInfo.value.unitPrice = orderData.unitPrice || ''
|
||||
orderInfo.value.orderType = orderData.orderType || ''
|
||||
|
||||
orderInfo.value.discountAmount = orderData.discountAmount||''
|
||||
|
||||
// 保存优惠券/会员卡相关信息
|
||||
orderInfo.value.canUseMember = orderData.canUseMember === true
|
||||
orderInfo.value.canUseCoupon = orderData.canUseCoupon === true
|
||||
orderInfo.value.userMemberCardId = orderData.userMemberCardId || ''
|
||||
orderInfo.value.userPurchaseId = orderData.userPurchaseId || ''
|
||||
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
|
||||
|
||||
orderInfo.value.originalFee = orderData.originalFee||''
|
||||
|
||||
// 保存快递归还开始时间(小时为单位)
|
||||
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
|
||||
|
||||
|
||||
// 保存是否支持快递归还
|
||||
orderInfo.value.isSupportExpressReturn = orderData.isSupportExpressReturn || 'yes'
|
||||
|
||||
|
||||
// 如果有有效的 expressReturnStart,立即更新倒计时阈值(小时转秒)
|
||||
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' && orderInfo.value.expressReturnStart > 0) {
|
||||
if (orderInfo.value.expressReturnStart && typeof orderInfo.value.expressReturnStart === 'number' && orderInfo
|
||||
.value.expressReturnStart > 0) {
|
||||
expressThresholdSeconds.value = orderInfo.value.expressReturnStart * 3600
|
||||
console.log('从订单数据更新快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value, '秒')
|
||||
console.log('从订单数据更新快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value,
|
||||
'秒')
|
||||
}
|
||||
|
||||
if (orderData.deviceNo && !deviceId.value) {
|
||||
deviceId.value = orderData.deviceNo
|
||||
}
|
||||
|
||||
|
||||
// 如果订单已完成,从监控中移除
|
||||
if (isOrderCompleted()) {
|
||||
removeFromOrderMonitor()
|
||||
console.log('订单已完成,已从监控队列移除')
|
||||
}
|
||||
|
||||
|
||||
// 如果订单状态从 'in_used' 变为其他状态,清理所有定时器
|
||||
if (oldStatus === 'in_used' && orderInfo.value.orderStatus !== 'in_used') {
|
||||
console.log('订单状态已从使用中变为:', orderInfo.value.orderStatus, ',清理所有定时器')
|
||||
clearTimer()
|
||||
clearStatusCheckTimer()
|
||||
clearExpressCountdown()
|
||||
|
||||
|
||||
// 显示订单完成提示
|
||||
if (orderInfo.value.orderStatus === 'used_done' || orderInfo.value.orderStatus === 'used_down') {
|
||||
uni.showToast({
|
||||
@@ -841,7 +866,8 @@
|
||||
// 如果订单已完成,从监控中移除
|
||||
if (isOrderCompleted()) {
|
||||
removeFromOrderMonitor()
|
||||
console.log('订单已完成,已从监控队列移除:', orderInfo.value.orderId, '当前状态:', orderInfo.value.orderStatus)
|
||||
console.log('订单已完成,已从监控队列移除:', orderInfo.value.orderId, '当前状态:', orderInfo.value
|
||||
.orderStatus)
|
||||
} else {
|
||||
// 订单未完成,添加到监控队列
|
||||
uni.setStorageSync('activeOrderId', orderInfo.value.orderId)
|
||||
@@ -853,7 +879,8 @@
|
||||
$orderMonitor.addOrder({
|
||||
orderId: orderInfo.value.orderId
|
||||
}, 'detail')
|
||||
console.log('订单已添加到监控队列:', orderInfo.value.orderId, '当前状态:', orderInfo.value.orderStatus)
|
||||
console.log('订单已添加到监控队列:', orderInfo.value.orderId, '当前状态:', orderInfo.value
|
||||
.orderStatus)
|
||||
} else {
|
||||
console.warn('$orderMonitor 未定义,无法添加订单到监控队列')
|
||||
}
|
||||
@@ -1035,7 +1062,7 @@
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
uni.showToast({
|
||||
title: t('payment.subscriptionSuccess'),
|
||||
icon: 'success',
|
||||
@@ -1055,7 +1082,8 @@
|
||||
// 处理"不想还了转为自用"(按最高费用)
|
||||
const handleConvertToOwnedWithMaxFee = () => {
|
||||
try {
|
||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.open === 'function' && convertToOwnPopup.value.open()
|
||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.open === 'function' && convertToOwnPopup.value
|
||||
.open()
|
||||
} catch (e) {
|
||||
console.error('打开弹窗失败', e)
|
||||
}
|
||||
@@ -1064,7 +1092,8 @@
|
||||
// 关闭转为自用弹窗
|
||||
const closeConvertToOwnPopup = () => {
|
||||
try {
|
||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.close === 'function' && convertToOwnPopup.value.close()
|
||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.close === 'function' && convertToOwnPopup.value
|
||||
.close()
|
||||
} catch (e) {
|
||||
console.error('关闭弹窗失败', e)
|
||||
}
|
||||
@@ -1077,9 +1106,9 @@
|
||||
uni.showLoading({
|
||||
title: t('common.processing')
|
||||
})
|
||||
|
||||
|
||||
const result = await closeWithMaxFee(orderInfo.value.orderNo)
|
||||
|
||||
|
||||
if (result.code === 200) {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
@@ -1087,7 +1116,7 @@
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
})
|
||||
|
||||
|
||||
// 刷新订单详情
|
||||
setTimeout(() => {
|
||||
getOrderDetails()
|
||||
@@ -1115,7 +1144,7 @@
|
||||
})
|
||||
|
||||
isPageActive.value = true
|
||||
|
||||
|
||||
// 从缓存读取通知内容(计费规则)
|
||||
try {
|
||||
const cachedNotice = uni.getStorageSync('noticeContent')
|
||||
@@ -1196,7 +1225,7 @@
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
|
||||
.header-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1218,7 +1247,7 @@
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height:100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1227,7 +1256,7 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
// padding: 12rpx 20rpx;
|
||||
// background: #E8F5E9;
|
||||
// border-radius: 12rpx;
|
||||
@@ -1251,7 +1280,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.promotion-tag {
|
||||
padding: 6rpx 16rpx;
|
||||
background: linear-gradient(135deg, #FFF4E6 0%, #FFE9CC 100%);
|
||||
@@ -1366,7 +1395,7 @@
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
z-index: 9;
|
||||
|
||||
|
||||
&:active {
|
||||
opacity: 0.85;
|
||||
}
|
||||
@@ -1408,14 +1437,14 @@
|
||||
color: #333;
|
||||
text-align: right;
|
||||
max-width: 400rpx;
|
||||
|
||||
|
||||
&.promotion-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
color: #FF8C00;
|
||||
font-weight: 500;
|
||||
|
||||
|
||||
.promotion-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
@@ -1431,7 +1460,7 @@
|
||||
align-items: baseline;
|
||||
padding-top: 20rpx;
|
||||
margin-top: 10rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
// border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.paid-label {
|
||||
font-size: 28rpx;
|
||||
@@ -1580,7 +1609,7 @@
|
||||
padding: 0 0 40rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
|
||||
|
||||
.popup-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
@@ -1596,7 +1625,7 @@
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
margin-left: -40rpx;
|
||||
margin-right: -40rpx;
|
||||
|
||||
|
||||
.popup-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
@@ -1605,20 +1634,20 @@
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
&.cancel-btn {
|
||||
color: #666;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
|
||||
|
||||
&:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&.confirm-btn {
|
||||
color: #07c160;
|
||||
font-weight: 600;
|
||||
|
||||
|
||||
&:active {
|
||||
background-color: #f0f9f4;
|
||||
}
|
||||
@@ -1626,5 +1655,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<view class="scan-page">
|
||||
<!-- 扫码区域 -->
|
||||
<view class="scan-window">
|
||||
<video id="scanVideo" ref="videoRef" class="scan-video" autoplay playsinline muted></video>
|
||||
<canvas id="scanCanvas" ref="canvasRef" class="hidden-canvas" style="display: none;"></canvas>
|
||||
|
||||
<!-- 扫描装饰 -->
|
||||
<view class="scan-mask">
|
||||
<view class="scan-frame">
|
||||
<view class="scan-line"></view>
|
||||
<view class="corner top-left"></view>
|
||||
<view class="corner top-right"></view>
|
||||
<view class="corner bottom-left"></view>
|
||||
<view class="corner bottom-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scan-tip">{{ tipText }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-actions">
|
||||
<view class="action-item" @click="chooseImage">
|
||||
<uv-icon name="photo" size="28" color="#fff"></uv-icon>
|
||||
<text>相册</text>
|
||||
</view>
|
||||
|
||||
<view class="action-item" @click="toggleInput">
|
||||
<uv-icon name="edit-pen" size="28" color="#fff"></uv-icon>
|
||||
<text>手动输入</text>
|
||||
</view>
|
||||
|
||||
<view class="action-item" @click="goBack">
|
||||
<uv-icon name="arrow-left" size="28" color="#fff"></uv-icon>
|
||||
<text>返回</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手动输入弹窗 -->
|
||||
<uv-popup ref="inputPopup" mode="center" round="16" :closeOnClickOverlay="true">
|
||||
<view class="input-dialog">
|
||||
<view class="dialog-title">手动输入设备号</view>
|
||||
<input
|
||||
v-model="manualDeviceNo"
|
||||
placeholder="请输入设备上的编号"
|
||||
class="device-input"
|
||||
type="text"
|
||||
focus
|
||||
/>
|
||||
<view class="dialog-btns">
|
||||
<button class="cancel-btn" @click="closeInput">取消</button>
|
||||
<button class="confirm-btn" @click="confirmManualInput">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</uv-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { getQueryString } from '@/util/index.js';
|
||||
|
||||
const videoRef = ref(null);
|
||||
const canvasRef = ref(null);
|
||||
const inputPopup = ref(null);
|
||||
const manualDeviceNo = ref('');
|
||||
const tipText = ref('正在启动扫描...');
|
||||
const scanning = ref(false);
|
||||
|
||||
let stream = null;
|
||||
let animationId = null;
|
||||
|
||||
// 动态加载解码库
|
||||
const loadJsQR = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.jsQR) {
|
||||
resolve(window.jsQR);
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js';
|
||||
script.onload = () => resolve(window.jsQR);
|
||||
script.onerror = (e) => {
|
||||
console.error('jsQR 加载失败:', e);
|
||||
reject(new Error('解码组件加载失败,请检查网络'));
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
const initScan = async () => {
|
||||
try {
|
||||
// 1. 检查环境
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
throw new Error('您的浏览器不支持摄像头访问,请使用微信扫描或手动输入');
|
||||
}
|
||||
|
||||
// 2. 加载解码库
|
||||
const jsQR = await loadJsQR();
|
||||
|
||||
// 3. 启动摄像头 - 尝试逐步降低约束
|
||||
let constraints = {
|
||||
video: {
|
||||
facingMode: 'environment',
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 }
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
} catch (e) {
|
||||
console.warn('尝试理想约束失败,降级请求:', e);
|
||||
// 降级:仅请求视频,不限制分辨率和模式
|
||||
stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
||||
}
|
||||
|
||||
if (videoRef.value) {
|
||||
const video = videoRef.value;
|
||||
// 关键:在赋值 srcObject 之前先重置
|
||||
video.pause();
|
||||
video.srcObject = stream;
|
||||
|
||||
// 使用更稳健的事件监听
|
||||
const startPlay = () => {
|
||||
video.play().then(() => {
|
||||
console.log('视频开始播放');
|
||||
scanning.value = true;
|
||||
tipText.value = '将二维码放入框内即可自动扫描';
|
||||
tick(jsQR);
|
||||
}).catch(e => {
|
||||
console.error('视频播放 Promise 失败:', e);
|
||||
// 如果是由于用户交互限制,提示用户点击
|
||||
tipText.value = '请点击屏幕启动扫描';
|
||||
});
|
||||
};
|
||||
|
||||
if (video.readyState >= 2) {
|
||||
startPlay();
|
||||
} else {
|
||||
video.onloadedmetadata = startPlay;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('摄像头初始化失败:', err);
|
||||
let errMsg = '摄像头开启失败';
|
||||
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||
errMsg = '请授予摄像头访问权限后重试';
|
||||
} else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
|
||||
errMsg = '未找到可用的摄像头';
|
||||
} else if (location.protocol !== 'https:' && location.hostname !== 'localhost') {
|
||||
errMsg = '非加密连接(HTTPS)无法开启摄像头';
|
||||
}
|
||||
|
||||
tipText.value = errMsg;
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: errMsg,
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
// 如果失败且无法恢复,引导手动输入
|
||||
toggleInput();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const tick = (jsQR) => {
|
||||
if (!scanning.value) return;
|
||||
|
||||
const video = videoRef.value;
|
||||
const canvas = canvasRef.value;
|
||||
|
||||
if (video && video.readyState === video.HAVE_ENOUGH_DATA && canvas) {
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.height = video.videoHeight;
|
||||
canvas.width = video.videoWidth;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, {
|
||||
inversionAttempts: 'dontInvert',
|
||||
});
|
||||
|
||||
if (code && code.data) {
|
||||
console.log('扫码结果:', code.data);
|
||||
handleSuccess(code.data);
|
||||
return; // 停止循环
|
||||
}
|
||||
}
|
||||
animationId = requestAnimationFrame(() => tick(jsQR));
|
||||
};
|
||||
|
||||
const handleSuccess = (result) => {
|
||||
stopScan();
|
||||
|
||||
// 通过全局事件通知首页
|
||||
uni.$emit('h5ScanSuccess', {
|
||||
result: result,
|
||||
scanType: 'QR_CODE'
|
||||
});
|
||||
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const stopScan = () => {
|
||||
scanning.value = false;
|
||||
if (stream) {
|
||||
stream.getTracks().forEach(track => track.stop());
|
||||
stream = null;
|
||||
}
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
animationId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const chooseImage = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sourceType: ['album'],
|
||||
success: async (res) => {
|
||||
uni.showLoading({ title: '正在识别...' });
|
||||
try {
|
||||
const jsQR = await loadJsQR();
|
||||
const img = new Image();
|
||||
img.src = res.tempFilePaths[0];
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height);
|
||||
|
||||
uni.hideLoading();
|
||||
if (code && code.data) {
|
||||
handleSuccess(code.data);
|
||||
} else {
|
||||
uni.showToast({ title: '未识别到二维码', icon: 'none' });
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '识别出错', icon: 'none' });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const toggleInput = () => {
|
||||
inputPopup.value.open();
|
||||
};
|
||||
|
||||
const closeInput = () => {
|
||||
inputPopup.value.close();
|
||||
};
|
||||
|
||||
const confirmManualInput = () => {
|
||||
if (!manualDeviceNo.value) return;
|
||||
|
||||
let deviceNo = manualDeviceNo.value.trim();
|
||||
if (deviceNo.includes('deviceNo=')) {
|
||||
deviceNo = getQueryString(deviceNo, 'deviceNo') || deviceNo;
|
||||
}
|
||||
|
||||
closeInput();
|
||||
stopScan();
|
||||
|
||||
uni.$emit('h5ScanSuccess', {
|
||||
result: deviceNo,
|
||||
scanType: 'MANUAL'
|
||||
});
|
||||
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initScan();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopScan();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scan-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.scan-window {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
.scan-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
|
||||
.scan-frame {
|
||||
width: 500rpx;
|
||||
height: 500rpx;
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 1000px rgba(0, 0, 0, 0.4);
|
||||
|
||||
.scan-line {
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
background: linear-gradient(to right, transparent, #3EAB64, transparent);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
animation: scanMove 3s linear infinite;
|
||||
}
|
||||
|
||||
.corner {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: 6rpx solid #3EAB64;
|
||||
}
|
||||
|
||||
.top-left { top: -2rpx; left: -2rpx; border-right: none; border-bottom: none; }
|
||||
.top-right { top: -2rpx; right: -2rpx; border-left: none; border-bottom: none; }
|
||||
.bottom-left { bottom: -2rpx; left: -2rpx; border-right: none; border-top: none; }
|
||||
.bottom-right { bottom: -2rpx; right: -2rpx; border-left: none; border-top: none; }
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scanMove {
|
||||
0% { top: 0; }
|
||||
100% { top: 100%; }
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
position: absolute;
|
||||
top: 15%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.bottom-actions {
|
||||
position: absolute;
|
||||
bottom: 80rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 0 60rpx;
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
text {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-dialog {
|
||||
width: 600rpx;
|
||||
background: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 24rpx;
|
||||
|
||||
.dialog-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.device-input {
|
||||
height: 88rpx;
|
||||
background: #F8F9FA;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
border: 1rpx solid #eee;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.dialog-btns {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: #3EAB64;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -122,8 +122,11 @@
|
||||
url: `/pages/device/detail?deviceNo=${option.deviceNo}`
|
||||
});
|
||||
} else {
|
||||
// uni.switchTab({
|
||||
// url:'/pages/index/index'
|
||||
// })
|
||||
// 如果连deviceNo都没有,则返回首页
|
||||
uni.switchTab({ url: '/pages/index/index' });
|
||||
uni.reLaunch({ url: '/pages/index/index' });
|
||||
}
|
||||
}, 2000);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user