add wxpay
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
"pages/terms/terms",
|
||||
"pages/privacy/privacy",
|
||||
"pages/analyze/analyze",
|
||||
"pages/coupon/coupon"
|
||||
"pages/coupon/coupon",
|
||||
"pages/order/order"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTextStyle": "black",
|
||||
@@ -25,4 +26,4 @@
|
||||
"useExtendedLib" : {
|
||||
"weui": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ App<IAppOption>({
|
||||
this.globalData.token = authInfo.token
|
||||
this.globalData.userInfo = authInfo.userInfo
|
||||
// 初始化词典等级
|
||||
this.globalData.dictLevel = authInfo.dictLevel || 'PRIMARY'
|
||||
this.globalData.dictLevel = authInfo.dictLevel || 'LEVEL1'
|
||||
console.log('登录状态有效,自动登录')
|
||||
} else {
|
||||
console.log('Token 已过期,清理本地数据')
|
||||
@@ -101,7 +101,7 @@ App<IAppOption>({
|
||||
this.globalData.token = loginData.access_token
|
||||
this.globalData.userInfo = loginData.userInfo
|
||||
// 更新词典等级
|
||||
this.globalData.dictLevel = loginData.dict_level || 'PRIMARY'
|
||||
this.globalData.dictLevel = loginData.dict_level || 'LEVEL1'
|
||||
|
||||
// 存储到本地
|
||||
wx.setStorageSync('token', loginData.access_token)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!--coupon.wxml-->
|
||||
<view class="coupon-container">
|
||||
<view class='coupon_box' wx:for="{{products}}" wx:key="id" wx:for-item="item">
|
||||
<view class='coupon_box {{item.one_time ? "one_time" : ""}}' wx:for="{{products}}" wx:key="id" wx:for-item="item">
|
||||
<view class='content' bindtap="handleCouponTap" data-id="{{item.id}}">
|
||||
<view class='title'>{{item.title}}</view>
|
||||
<view class='how_much'>{{item.points}}</view>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.coupon_box{
|
||||
background: linear-gradient(to right, #FF4B2B, #FF416C);
|
||||
background: linear-gradient(to right, #3e9eff, #5cbffc);
|
||||
width: 40%;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
@@ -21,6 +21,10 @@ background: linear-gradient(to right, #FF4B2B, #FF416C);
|
||||
margin: 5% 5% 0 5%;
|
||||
}
|
||||
|
||||
.coupon_box.one_time{
|
||||
background: linear-gradient(to right, #FF4B2B, #FF416C);
|
||||
}
|
||||
|
||||
.coupon_box::before{
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
16
miniprogram/pages/order/order.json
Normal file
16
miniprogram/pages/order/order.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"t-cell": "tdesign-miniprogram/cell/cell",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
|
||||
"t-checkbox-group": "tdesign-miniprogram/checkbox-group/checkbox-group",
|
||||
"t-skeleton": "tdesign-miniprogram/skeleton/skeleton"
|
||||
},
|
||||
"navigationBarTitleText": "订单",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#ffffff",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": true,
|
||||
"onReachBottomDistance": 50
|
||||
}
|
||||
148
miniprogram/pages/order/order.ts
Normal file
148
miniprogram/pages/order/order.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import apiManager from '../../utils/api'
|
||||
|
||||
Page({
|
||||
data: {
|
||||
orders: [] as Array<any>,
|
||||
page: 1,
|
||||
size: 20,
|
||||
total: 0,
|
||||
hasMore: true,
|
||||
isLoading: false,
|
||||
refundDialogVisible: false,
|
||||
refundOrderId: '',
|
||||
refundAmountCents: 0,
|
||||
selectedReasons: [] as Array<string>,
|
||||
reasonText: '',
|
||||
reasonMax: 200
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadOrders(1)
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.setData({ page: 1 })
|
||||
this.loadOrders(1).finally(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.data.hasMore && !this.data.isLoading) {
|
||||
const next = this.data.page + 1
|
||||
this.loadOrders(next)
|
||||
}
|
||||
},
|
||||
|
||||
async loadOrders(page: number) {
|
||||
this.setData({ isLoading: page === 1 })
|
||||
try {
|
||||
const res = await apiManager.listOrders(page, this.data.size)
|
||||
const items = (res.items || []).map((it: any) => ({
|
||||
...it,
|
||||
displayTime: this.formatTime(it.created_time),
|
||||
displayAmount: this.formatAmount(it.amount_cents),
|
||||
}))
|
||||
console.log(items)
|
||||
if (page === 1) {
|
||||
this.setData({ orders: items, page, total: res.total || items.length, hasMore: page * this.data.size < (res.total || items.length), isLoading: false })
|
||||
} else {
|
||||
const merged = [...this.data.orders, ...items]
|
||||
this.setData({ orders: merged, page, total: res.total || merged.length, hasMore: page * this.data.size < (res.total || merged.length), isLoading: false })
|
||||
}
|
||||
} catch (e) {
|
||||
this.setData({ isLoading: false })
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
formatTime(t: string) {
|
||||
try {
|
||||
const d = new Date(t)
|
||||
const y = d.getFullYear()
|
||||
const m = `${d.getMonth() + 1}`.padStart(2, '0')
|
||||
const dd = `${d.getDate()}`.padStart(2, '0')
|
||||
const hh = `${d.getHours()}`.padStart(2, '0')
|
||||
const mm = `${d.getMinutes()}`.padStart(2, '0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}`
|
||||
} catch (_) {
|
||||
return t
|
||||
}
|
||||
},
|
||||
|
||||
formatAmount(cents: number) {
|
||||
if (!cents && cents !== 0) return ''
|
||||
return (cents / 100).toFixed(2)
|
||||
},
|
||||
|
||||
async onRefundTap(e: any) {
|
||||
const id = e.currentTarget.dataset.id
|
||||
try {
|
||||
const detail = await apiManager.getOrderDetail(id)
|
||||
const updated = this.data.orders.map((it: any) => it.id === id ? {
|
||||
...it,
|
||||
refund_status: detail.refund_status || it.refund_status,
|
||||
amount_cents: detail.amount_cents ?? it.amount_cents,
|
||||
displayAmount: this.formatAmount(detail.amount_cents ?? it.amount_cents),
|
||||
refundable_amount_cents: detail.refundable_amount_cents ?? it.refundable_amount_cents,
|
||||
displayRefundable: this.formatAmount(detail.refundable_amount_cents ?? 0),
|
||||
can_refund: detail.can_refund ?? it.can_refund
|
||||
} : it)
|
||||
this.setData({ orders: updated })
|
||||
if (detail.refund_status) {
|
||||
wx.showToast({ title: `退款状态:${detail.refund_status}`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (detail.can_refund) {
|
||||
this.setData({ refundOrderId: id, refundAmountCents: Number(detail.refundable_amount_cents || 0), selectedReasons: [], reasonText: '', refundDialogVisible: true })
|
||||
} else {
|
||||
wx.showToast({ title: '当前订单不可退款', icon: 'none' })
|
||||
}
|
||||
} catch (_) {
|
||||
wx.showToast({ title: '获取订单详情失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
onReasonGroupChange(e: any) {
|
||||
this.setData({ selectedReasons: e.detail.value || [] })
|
||||
},
|
||||
|
||||
onReasonTextInput(e: any) {
|
||||
const v = e.detail.value || ''
|
||||
const max = this.data.reasonMax
|
||||
this.setData({ reasonText: v.length > max ? v.slice(0, max) : v })
|
||||
},
|
||||
|
||||
closeRefundDialog() {
|
||||
this.setData({ refundDialogVisible: false })
|
||||
},
|
||||
|
||||
async confirmRefundDialog() {
|
||||
const sel = this.data.selectedReasons
|
||||
const txt = (this.data.reasonText || '').trim()
|
||||
if ((!sel || sel.length === 0) && !txt) {
|
||||
wx.showToast({ title: '请至少选择或填写一项理由', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const reason = [...sel, txt].filter(Boolean).join('|')
|
||||
try {
|
||||
const r = await apiManager.refund(this.data.refundOrderId, reason, this.data.refundAmountCents)
|
||||
wx.showToast({ title: '已提交退款申请', icon: 'none' })
|
||||
this.setData({ refundDialogVisible: false })
|
||||
const outRefundNo = r && (r.out_refund_no || (r.data && r.data.out_refund_no))
|
||||
if (outRefundNo) {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
const s = await apiManager.getRefundStatus(outRefundNo)
|
||||
const status = s && (s.status || '')
|
||||
const updated = this.data.orders.map((it: any) => it.id === this.data.refundOrderId ? { ...it, refund_status: status, can_refund: false } : it)
|
||||
this.setData({ orders: updated })
|
||||
wx.showToast({ title: status ? `退款状态:${status}` : '查询成功', icon: 'none' })
|
||||
} catch (_) {
|
||||
wx.showToast({ title: '查询失败', icon: 'none' })
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
} catch (e) {
|
||||
wx.showToast({ title: '提交失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
39
miniprogram/pages/order/order.wxml
Normal file
39
miniprogram/pages/order/order.wxml
Normal file
@@ -0,0 +1,39 @@
|
||||
<view class="order-container">
|
||||
<view wx:if="{{isLoading && page === 1}}" class="skeleton-wrap">
|
||||
<t-skeleton theme="paragraph" animation="gradient" loading="{{true}}"></t-skeleton>
|
||||
</view>
|
||||
<view wx:elif="{{!isLoading && orders.length === 0}}" class="empty-wrap">没有记录</view>
|
||||
<view wx:else class="order-list">
|
||||
<block wx:for="{{orders}}" wx:key="id">
|
||||
<t-cell title="{{item.displayTime}}" hover>
|
||||
<view slot="description" class="order-desc">
|
||||
¥{{item.displayAmount}} · 积分 {{item.points}}
|
||||
<block wx:if="{{item.refundable_amount_cents > 0}}"> · 可退 ¥{{item.displayRefundable}}</block>
|
||||
</view>
|
||||
<view slot="right-icon">
|
||||
<view wx:if="{{!item.refund_status}}" class="refund-btn" bindtap="onRefundTap" data-id="{{item.id}}">退款</view>
|
||||
<view wx:else class="refund-status">{{item.refund_status}}</view>
|
||||
</view>
|
||||
</t-cell>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<t-dialog
|
||||
visible="{{refundDialogVisible}}"
|
||||
title="申请退款"
|
||||
confirm-btn="提交"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="confirmRefundDialog"
|
||||
bind:cancel="closeRefundDialog"
|
||||
>
|
||||
<view slot="content" class="refund-content">
|
||||
<t-checkbox-group value="{{selectedReasons}}" bind:change="onReasonGroupChange">
|
||||
<t-checkbox value="误操作下单">误操作下单</t-checkbox>
|
||||
<t-checkbox value="功能未达到预期">功能未达到预期</t-checkbox>
|
||||
<t-checkbox value="重复支付或订单异常">重复支付或订单异常</t-checkbox>
|
||||
</t-checkbox-group>
|
||||
<textarea class="refund-textarea" value="{{reasonText}}" maxlength="{{reasonMax}}" placeholder="补充说明(最多200字)" bindinput="onReasonTextInput"></textarea>
|
||||
<view class="reason-tip">至少选择一项或填写说明</view>
|
||||
</view>
|
||||
</t-dialog>
|
||||
</view>
|
||||
10
miniprogram/pages/order/order.wxss
Normal file
10
miniprogram/pages/order/order.wxss
Normal file
@@ -0,0 +1,10 @@
|
||||
.order-container { padding: 24rpx; }
|
||||
.skeleton-wrap { padding: 24rpx; }
|
||||
.empty-wrap { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.order-list { display: flex; flex-direction: column; gap: 8rpx; }
|
||||
.order-desc { color: #666; font-size: 26rpx; }
|
||||
.refund-btn { color: #0052d9; font-size: 26rpx; padding: 8rpx 16rpx; border: 1rpx solid #0052d9; border-radius: 8rpx; }
|
||||
.refund-status { color: #999; font-size: 26rpx; padding: 8rpx 0; }
|
||||
.refund-content { padding: 12rpx 0; }
|
||||
.refund-textarea { width: 100%; min-height: 160rpx; border: 1rpx solid #ddd; border-radius: 8rpx; padding: 12rpx; box-sizing: border-box; margin-top: 12rpx; }
|
||||
.reason-tip { color: #999; font-size: 24rpx; margin-top: 8rpx; }
|
||||
@@ -56,7 +56,7 @@ Page({
|
||||
},
|
||||
|
||||
// 词典等级配置
|
||||
dictLevel: 'PRIMARY',
|
||||
dictLevel: 'LEVEL1',
|
||||
dictLevelOptions: apiManager.getDictLevelOptions(),
|
||||
|
||||
// 应用信息
|
||||
@@ -213,7 +213,7 @@ Page({
|
||||
const userData = {
|
||||
isLoggedIn: app.globalData.isLoggedIn || false,
|
||||
userInfo: app.globalData.userInfo || null,
|
||||
dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
|
||||
dictLevel: app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'LEVEL1'
|
||||
}
|
||||
|
||||
console.log('更新用户信息:', userData)
|
||||
|
||||
@@ -74,12 +74,17 @@
|
||||
<t-icon slot="left-icon" name="star" size="44rpx"></t-icon>
|
||||
</t-cell>
|
||||
</navigator>
|
||||
<navigator url="/pages/order/order" class="cell-navigator">
|
||||
<t-cell title="订单记录" hover arrow>
|
||||
<t-icon slot="left-icon" name="shop" size="44rpx"></t-icon>
|
||||
</t-cell>
|
||||
</navigator>
|
||||
<t-cell title="兑换码" hover arrow bindtap="showCouponDialog">
|
||||
<t-icon slot="left-icon" name="coupon" size="44rpx"></t-icon>
|
||||
</t-cell>
|
||||
<t-cell title="消息" hover arrow>
|
||||
<!-- <t-cell title="消息" hover arrow>
|
||||
<t-icon slot="left-icon" name="notification" size="44rpx"></t-icon>
|
||||
</t-cell>
|
||||
</t-cell> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -827,10 +827,27 @@ Page({
|
||||
console.error('跳转结果页面失败:', error)
|
||||
this.setData({ isProcessing: false })
|
||||
this.resetPageState()
|
||||
wx.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
const msg = (error as any)?.message || ''
|
||||
if (typeof msg === 'string' && msg.indexOf('积分') !== -1) {
|
||||
wx.showModal({
|
||||
title: '积分不足',
|
||||
content: '您的积分不足,是否前往购买?',
|
||||
confirmText: '购买',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
wx.navigateTo({ url: '/pages/coupon/coupon' })
|
||||
} else {
|
||||
// wx.showToast({ title: '已取消', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: msg || '上传失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -412,8 +412,25 @@ class ApiManager {
|
||||
success: true
|
||||
});
|
||||
|
||||
// 直接返回响应数据,由调用方处理业务逻辑
|
||||
resolve(res.data);
|
||||
if (res.statusCode === 200) {
|
||||
// 直接返回响应数据,由调用方处理业务逻辑
|
||||
resolve(res.data);
|
||||
} else if (res.statusCode === 403) {
|
||||
const response = res.data as IApiResponse<T>
|
||||
const errorMsg = response.msg || '请求失败'
|
||||
console.error('403 错误:', errorMsg, response)
|
||||
reject(new Error(errorMsg))
|
||||
} else if (res.statusCode === 404 ) {
|
||||
const response = res.data as IApiResponse<T>
|
||||
const errorMsg = response.msg || '请求失败'
|
||||
console.error('404 错误:', errorMsg, response)
|
||||
reject(new Error(errorMsg))
|
||||
} else {
|
||||
const response = res.data as IApiResponse<T>
|
||||
const errorMsg = response.msg || '请求失败'
|
||||
console.error('微信云托管请求错误:', errorMsg, response)
|
||||
reject(new Error(errorMsg))
|
||||
}
|
||||
},
|
||||
fail: (error: any) => {
|
||||
if (showLoading) {
|
||||
@@ -1785,7 +1802,7 @@ class ApiManager {
|
||||
try {
|
||||
console.log('开始获取图片文本和评分信息', { imageId });
|
||||
const app = getApp<IAppOption>()
|
||||
const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
|
||||
const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'LEVEL1'
|
||||
const response = await this.request<{
|
||||
image_file_id: string,
|
||||
assessments: Array<{
|
||||
@@ -1925,6 +1942,28 @@ class ApiManager {
|
||||
return response.data
|
||||
}
|
||||
|
||||
async listOrders(page: number = 1, size: number = 20): Promise<{ items: Array<{ id: string; created_time: string; amount_cents: number; points?: number | null; product_id?: string | null; refund_status?: string | null }>; total: number }>{
|
||||
const response = await this.request<{ items: Array<{ id: string; created_time: string; amount_cents: number; points?: number | null; product_id?: string | null; refund_status?: string | null }>; total: number }>(`/api/v1/wxpay/order/list`, 'POST', { page, size })
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getOrderDetail(out_trade_no: string): Promise<{ out_trade_no: string; transaction_id?: string | null; trade_state: string; amount_cents: number; description: string; can_refund?: boolean | null; refund_status?: string | null; refundable_amount_cents?: number | null; refundable_amount_points?: number | null }>{
|
||||
const response = await this.request<{ out_trade_no: string; transaction_id?: string | null; trade_state: string; amount_cents: number; description: string; can_refund?: boolean | null; refund_status?: string | null; refundable_amount_cents?: number | null; refundable_amount_points?: number | null }>(`/api/v1/wxpay/order/${out_trade_no}/details`, 'GET')
|
||||
return response.data
|
||||
}
|
||||
|
||||
async refund(out_trade_no: string, reason: string, amount_cents?: number): Promise<any> {
|
||||
const payload: any = { out_trade_no, reason }
|
||||
if (typeof amount_cents === 'number') payload.amount_cents = amount_cents
|
||||
const response = await this.request<any>(`/api/v1/wxpay/refund`, 'POST', payload)
|
||||
return response.data
|
||||
}
|
||||
|
||||
async getRefundStatus(out_refund_no: string): Promise<{ out_refund_no: string; status: string }>{
|
||||
const response = await this.request<{ out_refund_no: string; status: string }>(`/api/v1/wxpay/refund/${out_refund_no}`, 'GET')
|
||||
return response.data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
||||
Reference in New Issue
Block a user