工单组件
Some checks are pending
Build and Release / Build (push) Waiting to run

This commit is contained in:
speakeloudest 2026-05-01 17:09:24 +03:00
parent 7400137b3c
commit 644b20ebef
2 changed files with 120 additions and 18 deletions

View File

@ -23,6 +23,10 @@ import {
getTicketList,
updateTicketStatus,
} from "@workspace/ui/services/admin/ticket";
import {
getUserDetail,
updateUserBasicInfo,
} from "@workspace/ui/services/admin/user";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@ -42,6 +46,7 @@ export default function Page() {
const [ticketId, setTicketId] = useState<any>(null);
const [message, setMessage] = useState("");
const [loadingId, setLoadingId] = useState<any>(null);
const { data: ticket, refetch: refetchTicket } = useQuery({
queryKey: ["getTicket", ticketId],
@ -76,43 +81,134 @@ export default function Page() {
render(row) {
if (row.status !== 4) {
return [
<Button key="reply" onClick={() => setTicketId(row.id)}>
{t("reply", "Reply")}
</Button>,
<ConfirmButton
cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")}
description={t(
"closeWarning",
"Once closed, the ticket cannot be operated on. Please proceed with caution."
)}
description={
<div className="flex flex-col gap-2">
<p>
线
</p>
<p className="font-bold text-rose-500 text-sm">
USDT 1 USDT
</p>
<p className="font-bold text-rose-500 text-sm">
/ 5%
</p>
</div>
}
key="colse"
onConfirm={async () => {
setLoadingId(row.id);
try {
const titleStr = row.title || "";
const parts = titleStr.split("-");
if (parts.length > 1) {
const deductAmount = Number.parseFloat(parts[1]);
if (!isNaN(deductAmount)) {
const res = await getUserDetail({ id: row.user_id });
const user = res?.data?.data;
if (user && typeof user.commission !== "undefined") {
const deductAmountCent = deductAmount * 100;
if (Number(user.commission) < deductAmountCent) {
toast.error("用户佣金余额不足,无法完成打款扣费");
setLoadingId(null);
return;
}
const newCommission =
Number(user.commission) - deductAmountCent;
await updateUserBasicInfo({
user_id: row.user_id,
commission: newCommission,
} as any);
}
}
}
} catch (e) {
console.error("Update commission failed", e);
}
await updateTicketStatus({
id: row.id,
status: 4,
});
toast.success(t("closeSuccess", "Closed successfully"));
ref.current?.refresh();
setLoadingId(null);
}}
title={t("confirmClose", "Are you sure you want to close?")}
title="确认已完成打款?"
trigger={
<Button variant="destructive">{t("close", "Close")}</Button>
<Button
disabled={loadingId === row.id}
variant="destructive"
>
{loadingId === row.id && (
<Icon
className="mr-2 animate-spin"
icon="lucide:loader-2"
/>
)}
</Button>
}
/>,
];
}
return [
<Button key="check" onClick={() => setTicketId(row.id)} size="sm">
{t("check", "Check")}
</Button>,
];
return [];
},
}}
columns={[
{
accessorKey: "title",
header: t("title", "Title"),
id: "withdrawal_type",
header: "提现类型",
cell: ({ row }) => {
const title = row.original.title || "";
return title.split("-")[0] || title;
},
},
{
id: "withdrawal_amount",
header: "提现金额",
cell: ({ row }) => {
const title = row.original.title || "";
const parts = title.split("-");
if (parts.length < 2) return "";
return (
<span className="font-bold text-rose-500">${parts[1]}</span>
);
},
},
{
id: "withdrawal_method",
header: "提现方式",
cell: ({ row }) => {
const desc = row.original.description || "";
return desc.split("-")[0] || "";
},
},
{
id: "withdrawal_content",
header: "提现内容",
cell: ({ row }) => {
const desc = row.original.description || "";
const parts = desc.split("-");
if (parts.length < 2) return "";
const content = parts[1];
if (content.startsWith("data:image/")) {
return (
<img
alt="withdrawal"
className="h-10 w-10 cursor-zoom-in rounded border object-cover"
onClick={() => {
const win = window.open();
win?.document.write(`<img src="${content}" />`);
}}
src={content}
/>
);
}
return <span className="font-semibold">{content}</span>;
},
},
{
accessorKey: "user_id",

View File

@ -17,7 +17,7 @@ import type { ReactNode } from "react";
interface ConfirmationButtonProps {
trigger: ReactNode;
title: string;
description: string;
description: ReactNode;
onConfirm: () => void | Promise<void>;
cancelText?: string;
confirmText?: string;
@ -36,7 +36,13 @@ export const ConfirmButton: React.FC<ConfirmationButtonProps> = ({
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
<AlertDialogDescription asChild>
{typeof description === "string" ? (
<span>{description}</span>
) : (
<div>{description}</div>
)}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{cancelText}</AlertDialogCancel>