You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

716 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="container">
<uv-sticky bgColor="#fff" :offsetTop="isWeChatH5 ? -50 : 0">
<div class="tabBox">
<div class="tabItem" :class="{selected:active==='全部'}" @click="switchTab('全部')"></div>
<div class="tabItem" :class="{selected:active==='图纸生成'}" @click="switchTab('图纸生成')"></div>
<div class="tabItem" :class="{selected:active==='会员购买'}" @click="switchTab('会员购买')"></div>
<div class="tabItem" :class="{selected:active==='钱包充值'}" @click="switchTab('钱包充值')"></div>
</div>
</uv-sticky>
<!-- 订单列表 -->
<view class="order-list">
<view class="order-item" v-for="order in list" :key="order.id">
<!-- 订单头部 -->
<div class="order-title">智能电梯图纸生成平台</div>
<view class="order-header">
<view class="order-info">
<text class="order-number">订单编号: {{ order.order_no }}</text>
<view class="order-type-tag" @click="copyOrderNo(order.order_no)">复制</view>
</view>
<view class="order-status" :class="{paid:order.status === 'paid',pending:order.status === 'created' || order.status === 'pending',
cancelled:order.status === 'failed' || order.status === 'canceled' || order.status === 'refunded'}">
<span v-if="order.status === 'created'">待付款</span>
<span v-if="order.status === 'pending'">待支付</span>
<span v-if="order.status === 'paid'">已支付</span>
<span v-if="order.status === 'failed'">支付失败</span>
<span v-if="order.status === 'canceled'">已取消</span>
<span v-if="order.status === 'refunded'">已退款</span>
</view>
</view>
<!-- 订单详情 -->
<view class="order-details">
<view class="detail-row">
<text class="label">用户名称:</text>
<text class="value">{{ order.user_name }}</text>
</view>
<view class="detail-row">
<text class="label">用户ID:</text>
<text class="value">{{ order.user_id }}</text>
</view>
<view class="detail-row">
<text class="label">订单类型:</text>
<text class="value">{{ order.order_type }}</text>
</view>
<view class="detail-row">
<text class="label">订单金额:</text>
<text class="value price">¥{{ order.amount }}</text>
</view>
<view class="detail-row">
<text class="label">支付渠道:</text>
<text class="value">
<template v-if="order.pay_channel === 'wallet'">钱包</template>
<template v-if="order.pay_channel === 'wxpay'">微信</template>
<template v-if="order.pay_channel === 'alipay'">支付宝</template>
</text>
</view>
<!-- <view class="detail-row" v-if="order.discount > 0">
<text class="label">折扣金额:</text>
<text class="value discount">-¥{{ order.discount }}</text>
</view> -->
<!-- <view class="detail-row">
<text class="label">实付金额:</text>
<text class="value final-price">¥{{ order.finalAmount }}</text>
</view> -->
<view class="detail-row">
<text class="label">创建时间</text>
<text
class="value">{{ order.created_at ? dayjs(order.created_at * 1000).format('YYYY-MM-DD HH:mm:ss') :'' }}</text>
</view>
</view>
<!-- 订单操作 -->
<view class="order-actions" v-if="order.status === 'pending' || order.status === 'created'">
<div>
<!-- <div class="countdown-text" v-if="order.status === 'pending'">支付剩余时间: {{ order.countdown }}
</div> -->
</div>
<div class="buttonBox">
<template v-if="order.status === 'pending' || order.status === 'created'">
<uv-button type="error" size="small" @click="cancelOrder(order)">
取消订单
</uv-button>
<uv-button type="primary" size="small" @click="payOrder(order)">
去支付
</uv-button>
</template>
<!-- <template v-else>
<uv-button type="default" size="small" @click="deleteOrder(order)">
删除订单
</uv-button>
<uv-button type="primary" color="#3F70FF" size="small" @click="viewOrderDetail(order)">
查看详情
</uv-button>
</template> -->
</div>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="list.length === 0 && loadingStatus !== 'loading'" class="empty-state">
<u-icon name="file-text" size="60" color="#ccc"></u-icon>
<text class="empty-text"></text>
</view>
<uv-load-more v-else :status="loadingStatus" />
</view>
</template>
<script setup>
import {
computed,
onMounted,
ref
} from "vue";
import {
onShow,
onReachBottom
} from "@dcloudio/uni-app";
import {
useAuthStore,
useUserStore
} from "@/store";
import HeaderCustom from '/components/HeaderCustom/HeaderCustom.vue'
import RPC from "@/utils/request";
import dayjs from "dayjs";
const isWeChatH5 = ref(false)
// #ifdef H5
if (navigator.userAgent.toLowerCase().indexOf('micromessenger') !== -1) {
isWeChatH5.value = true
}
// #endif
const active = ref('全部')
const authStore = useAuthStore()
const userStore = useUserStore()
const copyOrderNo = (orderNo) => {
uni.setClipboardData({
data: orderNo,
success: () => {
uni.showToast({
title: '订单号已复制',
icon: 'success'
})
}
})
}
const list = ref([{
id: 1,
orderNumber: 'ORD20230515001',
typeLabel: '续费',
status: 'paid',
statusText: '已支付',
userName: '张三',
userId: 'U12345678',
orderType: '包年会员',
amount: '1200.00',
discount: 200.00,
finalAmount: '1000.00',
payTime: '2025-05-15 10:30',
type: 'yearly'
},
{
id: 2,
orderNumber: 'ORD20230515002',
typeLabel: '续费',
status: 'paid',
statusText: '已支付',
userName: '李四',
userId: 'U12345666',
orderType: '充值',
amount: '500.00',
discount: 0,
finalAmount: '500.00',
payTime: '2025-06-15 11:30',
type: 'recharge'
},
{
id: 3,
orderNumber: 'ORD20230515332',
typeLabel: '续费',
status: 'pending',
statusText: '待支付',
userName: '小客',
userId: 'U12345555',
orderType: '包月会员',
amount: '120.00',
discount: 12.00,
finalAmount: '108.00',
payTime: '2025-08-12 15:20',
countdown: '30分钟',
type: 'monthly'
}
])
// 切换标签
const switchTab = (tab) => {
list.value = []
active.value = tab
page.value = 1
if (tab === '全部') {
typeParams.value = ''
} else {
typeParams.value = tab
}
getList()
}
// 查看订单详情
const viewOrderDetail = (order) => {
uni.navigateTo({
url: `/pagesA/order-detail/order-detail?id=${order.id}`
})
}
// 取消订单
const cancelOrder = (order) => {
uni.showModal({
title: '确认取消',
content: '确定要取消这个订单吗?',
success: async (res) => {
if (res.confirm) {
try {
// 调用取消订单API
await RPC.post('/membership/order/cancel', {
order_no: order.order_no
})
// 更新订单状态
const index = list.value.findIndex(item => item.id === order.id)
if (index !== -1) {
list.value[index].status = 'canceled'
}
uni.showToast({
title: '订单已取消',
icon: 'success'
})
} catch (error) {
console.error('取消订单失败:', error)
uni.showToast({
title: '取消失败,请重试',
icon: 'none'
})
}
}
}
})
}
// 删除订单
const deleteOrder = (order) => {
uni.showModal({
title: '确认删除',
content: '确定要删除这个订单吗?删除后无法恢复。',
success: async (res) => {
if (res.confirm) {
try {
// 调用删除订单API
// await RPC.post('/api/order/delete', { orderId: order.id })
// 从列表中移除订单
const index = orders.value.findIndex(item => item.id === order.id)
if (index !== -1) {
orders.value.splice(index, 1)
}
uni.showToast({
title: '订单已删除',
icon: 'success'
})
} catch (error) {
console.error('删除订单失败:', error)
uni.showToast({
title: '删除失败,请重试',
icon: 'none'
})
}
}
}
})
}
// 微信支付
const wechatPay = async (orderInfo) => {
// #ifdef MP-WEIXIN
// 微信小程序支付
try {
const paymentResult = await uni.requestPayment({
provider: "wxpay",
timeStamp: orderInfo.timeStamp,
nonceStr: orderInfo.nonceStr,
package: orderInfo.package,
signType: orderInfo.signType,
paySign: orderInfo.paySign,
});
console.log("微信支付成功:", paymentResult);
return {
success: true,
data: paymentResult
};
} catch (error) {
console.error("微信支付失败:", error);
if (error.errMsg === "requestPayment:fail cancel") {
throw new Error("用户取消支付");
}
throw new Error("微信支付失败");
}
// #endif
// #ifdef H5
// H5微信支付
const payUrl = orderInfo.h5_url; // 后端返回的H5支付链接
window.location.href = payUrl;
return {
success: true,
redirect: true
};
// #endif
};
// 支付宝支付仅H5环境
const alipayPay = async (orderInfo) => {
// #ifdef H5
// H5支付宝支付
const payUrl = orderInfo.pay_url; // 后端返回的支付宝H5支付链接
window.location.href = payUrl;
return {
success: true,
redirect: true
};
// #endif
};
function showModalAsync(options) {
return new Promise((resolve, reject) => {
uni.showModal({
...options,
success: (res) => {
resolve(res); // 将用户操作结果传递给 await
},
fail: (err) => {
reject(err); // 处理失败情况
}
});
});
}
// 支付订单
const payOrder = async (order) => {
try {
uni.showLoading({
title: "正在处理支付...",
});
// 根据订单的支付渠道调用对应的支付方法
let paymentResult;
if (order.pay_channel === "wxpay") {
// #ifdef H5
uni.showToast({
icon:"none",
title:'请在小程序端使用微信支付'
})
return
// #endif
// #ifdef MP-WEIXIN
const res = await uni.login({
provider: 'weixin'
})
orderResult = await RPC.post('/membership/order/pay_by_wechat_mini', {
code: res.code,
"order_no": orderNum.order_no
})
// #endif
// #ifdef H5
orderResult = await RPC.post('/membership/order/pay_by_wechat_h5', {
"order_no": orderNum.order_no
})
// #endif
uni.showLoading({
title: "正在跳转微信支付...",
});
console.log('orderResult', orderResult)
paymentResult = await wechatPay(orderResult);
} else if (order.pay_channel === "alipay") {
// #ifdef MP-WEIXIN
uni.showToast({
icon:"none",
title:'请在h5端使用支付宝支付'
})
return
// #endif
const orderResult = await RPC.post('/membership/order/pay_by_alipay', {
order_no: order.order_no
})
uni.showLoading({
title: "正在跳转支付宝...",
});
paymentResult = await alipayPay(orderResult);
} else if (order.pay_channel === 'wallet') {
uni.hideLoading()
const res = await showModalAsync({
title: '确认支付',
content: '确认使用钱包余额支付?'
});
// 同步判断逻辑
if (res.confirm) {
paymentResult = await RPC.post('/membership/order/pay_by_wallet', {
order_no: order.order_no
})
console.log('paymentResult', paymentResult)
} else if (res.cancel) {
console.log('用户点击了取消');
paymentResult = {
success: false
}
}
}
uni.hideLoading();
console.log('paymentResult', paymentResult)
// 处理支付结果
if (paymentResult?.success) {
if (paymentResult.redirect) {
// H5跳转支付不需要额外处理
uni.showToast({
title: "正在跳转支付页面...",
icon: "loading",
duration: 2000,
});
} else {
// 支付成功
setTimeout(() => {
uni.showToast({
title: "支付成功",
icon: "success",
});
// 更新订单状态
const index = list.value.findIndex(item => item.id === order.id)
if (index !== -1) {
list.value[index].status = 'paid'
}
// 刷新订单列表
setTimeout(() => {
getList()
}, 500)
}, 1000)
}
}
} catch (error) {
uni.hideLoading();
console.error("支付失败:", error);
let errorMessage = "支付失败,请重试";
if (error.message === "用户取消支付") {
errorMessage = "支付已取消";
} else if (error.message) {
errorMessage = error.message;
} else if (error) {
errorMessage = error
}
uni.showToast({
title: errorMessage,
icon: "none",
});
}
}
const page = ref(1)
const typeParams = ref('')
const loadingStatus = ref('loadmore')
const count = ref(0);
const getList = () => {
uni.showLoading();
RPC.post('/membership/order/list', {
user_id: userStore.userInfo.id,
page: page.value,
order_type: typeParams.value,
size: 20
}).then(res => {
list.value = [...list.value, ...res.list];
count.value = res.total;
if (list.value.length >= count.value) {
loadingStatus.value = "nomore";
} else {
loadingStatus.value = "loadmore";
}
}).catch(() => {
loadingStatus.value = "loadmore";
}).finally(() => {
uni.hideLoading();
})
}
onMounted(() => {
})
onReachBottom(() => {
if (list.value.length >= count.value || loadingStatus.value === "loading")
return;
console.log("上拉加载:");
loadingStatus.value = "loading";
page.value = ++page.value;
getList();
});
onShow(() => {
// 页面显示时刷新订单列表
list.value = []
page.value = 1
active.value = '全部'
typeParams.value = ''
getList()
})
</script>
<style lang="scss" scoped>
.container {
font-size: 14px;
min-height: 100vh;
padding-bottom: 50px;
background-color: #f8f8f8;
}
.tabBox {
height: 50px;
background-color: #fff;
display: flex;
.tabItem {
height: 100%;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
&.selected {
color: #5479ED;
border-bottom: 1px solid #5479ED;
}
}
}
.order-list {
padding: 16px;
.order-item {
background: white;
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
.order-title {
padding: 16px 16px 0;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 16px 0;
.order-info {
display: flex;
align-items: center;
gap: 8px;
.order-number {
font-size: 14px;
color: #333;
}
.order-type-tag {
background: #f0f0f0;
color: #666;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
}
}
.order-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
&.paid {
background: #e8f5e8;
color: #52c41a;
}
&.pending {
background: #fff7e6;
color: #fa8c16;
}
&.cancelled {
background: #fff1f0;
color: #ff4d4f;
}
}
}
.order-details {
padding: 16px;
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.label {
color: #666;
font-size: 14px;
}
.value {
color: #333;
font-size: 14px;
&.price {
color: #333;
font-weight: 500;
}
&.discount {
color: #ff4d4f;
}
&.final-price {
color: #333;
font-weight: bold;
}
}
}
}
.order-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
padding: 0 16px 16px;
border-top: 1px solid #f0f0f0;
padding-top: 16px;
.countdown-text {
color: #ff4d4f;
font-size: 12px;
}
.buttonBox {
display: flex;
align-items: center;
gap: 10px;
justify-content: flex-end;
}
}
.pending-footer {
border-top: 1px solid #f0f0f0;
padding: 16px;
.countdown {
margin-bottom: 12px;
.countdown-text {
color: #ff4d4f;
font-size: 12px;
}
}
.pending-actions {
display: flex;
gap: 12px;
.u-button {
flex: 1;
}
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 20px;
.empty-text {
color: #999;
font-size: 14px;
margin-top: 16px;
}
}
</style>