fix:修复bug
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version" : "1.0",
|
||||||
|
"configurations" : [
|
||||||
|
{
|
||||||
|
"playground" : "standard",
|
||||||
|
"type" : "uni-app:app-android"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -57,6 +57,9 @@
|
|||||||
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
||||||
try {
|
try {
|
||||||
const savedLang = uni.getStorageSync('language')
|
const savedLang = uni.getStorageSync('language')
|
||||||
|
if(savedLang){
|
||||||
|
uni.removeStorageSync('language');
|
||||||
|
}
|
||||||
console.log('App onShow - 缓存中的语言:', savedLang)
|
console.log('App onShow - 缓存中的语言:', savedLang)
|
||||||
|
|
||||||
// 获取当前 i18n 实例并检查语言
|
// 获取当前 i18n 实例并检查语言
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<view class="device-order-card" @click="onCardClick">
|
||||||
|
<!-- 头部:标题和状态 -->
|
||||||
|
<view class="card-header">
|
||||||
|
<view class="header-left">
|
||||||
|
<view class="tag-bar"></view>
|
||||||
|
<text class="title">定制化订单</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-badge">{{ statusText }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 产品信息区域 -->
|
||||||
|
<view class="product-section">
|
||||||
|
<image
|
||||||
|
:src="order.pictureUrl || order.productImage || '/static/default-product.png'"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="product-image"
|
||||||
|
></image>
|
||||||
|
<view class="product-info">
|
||||||
|
<view class="product-name">{{ order.productName || order.deviceName || '风电者2026新款' }}</view>
|
||||||
|
<view style="display: flex;justify-content: space-between;">
|
||||||
|
<view class="product-style">款式:{{ order.optionName || order.style || order.deviceStyle || '标准' }}</view>
|
||||||
|
<view class="product-price">¥ {{ totalAmount }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分割线 -->
|
||||||
|
<view class="divider-wrapper">
|
||||||
|
<uv-divider :hairline="false" lineColor="#f5f5f5"></uv-divider>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部时间和删除按钮 -->
|
||||||
|
<view class="card-footer">
|
||||||
|
<text class="order-time">{{ order.startTime || order.createTime }}</text>
|
||||||
|
<view class="footer-actions">
|
||||||
|
<!-- 待付款状态显示立即支付按钮 -->
|
||||||
|
<view v-if="isWaitingPayment" class="pay-btn" @click.stop="onPay">
|
||||||
|
<text>立即支付</text>
|
||||||
|
</view>
|
||||||
|
<view class="delete-btn" @click.stop="onDelete">
|
||||||
|
<uv-icon name="trash" size="20" color="#999"></uv-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
order: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['click', 'delete', 'pay']);
|
||||||
|
|
||||||
|
// 订单状态文本
|
||||||
|
const statusText = computed(() => {
|
||||||
|
const status = props.order.orderStatus || props.order.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 isWaitingPayment = computed(() => {
|
||||||
|
const status = props.order.orderStatus || props.order.status;
|
||||||
|
return status === 0 || status === '0';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 总金额
|
||||||
|
const totalAmount = computed(() => {
|
||||||
|
return props.order.totalAmount || props.order.amount || props.order.payAmount || '99.0';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 卡片点击事件
|
||||||
|
const onCardClick = () => {
|
||||||
|
emit('click', props.order);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除订单
|
||||||
|
const onDelete = () => {
|
||||||
|
emit('delete', props.order);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 立即支付
|
||||||
|
const onPay = () => {
|
||||||
|
emit('pay', props.order);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.device-order-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
// 头部区域
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 24rpx 0 24rpx;
|
||||||
|
// border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tag-bar {
|
||||||
|
width: 6rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background: #07c160;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #3EAB64;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background: rgba(7, 193, 96, 0.1);
|
||||||
|
color: #07c160;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产品信息区域
|
||||||
|
.product-section {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx 24rpx 0 24rpx;
|
||||||
|
|
||||||
|
.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: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-style {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #07c160;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割线区域
|
||||||
|
.divider-wrapper {
|
||||||
|
padding: 0 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部区域
|
||||||
|
.card-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0rpx 24rpx 24rpx;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.order-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
|
||||||
|
.pay-btn {
|
||||||
|
padding: 12rpx 32rpx;
|
||||||
|
background: linear-gradient(135deg, #07c160, #10d673);
|
||||||
|
border-radius: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="map-container" :class="{ 'full-width': props.fullWidth }" :style="{ '--map-height': props.customHeight || '72vh' }">
|
<view class="map-container" :class="{ 'full-width': props.fullWidth }">
|
||||||
<!-- 地图容器 -->
|
<!-- 地图容器 -->
|
||||||
<view class="map-wrapper">
|
<view class="map-wrapper">
|
||||||
<!-- 使用小程序原生地图组件 -->
|
<!-- 使用小程序原生地图组件 -->
|
||||||
@@ -26,16 +26,18 @@
|
|||||||
</cover-view>
|
</cover-view>
|
||||||
|
|
||||||
<cover-view class="map-side-controls" v-if="!props.hideControls && !props.hideMapOverlays">
|
<cover-view class="map-side-controls" v-if="!props.hideControls && !props.hideMapOverlays">
|
||||||
|
<cover-view class="side-btn guide" @tap="handleGuide">
|
||||||
|
<cover-image class="side-icon" src="/static/use_help.png" style="border-radius: 50%;"></cover-image>
|
||||||
|
</cover-view>
|
||||||
<cover-view class="side-btn locate" @tap="handleRelocate">
|
<cover-view class="side-btn locate" @tap="handleRelocate">
|
||||||
<cover-image class="side-icon" src="/static/location.png"></cover-image>
|
<cover-image class="side-icon" src="/static/location.png"></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
<cover-view class="side-btn service" @tap="handleService">
|
|
||||||
<cover-image class="side-icon" src="/static/customer-service.png"></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn search" @tap="handleSearch">
|
<cover-view class="side-btn search" @tap="handleSearch">
|
||||||
<cover-image class="side-icon" src="/static/other_device.png"></cover-image>
|
<cover-image class="side-icon" src="/static/other_device.png"></cover-image>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
|
<cover-view class="side-btn service" @tap="handleService">
|
||||||
|
<cover-image class="side-icon" src="/static/customer-service.png"></cover-image>
|
||||||
|
</cover-view>
|
||||||
</cover-view>
|
</cover-view>
|
||||||
</map>
|
</map>
|
||||||
|
|
||||||
@@ -128,7 +130,8 @@
|
|||||||
'showList',
|
'showList',
|
||||||
'markerTap',
|
'markerTap',
|
||||||
'mapCenterChange',
|
'mapCenterChange',
|
||||||
'bannerClick'
|
'bannerClick',
|
||||||
|
'guide'
|
||||||
])
|
])
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -380,6 +383,10 @@ const handleSearch = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGuide = () => {
|
||||||
|
emit('guide')
|
||||||
|
}
|
||||||
|
|
||||||
const handleJoinTap = () => {
|
const handleJoinTap = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/join/index'
|
url: '/pages/join/index'
|
||||||
@@ -494,7 +501,7 @@ const handleSearch = () => {
|
|||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 94vw;
|
width: 94vw;
|
||||||
height: var(--map-height, calc(100% - 20rpx)); /* 使用变量或默认高度 */
|
// height: var(--map-height, calc(100% - 20rpx)); /* 使用变量或默认高度 */
|
||||||
margin: 20rpx;
|
margin: 20rpx;
|
||||||
margin-bottom: 0; /* 底部不需要边距 */
|
margin-bottom: 0; /* 底部不需要边距 */
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
@@ -502,6 +509,14 @@ const handleSearch = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
height:78vh;
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
height: 72vh;
|
||||||
|
// #endif
|
||||||
|
|
||||||
&.full-width {
|
&.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -596,13 +611,13 @@ const handleSearch = () => {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
// height: 72rpx;
|
// height: 72rpx;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
border-radius: 36rpx;
|
border-radius: 24rpx;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
// box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
// box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
||||||
padding: 20rpx;
|
padding: 13rpx;
|
||||||
border: 2rpx solid #e0e0e0;
|
border: 2rpx solid #e0e0e0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -634,8 +649,8 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.side-icon {
|
.side-icon {
|
||||||
width: 44rpx;
|
width: 40rpx;
|
||||||
height: 44rpx;
|
height: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index-swiper {
|
.index-swiper {
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ export const getOrderList = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户端查询商品订单列表
|
||||||
|
export const getProductOrderList = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/product/order/list',
|
||||||
|
method: 'get',
|
||||||
|
data,
|
||||||
|
hideLoading: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 查询是否有订单
|
// 查询是否有订单
|
||||||
export const queryHasOrder = (deviceNo) => {
|
export const queryHasOrder = (deviceNo) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -49,6 +59,16 @@ export const queryById = (id) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户查询商品订单详情
|
||||||
|
export const getProductOrderDetail = (id) => {
|
||||||
|
console.log(`查询商品订单详情, orderId: ${id}`)
|
||||||
|
return request({
|
||||||
|
url: `/app/product/order/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
hideLoading: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
export const cancelOrder = (data) => {
|
export const cancelOrder = (data) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -166,6 +186,24 @@ export const updateOrderPackage = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户端删除商品订单(逻辑删除)
|
||||||
|
export const deleteProductOrder = (id) => {
|
||||||
|
console.log('删除商品订单,订单ID:', id)
|
||||||
|
return request({
|
||||||
|
url: `/app/product/order/${id}`,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户端取消商品订单支付
|
||||||
|
export const cancelProductOrder = (OutOrderNo) => {
|
||||||
|
console.log('取消商品订单支付,订单ID:', OutOrderNo)
|
||||||
|
return request({
|
||||||
|
url: `/app/product/order/${OutOrderNo}/cancel`,
|
||||||
|
method: 'put'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 弃用
|
* 弃用
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import request from '../http'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品列表查询接口
|
||||||
|
* @param {Object} params - 查询参数
|
||||||
|
* @param {string} params.productName - 商品名称(可选,模糊查询)
|
||||||
|
* @param {number} params.pageNum - 页码(默认1)
|
||||||
|
* @param {number} params.pageSize - 每页数量(默认10)
|
||||||
|
* @returns {Promise} 分页的商品列表
|
||||||
|
*/
|
||||||
|
export const getProductList = ({ productName = '', pageNum = 1, pageSize = 10 }) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/product/list',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
productName,
|
||||||
|
pageNum,
|
||||||
|
pageSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品详情查询接口
|
||||||
|
* @param {string|number} id - 商品ID
|
||||||
|
* @returns {Promise} 商品详细信息,包含规格列表(skuList)
|
||||||
|
*/
|
||||||
|
export const getProductDetail = (id) => {
|
||||||
|
return request({
|
||||||
|
url: `/app/product/${id}`,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建商品支付订单
|
||||||
|
* @param {Object} data - 订单数据
|
||||||
|
* @param {Array} data.items - 订单项列表 [{skuId, quantity}]
|
||||||
|
* @param {string} data.receiverName - 收件人姓名
|
||||||
|
* @param {string} data.receiverPhone - 收件人手机号
|
||||||
|
* @param {string} data.receiverAddress - 收件人详细地址
|
||||||
|
* @param {string} data.remark - 用户备注(可选)
|
||||||
|
* @returns {Promise} 微信支付参数
|
||||||
|
*/
|
||||||
|
export const createProductOrder = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/product/pay',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品订单退款
|
||||||
|
* @param {Object} data - 退款数据
|
||||||
|
* @param {number} data.productOrderId - 商品订单ID
|
||||||
|
* @param {number} data.refundAmount - 退款金额(可选,不传则全额退款)
|
||||||
|
* @param {string} data.refundReason - 退款原因(可选)
|
||||||
|
* @returns {Promise} 退款结果
|
||||||
|
*/
|
||||||
|
export const refundProductOrder = (data) => {
|
||||||
|
return request({
|
||||||
|
url: '/app/product/refund',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收货地址
|
||||||
|
* @returns {Promise} 用户收货地址信息
|
||||||
|
*/
|
||||||
|
export const getUserAddress = () => {
|
||||||
|
return request({
|
||||||
|
url: '/app/product/address',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Console 日志配置
|
||||||
|
* 用于控制是否在控制台打印日志
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 配置项:true 表示打印日志,false 表示不打印日志
|
||||||
|
export const CONSOLE_CONFIG = {
|
||||||
|
// 是否启用 console.log
|
||||||
|
enableLog: true,
|
||||||
|
// 是否启用 console.warn
|
||||||
|
enableWarn: true,
|
||||||
|
// 是否启用 console.error
|
||||||
|
enableError: true,
|
||||||
|
// 是否启用 console.info
|
||||||
|
enableInfo: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存原始的 console 方法
|
||||||
|
const originalConsole = {
|
||||||
|
log: console.log,
|
||||||
|
warn: console.warn,
|
||||||
|
error: console.error,
|
||||||
|
info: console.info
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 console 控制
|
||||||
|
* 根据配置决定是否打印日志
|
||||||
|
*/
|
||||||
|
export function initConsoleControl() {
|
||||||
|
// 重写 console.log
|
||||||
|
console.log = function(...args) {
|
||||||
|
if (CONSOLE_CONFIG.enableLog) {
|
||||||
|
originalConsole.log.apply(console, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重写 console.warn
|
||||||
|
console.warn = function(...args) {
|
||||||
|
if (CONSOLE_CONFIG.enableWarn) {
|
||||||
|
originalConsole.warn.apply(console, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重写 console.error
|
||||||
|
console.error = function(...args) {
|
||||||
|
if (CONSOLE_CONFIG.enableError) {
|
||||||
|
originalConsole.error.apply(console, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重写 console.info
|
||||||
|
console.info = function(...args) {
|
||||||
|
if (CONSOLE_CONFIG.enableInfo) {
|
||||||
|
originalConsole.info.apply(console, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复原始的 console 方法
|
||||||
|
*/
|
||||||
|
export function restoreConsole() {
|
||||||
|
console.log = originalConsole.log
|
||||||
|
console.warn = originalConsole.warn
|
||||||
|
console.error = originalConsole.error
|
||||||
|
console.info = originalConsole.info
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态设置 console 配置
|
||||||
|
* @param {Object} config - 配置对象
|
||||||
|
*/
|
||||||
|
export function setConsoleConfig(config) {
|
||||||
|
Object.assign(CONSOLE_CONFIG, config)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ export default {
|
|||||||
scanToUse: 'Scan',
|
scanToUse: 'Scan',
|
||||||
personalCenter: 'Profile',
|
personalCenter: 'Profile',
|
||||||
useGuide: 'Guide',
|
useGuide: 'Guide',
|
||||||
|
buyDevice: 'Custom Powewr',
|
||||||
navigate: 'Navigate',
|
navigate: 'Navigate',
|
||||||
relocate: 'Relocate',
|
relocate: 'Relocate',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
@@ -786,6 +787,7 @@ export default {
|
|||||||
useNow: 'Use Now',
|
useNow: 'Use Now',
|
||||||
usedStatus: 'Used',
|
usedStatus: 'Used',
|
||||||
expiredStatus: 'Expired',
|
expiredStatus: 'Expired',
|
||||||
|
refundedStatus:'Refunded',
|
||||||
noAvailableCoupons: 'No available coupons',
|
noAvailableCoupons: 'No available coupons',
|
||||||
noUsedCoupons: 'No used coupons',
|
noUsedCoupons: 'No used coupons',
|
||||||
noExpiredCoupons: 'No expired coupons',
|
noExpiredCoupons: 'No expired coupons',
|
||||||
@@ -793,6 +795,27 @@ export default {
|
|||||||
getListFailed: 'Failed to get coupon list',
|
getListFailed: 'Failed to get coupon list',
|
||||||
onlyForRegionBefore: 'Only for ',
|
onlyForRegionBefore: 'Only for ',
|
||||||
onlyForRegionAfter: ''
|
onlyForRegionAfter: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
goods: {
|
||||||
|
title: 'Product Details',
|
||||||
|
productName: 'FengDianZhe Shared Fan + Power Bank + Hand Warmer Series - Cherry Blossom Pink',
|
||||||
|
perUnit: '/pc',
|
||||||
|
buyNow: 'Buy Now',
|
||||||
|
productDetail: 'Product Details',
|
||||||
|
features: {
|
||||||
|
battery: '8000Ahm',
|
||||||
|
batteryDesc: 'Large Capacity Battery',
|
||||||
|
wind: 'Efficient Fan',
|
||||||
|
temp: 'Smart Temperature',
|
||||||
|
charge: 'Fast Charging'
|
||||||
|
},
|
||||||
|
description: 'FengDianZhe shared fan, integrating fan, power bank, and hand warmer functions. Equipped with 8000mAh large capacity battery for long-lasting use. Efficient fan design with 3-speed adjustment. Smart temperature control hand warmer, warm in winter and cool in summer. Fast charging technology supports multiple device charging. Cherry blossom pink color, fashionable and beautiful, your best travel companion.',
|
||||||
|
confirmPurchase: 'Confirm Purchase',
|
||||||
|
confirmPurchaseContent: 'Confirm to purchase this product for ¥{price}?',
|
||||||
|
purchaseSuccess: 'Purchase Successful',
|
||||||
|
purchaseFailed: 'Purchase Failed',
|
||||||
|
processing: 'Processing...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ export default {
|
|||||||
scanToUse: '扫码使用',
|
scanToUse: '扫码使用',
|
||||||
personalCenter: '个人中心',
|
personalCenter: '个人中心',
|
||||||
useGuide: '使用指南',
|
useGuide: '使用指南',
|
||||||
|
buyDevice: '充电宝定制',
|
||||||
navigate: '导航',
|
navigate: '导航',
|
||||||
relocate: '重新定位',
|
relocate: '重新定位',
|
||||||
search: '搜索',
|
search: '搜索',
|
||||||
@@ -252,7 +253,7 @@ export default {
|
|||||||
deviceNoEject: '宝未弹出',
|
deviceNoEject: '宝未弹出',
|
||||||
returnReminder: '归还提醒',
|
returnReminder: '归还提醒',
|
||||||
canUsePromotion: '可使用优惠券、会员卡',
|
canUsePromotion: '可使用优惠券、会员卡',
|
||||||
usedPromotion: '已享受优惠',
|
usedPromotion: '优惠类型',
|
||||||
convertToOwn: '不想还了?点击转为自用',
|
convertToOwn: '不想还了?点击转为自用',
|
||||||
convertToOwnTitle: '转为自用',
|
convertToOwnTitle: '转为自用',
|
||||||
convertToOwnConfirm: '仅需花费99元,即可转为自用,充电宝将归您所有,确认操作吗?',
|
convertToOwnConfirm: '仅需花费99元,即可转为自用,充电宝将归您所有,确认操作吗?',
|
||||||
@@ -786,6 +787,7 @@ export default {
|
|||||||
useNow: '去使用',
|
useNow: '去使用',
|
||||||
usedStatus: '已使用',
|
usedStatus: '已使用',
|
||||||
expiredStatus: '已过期',
|
expiredStatus: '已过期',
|
||||||
|
refundedStatus:'已退款',
|
||||||
noAvailableCoupons: '暂无可用优惠券',
|
noAvailableCoupons: '暂无可用优惠券',
|
||||||
noUsedCoupons: '暂无已使用优惠券',
|
noUsedCoupons: '暂无已使用优惠券',
|
||||||
noExpiredCoupons: '暂无已过期优惠券',
|
noExpiredCoupons: '暂无已过期优惠券',
|
||||||
@@ -793,6 +795,27 @@ export default {
|
|||||||
getListFailed: '获取优惠券列表失败',
|
getListFailed: '获取优惠券列表失败',
|
||||||
onlyForRegionBefore: '仅限',
|
onlyForRegionBefore: '仅限',
|
||||||
onlyForRegionAfter: '使用'
|
onlyForRegionAfter: '使用'
|
||||||
|
},
|
||||||
|
|
||||||
|
goods: {
|
||||||
|
title: '商品详情',
|
||||||
|
productName: '风电者共享风扇 + 充电宝 + 暖手宝系列-樱花粉',
|
||||||
|
perUnit: '/个',
|
||||||
|
buyNow: '立即购买',
|
||||||
|
productDetail: '商品详情',
|
||||||
|
features: {
|
||||||
|
battery: '8000Ahm',
|
||||||
|
batteryDesc: '大容量电池',
|
||||||
|
wind: '高效风扇',
|
||||||
|
temp: '智能控温',
|
||||||
|
charge: '快速充电'
|
||||||
|
},
|
||||||
|
description: '风电者共享风扇,集风扇、充电宝、暖手宝三合一功能。采用8000mAh大容量电池,续航持久。高效风扇设计,三档风力可调。智能控温暖手宝,冬暖夏凉。快速充电技术,支持多设备充电。樱花粉配色,时尚美观,是您出行的最佳伴侣。',
|
||||||
|
confirmPurchase: '确认购买',
|
||||||
|
confirmPurchaseContent: '确认购买该商品,需支付 ¥{price}?',
|
||||||
|
purchaseSuccess: '购买成功',
|
||||||
|
purchaseFailed: '购买失败',
|
||||||
|
processing: '正在处理...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import { createI18n } from 'vue-i18n'
|
|||||||
import zhCN from './locale/zh-CN.js'
|
import zhCN from './locale/zh-CN.js'
|
||||||
import enUS from './locale/en-US.js'
|
import enUS from './locale/en-US.js'
|
||||||
import uView from '@climblee/uv-ui'
|
import uView from '@climblee/uv-ui'
|
||||||
|
import { initConsoleControl } from './config/console.js'
|
||||||
|
|
||||||
|
// 初始化 console 控制
|
||||||
|
initConsoleControl()
|
||||||
|
|
||||||
// 获取系统语言
|
// 获取系统语言
|
||||||
const getSystemLanguage = () => {
|
const getSystemLanguage = () => {
|
||||||
let language = 'zh-CN'
|
let language = 'en-US'
|
||||||
try {
|
try {
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
if (systemInfo && systemInfo.language) {
|
if (systemInfo && systemInfo.language) {
|
||||||
|
|||||||
@@ -15,8 +15,16 @@
|
|||||||
"enableShareTimeline": true
|
"enableShareTimeline": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/login/index",
|
"path": "pages/scan/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "扫码使用",
|
||||||
|
"navigationBarBackgroundColor": "#000000",
|
||||||
|
"navigationBarTextStyle": "white"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
"navigationBarBackgroundColor": "#C8F4D9",
|
"navigationBarBackgroundColor": "#C8F4D9",
|
||||||
@@ -109,6 +117,20 @@
|
|||||||
"navigationBarTitleText": ""
|
"navigationBarTitleText": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/device/orderList",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/device/orderDetail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单详情",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/order/index",
|
"path": "pages/order/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -280,6 +302,14 @@
|
|||||||
"navigationBarBackgroundColor": "#ffffff",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/device/goods",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<!-- 底部操作区 -->
|
<!-- 底部操作区 -->
|
||||||
<view class="footer">
|
<view class="footer">
|
||||||
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
<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>
|
<text>{{ hasActiveOrder ? $t('order.returnDevice') : $t('device.rentDepositFree') }}</text>
|
||||||
</button>
|
</button>
|
||||||
<view class="wechat-credit">
|
<view class="wechat-credit">
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
:enableMarkers="true" :bannerImages="bannerImages"
|
:enableMarkers="true" :bannerImages="bannerImages"
|
||||||
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
:hideMapOverlays="showGuidePopup || showNoticePopup || showActivityPopup" @relocate="handleRelocate"
|
||||||
@scan="handleScan" @showList="showLocationList" @markerTap="selectPosition"
|
@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">
|
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
<text class="action-label">{{ $t('home.nearbyDevices') }}</text>
|
<text class="action-label">{{ $t('home.nearbyDevices') }}</text>
|
||||||
</view> -->
|
</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">
|
<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>
|
</view>
|
||||||
<text class="action-label">{{ $t('home.useGuide') }}</text>
|
<text class="action-label">{{ $t('home.buyDevice') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="action-btn primary btn-scan" @click="handleScan">
|
<view class="action-btn primary btn-scan" @click="handleScan">
|
||||||
@@ -206,6 +206,9 @@
|
|||||||
getCurrentAnnouncement,
|
getCurrentAnnouncement,
|
||||||
getCurrentAdvertisement
|
getCurrentAdvertisement
|
||||||
} from '../../config/api/system.js'
|
} from '../../config/api/system.js'
|
||||||
|
import {
|
||||||
|
getProductList
|
||||||
|
} from '../../config/api/product.js'
|
||||||
// 导入地图工具函数
|
// 导入地图工具函数
|
||||||
import {
|
import {
|
||||||
getUserLocation,
|
getUserLocation,
|
||||||
@@ -524,10 +527,20 @@
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initNavBarHeight()
|
initNavBarHeight()
|
||||||
init()
|
init()
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
// 监听 H5 扫码结果
|
||||||
|
uni.$on('h5ScanSuccess', (data) => {
|
||||||
|
console.log('接收到 H5 扫码结果:', data);
|
||||||
|
processScanResult(data);
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 清理工作在子组件中处理
|
// #ifdef H5
|
||||||
|
uni.$off('h5ScanSuccess');
|
||||||
|
// #endif
|
||||||
})
|
})
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
@@ -564,10 +577,10 @@
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化失败:', error)
|
console.error('初始化失败:', error)
|
||||||
uni.showToast({
|
// uni.showToast({
|
||||||
title: t('home.getLocationFailed'),
|
// title: t('home.getLocationFailed'),
|
||||||
icon: 'none'
|
// icon: 'none'
|
||||||
})
|
// })
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -760,10 +773,10 @@
|
|||||||
try {
|
try {
|
||||||
isRelocating.value = true
|
isRelocating.value = true
|
||||||
|
|
||||||
uni.showLoading({
|
// uni.showLoading({
|
||||||
title: t('home.relocating'),
|
// title: t('home.relocating'),
|
||||||
mask: true
|
// mask: true
|
||||||
})
|
// })
|
||||||
|
|
||||||
// 重新获取用户真实位置
|
// 重新获取用户真实位置
|
||||||
const loc = await getUserLocation()
|
const loc = await getUserLocation()
|
||||||
@@ -804,20 +817,20 @@
|
|||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
uni.showToast({
|
// uni.showToast({
|
||||||
title: t('home.locateSuccess'),
|
// title: t('home.locateSuccess'),
|
||||||
icon: 'success',
|
// icon: 'success',
|
||||||
duration: 1500
|
// duration: 1500
|
||||||
})
|
// })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('定位失败:', e)
|
console.error('定位失败:', e)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
uni.showToast({
|
// uni.showToast({
|
||||||
title: e.errMsg || t('home.locateFailed'),
|
// title: e.errMsg || t('home.locateFailed'),
|
||||||
icon: 'none',
|
// icon: 'none',
|
||||||
duration: 2000
|
// duration: 2000
|
||||||
})
|
// })
|
||||||
} finally {
|
} finally {
|
||||||
// 1秒后解除防抖锁定
|
// 1秒后解除防抖锁定
|
||||||
setTimeout(() => {
|
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) => {
|
const selectPositionFromPopup = (position) => {
|
||||||
// 先关闭弹窗
|
// 先关闭弹窗
|
||||||
hideLocationList()
|
hideLocationList()
|
||||||
@@ -905,6 +965,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleScan = async () => {
|
const handleScan = async () => {
|
||||||
|
// #ifdef H5
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/scan/index'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
// #endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const scanResult = await new Promise((resolve, reject) => {
|
const scanResult = await new Promise((resolve, reject) => {
|
||||||
uni.scanCode({
|
uni.scanCode({
|
||||||
@@ -914,16 +981,23 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
console.log(scanResult);
|
console.log(scanResult);
|
||||||
let deviceNo;
|
processScanResult(scanResult);
|
||||||
if (scanResult.scanType=='"QR_CODE"') {
|
} 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')
|
deviceNo = getQueryString(scanResult.result, 'deviceNo')
|
||||||
} else {
|
} else {
|
||||||
deviceNo = getQueryString(scanResult.path, 'deviceNo')
|
deviceNo = getQueryString(scanResult.path || scanResult.result, 'deviceNo')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!deviceNo) {
|
if (!deviceNo) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('home.invalidQRCode'),
|
title: t('home.invalidQRCode'),
|
||||||
@@ -975,10 +1049,10 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
// uni.showToast({
|
||||||
title: t('device.getDeviceInfoFailed'),
|
// title: t('device.getDeviceInfoFailed'),
|
||||||
icon: 'none'
|
// icon: 'none'
|
||||||
})
|
// })
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
||||||
})
|
})
|
||||||
@@ -991,11 +1065,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('扫码处理失败:', error)
|
console.error('处理扫码结果失败:', error)
|
||||||
// uni.showToast({
|
|
||||||
// title: '扫码失败',
|
|
||||||
// icon: 'none'
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,6 +1085,16 @@
|
|||||||
|
|
||||||
// 使用指南弹窗控制
|
// 使用指南弹窗控制
|
||||||
const openPopup = () => {
|
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 {
|
try {
|
||||||
showGuidePopup.value = true
|
showGuidePopup.value = true
|
||||||
guidePopup.value && typeof guidePopup.value.open === 'function' && guidePopup.value.open()
|
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);
|
// box-shadow: 0 8rpx 24rpx rgba(62, 171, 100, 0.3);
|
||||||
|
|
||||||
.icon-wrap {
|
.icon-wrap {
|
||||||
width: 48rpx;
|
width: 52rpx;
|
||||||
height: 48rpx;
|
height: 52rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -1745,7 +1825,7 @@
|
|||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 使用指南弹窗样式 */
|
/* 弹窗样式 */
|
||||||
.guide-popup {
|
.guide-popup {
|
||||||
width: 640rpx;
|
width: 640rpx;
|
||||||
max-width: 86vw;
|
max-width: 86vw;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 会员卡列表 -->
|
<!-- 会员卡列表 -->
|
||||||
<view class="card-list" v-if="cardList.length > 0">
|
<view class="card-list" v-if="cardList.length > 0">
|
||||||
<view v-for="card in cardList" :key="card.id" style="position: relative;background-color: #f5f5f5;"
|
<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
|
<view
|
||||||
style="height: 120rpx;background-color: #ffffff;z-index: 999;border-radius: 25rpx;padding: 32rpx;position: absolute;top: 0;left: 0;right: 0;">
|
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>
|
class="region-text">{{ $t('myCard.onlyForRegionBefore') }}{{ card.positionName }}{{ $t('myCard.onlyForRegionAfter') }}</text>
|
||||||
<!-- 状态标签 / 去使用按钮 -->
|
<!-- 状态标签 / 去使用按钮 -->
|
||||||
<view v-if="card.status !== 'expired'" class="status-tag active"
|
<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)">
|
@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> -->
|
<!-- <uv-icon name="scan" size="12" color="#D4A574"></uv-icon> -->
|
||||||
</view>
|
</view>
|
||||||
<view v-else class="status-tag" :class="getStatusClass(card.status)">
|
<view v-else class="status-tag" :class="getStatusClass(card.status)">
|
||||||
@@ -33,8 +33,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 使用情况和操作按钮 -->
|
<!-- 使用情况和操作按钮 -->
|
||||||
<view style="position: absolute; bottom: -10rpx; left: 0; right: 0; padding: 20rpx;z-index:1;"
|
<view style="position: absolute; bottom: -20rpx; left: 0; right: 0; padding: 20rpx;z-index:1;">
|
||||||
v-if="card.cardType==='COUNT'">
|
|
||||||
<view class="card-footer">
|
<view class="card-footer">
|
||||||
<!-- 次卡信息 -->
|
<!-- 次卡信息 -->
|
||||||
<view v-if="card.cardType === 'COUNT'" class="card-usage-info">
|
<view v-if="card.cardType === 'COUNT'" class="card-usage-info">
|
||||||
@@ -42,8 +41,8 @@
|
|||||||
$t('myCard.times') }}</text>
|
$t('myCard.times') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 时长卡信息 -->
|
<!-- 时长卡信息 -->
|
||||||
<view v-if="card.cardType === 'TIME'" class="card-usage-info">
|
<view v-else class="card-usage-info">
|
||||||
<text class="usage-text">{{ $t('myCard.durationCard') }}</text>
|
<text class="usage-text">每日限用次数:{{card.dailyLimitCount}}次</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
@@ -52,7 +51,10 @@
|
|||||||
<view v-if="card.cardType === 'COUNT'" class="renew-btn" @click.stop="renewCard(card)">
|
<view v-if="card.cardType === 'COUNT'" class="renew-btn" @click.stop="renewCard(card)">
|
||||||
<text class="renew-text">{{ $t('myCard.renew') }}</text>
|
<text class="renew-text">{{ $t('myCard.renew') }}</text>
|
||||||
<uv-icon name="arrow-right" size="14" color="#D4A574"></uv-icon>
|
<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>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
@@ -356,7 +358,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* padding-top: 24rpx ; */
|
padding: 20rpx;
|
||||||
|
padding-top: 50rpx;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(255, 244, 227, 1);
|
background: rgba(255, 244, 227, 1);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -364,7 +367,7 @@
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
border-radius: 0 0 25rpx 25rpx;
|
border-radius: 0 0 25rpx 25rpx;
|
||||||
padding: 20rpx;
|
|
||||||
/* text-align: 30rpx ; */
|
/* text-align: 30rpx ; */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
style="font-size: 22rpx; color: #999; margin-top: 4rpx;">
|
style="font-size: 22rpx; color: #999; margin-top: 4rpx;">
|
||||||
{{ $t('myCoupon.onlyForRegionBefore') }}{{ coupon.positionName }}{{ $t('myCoupon.onlyForRegionAfter') }}
|
{{ $t('myCoupon.onlyForRegionBefore') }}{{ coupon.positionName }}{{ $t('myCoupon.onlyForRegionAfter') }}
|
||||||
</text> -->
|
</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>
|
<text class="use-text">{{ $t('myCoupon.useNow') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="coupon-status" v-else>{{ getStatusText(coupon.status) }}</text>
|
<text class="coupon-status" v-else>{{ getStatusText(coupon.status) }}</text>
|
||||||
@@ -85,7 +85,7 @@ const couponList = ref([])
|
|||||||
|
|
||||||
// 过滤后的优惠券
|
// 过滤后的优惠券
|
||||||
const filteredCoupons = computed(() => {
|
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) {
|
} else if (item.couponEndTime) {
|
||||||
validity = `于 ${item.couponEndTime.split(' ')[0]} 过期`
|
validity = `于 ${item.couponEndTime.split(' ')[0]} 过期`
|
||||||
}
|
}
|
||||||
|
console.log(item.status);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -123,7 +124,7 @@ const getCouponList = async () => {
|
|||||||
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
|
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
|
||||||
condition: condition,
|
condition: condition,
|
||||||
validity: validity,
|
validity: validity,
|
||||||
status: currentTab.value,
|
status: item.status,
|
||||||
positionName: item.positionName
|
positionName: item.positionName
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -148,15 +149,17 @@ const switchTab = (tab) => {
|
|||||||
|
|
||||||
// 获取优惠券样式类名
|
// 获取优惠券样式类名
|
||||||
const getCouponClass = (status) => {
|
const getCouponClass = (status) => {
|
||||||
return status === 'available' ? '' : 'disabled'
|
return status === 'unused' ? '' : 'disabled'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'used': t('myCoupon.usedStatus'),
|
'used': t('myCoupon.usedStatus'),
|
||||||
'expired': t('myCoupon.expiredStatus')
|
'expired': t('myCoupon.expiredStatus'),
|
||||||
|
'refunded':t('myCoupon.refundedStatus')
|
||||||
}
|
}
|
||||||
|
console.log("获取状态文本:"+statusMap[status]);
|
||||||
return statusMap[status] || ''
|
return statusMap[status] || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,8 @@
|
|||||||
<view class="rent-label">{{ $t('order.usedPromotion') }}</view>
|
<view class="rent-label">{{ $t('order.usedPromotion') }}</view>
|
||||||
<view class="rent-value promotion-value">
|
<view class="rent-value promotion-value">
|
||||||
<image src="/static/promotion-icon.png" class="promotion-icon" mode="aspectFit"></image>
|
<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>
|
</view>
|
||||||
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
|
<view class="rent-item" v-if="isOrderCompleted() && orderInfo.endTime">
|
||||||
@@ -108,7 +109,8 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="">
|
<view class="">
|
||||||
<view class="" style="font-size: 24rpx;text-align: center;">
|
<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>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -161,7 +163,8 @@
|
|||||||
|
|
||||||
<!-- 已完成状态 -->
|
<!-- 已完成状态 -->
|
||||||
<template v-if="isOrderCompleted()">
|
<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>
|
<image src="/static/suggess.png" class="icon" mode="aspectFit"></image>
|
||||||
<text>{{ $t('order.feeAppeal') }}</text>
|
<text>{{ $t('order.feeAppeal') }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -187,14 +190,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 转为自用确认弹窗 -->
|
<!-- 转为自用确认弹窗 -->
|
||||||
<uv-popup
|
<uv-popup ref="convertToOwnPopup" mode="center" round="20" :overlay="true" :closeOnClickOverlay="false"
|
||||||
ref="convertToOwnPopup"
|
:safeAreaInsetBottom="false">
|
||||||
mode="center"
|
|
||||||
round="20"
|
|
||||||
:overlay="true"
|
|
||||||
:closeOnClickOverlay="false"
|
|
||||||
:safeAreaInsetBottom="false"
|
|
||||||
>
|
|
||||||
<view class="convert-to-own-popup">
|
<view class="convert-to-own-popup">
|
||||||
<view class="popup-header">
|
<view class="popup-header">
|
||||||
<text class="popup-title">{{ $t('order.convertToOwnWithMaxFeeTitle') }}</text>
|
<text class="popup-title">{{ $t('order.convertToOwnWithMaxFeeTitle') }}</text>
|
||||||
@@ -216,8 +213,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from 'vue'
|
import {
|
||||||
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
|
ref,
|
||||||
|
computed,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
getCurrentInstance
|
||||||
|
} from 'vue'
|
||||||
|
import {
|
||||||
|
onLoad,
|
||||||
|
onShow,
|
||||||
|
onHide,
|
||||||
|
onUnload
|
||||||
|
} from '@dcloudio/uni-app'
|
||||||
import {
|
import {
|
||||||
queryById,
|
queryById,
|
||||||
cancelOrder,
|
cancelOrder,
|
||||||
@@ -238,9 +246,13 @@
|
|||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
} from "@/config/url.js"
|
} 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 instance = getCurrentInstance()
|
||||||
const $orderMonitor = instance?.proxy?.$orderMonitor || null
|
const $orderMonitor = instance?.proxy?.$orderMonitor || null
|
||||||
|
|
||||||
@@ -274,7 +286,9 @@
|
|||||||
canUseCoupon: false,
|
canUseCoupon: false,
|
||||||
userMemberCardId: '',
|
userMemberCardId: '',
|
||||||
userPurchaseId: '',
|
userPurchaseId: '',
|
||||||
discountTypeName: ''
|
discountTypeName: '',
|
||||||
|
originalFee:'',
|
||||||
|
discountAmount:''
|
||||||
})
|
})
|
||||||
const timer = ref(null)
|
const timer = ref(null)
|
||||||
const statusCheckTimer = ref(null)
|
const statusCheckTimer = ref(null)
|
||||||
@@ -312,7 +326,7 @@
|
|||||||
// 判断订单是否已完成
|
// 判断订单是否已完成
|
||||||
const isOrderCompleted = () => {
|
const isOrderCompleted = () => {
|
||||||
return orderInfo.value.orderStatus === 'used_done' ||
|
return orderInfo.value.orderStatus === 'used_done' ||
|
||||||
orderInfo.value.orderStatus === 'used_down'
|
orderInfo.value.orderStatus === 'used_down'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取订单状态文字
|
// 获取订单状态文字
|
||||||
@@ -421,7 +435,8 @@
|
|||||||
if (totalMinutes >= 60) {
|
if (totalMinutes >= 60) {
|
||||||
const hours = Math.floor(totalMinutes / 60)
|
const hours = Math.floor(totalMinutes / 60)
|
||||||
const minutes = 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 {
|
} else {
|
||||||
usedTime = `${totalMinutes}${t('time.minute')}`
|
usedTime = `${totalMinutes}${t('time.minute')}`
|
||||||
}
|
}
|
||||||
@@ -472,7 +487,8 @@
|
|||||||
|
|
||||||
// 获取订单费用(不含单位)
|
// 获取订单费用(不含单位)
|
||||||
const getOrderFee = () => {
|
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, '')
|
return String(fee).replace(/[元¥]/g, '')
|
||||||
}
|
}
|
||||||
@@ -590,13 +606,16 @@
|
|||||||
const loadSystemConfig = async () => {
|
const loadSystemConfig = async () => {
|
||||||
try {
|
try {
|
||||||
// 优先使用订单数据中的 expressReturnStart(小时转秒)
|
// 优先使用订单数据中的 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
|
expressThresholdSeconds.value = orderInfo.value.expressReturnStart * 3600
|
||||||
console.log('使用订单配置的快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds.value, '秒')
|
console.log('使用订单配置的快递归还阈值:', orderInfo.value.expressReturnStart, '小时 =>', expressThresholdSeconds
|
||||||
|
.value, '秒')
|
||||||
} else {
|
} else {
|
||||||
// 如果订单数据中没有,则使用系统配置
|
// 如果订单数据中没有,则使用系统配置
|
||||||
const res = await getSystemConfig()
|
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
|
const seconds = res.data.expressReturnCountdownSeconds
|
||||||
if (seconds > 0) {
|
if (seconds > 0) {
|
||||||
expressThresholdSeconds.value = seconds
|
expressThresholdSeconds.value = seconds
|
||||||
@@ -732,7 +751,8 @@
|
|||||||
const oldStatus = orderInfo.value.orderStatus
|
const oldStatus = orderInfo.value.orderStatus
|
||||||
|
|
||||||
orderInfo.value.usedTime = orderData.usedTime || '0分钟'
|
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.orderStatus = orderData.orderStatus || ''
|
||||||
orderInfo.value.payWay = orderData.payWay || ''
|
orderInfo.value.payWay = orderData.payWay || ''
|
||||||
orderInfo.value.startTime = orderData.startTime || orderData.createTime || ''
|
orderInfo.value.startTime = orderData.startTime || orderData.createTime || ''
|
||||||
@@ -747,10 +767,12 @@
|
|||||||
orderInfo.value.withdrawStatus = orderData.withdrawStatus || 'waiting'
|
orderInfo.value.withdrawStatus = orderData.withdrawStatus || 'waiting'
|
||||||
orderInfo.value.isWithdrawn = orderData.withdrawStatus === 'success'
|
orderInfo.value.isWithdrawn = orderData.withdrawStatus === 'success'
|
||||||
orderInfo.value.positionName = orderData.positionName || orderData.positionLocation || ''
|
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.freeRentTime = orderData.freeRentTime || ''
|
||||||
orderInfo.value.unitPrice = orderData.unitPrice || ''
|
orderInfo.value.unitPrice = orderData.unitPrice || ''
|
||||||
orderInfo.value.orderType = orderData.orderType || ''
|
orderInfo.value.orderType = orderData.orderType || ''
|
||||||
|
orderInfo.value.discountAmount = orderData.discountAmount||''
|
||||||
|
|
||||||
// 保存优惠券/会员卡相关信息
|
// 保存优惠券/会员卡相关信息
|
||||||
orderInfo.value.canUseMember = orderData.canUseMember === true
|
orderInfo.value.canUseMember = orderData.canUseMember === true
|
||||||
@@ -758,6 +780,7 @@
|
|||||||
orderInfo.value.userMemberCardId = orderData.userMemberCardId || ''
|
orderInfo.value.userMemberCardId = orderData.userMemberCardId || ''
|
||||||
orderInfo.value.userPurchaseId = orderData.userPurchaseId || ''
|
orderInfo.value.userPurchaseId = orderData.userPurchaseId || ''
|
||||||
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
|
orderInfo.value.discountTypeName = orderData.discountTypeName || ''
|
||||||
|
orderInfo.value.originalFee = orderData.originalFee||''
|
||||||
|
|
||||||
// 保存快递归还开始时间(小时为单位)
|
// 保存快递归还开始时间(小时为单位)
|
||||||
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
|
orderInfo.value.expressReturnStart = orderData.expressReturnStart || null
|
||||||
@@ -766,9 +789,11 @@
|
|||||||
orderInfo.value.isSupportExpressReturn = orderData.isSupportExpressReturn || 'yes'
|
orderInfo.value.isSupportExpressReturn = orderData.isSupportExpressReturn || 'yes'
|
||||||
|
|
||||||
// 如果有有效的 expressReturnStart,立即更新倒计时阈值(小时转秒)
|
// 如果有有效的 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
|
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) {
|
if (orderData.deviceNo && !deviceId.value) {
|
||||||
@@ -841,7 +866,8 @@
|
|||||||
// 如果订单已完成,从监控中移除
|
// 如果订单已完成,从监控中移除
|
||||||
if (isOrderCompleted()) {
|
if (isOrderCompleted()) {
|
||||||
removeFromOrderMonitor()
|
removeFromOrderMonitor()
|
||||||
console.log('订单已完成,已从监控队列移除:', orderInfo.value.orderId, '当前状态:', orderInfo.value.orderStatus)
|
console.log('订单已完成,已从监控队列移除:', orderInfo.value.orderId, '当前状态:', orderInfo.value
|
||||||
|
.orderStatus)
|
||||||
} else {
|
} else {
|
||||||
// 订单未完成,添加到监控队列
|
// 订单未完成,添加到监控队列
|
||||||
uni.setStorageSync('activeOrderId', orderInfo.value.orderId)
|
uni.setStorageSync('activeOrderId', orderInfo.value.orderId)
|
||||||
@@ -853,7 +879,8 @@
|
|||||||
$orderMonitor.addOrder({
|
$orderMonitor.addOrder({
|
||||||
orderId: orderInfo.value.orderId
|
orderId: orderInfo.value.orderId
|
||||||
}, 'detail')
|
}, 'detail')
|
||||||
console.log('订单已添加到监控队列:', orderInfo.value.orderId, '当前状态:', orderInfo.value.orderStatus)
|
console.log('订单已添加到监控队列:', orderInfo.value.orderId, '当前状态:', orderInfo.value
|
||||||
|
.orderStatus)
|
||||||
} else {
|
} else {
|
||||||
console.warn('$orderMonitor 未定义,无法添加订单到监控队列')
|
console.warn('$orderMonitor 未定义,无法添加订单到监控队列')
|
||||||
}
|
}
|
||||||
@@ -1055,7 +1082,8 @@
|
|||||||
// 处理"不想还了转为自用"(按最高费用)
|
// 处理"不想还了转为自用"(按最高费用)
|
||||||
const handleConvertToOwnedWithMaxFee = () => {
|
const handleConvertToOwnedWithMaxFee = () => {
|
||||||
try {
|
try {
|
||||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.open === 'function' && convertToOwnPopup.value.open()
|
convertToOwnPopup.value && typeof convertToOwnPopup.value.open === 'function' && convertToOwnPopup.value
|
||||||
|
.open()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('打开弹窗失败', e)
|
console.error('打开弹窗失败', e)
|
||||||
}
|
}
|
||||||
@@ -1064,7 +1092,8 @@
|
|||||||
// 关闭转为自用弹窗
|
// 关闭转为自用弹窗
|
||||||
const closeConvertToOwnPopup = () => {
|
const closeConvertToOwnPopup = () => {
|
||||||
try {
|
try {
|
||||||
convertToOwnPopup.value && typeof convertToOwnPopup.value.close === 'function' && convertToOwnPopup.value.close()
|
convertToOwnPopup.value && typeof convertToOwnPopup.value.close === 'function' && convertToOwnPopup.value
|
||||||
|
.close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('关闭弹窗失败', e)
|
console.error('关闭弹窗失败', e)
|
||||||
}
|
}
|
||||||
@@ -1218,7 +1247,7 @@
|
|||||||
.header-right {
|
.header-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height:100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1431,7 +1460,7 @@
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
padding-top: 20rpx;
|
padding-top: 20rpx;
|
||||||
margin-top: 10rpx;
|
margin-top: 10rpx;
|
||||||
border-top: 1rpx solid #f0f0f0;
|
// border-top: 1rpx solid #f0f0f0;
|
||||||
|
|
||||||
.paid-label {
|
.paid-label {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
@@ -1627,4 +1656,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</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}`
|
url: `/pages/device/detail?deviceNo=${option.deviceNo}`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// uni.switchTab({
|
||||||
|
// url:'/pages/index/index'
|
||||||
|
// })
|
||||||
// 如果连deviceNo都没有,则返回首页
|
// 如果连deviceNo都没有,则返回首页
|
||||||
uni.switchTab({ url: '/pages/index/index' });
|
uni.reLaunch({ url: '/pages/index/index' });
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 847 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
@@ -11,7 +11,7 @@ export function useI18n() {
|
|||||||
if (!instance || !instance.proxy) {
|
if (!instance || !instance.proxy) {
|
||||||
return {
|
return {
|
||||||
t: (key) => key,
|
t: (key) => key,
|
||||||
locale: 'zh-CN',
|
locale: 'en_US',
|
||||||
i18n: null
|
i18n: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export function useI18n() {
|
|||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
},
|
},
|
||||||
locale: proxy.$i18n?.locale || 'zh-CN',
|
locale: proxy.$i18n?.locale || 'en_US',
|
||||||
i18n: proxy.$i18n
|
i18n: proxy.$i18n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||