FEAT => 本次更新项目为:
充值记录完成
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Card, Button, Toast, NavBar, Tabs } from "antd-mobile";
|
import { Card, Button, Toast, Tabs } from "antd-mobile";
|
||||||
import { useUserStore } from "@/store/module/user";
|
import { useUserStore } from "@/store/module/user";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
import {
|
import {
|
||||||
@@ -338,7 +338,7 @@ const Recharge: React.FC = () => {
|
|||||||
right={
|
right={
|
||||||
<div
|
<div
|
||||||
className={style["record-btn"]}
|
className={style["record-btn"]}
|
||||||
onClick={() => navigate("/mine/consumption-records")}
|
onClick={() => navigate("/recharge/order")}
|
||||||
>
|
>
|
||||||
<ClockCircleOutlined />
|
<ClockCircleOutlined />
|
||||||
记录
|
记录
|
||||||
197
nkebao/src/pages/mobile/mine/recharge/order/api.ts
Normal file
197
nkebao/src/pages/mobile/mine/recharge/order/api.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import {
|
||||||
|
RechargeOrdersResponse,
|
||||||
|
RechargeOrderDetail,
|
||||||
|
RechargeOrderParams,
|
||||||
|
} from "./data";
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
const mockOrders = [
|
||||||
|
{
|
||||||
|
id: "1",
|
||||||
|
orderNo: "RC20241201001",
|
||||||
|
amount: 100.0,
|
||||||
|
paymentMethod: "wechat",
|
||||||
|
status: "success" as const,
|
||||||
|
createTime: "2024-12-01T10:30:00Z",
|
||||||
|
payTime: "2024-12-01T10:32:15Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 150.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "2",
|
||||||
|
orderNo: "RC20241201002",
|
||||||
|
amount: 200.0,
|
||||||
|
paymentMethod: "alipay",
|
||||||
|
status: "pending" as const,
|
||||||
|
createTime: "2024-12-01T14:20:00Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 350.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3",
|
||||||
|
orderNo: "RC20241130001",
|
||||||
|
amount: 50.0,
|
||||||
|
paymentMethod: "bank",
|
||||||
|
status: "success" as const,
|
||||||
|
createTime: "2024-11-30T09:15:00Z",
|
||||||
|
payTime: "2024-11-30T09:18:30Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 50.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4",
|
||||||
|
orderNo: "RC20241129001",
|
||||||
|
amount: 300.0,
|
||||||
|
paymentMethod: "wechat",
|
||||||
|
status: "failed" as const,
|
||||||
|
createTime: "2024-11-29T16:45:00Z",
|
||||||
|
description: "账户充值",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "5",
|
||||||
|
orderNo: "RC20241128001",
|
||||||
|
amount: 150.0,
|
||||||
|
paymentMethod: "alipay",
|
||||||
|
status: "cancelled" as const,
|
||||||
|
createTime: "2024-11-28T11:20:00Z",
|
||||||
|
description: "账户充值",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "6",
|
||||||
|
orderNo: "RC20241127001",
|
||||||
|
amount: 80.0,
|
||||||
|
paymentMethod: "wechat",
|
||||||
|
status: "success" as const,
|
||||||
|
createTime: "2024-11-27T13:10:00Z",
|
||||||
|
payTime: "2024-11-27T13:12:45Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 80.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "7",
|
||||||
|
orderNo: "RC20241126001",
|
||||||
|
amount: 120.0,
|
||||||
|
paymentMethod: "bank",
|
||||||
|
status: "success" as const,
|
||||||
|
createTime: "2024-11-26T08:30:00Z",
|
||||||
|
payTime: "2024-11-26T08:33:20Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 120.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8",
|
||||||
|
orderNo: "RC20241125001",
|
||||||
|
amount: 250.0,
|
||||||
|
paymentMethod: "alipay",
|
||||||
|
status: "pending" as const,
|
||||||
|
createTime: "2024-11-25T15:45:00Z",
|
||||||
|
description: "账户充值",
|
||||||
|
balance: 370.0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟延迟
|
||||||
|
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
// 获取充值记录列表
|
||||||
|
export async function getRechargeOrders(
|
||||||
|
params: RechargeOrderParams,
|
||||||
|
): Promise<RechargeOrdersResponse> {
|
||||||
|
await delay(800); // 模拟网络延迟
|
||||||
|
|
||||||
|
let filteredOrders = [...mockOrders];
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
if (params.status && params.status !== "all") {
|
||||||
|
filteredOrders = filteredOrders.filter(
|
||||||
|
order => order.status === params.status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间筛选
|
||||||
|
if (params.startTime) {
|
||||||
|
filteredOrders = filteredOrders.filter(
|
||||||
|
order => new Date(order.createTime) >= new Date(params.startTime!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (params.endTime) {
|
||||||
|
filteredOrders = filteredOrders.filter(
|
||||||
|
order => new Date(order.createTime) <= new Date(params.endTime!),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const startIndex = (params.page - 1) * params.limit;
|
||||||
|
const endIndex = startIndex + params.limit;
|
||||||
|
const paginatedOrders = filteredOrders.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: paginatedOrders,
|
||||||
|
total: filteredOrders.length,
|
||||||
|
page: params.page,
|
||||||
|
limit: params.limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取充值记录详情
|
||||||
|
export async function getRechargeOrderDetail(
|
||||||
|
id: string,
|
||||||
|
): Promise<RechargeOrderDetail> {
|
||||||
|
await delay(500);
|
||||||
|
|
||||||
|
const order = mockOrders.find(o => o.id === id);
|
||||||
|
if (!order) {
|
||||||
|
throw new Error("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...order,
|
||||||
|
paymentChannel:
|
||||||
|
order.paymentMethod === "wechat"
|
||||||
|
? "微信支付"
|
||||||
|
: order.paymentMethod === "alipay"
|
||||||
|
? "支付宝"
|
||||||
|
: "银行转账",
|
||||||
|
transactionId: `TX${order.orderNo}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消充值订单
|
||||||
|
export async function cancelRechargeOrder(id: string): Promise<void> {
|
||||||
|
await delay(1000);
|
||||||
|
|
||||||
|
const orderIndex = mockOrders.findIndex(o => o.id === id);
|
||||||
|
if (orderIndex === -1) {
|
||||||
|
throw new Error("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mockOrders[orderIndex].status !== "pending") {
|
||||||
|
throw new Error("只能取消处理中的订单");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟更新订单状态
|
||||||
|
(mockOrders[orderIndex] as any).status = "cancelled";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申请退款
|
||||||
|
export async function refundRechargeOrder(
|
||||||
|
id: string,
|
||||||
|
reason: string,
|
||||||
|
): Promise<void> {
|
||||||
|
await delay(1200);
|
||||||
|
|
||||||
|
const orderIndex = mockOrders.findIndex(o => o.id === id);
|
||||||
|
if (orderIndex === -1) {
|
||||||
|
throw new Error("订单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mockOrders[orderIndex].status !== "success") {
|
||||||
|
throw new Error("只能对成功的订单申请退款");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟添加退款信息
|
||||||
|
const order = mockOrders[orderIndex];
|
||||||
|
(order as any).refundAmount = order.amount;
|
||||||
|
(order as any).refundTime = new Date().toISOString();
|
||||||
|
(order as any).refundReason = reason;
|
||||||
|
}
|
||||||
40
nkebao/src/pages/mobile/mine/recharge/order/data.ts
Normal file
40
nkebao/src/pages/mobile/mine/recharge/order/data.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// 充值记录类型定义
|
||||||
|
export interface RechargeOrder {
|
||||||
|
id: string;
|
||||||
|
orderNo: string;
|
||||||
|
amount: number;
|
||||||
|
paymentMethod: string;
|
||||||
|
status: "success" | "pending" | "failed" | "cancelled";
|
||||||
|
createTime: string;
|
||||||
|
payTime?: string;
|
||||||
|
description?: string;
|
||||||
|
remark?: string;
|
||||||
|
operator?: string;
|
||||||
|
balance?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API响应类型
|
||||||
|
export interface RechargeOrdersResponse {
|
||||||
|
list: RechargeOrder[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 充值记录详情
|
||||||
|
export interface RechargeOrderDetail extends RechargeOrder {
|
||||||
|
paymentChannel?: string;
|
||||||
|
transactionId?: string;
|
||||||
|
refundAmount?: number;
|
||||||
|
refundTime?: string;
|
||||||
|
refundReason?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
export interface RechargeOrderParams {
|
||||||
|
page: number;
|
||||||
|
limit: number;
|
||||||
|
status?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
}
|
||||||
242
nkebao/src/pages/mobile/mine/recharge/order/index.module.scss
Normal file
242
nkebao/src/pages/mobile/mine/recharge/order/index.module.scss
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
.recharge-orders-page {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
.orders-list {
|
||||||
|
.order-card {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.order-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
|
.order-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.order-no {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-time {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-amount {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
.amount-text {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #52c41a;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-details {
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.method-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-info {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-actions {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #0958d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
background: #ff4d4f;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #cf1322;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
color: #d9d9d9;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
color: #1677ff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f0f8ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
.filter-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.filter-tab {
|
||||||
|
flex: 1;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
background: #fff;
|
||||||
|
color: #666;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.active) {
|
||||||
|
border-color: #1677ff;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
344
nkebao/src/pages/mobile/mine/recharge/order/index.tsx
Normal file
344
nkebao/src/pages/mobile/mine/recharge/order/index.tsx
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Card, SpinLoading, Empty, Toast, Dialog } from "antd-mobile";
|
||||||
|
import {
|
||||||
|
WalletOutlined,
|
||||||
|
ClockCircleOutlined,
|
||||||
|
WechatOutlined,
|
||||||
|
AlipayCircleOutlined,
|
||||||
|
BankOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import NavCommon from "@/components/NavCommon";
|
||||||
|
import Layout from "@/components/Layout/Layout";
|
||||||
|
import {
|
||||||
|
getRechargeOrders,
|
||||||
|
cancelRechargeOrder,
|
||||||
|
refundRechargeOrder,
|
||||||
|
} from "./api";
|
||||||
|
import { RechargeOrder } from "./data";
|
||||||
|
import style from "./index.module.scss";
|
||||||
|
|
||||||
|
const RechargeOrders: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [orders, setOrders] = useState<RechargeOrder[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [statusFilter, setStatusFilter] = useState<string>("all");
|
||||||
|
|
||||||
|
const loadOrders = async (reset = false) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const currentPage = reset ? 1 : page;
|
||||||
|
const params = {
|
||||||
|
page: currentPage,
|
||||||
|
limit: 20,
|
||||||
|
...(statusFilter !== "all" && { status: statusFilter }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await getRechargeOrders(params);
|
||||||
|
const newOrders = response.list || [];
|
||||||
|
setOrders(prev => (reset ? newOrders : [...prev, ...newOrders]));
|
||||||
|
setHasMore(newOrders.length === 20);
|
||||||
|
if (reset) setPage(1);
|
||||||
|
else setPage(currentPage + 1);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("加载充值记录失败:", error);
|
||||||
|
Toast.show({ content: "加载失败,请重试", position: "top" });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化加载
|
||||||
|
useEffect(() => {
|
||||||
|
loadOrders(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 筛选条件变化时重新加载
|
||||||
|
const handleFilterChange = (newStatus: string) => {
|
||||||
|
setStatusFilter(newStatus);
|
||||||
|
setPage(1);
|
||||||
|
setOrders([]);
|
||||||
|
loadOrders(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusText = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "success":
|
||||||
|
return "充值成功";
|
||||||
|
case "pending":
|
||||||
|
return "处理中";
|
||||||
|
case "failed":
|
||||||
|
return "充值失败";
|
||||||
|
case "cancelled":
|
||||||
|
return "已取消";
|
||||||
|
default:
|
||||||
|
return "未知状态";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case "success":
|
||||||
|
return "#52c41a";
|
||||||
|
case "pending":
|
||||||
|
return "#faad14";
|
||||||
|
case "failed":
|
||||||
|
return "#ff4d4f";
|
||||||
|
case "cancelled":
|
||||||
|
return "#999";
|
||||||
|
default:
|
||||||
|
return "#666";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPaymentMethodIcon = (method: string) => {
|
||||||
|
switch (method.toLowerCase()) {
|
||||||
|
case "wechat":
|
||||||
|
return <WechatOutlined style={{ color: "#07c160" }} />;
|
||||||
|
case "alipay":
|
||||||
|
return <AlipayCircleOutlined style={{ color: "#1677ff" }} />;
|
||||||
|
case "bank":
|
||||||
|
return <BankOutlined style={{ color: "#722ed1" }} />;
|
||||||
|
default:
|
||||||
|
return <WalletOutlined style={{ color: "#666" }} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPaymentMethodColor = (method: string) => {
|
||||||
|
switch (method.toLowerCase()) {
|
||||||
|
case "wechat":
|
||||||
|
return "#07c160";
|
||||||
|
case "alipay":
|
||||||
|
return "#1677ff";
|
||||||
|
case "bank":
|
||||||
|
return "#722ed1";
|
||||||
|
default:
|
||||||
|
return "#666";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (timeStr: string) => {
|
||||||
|
const date = new Date(timeStr);
|
||||||
|
const now = new Date();
|
||||||
|
const diff = now.getTime() - date.getTime();
|
||||||
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (days === 0) {
|
||||||
|
return date.toLocaleTimeString("zh-CN", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
});
|
||||||
|
} else if (days === 1) {
|
||||||
|
return (
|
||||||
|
"昨天 " +
|
||||||
|
date.toLocaleTimeString("zh-CN", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (days < 7) {
|
||||||
|
return `${days}天前`;
|
||||||
|
} else {
|
||||||
|
return date.toLocaleDateString("zh-CN");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelOrder = async (orderId: string) => {
|
||||||
|
const result = await Dialog.confirm({
|
||||||
|
content: "确定要取消这个充值订单吗?",
|
||||||
|
confirmText: "确定取消",
|
||||||
|
cancelText: "再想想",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
try {
|
||||||
|
await cancelRechargeOrder(orderId);
|
||||||
|
Toast.show({ content: "订单已取消", position: "top" });
|
||||||
|
loadOrders(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("取消订单失败:", error);
|
||||||
|
Toast.show({ content: "取消失败,请重试", position: "top" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefundOrder = async (orderId: string) => {
|
||||||
|
const result = await Dialog.confirm({
|
||||||
|
content: "确定要申请退款吗?退款将在1-3个工作日内处理。",
|
||||||
|
confirmText: "申请退款",
|
||||||
|
cancelText: "取消",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
try {
|
||||||
|
await refundRechargeOrder(orderId, "用户主动申请退款");
|
||||||
|
Toast.show({ content: "退款申请已提交", position: "top" });
|
||||||
|
loadOrders(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("申请退款失败:", error);
|
||||||
|
Toast.show({ content: "申请失败,请重试", position: "top" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderOrderItem = (order: RechargeOrder) => (
|
||||||
|
<Card key={order.id} className={style["order-card"]}>
|
||||||
|
<div className={style["order-header"]}>
|
||||||
|
<div className={style["order-info"]}>
|
||||||
|
<div className={style["order-no"]}>订单号:{order.orderNo}</div>
|
||||||
|
<div className={style["order-time"]}>
|
||||||
|
<ClockCircleOutlined style={{ fontSize: 12 }} />
|
||||||
|
{formatTime(order.createTime)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={style["order-amount"]}>
|
||||||
|
<div className={style["amount-text"]}>
|
||||||
|
¥{order.amount.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={style["status-tag"]}
|
||||||
|
style={{
|
||||||
|
backgroundColor: `${getStatusColor(order.status)}20`,
|
||||||
|
color: getStatusColor(order.status),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getStatusText(order.status)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style["order-details"]}>
|
||||||
|
<div className={style["payment-method"]}>
|
||||||
|
<div
|
||||||
|
className={style["method-icon"]}
|
||||||
|
style={{
|
||||||
|
backgroundColor: getPaymentMethodColor(order.paymentMethod),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getPaymentMethodIcon(order.paymentMethod)}
|
||||||
|
</div>
|
||||||
|
<div className={style["method-text"]}>{order.paymentMethod}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{order.description && (
|
||||||
|
<div className={style["detail-row"]}>
|
||||||
|
<span className={style["label"]}>备注</span>
|
||||||
|
<span className={style["value"]}>{order.description}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{order.payTime && (
|
||||||
|
<div className={style["detail-row"]}>
|
||||||
|
<span className={style["label"]}>支付时间</span>
|
||||||
|
<span className={style["value"]}>{formatTime(order.payTime)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{order.balance !== undefined && (
|
||||||
|
<div className={style["balance-info"]}>
|
||||||
|
充值后余额: ¥{order.balance.toFixed(2)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{order.status === "pending" && (
|
||||||
|
<div className={style["order-actions"]}>
|
||||||
|
<button
|
||||||
|
className={`${style["action-btn"]} ${style["danger"]}`}
|
||||||
|
onClick={() => handleCancelOrder(order.id)}
|
||||||
|
>
|
||||||
|
取消订单
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{order.status === "success" && (
|
||||||
|
<div className={style["order-actions"]}>
|
||||||
|
<button
|
||||||
|
className={`${style["action-btn"]} ${style["secondary"]}`}
|
||||||
|
onClick={() => navigate(`/recharge/order/${order.id}`)}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${style["action-btn"]} ${style["primary"]}`}
|
||||||
|
onClick={() => handleRefundOrder(order.id)}
|
||||||
|
>
|
||||||
|
申请退款
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{order.status === "failed" && (
|
||||||
|
<div className={style["order-actions"]}>
|
||||||
|
<button
|
||||||
|
className={`${style["action-btn"]} ${style["primary"]}`}
|
||||||
|
onClick={() => navigate("/recharge")}
|
||||||
|
>
|
||||||
|
重新充值
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterTabs = [
|
||||||
|
{ key: "all", label: "全部" },
|
||||||
|
{ key: "success", label: "成功" },
|
||||||
|
{ key: "pending", label: "处理中" },
|
||||||
|
{ key: "failed", label: "失败" },
|
||||||
|
{ key: "cancelled", label: "已取消" },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
header={<NavCommon title="充值记录" />}
|
||||||
|
loading={loading && page === 1}
|
||||||
|
>
|
||||||
|
<div className={style["recharge-orders-page"]}>
|
||||||
|
<div className={style["filter-bar"]}>
|
||||||
|
<div className={style["filter-tabs"]}>
|
||||||
|
{filterTabs.map(tab => (
|
||||||
|
<button
|
||||||
|
key={tab.key}
|
||||||
|
className={`${style["filter-tab"]} ${
|
||||||
|
statusFilter === tab.key ? style["active"] : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleFilterChange(tab.key)}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{orders.length === 0 && !loading ? (
|
||||||
|
<Empty
|
||||||
|
className={style["empty-state"]}
|
||||||
|
description="暂无充值记录"
|
||||||
|
image={<WalletOutlined className={style["empty-icon"]} />}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className={style["orders-list"]}>
|
||||||
|
{orders.map(renderOrderItem)}
|
||||||
|
{loading && page > 1 && (
|
||||||
|
<div className={style["loading-container"]}>
|
||||||
|
<SpinLoading color="primary" />
|
||||||
|
<div className={style["loading-text"]}>加载中...</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!loading && hasMore && (
|
||||||
|
<div className={style["load-more"]} onClick={() => loadOrders()}>
|
||||||
|
加载更多
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RechargeOrders;
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { Card } from "antd-mobile";
|
||||||
import { NavBar, Card } from "antd-mobile";
|
|
||||||
import Layout from "@/components/Layout/Layout";
|
import Layout from "@/components/Layout/Layout";
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
import NavCommon from "@/components/NavCommon";
|
||||||
|
|
||||||
const Privacy: React.FC = () => {
|
const Privacy: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout header={<NavCommon title="用户隐私协议" />}>
|
||||||
header={
|
|
||||||
<NavBar onBack={() => navigate(-1)} style={{ background: "#fff" }}>
|
|
||||||
<span style={{ color: "var(--primary-color)", fontWeight: 600 }}>
|
|
||||||
用户隐私协议
|
|
||||||
</span>
|
|
||||||
</NavBar>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className={style["setting-page"]}>
|
<div className={style["setting-page"]}>
|
||||||
<Card className={style["privacy-card"]}>
|
<Card className={style["privacy-card"]}>
|
||||||
<div className={style["privacy-content"]}>
|
<div className={style["privacy-content"]}>
|
||||||
<h2>用户隐私协议</h2>
|
<h2>用户隐私协议</h2>
|
||||||
<p className={style["update-time"]}>更新时间:2024年12月1日</p>
|
<p className={style["update-time"]}>更新时间:2025年8月1日</p>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>1. 信息收集</h3>
|
<h3>1. 信息收集</h3>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import TrafficPoolDetail from "@/pages/mobile/mine/traffic-pool/detail/index";
|
|||||||
import WechatAccounts from "@/pages/mobile/mine/wechat-accounts/list/index";
|
import WechatAccounts from "@/pages/mobile/mine/wechat-accounts/list/index";
|
||||||
import WechatAccountDetail from "@/pages/mobile/mine/wechat-accounts/detail/index";
|
import WechatAccountDetail from "@/pages/mobile/mine/wechat-accounts/detail/index";
|
||||||
import Recharge from "@/pages/mobile/mine/recharge/index";
|
import Recharge from "@/pages/mobile/mine/recharge/index";
|
||||||
|
import RechargeOrder from "@/pages/mobile/mine/recharge/order/index";
|
||||||
import Setting from "@/pages/mobile/mine/setting/index";
|
import Setting from "@/pages/mobile/mine/setting/index";
|
||||||
import SecuritySetting from "@/pages/mobile/mine/setting/SecuritySetting";
|
import SecuritySetting from "@/pages/mobile/mine/setting/SecuritySetting";
|
||||||
import About from "@/pages/mobile/mine/setting/About";
|
import About from "@/pages/mobile/mine/setting/About";
|
||||||
@@ -54,6 +55,11 @@ const routes = [
|
|||||||
element: <Recharge />,
|
element: <Recharge />,
|
||||||
auth: true,
|
auth: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/recharge/order",
|
||||||
|
element: <RechargeOrder />,
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
element: <Setting />,
|
element: <Setting />,
|
||||||
|
|||||||
Reference in New Issue
Block a user