工单组件
Some checks failed
Build and Release / Build (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled

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, getTicketList,
updateTicketStatus, updateTicketStatus,
} from "@workspace/ui/services/admin/ticket"; } from "@workspace/ui/services/admin/ticket";
import {
getUserDetail,
updateUserBasicInfo,
} from "@workspace/ui/services/admin/user";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
@ -42,6 +46,7 @@ export default function Page() {
const [ticketId, setTicketId] = useState<any>(null); const [ticketId, setTicketId] = useState<any>(null);
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [loadingId, setLoadingId] = useState<any>(null);
const { data: ticket, refetch: refetchTicket } = useQuery({ const { data: ticket, refetch: refetchTicket } = useQuery({
queryKey: ["getTicket", ticketId], queryKey: ["getTicket", ticketId],
@ -76,43 +81,134 @@ export default function Page() {
render(row) { render(row) {
if (row.status !== 4) { if (row.status !== 4) {
return [ return [
<Button key="reply" onClick={() => setTicketId(row.id)}>
{t("reply", "Reply")}
</Button>,
<ConfirmButton <ConfirmButton
cancelText={t("cancel", "Cancel")} cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")} confirmText={t("confirm", "Confirm")}
description={t( description={
"closeWarning", <div className="flex flex-col gap-2">
"Once closed, the ticket cannot be operated on. Please proceed with caution." <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" key="colse"
onConfirm={async () => { 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({ await updateTicketStatus({
id: row.id, id: row.id,
status: 4, status: 4,
}); });
toast.success(t("closeSuccess", "Closed successfully")); toast.success(t("closeSuccess", "Closed successfully"));
ref.current?.refresh(); ref.current?.refresh();
setLoadingId(null);
}} }}
title={t("confirmClose", "Are you sure you want to close?")} title="确认已完成打款?"
trigger={ 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 [ return [];
<Button key="check" onClick={() => setTicketId(row.id)} size="sm">
{t("check", "Check")}
</Button>,
];
}, },
}} }}
columns={[ columns={[
{ {
accessorKey: "title", id: "withdrawal_type",
header: t("title", "Title"), 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", accessorKey: "user_id",

View File

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