Initial commit

This commit is contained in:
sjk
2025-11-17 13:32:54 +08:00
commit e788eab6eb
1659 changed files with 171560 additions and 0 deletions

View File

@@ -0,0 +1,330 @@
// pages/refund/refund-detail/index.js
import Toast from 'tdesign-miniprogram/toast/index';
import { config } from '../../../config/index';
Page({
data: {
refundDetail: null,
loading: true,
error: null
},
onLoad(options) {
console.log('[退款详情] 页面加载', options);
this.refundId = options.id;
if (!this.refundId) {
this.setData({
error: '退款记录ID不能为空',
loading: false
});
return;
}
this.loadRefundDetail();
},
onShow() {
// 页面显示时刷新数据
if (this.refundId) {
this.loadRefundDetail();
}
},
onPullDownRefresh() {
this.loadRefundDetail();
},
// 加载退款详情
loadRefundDetail() {
this.setData({
loading: true,
error: null
});
let token = wx.getStorageSync('token');
if (!token) {
// 自动设置测试token
const testToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjozNywiZXhwIjoxNzYxNjE5NjAyLCJpYXQiOjE3NjE1MzMyMDJ9.xyZLQbwhYyUDiF9_UOCX39nVwYOHvvd6d4TqwFnT_yg';
wx.setStorageSync('token', testToken);
token = testToken;
console.log('[退款详情] 自动设置测试token');
}
console.log('[退款详情] 请求退款详情', { refundId: this.refundId });
wx.request({
url: `${config.apiBase}/refunds/${this.refundId}`,
method: 'GET',
header: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
success: (res) => {
console.log('[退款详情] API响应', res);
if (res.statusCode === 200 && res.data.code === 200) {
const refundDetail = this.processRefundDetail(res.data.data);
this.setData({
refundDetail: refundDetail,
loading: false
});
wx.stopPullDownRefresh();
} else {
console.error('[退款详情] API错误', res.data);
this.setData({
error: res.data.message || '获取退款详情失败',
loading: false
});
}
},
fail: (error) => {
console.error('[退款详情] 网络错误', error);
this.setData({
error: '网络错误',
loading: false
});
}
});
},
// 处理退款详情数据
processRefundDetail(data) {
// 状态映射 - 后端返回的是数字状态
const statusMap = {
1: {
text: '待审核',
desc: '您的退款申请已提交,请耐心等待审核',
icon: '⏳'
},
2: {
text: '审核通过',
desc: '退款申请已通过,正在处理退款',
icon: '✓'
},
3: {
text: '审核拒绝',
desc: '退款申请被拒绝,如有疑问请联系客服',
icon: '✗'
},
4: {
text: '退款中',
desc: '退款正在处理中,请耐心等待',
icon: '⏳'
},
5: {
text: '退款成功',
desc: '退款已完成,请查看您的账户余额',
icon: '✓'
},
6: {
text: '退款失败',
desc: '退款处理失败,如有疑问请联系客服',
icon: '✗'
}
};
const statusInfo = statusMap[data.status] || {
text: '未知状态',
desc: '',
icon: '?'
};
// 格式化金额 - 添加安全检查,将分转换为元
const refundAmount = ((data.refund_amount || 0) / 100).toFixed(2);
// 订单金额从关联的订单对象中获取,将分转换为元
const orderAmount = ((data.order && data.order.total_amount ? data.order.total_amount : 0) / 100).toFixed(2);
// 格式化时间
const createdAt = this.formatTime(data.created_at);
const processedAt = data.processed_at ? this.formatTime(data.processed_at) : null;
const completedAt = data.completed_at ? this.formatTime(data.completed_at) : null;
// 退款方式
const refundMethodMap = {
'wechat': '微信支付',
'alipay': '支付宝',
'bank': '银行卡',
'balance': '账户余额'
};
// 订单状态
const orderStatusMap = {
1: '待付款',
2: '待发货',
3: '待发货',
4: '已发货',
5: '待收货',
6: '已完成',
7: '已取消',
8: '退货中',
9: '已退款'
};
// 处理退款日志
const logs = (data.logs || []).map(log => ({
id: log.id,
action: log.action,
remark: log.remark,
createdAt: this.formatTime(log.created_at)
})).reverse(); // 倒序显示,最新的在前面
// 生成操作按钮
const actions = this.generateActions(data.status);
return {
id: data.id,
orderNo: data.order_no,
refundAmount: refundAmount,
orderAmount: orderAmount,
reason: data.reason,
adminRemark: data.admin_remark,
status: data.status,
statusText: statusInfo.text,
statusDesc: statusInfo.desc,
statusIcon: statusInfo.icon,
createdAt: createdAt,
processedAt: processedAt,
completedAt: completedAt,
refundMethod: refundMethodMap[data.refund_method] || '未知方式',
orderStatusText: orderStatusMap[data.order_status] || '未知状态',
logs: logs,
actions: actions
};
},
// 生成操作按钮
generateActions(status) {
const actions = [];
switch (status) {
case 'pending':
actions.push({ type: 'cancel', text: '取消申请' });
break;
case 'rejected':
actions.push({ type: 'reapply', text: '重新申请' });
break;
}
return actions;
},
// 格式化时间
formatTime(timeStr) {
if (!timeStr) return '';
const date = new Date(timeStr);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hour = String(date.getHours()).padStart(2, '0');
const minute = String(date.getMinutes()).padStart(2, '0');
const second = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
},
// 订单点击
onOrderTap() {
const { refundDetail } = this.data;
if (refundDetail && refundDetail.orderNo) {
wx.navigateTo({
url: `/pages/order/order-detail/index?orderNo=${refundDetail.orderNo}`
});
}
},
// 操作按钮点击
onActionTap(e) {
const action = e.currentTarget.dataset.action;
console.log('[退款详情] 操作按钮点击', action);
switch (action) {
case 'cancel':
this.cancelRefund();
break;
case 'reapply':
this.reapplyRefund();
break;
}
},
// 取消退款申请
cancelRefund() {
wx.showModal({
title: '取消申请',
content: '确定要取消这个退款申请吗?',
success: (res) => {
if (res.confirm) {
this.performCancelRefund();
}
}
});
},
// 执行取消退款
performCancelRefund() {
const token = wx.getStorageSync('token');
wx.showLoading({ title: '处理中...' });
wx.request({
url: `${config.apiBase}/refunds/${this.refundId}/cancel`,
method: 'PUT',
header: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
success: (res) => {
wx.hideLoading();
if (res.statusCode === 200 && res.data.code === 200) {
Toast({
context: this,
selector: '#t-toast',
message: '取消成功',
theme: 'success',
});
// 刷新详情
setTimeout(() => {
this.loadRefundDetail();
}, 1000);
} else {
Toast({
context: this,
selector: '#t-toast',
message: res.data.message || '取消失败',
theme: 'error',
});
}
},
fail: (error) => {
wx.hideLoading();
console.error('[退款详情] 取消退款失败', error);
Toast({
context: this,
selector: '#t-toast',
message: '网络错误',
theme: 'error',
});
}
});
},
// 重新申请退款
reapplyRefund() {
const { refundDetail } = this.data;
if (refundDetail) {
wx.navigateTo({
url: `/pages/refund/refund-apply/index?reapply=1&orderNo=${refundDetail.orderNo}&refundId=${this.refundId}`
});
}
},
// 重试
onRetry() {
this.loadRefundDetail();
}
});

View File

@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "退款详情",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50,
"usingComponents": {
"t-toast": "tdesign-miniprogram/toast/toast"
}
}

View File

@@ -0,0 +1,115 @@
<!--pages/refund/refund-detail/index.wxml-->
<view class="refund-detail" wx:if="{{refundDetail}}">
<!-- 退款状态 -->
<view class="status-section">
<view class="status-icon {{refundDetail.status}}">
<text class="icon-text">{{refundDetail.statusIcon}}</text>
</view>
<view class="status-info">
<text class="status-text">{{refundDetail.statusText}}</text>
<text class="status-desc">{{refundDetail.statusDesc}}</text>
</view>
</view>
<!-- 退款信息 -->
<view class="info-section">
<view class="section-title">退款信息</view>
<view class="info-item">
<text class="info-label">退款金额</text>
<text class="info-value amount">¥{{refundDetail.refundAmount}}</text>
</view>
<view class="info-item">
<text class="info-label">申请时间</text>
<text class="info-value">{{refundDetail.createdAt}}</text>
</view>
<view class="info-item" wx:if="{{refundDetail.processedAt}}">
<text class="info-label">处理时间</text>
<text class="info-value">{{refundDetail.processedAt}}</text>
</view>
<view class="info-item" wx:if="{{refundDetail.completedAt}}">
<text class="info-label">完成时间</text>
<text class="info-value">{{refundDetail.completedAt}}</text>
</view>
<view class="info-item">
<text class="info-label">退款方式</text>
<text class="info-value">{{refundDetail.refundMethod}}</text>
</view>
</view>
<!-- 订单信息 -->
<view class="info-section">
<view class="section-title">相关订单</view>
<view class="order-card" bindtap="onOrderTap">
<view class="order-header">
<text class="order-no">订单号:{{refundDetail.orderNo}}</text>
<text class="order-status">{{refundDetail.orderStatusText}}</text>
</view>
<view class="order-amount">
<text class="amount-label">订单金额:</text>
<text class="amount-value">¥{{refundDetail.orderAmount}}</text>
</view>
</view>
</view>
<!-- 退款原因 -->
<view class="info-section" wx:if="{{refundDetail.reason}}">
<view class="section-title">退款原因</view>
<view class="reason-content">
<text>{{refundDetail.reason}}</text>
</view>
</view>
<!-- 处理备注 -->
<view class="info-section" wx:if="{{refundDetail.adminRemark}}">
<view class="section-title">处理备注</view>
<view class="remark-content">
<text>{{refundDetail.adminRemark}}</text>
</view>
</view>
<!-- 退款进度 -->
<view class="info-section" wx:if="{{refundDetail.logs && refundDetail.logs.length > 0}}">
<view class="section-title">退款进度</view>
<view class="progress-list">
<view
class="progress-item {{index === 0 ? 'current' : ''}}"
wx:for="{{refundDetail.logs}}"
wx:key="id"
>
<view class="progress-dot"></view>
<view class="progress-content">
<view class="progress-title">{{item.action}}</view>
<view class="progress-time">{{item.createdAt}}</view>
<view class="progress-remark" wx:if="{{item.remark}}">{{item.remark}}</view>
</view>
</view>
</view>
</view>
<!-- 操作按钮 -->
<view class="action-section" wx:if="{{refundDetail.actions && refundDetail.actions.length > 0}}">
<view
class="action-btn {{action.type}}"
wx:for="{{refundDetail.actions}}"
wx:key="type"
data-action="{{action.type}}"
bindtap="onActionTap"
>
{{action.text}}
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-state" wx:if="{{loading}}">
<text>加载中...</text>
</view>
<!-- 错误状态 -->
<view class="error-state" wx:if="{{error}}">
<text>{{error}}</text>
<button class="retry-btn" bindtap="onRetry">重试</button>
</view>
<!-- Toast 组件 -->
<t-toast id="t-toast" />

View File

@@ -0,0 +1,299 @@
/* pages/refund/refund-detail/index.wxss */
.refund-detail {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 120rpx;
}
/* 状态部分 */
.status-section {
background-color: #fff;
padding: 48rpx 32rpx;
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.status-icon {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 32rpx;
}
.status-icon.pending {
background-color: #fff3cd;
}
.status-icon.approved {
background-color: #d1ecf1;
}
.status-icon.rejected {
background-color: #f8d7da;
}
.status-icon.completed {
background-color: #d4edda;
}
.icon-text {
font-size: 32rpx;
font-weight: 600;
}
.status-icon.pending .icon-text {
color: #856404;
}
.status-icon.approved .icon-text {
color: #0c5460;
}
.status-icon.rejected .icon-text {
color: #721c24;
}
.status-icon.completed .icon-text {
color: #155724;
}
.status-info {
flex: 1;
}
.status-text {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
}
.status-desc {
font-size: 26rpx;
color: #666;
}
/* 信息部分 */
.info-section {
background-color: #fff;
margin-bottom: 24rpx;
padding: 32rpx;
}
.section-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
padding-bottom: 16rpx;
border-bottom: 1rpx solid #eee;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #666;
}
.info-value {
font-size: 28rpx;
color: #333;
}
.info-value.amount {
color: #ff6b35;
font-weight: 600;
font-size: 32rpx;
}
/* 订单卡片 */
.order-card {
border: 1rpx solid #eee;
border-radius: 12rpx;
padding: 24rpx;
background-color: #fafafa;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.order-no {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.order-status {
font-size: 24rpx;
color: #666;
padding: 4rpx 12rpx;
background-color: #e9ecef;
border-radius: 6rpx;
}
.order-amount {
display: flex;
align-items: center;
}
.amount-label {
font-size: 26rpx;
color: #666;
margin-right: 16rpx;
}
.amount-value {
font-size: 28rpx;
color: #ff6b35;
font-weight: 600;
}
/* 原因和备注内容 */
.reason-content,
.remark-content {
padding: 24rpx;
background-color: #f8f9fa;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
/* 进度列表 */
.progress-list {
position: relative;
}
.progress-item {
display: flex;
padding: 24rpx 0;
position: relative;
}
.progress-item:not(:last-child)::after {
content: '';
position: absolute;
left: 15rpx;
top: 60rpx;
bottom: -24rpx;
width: 2rpx;
background-color: #dee2e6;
}
.progress-item.current .progress-dot {
background-color: #ff6b35;
border-color: #ff6b35;
}
.progress-item.current::after {
background-color: #ff6b35;
}
.progress-dot {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
background-color: #dee2e6;
border: 4rpx solid #dee2e6;
margin-right: 24rpx;
flex-shrink: 0;
margin-top: 8rpx;
}
.progress-content {
flex: 1;
}
.progress-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-bottom: 8rpx;
}
.progress-time {
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
}
.progress-remark {
font-size: 26rpx;
color: #666;
line-height: 1.5;
}
/* 操作按钮 */
.action-section {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 24rpx 32rpx;
border-top: 1rpx solid #eee;
display: flex;
gap: 24rpx;
}
.action-btn {
flex: 1;
padding: 24rpx 0;
text-align: center;
border-radius: 12rpx;
font-size: 28rpx;
font-weight: 500;
}
.action-btn.primary {
background-color: #ff6b35;
color: #fff;
}
.action-btn.secondary {
background-color: #f8f9fa;
color: #666;
border: 1rpx solid #dee2e6;
}
/* 加载和错误状态 */
.loading-state,
.error-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 32rpx;
font-size: 28rpx;
color: #999;
}
.retry-btn {
margin-top: 32rpx;
padding: 16rpx 32rpx;
background-color: #ff6b35;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 26rpx;
}