feat: ui update
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"navigation-bar": "/components/navigation-bar/navigation-bar",
|
||||
"empty-state": "/components/empty-state/empty-state"
|
||||
"empty-state": "/components/empty-state/empty-state",
|
||||
"v2-overlay": "/components/v2-overlay/index"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { request } from '../../utils/request'
|
||||
import { PagedResult, OrderItem } from '../../types/models'
|
||||
import { fenToYuan, formatDateTime, formatCountdown } from '../../utils/format'
|
||||
import { normalizePaged, mergePaged, hasMorePaged } from '../../utils/paged'
|
||||
import { navScroll } from '../../utils/nav'
|
||||
import { logger } from '../../utils/logger'
|
||||
|
||||
// 日志模块名
|
||||
@@ -17,7 +18,14 @@ const STATUS_FILTERS = [
|
||||
]
|
||||
|
||||
const STATUS_TEXT: Record<number, string> = { 1: '待支付', 2: '已支付', 3: '已取消', 4: '已过期', 5: '已退款' }
|
||||
const STATUS_TYPE: Record<number, string> = { 1: 'orange', 2: 'green', 3: 'gray', 4: 'gray', 5: 'blue' }
|
||||
// 零彩色状态语义:待支付 = 实心黑;已支付/已退款 = 描边;已取消/已过期 = 降透明
|
||||
const STATUS_CLS: Record<number, string> = {
|
||||
1: 'pill--ink',
|
||||
2: 'pill--line',
|
||||
3: 'pill--dim',
|
||||
4: 'pill--dim',
|
||||
5: 'pill--line pill--dim',
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
@@ -29,8 +37,28 @@ Page({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
hasMore: true,
|
||||
// 滚动收缩:由 utils/nav.ts 的共享 handler 驱动
|
||||
navScrolled: false,
|
||||
// 取消订单确认
|
||||
cancelVisible: false,
|
||||
cancelOrderNo: '',
|
||||
cancelButtons: [
|
||||
{ text: '再想想', value: '' },
|
||||
{ text: '确认取消', value: 'ok', type: 'main' },
|
||||
],
|
||||
// 退款申请(抽屉 + 原因输入)
|
||||
refundVisible: false,
|
||||
refundOrderNo: '',
|
||||
refundReason: '',
|
||||
refundSubmitting: false,
|
||||
},
|
||||
|
||||
// scroll-view 滚动 → 导航收缩为玻璃小标题
|
||||
onNavScroll: navScroll,
|
||||
|
||||
// 抽屉内容点击不穿透到遮罩
|
||||
noop() {},
|
||||
|
||||
onShow() {
|
||||
this.refresh()
|
||||
this.startCountdown()
|
||||
@@ -103,7 +131,7 @@ Page({
|
||||
const list = raw.map((it) => ({
|
||||
...it,
|
||||
_statusText: STATUS_TEXT[it.order_status] || '',
|
||||
_statusType: STATUS_TYPE[it.order_status] || 'gray',
|
||||
_statusCls: STATUS_CLS[it.order_status] || 'pill--line',
|
||||
_amount: fenToYuan(it.pay_amount),
|
||||
_time: formatDateTime(it.created_at),
|
||||
_orderTail: `…${it.order_no.slice(-4)}`,
|
||||
@@ -137,21 +165,24 @@ Page({
|
||||
|
||||
onCancel(e: WechatMiniprogram.TouchEvent) {
|
||||
const orderNo = e.currentTarget.dataset.no as string
|
||||
wx.showModal({
|
||||
title: '取消订单',
|
||||
content: '确认取消该订单?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
request({ url: `/order/${orderNo}/cancel`, method: 'POST' })
|
||||
.then(() => {
|
||||
logger.info(LOG, '取消订单成功, orderNo=', orderNo)
|
||||
wx.showToast({ title: '已取消', icon: 'success' })
|
||||
this.refresh()
|
||||
})
|
||||
.catch((e: any) => logger.warn(LOG, '取消订单失败, orderNo=', orderNo, 'code=', e && e.code))
|
||||
},
|
||||
fail: (e) => logger.warn(LOG, '取消订单弹窗失败, err=', e && e.errMsg),
|
||||
})
|
||||
this.setData({ cancelVisible: true, cancelOrderNo: orderNo })
|
||||
},
|
||||
|
||||
onCancelCancel() {
|
||||
this.setData({ cancelVisible: false })
|
||||
},
|
||||
|
||||
onCancelConfirm(ev: WechatMiniprogram.CustomEvent) {
|
||||
const orderNo = this.data.cancelOrderNo
|
||||
this.setData({ cancelVisible: false })
|
||||
if (ev.detail.value !== 'ok' || !orderNo) return
|
||||
request({ url: `/order/${orderNo}/cancel`, method: 'POST' })
|
||||
.then(() => {
|
||||
logger.info(LOG, '取消订单成功, orderNo=', orderNo)
|
||||
wx.showToast({ title: '已取消', icon: 'success' })
|
||||
this.refresh()
|
||||
})
|
||||
.catch((err: any) => logger.warn(LOG, '取消订单失败, orderNo=', orderNo, 'code=', err && err.code))
|
||||
},
|
||||
|
||||
onPay() {
|
||||
@@ -160,27 +191,37 @@ Page({
|
||||
|
||||
onRefund(e: WechatMiniprogram.TouchEvent) {
|
||||
const orderNo = e.currentTarget.dataset.no as string
|
||||
wx.showModal({
|
||||
title: '申请退款',
|
||||
editable: true,
|
||||
placeholderText: '请填写退款原因',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
const reason = (res.content || '').trim()
|
||||
if (!reason) {
|
||||
wx.showToast({ title: '请填写退款原因', icon: 'none' })
|
||||
return
|
||||
}
|
||||
request({ url: '/order/refund', method: 'POST', data: { order_no: orderNo, reason } })
|
||||
.then(() => {
|
||||
logger.info(LOG, '退款申请提交成功, orderNo=', orderNo)
|
||||
wx.showToast({ title: '退款申请已提交,等待审核', icon: 'none' })
|
||||
this.refresh()
|
||||
})
|
||||
.catch((e: any) => logger.warn(LOG, '退款申请失败, orderNo=', orderNo, 'code=', e && e.code))
|
||||
},
|
||||
fail: (e) => logger.warn(LOG, '退款弹窗失败, err=', e && e.errMsg),
|
||||
})
|
||||
this.setData({ refundVisible: true, refundOrderNo: orderNo, refundReason: '' })
|
||||
},
|
||||
|
||||
onRefundClose() {
|
||||
this.setData({ refundVisible: false })
|
||||
},
|
||||
|
||||
onRefundInput(e: WechatMiniprogram.Input) {
|
||||
this.setData({ refundReason: e.detail.value })
|
||||
},
|
||||
|
||||
onRefundSubmit() {
|
||||
if (this.data.refundSubmitting) return
|
||||
const orderNo = this.data.refundOrderNo
|
||||
const reason = (this.data.refundReason || '').trim()
|
||||
if (!reason) {
|
||||
wx.showToast({ title: '请填写退款原因', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.setData({ refundSubmitting: true })
|
||||
request({ url: '/order/refund', method: 'POST', data: { order_no: orderNo, reason } })
|
||||
.then(() => {
|
||||
logger.info(LOG, '退款申请提交成功, orderNo=', orderNo)
|
||||
wx.showToast({ title: '退款申请已提交,等待审核', icon: 'none' })
|
||||
this.setData({ refundVisible: false, refundSubmitting: false })
|
||||
this.refresh()
|
||||
})
|
||||
.catch((err: any) => {
|
||||
logger.warn(LOG, '退款申请失败, orderNo=', orderNo, 'code=', err && err.code)
|
||||
this.setData({ refundSubmitting: false })
|
||||
})
|
||||
},
|
||||
|
||||
onGoMall() {
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
<view class="page page-flex">
|
||||
<navigation-bar title="我的订单" />
|
||||
<navigation-bar title="我的订单" scrolled="{{navScrolled}}" />
|
||||
|
||||
<scroll-view scroll-y class="scroll-body" bindscrolltolower="onReachBottom">
|
||||
<!-- 筛选 -->
|
||||
<scroll-view scroll-x class="filter-scroll" show-scrollbar="{{false}}">
|
||||
<view class="filter-row">
|
||||
<view
|
||||
wx:for="{{filters}}"
|
||||
wx:key="name"
|
||||
class="filter-item {{activeFilter === index ? 'active' : ''}}"
|
||||
data-index="{{index}}"
|
||||
bindtap="onFilterTap"
|
||||
>{{item.name}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view scroll-y class="scroll-body pb-safe" bindscrolltolower="onReachBottom" bindscroll="onNavScroll">
|
||||
<view class="head">
|
||||
<view class="eyebrow">Orders</view>
|
||||
<view class="display">我的订单</view>
|
||||
</view>
|
||||
|
||||
<!-- 下划线式筛选 -->
|
||||
<view class="segbar">
|
||||
<scroll-view scroll-x class="seg-scroll" show-scrollbar="{{false}}">
|
||||
<view class="seg">
|
||||
<view
|
||||
wx:for="{{filters}}"
|
||||
wx:key="name"
|
||||
class="seg-item {{activeFilter === index ? 'is-on' : ''}}"
|
||||
data-index="{{index}}"
|
||||
bindtap="onFilterTap"
|
||||
>{{item.name}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<empty-state
|
||||
wx:if="{{!loading && !list.length}}"
|
||||
@@ -24,32 +31,79 @@
|
||||
bind:action="onGoMall"
|
||||
/>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<view class="list">
|
||||
<view class="order-card card" wx:for="{{list}}" wx:key="order_no">
|
||||
<view class="order-top">
|
||||
<text class="order-no">订单 {{item._orderTail}}</text>
|
||||
<text wx:if="{{item._payChannelText}}" class="tag tag-gray">{{item._payChannelText}}</text>
|
||||
<text class="tag tag-{{item._statusType}}">{{item._statusText}}</text>
|
||||
<!-- 订单列表:状态用描边胶囊表达,不依赖颜色 -->
|
||||
<view class="od-list">
|
||||
<view class="od-card" wx:for="{{list}}" wx:key="order_no">
|
||||
<view class="od-top">
|
||||
<text class="od-no num">订单 {{item._orderTail}}</text>
|
||||
<text wx:if="{{item._payChannelText}}" class="pill">{{item._payChannelText}}</text>
|
||||
<text class="pill {{item._statusCls}}">{{item._statusText}}</text>
|
||||
</view>
|
||||
<view class="order-body">
|
||||
<view class="order-goods">{{item.goods_name}}</view>
|
||||
<view class="order-benefit" wx:if="{{item._benefit}}">{{item._benefit}}</view>
|
||||
<view class="order-bottom">
|
||||
<text class="order-amount">实付 <text class="num">{{item._amount}}</text></text>
|
||||
<view class="order-actions">
|
||||
<block wx:if="{{item.order_status === 1}}">
|
||||
<text wx:if="{{item._countdown}}" class="countdown">剩余 {{item._countdown}}</text>
|
||||
<view wx:if="{{item._canCancel}}" class="action-btn outline" data-no="{{item.order_no}}" bindtap="onCancel">取消订单</view>
|
||||
<view class="action-btn primary" data-no="{{item.order_no}}" bindtap="onPay">去支付</view>
|
||||
</block>
|
||||
<block wx:elif="{{item.order_status === 2}}">
|
||||
<view class="action-btn outline" data-no="{{item.order_no}}" bindtap="onRefund">申请退款</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<view class="od-goods">{{item.goods_name}}</view>
|
||||
<view class="od-benefit" wx:if="{{item._benefit}}">{{item._benefit}}</view>
|
||||
|
||||
<view class="od-bottom">
|
||||
<view class="od-amount">实付 <text class="num">{{item._amount}}</text></view>
|
||||
<view class="od-actions">
|
||||
<block wx:if="{{item.order_status === 1}}">
|
||||
<text wx:if="{{item._countdown}}" class="od-countdown num">剩余 {{item._countdown}}</text>
|
||||
<view
|
||||
wx:if="{{item._canCancel}}"
|
||||
class="btn btn--ghost btn--sm"
|
||||
data-no="{{item.order_no}}"
|
||||
bindtap="onCancel"
|
||||
>取消订单</view>
|
||||
<view class="btn btn--ink btn--sm" data-no="{{item.order_no}}" bindtap="onPay">去支付</view>
|
||||
</block>
|
||||
<block wx:elif="{{item.order_status === 2}}">
|
||||
<view class="btn btn--ghost btn--sm" data-no="{{item.order_no}}" bindtap="onRefund">申请退款</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{list.length && !hasMore}}" class="foot-note">没有更多了</view>
|
||||
<view class="gap-64"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 取消订单确认 -->
|
||||
<v2-overlay
|
||||
visible="{{cancelVisible}}"
|
||||
type="modal"
|
||||
title="取消订单"
|
||||
content="确认取消该订单?"
|
||||
buttons="{{cancelButtons}}"
|
||||
bind:confirm="onCancelConfirm"
|
||||
bind:cancel="onCancelCancel"
|
||||
/>
|
||||
|
||||
<!-- 退款申请:底部玻璃抽屉 -->
|
||||
<view wx:if="{{refundVisible}}" class="scrim" bindtap="onRefundClose"></view>
|
||||
<view wx:if="{{refundVisible}}" class="sheet-wrap">
|
||||
<view class="sheet" catchtap="noop">
|
||||
<view class="sheet-grab"></view>
|
||||
<view class="sheet-hd">
|
||||
<text class="sheet-t">申请退款</text>
|
||||
<text class="sheet-x" bindtap="onRefundClose">取消</text>
|
||||
</view>
|
||||
<view class="sheet-body">
|
||||
<textarea
|
||||
class="textarea od-refund-input"
|
||||
value="{{refundReason}}"
|
||||
bindinput="onRefundInput"
|
||||
placeholder="请填写退款原因"
|
||||
placeholder-class="input-ph"
|
||||
maxlength="200"
|
||||
/>
|
||||
</view>
|
||||
<view class="sheet-foot">
|
||||
<view
|
||||
class="btn btn--ink btn--block {{refundSubmitting ? 'is-off' : ''}}"
|
||||
bindtap="onRefundSubmit"
|
||||
>{{refundSubmitting ? '提交中…' : '提交申请'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** 订单:下划线筛选 + 描边状态胶囊 + tabular 倒计时 **/
|
||||
.page-flex {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
@@ -10,113 +11,87 @@
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.filter-scroll {
|
||||
background: #fff;
|
||||
white-space: nowrap;
|
||||
.segbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
margin-top: 36rpx;
|
||||
background: var(--glass);
|
||||
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
||||
backdrop-filter: saturate(180%) blur(20px);
|
||||
border-bottom: 1rpx solid var(--hairline);
|
||||
}
|
||||
|
||||
.filter-row {
|
||||
display: inline-flex;
|
||||
padding: 20rpx 24rpx;
|
||||
.od-list {
|
||||
padding: 40rpx var(--pad) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
flex-shrink: 0;
|
||||
padding: 0 28rpx;
|
||||
height: 56rpx;
|
||||
line-height: 56rpx;
|
||||
border-radius: 28rpx;
|
||||
font-size: 24rpx;
|
||||
color: #3C3C43;
|
||||
background: #F2F2F7;
|
||||
margin-right: 16rpx;
|
||||
.od-card {
|
||||
padding-bottom: 36rpx;
|
||||
border-bottom: 1rpx solid var(--hairline);
|
||||
}
|
||||
|
||||
.filter-item.active {
|
||||
background: #007AFF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.list {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 28rpx;
|
||||
}
|
||||
|
||||
.order-top {
|
||||
.od-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
.od-no {
|
||||
flex: 1;
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-3);
|
||||
}
|
||||
|
||||
.order-body {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.order-goods {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.order-benefit {
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.order-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.od-goods {
|
||||
margin-top: 24rpx;
|
||||
font-family: var(--serif);
|
||||
font-size: 34rpx;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.order-amount {
|
||||
font-size: 26rpx;
|
||||
color: #3C3C43;
|
||||
.od-benefit {
|
||||
margin-top: 10rpx;
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.order-amount .num {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
.od-bottom {
|
||||
margin-top: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.order-actions {
|
||||
.od-amount {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.od-amount .num {
|
||||
font-family: var(--serif);
|
||||
font-size: 36rpx;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.od-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-size: 24rpx;
|
||||
color: #8E8E93;
|
||||
.od-countdown {
|
||||
font-size: 23rpx;
|
||||
color: var(--ink-3);
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
height: 56rpx;
|
||||
padding: 0 28rpx;
|
||||
border-radius: 28rpx;
|
||||
font-size: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-btn.outline {
|
||||
border: 1rpx solid #C7C7CC;
|
||||
color: #3C3C43;
|
||||
}
|
||||
|
||||
.action-btn.primary {
|
||||
background: #007AFF;
|
||||
color: #fff;
|
||||
.od-refund-input {
|
||||
margin: 0 var(--pad);
|
||||
width: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user