| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- <template>
- <view class="container">
- <!-- 顶部状态栏 -->
- <view class="tab-header">
- <view
- v-for="tab in tabs"
- :key="tab.key"
- :class="['tab-item', activeTab === tab.key ? 'active' : '']"
- @click="activeTab = tab.key"
- >
- {{ tab.name }}
- </view>
- </view>
- <!-- 订单列表 -->
- <scroll-view class="order-scroll" scroll-y :show-scrollbar="false">
- <view class="list-wrap">
- <view v-for="(order, index) in filteredOrders" :key="index" class="order-card">
- <!-- 卡片头部:编号与状态 -->
- <view class="card-header">
- <text class="order-no">{{ order.orderNo }}</text>
- <view :class="['status-badge', order.statusKey]">
- <text>{{ order.statusText }}</text>
- </view>
- </view>
-
- <!-- 订单主体 -->
- <view class="card-body">
- <view class="body-top">
- <view class="order-info">
- <text class="order-name">{{ order.jobName }}</text>
- <text class="order-company">{{ order.company }}</text>
- </view>
- <view class="price-block">
- <text class="price-symbol">¥</text>
- <text class="price-num">{{ order.price }}</text>
- </view>
- </view>
- <view class="order-meta">
- <text class="meta-item">{{ order.createTime }}</text>
- <text class="meta-item" v-if="order.isDeposit">定金订单</text>
- </view>
- </view>
-
- <!-- 底部操作按钮 -->
- <view class="card-footer" v-if="order.statusKey === 'unpaid'">
- <button class="action-btn pay-btn" @click="handlePay(order)">立即支付</button>
- </view>
- <view class="card-footer" v-else-if="order.statusKey === 'paid'">
- <text class="footer-tip">支付完成,感谢您的购买</text>
- </view>
- </view>
- <!-- 空状态 -->
- <view class="empty-state" v-if="filteredOrders.length === 0 && !loading">
- <image src="/static/icons/empty-box.svg" class="empty-img"></image>
- <text>暂无相关订单记录</text>
- </view>
- <view v-if="filteredOrders.length > 0" class="no-more">—— 已到底啦 ——</view>
- </view>
- </scroll-view>
- <!-- 微信支付确认弹窗 -->
- <view class="modal-mask" v-if="showPayModal" @touchmove.stop.prevent>
- <view class="modal-container">
- <view class="modal-title">确认支付</view>
- <view class="pay-info">
- <view class="pay-row">
- <text class="pay-label">订单名称</text>
- <text class="pay-value">{{ currentOrder?.jobName }}</text>
- </view>
- <view class="pay-row">
- <text class="pay-label">支付金额</text>
- <text class="pay-value price">¥{{ currentOrder?.price }}</text>
- </view>
- </view>
- <view class="modal-btns">
- <button class="modal-btn cancel" @click="showPayModal = false">取消</button>
- <button class="modal-btn confirm" @click="confirmPay" :disabled="payLoading">确认支付</button>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, computed, onMounted } from 'vue';
- import { onPullDownRefresh } from '@dcloudio/uni-app';
- import { listOrder } from '@/api/order';
- import { createWxPayOrder } from '@/api/message';
- const activeTab = ref('all');
- const tabs = [
- { name: '全部', key: 'all' },
- { name: '待支付', key: 'unpaid' },
- { name: '已支付', key: 'paid' },
- ];
- const loading = ref(false);
- const orders = ref([]);
- const showPayModal = ref(false);
- const currentOrder = ref(null);
- const payLoading = ref(false);
- onMounted(() => {
- fetchOrders();
- });
- onPullDownRefresh(async () => {
- await fetchOrders();
- uni.stopPullDownRefresh();
- });
- const fetchOrders = async () => {
- loading.value = true;
- try {
- const userInfo = uni.getStorageSync('userInfo');
- if (!userInfo || !userInfo.studentId) {
- uni.showToast({ title: '请先登录', icon: 'none' });
- return;
- }
-
- const res = await listOrder({
- buyerId: userInfo.studentId,
- buyerType: 2
- });
-
- if (res && res.rows) {
- orders.value = res.rows.map(item => {
- const statusInfo = resolveOrderStatus(item.orderStatus, item.payStatus);
- return {
- ...item,
- id: item.id,
- orderNo: item.orderNo,
- statusKey: statusInfo.key,
- statusText: statusInfo.text,
- jobName: item.productName || item.remark || '测评服务',
- company: item.sellerName || '审计之家',
- price: item.totalAmount || '0.00',
- isDeposit: item.orderType === 2,
- createTime: formatTime(item.createTime),
- };
- });
- }
- } catch (err) {
- console.error('获取订单列表失败:', err);
- uni.showToast({ title: '获取订单失败', icon: 'none' });
- } finally {
- loading.value = false;
- }
- };
- /**
- * 根据 orderStatus + payStatus 综合判断订单状态
- *
- * orderStatus: 0=待处理 1=已完成 2=部分退款 4=全额退款
- * payStatus: 0=未支付 1=微信已支付 2=余额已支付
- */
- const resolveOrderStatus = (orderStatus, payStatus) => {
- // 已退款
- if (orderStatus === 4) return { key: 'refunded', text: '已退款' };
- if (orderStatus === 2) return { key: 'partial_refund', text: '部分退款' };
-
- // 已支付
- if (payStatus === 1 || payStatus === 2) {
- if (orderStatus === 1) return { key: 'paid', text: '已完成' };
- return { key: 'paid', text: '已支付' };
- }
-
- // 未支付
- if (orderStatus === 0 && (payStatus === 0 || payStatus === null)) {
- return { key: 'unpaid', text: '待支付' };
- }
-
- // 兜底
- return { key: 'unknown', text: '未知状态' };
- };
- const filteredOrders = computed(() => {
- if (activeTab.value === 'all') return orders.value;
- return orders.value.filter(o => o.statusKey === activeTab.value);
- });
- const formatTime = (time) => {
- if (!time) return '--';
- const d = new Date(time);
- const year = d.getFullYear();
- const month = String(d.getMonth() + 1).padStart(2, '0');
- const day = String(d.getDate()).padStart(2, '0');
- const hour = String(d.getHours()).padStart(2, '0');
- const minute = String(d.getMinutes()).padStart(2, '0');
- return `${year}-${month}-${day} ${hour}:${minute}`;
- };
- const handlePay = (order) => {
- currentOrder.value = order;
- showPayModal.value = true;
- };
- const confirmPay = async () => {
- if (!currentOrder.value || payLoading.value) return;
- payLoading.value = true;
- try {
- const userInfo = uni.getStorageSync('userInfo') || {};
- const userId = userInfo.studentId;
- if (!userId) {
- uni.showToast({ title: '请先登录', icon: 'none' });
- return;
- }
- // 该订单的 businessId 关联的是结算单ID(cs_order_card.id)
- const businessId = currentOrder.value.businessId;
- if (!businessId) {
- // 没有 businessId,说明不是结算单创建的订单,走订单本身的支付流程
- uni.showToast({ title: '该订单暂不支持在线支付', icon: 'none' });
- return;
- }
- const payRes = await createWxPayOrder(businessId, userId);
- if (!(payRes.code === 200 || payRes.code === 0)) {
- uni.showToast({ title: payRes.msg || '创建支付订单失败', icon: 'none' });
- return;
- }
- showPayModal.value = false;
- // 检查是否返回了微信支付参数
- if (payRes.data && payRes.data.wechatPayParams) {
- const wxPayParams = payRes.data.wechatPayParams;
-
- uni.showLoading({ title: '发起微信支付...' });
-
- uni.requestPayment({
- provider: 'wxpay',
- timeStamp: wxPayParams.timeStamp,
- nonceStr: wxPayParams.nonceStr,
- package: wxPayParams.package,
- signType: wxPayParams.signType || 'RSA',
- paySign: wxPayParams.paySign,
- success: (res) => {
- uni.hideLoading();
- uni.showToast({ title: '支付成功', icon: 'success' });
- // 刷新订单列表
- setTimeout(() => { fetchOrders(); }, 1000);
- },
- fail: (err) => {
- uni.hideLoading();
- if (err.errMsg && err.errMsg.includes('cancel')) {
- uni.showToast({ title: '已取消支付', icon: 'none' });
- } else {
- uni.showToast({ title: '支付失败,请重试', icon: 'none' });
- }
- }
- });
- } else {
- uni.showToast({ title: '支付参数异常', icon: 'none' });
- }
- } catch (err) {
- console.error('支付异常:', err);
- uni.showToast({ title: '支付异常,请重试', icon: 'none' });
- } finally {
- payLoading.value = false;
- }
- };
- </script>
- <style lang="scss" scoped>
- .container {
- min-height: 100vh;
- background-color: #F5F6F8;
- display: flex;
- flex-direction: column;
- }
- /* Tabs */
- .tab-header {
- display: flex;
- background-color: #FFF;
- padding: 0 40rpx;
- border-bottom: 2rpx solid #F0F2F5;
- position: sticky;
- top: 0;
- z-index: 10;
-
- .tab-item {
- flex: 1;
- height: 100rpx;
- line-height: 100rpx;
- text-align: center;
- font-size: 28rpx;
- color: #666;
- position: relative;
- transition: all 0.2s;
-
- &.active {
- color: #1F6CFF;
- font-weight: bold;
- &::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- width: 48rpx;
- height: 6rpx;
- background-color: #1F6CFF;
- border-radius: 3rpx;
- }
- }
- }
- }
- /* 列表 */
- .order-scroll {
- flex: 1;
- .list-wrap {
- padding: 24rpx 30rpx;
- }
- }
- /* 订单卡片 */
- .order-card {
- background: #FFF;
- border-radius: 24rpx;
- padding: 32rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding-bottom: 24rpx;
- margin-bottom: 24rpx;
- border-bottom: 1rpx solid #F0F2F5;
-
- .order-no { font-size: 24rpx; color: #999; }
- .status-badge {
- padding: 4rpx 16rpx;
- border-radius: 16rpx;
- font-size: 22rpx;
- font-weight: 500;
- &.unpaid { background: #FFF7E6; color: #FA8C16; }
- &.paid { background: #F0FFF4; color: #52C41A; }
- &.refunded { background: #F5F5F5; color: #999; }
- &.partial_refund { background: #E6F7FF; color: #1890FF; }
- }
- }
-
- .card-body {
- .body-top {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: 16rpx;
- .order-info {
- flex: 1;
- min-width: 0;
- .order-name {
- font-size: 32rpx;
- font-weight: 600;
- color: #1A1A1A;
- display: block;
- margin-bottom: 8rpx;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .order-company {
- font-size: 26rpx;
- color: #888;
- display: block;
- }
- }
- .price-block {
- flex-shrink: 0;
- margin-left: 16rpx;
- text-align: right;
- .price-symbol { font-size: 26rpx; color: #FF4D4F; font-weight: 500; }
- .price-num { font-size: 40rpx; color: #FF4D4F; font-weight: bold; }
- }
- }
- .order-meta {
- display: flex;
- gap: 24rpx;
- .meta-item {
- font-size: 24rpx;
- color: #BBB;
- }
- }
- }
-
- .card-footer {
- margin-top: 24rpx;
- padding-top: 24rpx;
- border-top: 1rpx solid #F0F2F5;
- display: flex;
- justify-content: flex-end;
- align-items: center;
- .footer-tip {
- font-size: 24rpx;
- color: #52C41A;
- }
- .action-btn {
- height: 64rpx;
- line-height: 62rpx;
- padding: 0 40rpx;
- font-size: 26rpx;
- font-weight: 600;
- border-radius: 32rpx;
- margin: 0;
- &::after { border: none; }
-
- &.pay-btn {
- background: linear-gradient(135deg, #1F6CFF, #4D8FFF);
- color: #FFF;
- box-shadow: 0 4rpx 16rpx rgba(31, 108, 255, 0.25);
- }
- }
- }
- }
- /* 支付弹窗 */
- .modal-mask {
- position: fixed; top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.6); z-index: 2000;
- display: flex; align-items: center; justify-content: center;
- }
- .modal-container {
- width: 580rpx;
- background: #FFF;
- border-radius: 32rpx;
- padding: 48rpx 40rpx 40rpx;
-
- .modal-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #1A1A1A;
- text-align: center;
- margin-bottom: 40rpx;
- }
-
- .pay-info {
- background: #F8F9FB;
- border-radius: 20rpx;
- padding: 32rpx;
- margin-bottom: 40rpx;
-
- .pay-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20rpx;
- &:last-child { margin-bottom: 0; }
- .pay-label { font-size: 28rpx; color: #888; }
- .pay-value { font-size: 28rpx; color: #333; font-weight: 500; &.price { color: #FF4D4F; font-size: 34rpx; font-weight: bold; } }
- }
- }
- .modal-btns {
- display: flex;
- gap: 20rpx;
-
- .modal-btn {
- flex: 1;
- height: 80rpx;
- line-height: 80rpx;
- border-radius: 40rpx;
- font-size: 28rpx;
- font-weight: 600;
- &::after { border: none; }
- &.cancel { background: #F5F5F7; color: #666; }
- &.confirm { background: #1F6CFF; color: #FFF; &:disabled { opacity: 0.5; } }
- }
- }
- }
- /* 空状态 & 底部 */
- .empty-state {
- display: flex; flex-direction: column; align-items: center; padding-top: 150rpx;
- .empty-img { width: 240rpx; height: 240rpx; margin-bottom: 20rpx; opacity: 0.5; }
- text { font-size: 28rpx; color: #CCC; }
- }
- .no-more { text-align: center; font-size: 24rpx; color: #CCC; padding: 40rpx 0; }
- </style>
|