From e9cb65b38848ff04f86cd1dc8bfa50a659514a26 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Thu, 11 Jun 2026 20:35:06 -0700 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=8A=9F=E8=83=BD(#25):=20=E6=8B=86?= =?UTF-8?q?=E5=88=86=E7=94=A8=E6=88=B7=E4=B8=AD=E5=BF=83=E6=8F=90=E7=8E=B0?= =?UTF-8?q?=E9=80=80=E6=AC=BE=E8=AE=B0=E5=BD=95=E9=A1=B5=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../assets/locales/en-US/affiliate.json | 61 ++- .../assets/locales/zh-CN/affiliate.json | 61 ++- .../src/sections/user/affiliate/index.tsx | 175 ++------ .../affiliate/withdrawal-records.test.tsx | 254 +++++++++++ .../user/affiliate/withdrawal-records.tsx | 410 ++++++++++++++++++ .../ui/src/composed/pro-list/pro-list.tsx | 32 +- packages/ui/src/services/user/typings.d.ts | 25 ++ packages/ui/src/services/user/user.ts | 21 +- 8 files changed, 881 insertions(+), 158 deletions(-) create mode 100644 apps/user/src/sections/user/affiliate/withdrawal-records.test.tsx create mode 100644 apps/user/src/sections/user/affiliate/withdrawal-records.tsx diff --git a/apps/user/public/assets/locales/en-US/affiliate.json b/apps/user/public/assets/locales/en-US/affiliate.json index a17fcb8..5952c1e 100644 --- a/apps/user/public/assets/locales/en-US/affiliate.json +++ b/apps/user/public/assets/locales/en-US/affiliate.json @@ -33,5 +33,64 @@ "pending": "Pending Review", "rejected": "Rejected" }, - "withdrawSuccess": "Withdrawal request submitted" + "withdrawSuccess": "Withdrawal request submitted", + "withdrawal": { + "amount": "Amount", + "amountHint": "Available", + "amountPlaceholder": "Enter withdrawal amount", + "apply": "Apply for Withdrawal", + "applyDescription": "Enter the withdrawal amount and payout details.", + "applyTitle": "Apply for Withdrawal", + "availableCommission": "Available for withdrawal", + "cancel": "Cancel", + "empty": { + "refund": "No refund records yet.", + "withdrawal": "No withdrawal records yet." + }, + "error": { + "refundDescription": "Please try again later.", + "refundTitle": "Failed to load refund records", + "withdrawalDescription": "Please try again later.", + "withdrawalTitle": "Failed to load withdrawal records" + }, + "record": { + "account": "Payout Account", + "content": "Payout Information", + "createdAt": "Created At", + "method": "Payout Method", + "qrCode": "Payment QR Code", + "reason": "Reject Reason" + }, + "records": "Withdrawal Records", + "recordsDescription": "View withdrawal and refund records separately.", + "refund": { + "amount": "Amount", + "content": "Description", + "createdAt": "Created At", + "event": { + "333": "Order refund", + "337": "Withdrawal rejected refund", + "338": "Withdrawal cancelled refund", + "other": "Other refund" + }, + "eventType": "Type" + }, + "retry": "Retry", + "status": { + "approved": "Approved", + "cancelled": "Cancelled", + "pending": "Pending", + "rejected": "Rejected" + }, + "submit": "Submit Withdrawal", + "submitSuccess": "Withdrawal request submitted", + "submitting": "Submitting...", + "tabs": { + "refund": "Refund Records", + "withdrawal": "Withdrawal Records" + }, + "validation": { + "amountExceeded": "Amount cannot exceed available commission" + } + } } diff --git a/apps/user/public/assets/locales/zh-CN/affiliate.json b/apps/user/public/assets/locales/zh-CN/affiliate.json index 0b331b0..a4f27cd 100644 --- a/apps/user/public/assets/locales/zh-CN/affiliate.json +++ b/apps/user/public/assets/locales/zh-CN/affiliate.json @@ -33,5 +33,64 @@ "pending": "审核中", "rejected": "已拒绝" }, - "withdrawSuccess": "提现申请已提交" + "withdrawSuccess": "提现申请已提交", + "withdrawal": { + "amount": "金额", + "amountHint": "可提现", + "amountPlaceholder": "请输入提现金额", + "apply": "申请提现", + "applyDescription": "填写提现金额和收款信息后提交,等待审核。", + "applyTitle": "申请提现", + "availableCommission": "可提现佣金", + "cancel": "取消", + "empty": { + "refund": "暂无退款记录。", + "withdrawal": "暂无提现记录。" + }, + "error": { + "refundDescription": "请稍后重试。", + "refundTitle": "退款记录加载失败", + "withdrawalDescription": "请稍后重试。", + "withdrawalTitle": "提现记录加载失败" + }, + "record": { + "account": "收款账号", + "content": "收款信息", + "createdAt": "创建时间", + "method": "收款方式", + "qrCode": "收款码", + "reason": "拒绝原因" + }, + "records": "提现记录", + "recordsDescription": "提现记录与退款记录独立展示。", + "refund": { + "amount": "金额", + "content": "说明", + "createdAt": "时间", + "event": { + "333": "订单退款", + "337": "提现拒绝退回", + "338": "用户取消提现退回", + "other": "其他退款" + }, + "eventType": "类型" + }, + "retry": "重试", + "status": { + "approved": "已通过", + "cancelled": "已取消", + "pending": "审核中", + "rejected": "已拒绝" + }, + "submit": "提交提现", + "submitSuccess": "提现申请已提交", + "submitting": "提交中...", + "tabs": { + "refund": "退款记录", + "withdrawal": "提现记录" + }, + "validation": { + "amountExceeded": "提现金额不能超过可提现佣金" + } + } } diff --git a/apps/user/src/sections/user/affiliate/index.tsx b/apps/user/src/sections/user/affiliate/index.tsx index 989770d..d850819 100644 --- a/apps/user/src/sections/user/affiliate/index.tsx +++ b/apps/user/src/sections/user/affiliate/index.tsx @@ -36,7 +36,6 @@ import { SelectValue, } from "@workspace/ui/components/select"; import { Textarea } from "@workspace/ui/components/textarea"; -import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import type { ProListActions } from "@workspace/ui/composed/pro-list/pro-list"; import { ProList } from "@workspace/ui/composed/pro-list/pro-list"; import { UploadImage } from "@workspace/ui/composed/upload-image"; @@ -44,7 +43,6 @@ import { commissionWithdraw, queryUserAffiliate, queryUserAffiliateList, - queryWithdrawalLog, uploadFile, withdrawalCancel, } from "@workspace/ui/services/user/user"; @@ -59,6 +57,7 @@ import { toast } from "sonner"; import { z } from "zod"; import { Display } from "@/components/display"; import { useGlobalStore } from "@/stores/global"; +import { WithdrawalRecords } from "./withdrawal-records"; const WITHDRAWAL_METHODS = [ { value: 1, label: "支付宝" }, @@ -67,38 +66,6 @@ const WITHDRAWAL_METHODS = [ { value: 0, label: "其他" }, ] as const; -function getWithdrawalMethodLabel(method: number): string { - return WITHDRAWAL_METHODS.find((m) => m.value === method)?.label ?? "未知"; -} - -function getWithdrawalStatusMeta( - status: number, - t: ReturnType>["t"] -) { - switch (status) { - case 1: - return { - label: t("withdrawal.status.approved", "Approved"), - className: "bg-emerald-500/10 text-emerald-600", - }; - case 2: - return { - label: t("withdrawal.status.rejected", "Rejected"), - className: "bg-destructive/10 text-destructive", - }; - case 3: - return { - label: t("withdrawal.status.cancelled", "Cancelled"), - className: "bg-muted text-muted-foreground", - }; - default: - return { - label: t("withdrawal.status.pending", "Pending"), - className: "bg-amber-500/10 text-amber-600", - }; - } -} - function QrCodeView({ url }: { url: string }) { const [zoomOpen, setZoomOpen] = useState(false); return ( @@ -111,13 +78,21 @@ function QrCodeView({ url }: { url: string }) { 收款码 {zoomOpen && ( - 收款码 + 收款码 )} @@ -157,14 +132,12 @@ const withdrawalFormSchema = z path: ["account"], }); } - } else if (data.method === 0) { - if (!data.account && !data.content) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "收款账号和备注至少填一项", - path: ["account"], - }); - } + } else if (data.method === 0 && !(data.account || data.content)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "收款账号和备注至少填一项", + path: ["account"], + }); } }); @@ -421,9 +394,7 @@ export default function Affiliate() { 收款码 - - * - + * {watchedQrCodeUrl ? (
@@ -446,7 +417,9 @@ export default function Affiliate() { returnType="file" >
- {qrUploading ? "上传中..." : "点击上传\n收款码"} + {qrUploading + ? "上传中..." + : "点击上传\n收款码"}
)} @@ -519,9 +492,7 @@ export default function Affiliate() { - } - /> - )} -
- -
    -
  • - 收款方式 - {getWithdrawalMethodLabel(item.method)} -
  • - {item.account && ( -
  • - 收款账号 - {item.account} -
  • - )} - {item.qr_code_url && ( -
  • - 收款码 - -
  • - )} - {item.content && ( -
  • - - {t("withdrawal.record.content", "Payout Information")} - - - {item.content} - -
  • - )} -
  • - - {t("withdrawal.record.createdAt", "Created At")} - - -
  • - {item.reason && ( -
  • - - {t("withdrawal.record.reason", "Reject Reason")} - - - {item.reason} - -
  • - )} -
- - - ); - }} - request={async (pagination) => { - const response = await queryWithdrawalLog({ - page: pagination.page, - size: pagination.size, - }); - return { - list: response.data.data?.list || [], - total: response.data.data?.total || 0, - }; - }} + > header={{ diff --git a/apps/user/src/sections/user/affiliate/withdrawal-records.test.tsx b/apps/user/src/sections/user/affiliate/withdrawal-records.test.tsx new file mode 100644 index 0000000..13e7dfb --- /dev/null +++ b/apps/user/src/sections/user/affiliate/withdrawal-records.test.tsx @@ -0,0 +1,254 @@ +/** + * @vitest-environment jsdom + */ +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { + queryUserCommissionReturnLog, + queryWithdrawalLog, +} from "@workspace/ui/services/user/user"; +import type { AxiosResponse } from "axios"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { WithdrawalRecords } from "./withdrawal-records"; + +vi.mock("@workspace/ui/services/user/user", () => ({ + queryUserCommissionReturnLog: vi.fn(), + queryWithdrawalLog: vi.fn(), +})); + +vi.mock("@/components/display", () => ({ + Display: ({ + value, + }: { + type: string; + value: number | string | null | undefined; + }) => {String(value ?? "")}, +})); + +const mockedQueryWithdrawalLog = vi.mocked(queryWithdrawalLog); +const mockedQueryUserCommissionReturnLog = vi.mocked( + queryUserCommissionReturnLog +); + +function t(key: string, defaultValue?: string): string { + return defaultValue ?? key; +} + +function createResponse>( + data: T +): AxiosResponse { + return { + data: { + code: 200, + data, + }, + status: 200, + statusText: "OK", + headers: {}, + config: { + headers: {} as AxiosResponse["config"]["headers"], + }, + }; +} + +function renderWithdrawalRecords( + defaultTab: "withdrawal" | "refund" = "withdrawal" +) { + return render( + + >["t"] + } + withdrawalListActionRef={{ current: undefined }} + /> + ); +} + +afterEach(() => { + cleanup(); + vi.clearAllMocks(); +}); + +describe("WithdrawalRecords", () => { + it("renders withdrawal records by default", async () => { + mockedQueryWithdrawalLog.mockResolvedValue( + createResponse({ + list: [ + { + id: 1, + user_id: 1, + amount: 3200, + method: 3, + account: "6222", + status: 1, + created_at: 1_716_800_000, + updated_at: 1_716_800_000, + }, + ], + total: 1, + }) + ); + mockedQueryUserCommissionReturnLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + + renderWithdrawalRecords(); + + await waitFor(() => + expect(mockedQueryWithdrawalLog).toHaveBeenCalledWith({ + page: 1, + size: 10, + }) + ); + expect(await screen.findByText("32")).not.toBeNull(); + expect(screen.getByText("6222")).not.toBeNull(); + expect(screen.getByText("Approved")).not.toBeNull(); + }); + + it("shows loading state while withdrawal records are being fetched", () => { + mockedQueryWithdrawalLog.mockReturnValue( + new Promise(() => { + // keep pending to assert the loading state + }) + ); + mockedQueryUserCommissionReturnLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + + renderWithdrawalRecords(); + + expect(screen.getByRole("status", { name: "Loading data" })).not.toBeNull(); + }); + + it("shows refund empty state independently", async () => { + mockedQueryWithdrawalLog.mockResolvedValue( + createResponse({ + list: [ + { + id: 1, + user_id: 1, + amount: 3200, + method: 3, + account: "6222", + status: 1, + created_at: 1_716_800_000, + updated_at: 1_716_800_000, + }, + ], + total: 1, + }) + ); + mockedQueryUserCommissionReturnLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + + renderWithdrawalRecords("refund"); + + await waitFor(() => + expect(mockedQueryUserCommissionReturnLog).toHaveBeenCalledWith({ + page: 1, + size: 10, + }) + ); + expect(await screen.findByText("No refund records yet.")).not.toBeNull(); + expect(screen.queryByText("3200")).toBeNull(); + }); + + it("shows refund error state and retries", async () => { + mockedQueryWithdrawalLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + mockedQueryUserCommissionReturnLog + .mockRejectedValueOnce(new Error("network failed")) + .mockResolvedValueOnce( + createResponse({ + list: [ + { + id: 2, + user_id: 1, + amount: 500, + event_type: 333, + content: "order refunded", + created_at: 1_716_800_000, + updated_at: 1_716_800_000, + }, + ], + total: 1, + }) + ); + + renderWithdrawalRecords("refund"); + + expect( + await screen.findByText("Failed to load refund records") + ).not.toBeNull(); + + fireEvent.click(screen.getByRole("button", { name: "Retry" })); + + expect(await screen.findByText("Order refund")).not.toBeNull(); + expect(screen.getByText("5")).not.toBeNull(); + }); + + it("falls back for unknown refund event types and warns once", async () => { + const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => { + // suppress expected warning in test output + }); + mockedQueryWithdrawalLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + mockedQueryUserCommissionReturnLog.mockResolvedValue( + createResponse({ + list: [ + { + id: 2, + user_id: 1, + amount: 500, + event_type: 999, + content: "unexpected", + created_at: 1_716_800_000, + updated_at: 1_716_800_000, + }, + ], + total: 1, + }) + ); + + renderWithdrawalRecords("refund"); + + expect(await screen.findByText("Other refund")).not.toBeNull(); + expect(warnSpy).toHaveBeenCalledWith( + "Unknown commission return event type:", + 999 + ); + + warnSpy.mockRestore(); + }); + + it("keeps mobile-friendly tab layout classes", async () => { + mockedQueryWithdrawalLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + mockedQueryUserCommissionReturnLog.mockResolvedValue( + createResponse({ list: [], total: 0 }) + ); + + const { container } = renderWithdrawalRecords(); + + await screen.findByText("No withdrawal records yet."); + + expect(container.querySelector(".grid-cols-2")).not.toBeNull(); + expect(container.querySelector(".w-full")).not.toBeNull(); + }); +}); diff --git a/apps/user/src/sections/user/affiliate/withdrawal-records.tsx b/apps/user/src/sections/user/affiliate/withdrawal-records.tsx new file mode 100644 index 0000000..d173ec3 --- /dev/null +++ b/apps/user/src/sections/user/affiliate/withdrawal-records.tsx @@ -0,0 +1,410 @@ +"use client"; + +import { Button } from "@workspace/ui/components/button"; +import { Card, CardContent } from "@workspace/ui/components/card"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@workspace/ui/components/tabs"; +import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; +import Empty from "@workspace/ui/composed/empty"; +import { + ProList, + type ProListActions, +} from "@workspace/ui/composed/pro-list/pro-list"; +import { + queryUserCommissionReturnLog, + queryWithdrawalLog, +} from "@workspace/ui/services/user/user"; +import { formatDate } from "@workspace/ui/utils/formatting"; +import { unitConversion } from "@workspace/ui/utils/unit-conversions"; +import { AlertCircleIcon, X } from "lucide-react"; +import { type RefObject, useState } from "react"; + +type AffiliateTranslation = ReturnType< + typeof import("react-i18next").useTranslation<"affiliate"> +>["t"]; + +const WITHDRAWAL_METHODS = [ + { value: 1, label: "支付宝" }, + { value: 2, label: "微信" }, + { value: 3, label: "银行卡" }, + { value: 0, label: "其他" }, +] as const; + +const warnedCommissionReturnEventTypes = new Set(); + +interface WithdrawalRecordsProps { + t: AffiliateTranslation; + cancelMutation: { + isPending: boolean; + mutateAsync: (withdrawalId: number) => Promise; + }; + defaultTab?: "withdrawal" | "refund"; + withdrawalListActionRef: RefObject; +} + +function CurrencyValue({ value }: { value?: number | null }) { + const amount = Number(value ?? 0); + return <>{unitConversion("centsToDollars", amount)}; +} + +function getWithdrawalMethodLabel(method: number): string { + return WITHDRAWAL_METHODS.find((m) => m.value === method)?.label ?? "未知"; +} + +function getWithdrawalStatusMeta(status: number, t: AffiliateTranslation) { + switch (status) { + case 1: + return { + label: t("withdrawal.status.approved", "Approved"), + className: "bg-emerald-500/10 text-emerald-600", + }; + case 2: + return { + label: t("withdrawal.status.rejected", "Rejected"), + className: "bg-destructive/10 text-destructive", + }; + case 3: + return { + label: t("withdrawal.status.cancelled", "Cancelled"), + className: "bg-muted text-muted-foreground", + }; + default: + return { + label: t("withdrawal.status.pending", "Pending"), + className: "bg-amber-500/10 text-amber-600", + }; + } +} + +export function getCommissionReturnEventLabel( + eventType: number, + t: AffiliateTranslation +) { + switch (eventType) { + case 333: + return t("withdrawal.refund.event.333", "Order refund"); + case 337: + return t("withdrawal.refund.event.337", "Withdrawal rejected refund"); + case 338: + return t("withdrawal.refund.event.338", "Withdrawal cancelled refund"); + default: + if (!warnedCommissionReturnEventTypes.has(eventType)) { + warnedCommissionReturnEventTypes.add(eventType); + console.warn("Unknown commission return event type:", eventType); + } + return t("withdrawal.refund.event.other", "Other refund"); + } +} + +function renderLogErrorState( + t: AffiliateTranslation, + title: string, + description: string, + retry: () => void +) { + return ( +
+ + + +
+

{title}

+

{description}

+
+ +
+
+
+ ); +} + +function QrCodeView({ url }: { url: string }) { + const [zoomOpen, setZoomOpen] = useState(false); + return ( + <> + + {zoomOpen && ( +
+
+ + 收款码 +
+
+ )} + + ); +} + +export function WithdrawalRecords({ + t, + cancelMutation, + defaultTab = "withdrawal", + withdrawalListActionRef, +}: WithdrawalRecordsProps) { + return ( + +
+
+

+ {t("withdrawal.records", "Withdrawal Records")} +

+

+ {t( + "withdrawal.recordsDescription", + "View withdrawal and refund records separately." + )} +

+
+ + + {t("withdrawal.tabs.withdrawal", "Withdrawal Records")} + + + {t("withdrawal.tabs.refund", "Refund Records")} + + +
+ + + > + action={withdrawalListActionRef} + empty={ + + } + error={(retry) => + renderLogErrorState( + t, + t( + "withdrawal.error.withdrawalTitle", + "Failed to load withdrawal records" + ), + t( + "withdrawal.error.withdrawalDescription", + "Please try again later." + ), + retry + ) + } + renderItem={(item) => { + const statusMeta = getWithdrawalStatusMeta(item.status, t); + return ( + + +
+
+ +
+
+ + {statusMeta.label} + + {item.status === 0 && ( + { + await cancelMutation.mutateAsync(item.id); + }} + title="取消提现申请" + trigger={ + + } + /> + )} +
+
+
    +
  • + + {t("withdrawal.record.method", "Payout Method")} + + {getWithdrawalMethodLabel(item.method)} +
  • + {item.account && ( +
  • + + {t("withdrawal.record.account", "Payout Account")} + + {item.account} +
  • + )} + {item.qr_code_url && ( +
  • + + {t("withdrawal.record.qrCode", "Payment QR Code")} + + +
  • + )} + {item.content && ( +
  • + + {t("withdrawal.record.content", "Payout Information")} + + + {item.content} + +
  • + )} +
  • + + {t("withdrawal.record.createdAt", "Created At")} + + +
  • + {item.reason && ( +
  • + + {t("withdrawal.record.reason", "Reject Reason")} + + + {item.reason} + +
  • + )} +
+
+
+ ); + }} + request={async (pagination) => { + const response = await queryWithdrawalLog({ + page: pagination.page, + size: pagination.size, + }); + return { + list: response.data.data?.list || [], + total: response.data.data?.total || 0, + }; + }} + /> +
+ + + > + empty={ + + } + error={(retry) => + renderLogErrorState( + t, + t( + "withdrawal.error.refundTitle", + "Failed to load refund records" + ), + t( + "withdrawal.error.refundDescription", + "Please try again later." + ), + retry + ) + } + renderItem={(item) => ( + + +
    +
  • + + {t("withdrawal.refund.createdAt", "Created At")} + + +
  • +
  • + + {t("withdrawal.refund.eventType", "Type")} + + + {getCommissionReturnEventLabel(item.event_type, t)} + +
  • +
  • + + {t("withdrawal.refund.amount", "Amount")} + + + + +
  • + {item.content && ( +
  • + + {t("withdrawal.refund.content", "Description")} + + + {item.content} + +
  • + )} +
+
+
+ )} + request={async (pagination) => { + const response = await queryUserCommissionReturnLog({ + page: pagination.page, + size: pagination.size, + }); + return { + list: response.data.data?.list || [], + total: response.data.data?.total || 0, + }; + }} + /> +
+
+ ); +} diff --git a/packages/ui/src/composed/pro-list/pro-list.tsx b/packages/ui/src/composed/pro-list/pro-list.tsx index 1d76adb..bd36902 100644 --- a/packages/ui/src/composed/pro-list/pro-list.tsx +++ b/packages/ui/src/composed/pro-list/pro-list.tsx @@ -14,6 +14,7 @@ import { } from "@workspace/ui/components/alert"; import { Button } from "@workspace/ui/components/button"; import { Checkbox } from "@workspace/ui/components/checkbox"; +import { Spinner } from "@workspace/ui/components/spinner"; import Empty from "@workspace/ui/composed/empty"; import { ColumnFilter, @@ -21,7 +22,7 @@ import { } from "@workspace/ui/composed/pro-list/column-filter"; import { Pagination } from "@workspace/ui/composed/pro-list/pagination"; import { cn } from "@workspace/ui/lib/utils"; -import { ListRestart, Loader, RefreshCcw } from "lucide-react"; +import { AlertCircleIcon, ListRestart, RefreshCcw } from "lucide-react"; import type React from "react"; import { useEffect, useImperativeHandle, useState } from "react"; @@ -47,6 +48,7 @@ export interface ProListProps { selectedRowsText: (total: number) => string; }>; empty?: React.ReactNode; + error?: (retry: () => void) => React.ReactNode; } export interface ProListActions { refresh: () => void; @@ -62,6 +64,7 @@ export function ProList>({ action, texts, empty, + error, }: ProListProps) { const [columnFilters, setColumnFilters] = useState([]); const [rowSelection, setRowSelection] = useState<{ [key: number]: boolean }>( @@ -74,6 +77,7 @@ export function ProList>({ pageSize: 10, }); const [loading, setLoading] = useState(false); + const [requestError, setRequestError] = useState(false); const table = useReactTable({ data, @@ -96,6 +100,7 @@ export function ProList>({ const fetchData = async () => { setLoading(true); + setRequestError(false); try { const response = await request( { @@ -108,8 +113,10 @@ export function ProList>({ ); setData(response.list); setRowCount(response.total); - } catch (error) { - console.log("Fetch data error:", error); + } catch { + setData([]); + setRowCount(0); + setRequestError(true); } finally { setLoading(false); } @@ -196,7 +203,22 @@ export function ProList>({ })} >
- {data.length ? ( + {requestError ? ( +
+ {error?.(fetchData) || ( + + + Failed to load data + +

Please try again.

+ +
+
+ )} +
+ ) : data.length ? ( data.map((item, index) => { const isSelected = !!rowSelection[index]; @@ -221,7 +243,7 @@ export function ProList>({ {loading && (
- +
)}
diff --git a/packages/ui/src/services/user/typings.d.ts b/packages/ui/src/services/user/typings.d.ts index 9a5e1be..cba2482 100644 --- a/packages/ui/src/services/user/typings.d.ts +++ b/packages/ui/src/services/user/typings.d.ts @@ -170,6 +170,16 @@ declare namespace API { timestamp: number; }; + type CommissionReturnLog = { + id: number; + user_id: number; + amount: number; + event_type: number; + content: string; + created_at: number; + updated_at: number; + }; + type CommissionWithdrawRequest = { amount: number; method: number; @@ -816,11 +826,26 @@ declare namespace API { total: number; }; + type QueryUserCommissionReturnLogListRequest = { + page: number; + size: number; + }; + + type QueryUserCommissionReturnLogListResponse = { + list: CommissionReturnLog[]; + total: number; + }; + type QueryUserCommissionLogParams = { page: number; size: number; }; + type QueryUserCommissionReturnLogParams = { + page: number; + size: number; + }; + type QueryUserSubscribeListResponse = { list: UserSubscribe[]; total: number; diff --git a/packages/ui/src/services/user/user.ts b/packages/ui/src/services/user/user.ts index 16a6950..83b4ca1 100644 --- a/packages/ui/src/services/user/user.ts +++ b/packages/ui/src/services/user/user.ts @@ -1,4 +1,3 @@ -// @ts-expect-error /* eslint-disable */ import request from "@workspace/ui/lib/request"; @@ -144,6 +143,26 @@ export async function queryUserCommissionLog( }); } +/** Query User Commission Return Log GET /v1/public/user/commission_return_log */ +export async function queryUserCommissionReturnLog( + // 叠加生成的Param类型 (非body参数swagger默认没有生成对象) + params: API.QueryUserCommissionReturnLogParams, + options?: { [key: string]: any } +) { + return request< + API.Response & { data?: API.QueryUserCommissionReturnLogListResponse } + >( + `${import.meta.env.VITE_API_PREFIX || ""}/v1/public/user/commission_return_log`, + { + method: "GET", + params: { + ...params, + }, + ...(options || {}), + } + ); +} + /** Commission Withdraw POST /v1/public/user/commission_withdraw */ export async function commissionWithdraw( body: API.CommissionWithdrawRequest,