增加备注功能
Build and Release / Build (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled

This commit is contained in:
2026-05-09 06:11:07 +03:00
parent 3e2873fb90
commit 750cab5cc9
3 changed files with 90 additions and 19 deletions
+87 -18
View File
@@ -31,6 +31,13 @@ import {
SheetTitle, SheetTitle,
SheetTrigger, SheetTrigger,
} from "@workspace/ui/components/sheet"; } from "@workspace/ui/components/sheet";
import { FilePenLine } from 'lucide-react';
import {
Popover,
PopoverClose,
PopoverContent,
PopoverTrigger,
} from '@workspace/ui/components/popover';
import { Switch } from "@workspace/ui/components/switch"; import { Switch } from "@workspace/ui/components/switch";
import { import {
Tabs, Tabs,
@@ -39,7 +46,7 @@ import {
TabsTrigger, TabsTrigger,
} from "@workspace/ui/components/tabs"; } from "@workspace/ui/components/tabs";
import { Combobox } from "@workspace/ui/composed/combobox"; import { Combobox } from "@workspace/ui/composed/combobox";
import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; // import { ConfirmButton } from "@workspace/ui/composed/confirm-button";
import { import {
ProTable, ProTable,
type ProTableActions, type ProTableActions,
@@ -50,13 +57,13 @@ import {
} from "@workspace/ui/services/admin/group"; } from "@workspace/ui/services/admin/group";
import { import {
createUser, createUser,
deleteUser, // deleteUser,
getUserDetail, getUserDetail,
getUserList, getUserList,
updateUserBasicInfo, updateUserBasicInfo,
} from "@workspace/ui/services/admin/user"; } from "@workspace/ui/services/admin/user";
import { parseDeviceType } from "@workspace/ui/utils/device"; import { parseDeviceType } from "@workspace/ui/utils/device";
import { useCallback, useRef, useState } from "react"; import React, { useRef, useState, useCallback } from 'react';
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { toast } from "sonner"; import { toast } from "sonner";
import { Display } from "@/components/display"; import { Display } from "@/components/display";
@@ -72,6 +79,44 @@ import { NotifySettingsForm } from "./user-profile/notify-settings-form";
import UserSubscription from "./user-subscription"; import UserSubscription from "./user-subscription";
// import EditUserGroupDialog from "./edit-user-group-dialog"; // import EditUserGroupDialog from "./edit-user-group-dialog";
// 为 RemarkForm 组件定义 props 类型
interface RemarkFormProps {
initialRemark?: string | null;
onSave: (remark: string) => void;
CloseComponent: React.ComponentType<{ asChild?: boolean; children: React.ReactNode }>;
}
// 新的子组件,在管理它自己的备注状态
const RemarkForm: React.FC<RemarkFormProps> = ({ onSave, initialRemark, CloseComponent }) => {
const [remark, setRemark] = useState<string>(initialRemark ?? '');
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setRemark(event.target.value);
};
const handleSaveClick = () => {
onSave(remark);
};
return (
<>
<div className='mb-2 text-sm font-semibold'></div>
<Input
type='text'
value={remark}
onChange={handleInputChange}
placeholder='在此输入备注...'
className='w-full'
/>
<CloseComponent asChild>
<Button onClick={handleSaveClick} variant='default' size={'sm'} className={'mt-2'}>
</Button>
</CloseComponent>
</>
);
};
export default function User() { export default function User() {
const { t } = useTranslation("user"); const { t } = useTranslation("user");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -139,7 +184,7 @@ export default function User() {
userId={row.id} userId={row.id}
/>, />,
<PreviewNodesDialog key="preview-nodes" userId={row.id} />, <PreviewNodesDialog key="preview-nodes" userId={row.id} />,
<ConfirmButton /* <ConfirmButton
cancelText={t("cancel", "Cancel")} cancelText={t("cancel", "Cancel")}
confirmText={t("confirm", "Confirm")} confirmText={t("confirm", "Confirm")}
description={t( description={t(
@@ -156,7 +201,7 @@ export default function User() {
trigger={ trigger={
<Button variant="destructive">{t("delete", "Delete")}</Button> <Button variant="destructive">{t("delete", "Delete")}</Button>
} }
/>, />,*/
<DropdownMenu key="more" modal={false}> <DropdownMenu key="more" modal={false}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="outline">{t("more", "More")}</Button> <Button variant="outline">{t("more", "More")}</Button>
@@ -232,7 +277,7 @@ export default function User() {
accessorKey: "id", accessorKey: "id",
header: "ID", header: "ID",
}, },
{ /*{
id: "deleted_at", id: "deleted_at",
accessorKey: "deleted_at", accessorKey: "deleted_at",
header: t("isDeleted", "Deleted"), header: t("isDeleted", "Deleted"),
@@ -244,11 +289,11 @@ export default function User() {
<Badge variant="outline">{t("normal", "Normal")}</Badge> <Badge variant="outline">{t("normal", "Normal")}</Badge>
); );
}, },
}, },*/
{ {
id: "auth_methods", id: "auth_methods",
accessorKey: "auth_methods", accessorKey: "auth_methods",
header: t("userName", "Username"), header: '设备码/邮箱',
cell: ({ row }) => { cell: ({ row }) => {
const method = row.original.auth_methods?.[0]; const method = row.original.auth_methods?.[0];
const identifier = method?.auth_identifier || ""; const identifier = method?.auth_identifier || "";
@@ -259,23 +304,48 @@ export default function User() {
const display = isDevice ? deviceNo || identifier : identifier; const display = isDevice ? deviceNo || identifier : identifier;
return ( return (
<div className="flex items-center"> <div className="flex items-center">
<Badge {/* <Badge
className="mr-1 uppercase" className="mr-1 uppercase"
title={method?.verified ? t("verified", "Verified") : ""} title={method?.verified ? t("verified", "Verified") : ""}
> >
{method?.auth_type} {method?.auth_type}
</Badge> </Badge>*/}
{deviceType && ( {deviceType && (
<Badge className="mr-1" variant="secondary"> <Badge className="mr-1" variant="secondary">
{deviceType} {deviceType}
</Badge> </Badge>
)} )}
<span title={isDevice ? display : undefined}>{display}</span> <span title={isDevice ? display : undefined}>{display}</span>
<Popover>
<PopoverTrigger>
<div className={'flex items-center'}>
{row.original?.remark ? `${row.original.remark}` : ''}
<FilePenLine size={14} className={'text-primary ml-2'} />
</div>
</PopoverTrigger>
<PopoverContent className={'w-64'}>
<RemarkForm
initialRemark={row.original.remark}
CloseComponent={PopoverClose}
onSave={async (remark) => {
const {
id,
} = row.original;
await updateUserBasicInfo({
user_id: id,
remark,
} as unknown as API.UpdateUserBasiceInfoRequest);
toast.success(t('updateSuccess'));
ref.current?.refresh();
}}
/>
</PopoverContent>
</Popover>
</div> </div>
); );
}, },
}, },
{ /* {
id: "balance", id: "balance",
accessorKey: "balance", accessorKey: "balance",
header: t("balance", "Balance"), header: t("balance", "Balance"),
@@ -290,7 +360,7 @@ export default function User() {
cell: ({ row }) => ( cell: ({ row }) => (
<Display type="currency" value={row.getValue("gift_amount")} /> <Display type="currency" value={row.getValue("gift_amount")} />
), ),
}, },*/
{ {
id: "commission", id: "commission",
accessorKey: "commission", accessorKey: "commission",
@@ -531,12 +601,11 @@ function UserSearchBar({
}} }}
value={searchType} value={searchType}
> >
<SelectTrigger className="w-24"> <SelectTrigger className="w-48">
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="email">{t("email", "Email")}</SelectItem> <SelectItem value="email">/</SelectItem>
<SelectItem value="device">{t("deviceSearch", "Device")}</SelectItem>
<SelectItem value="user_id">{t("userId", "User ID")}</SelectItem> <SelectItem value="user_id">{t("userId", "User ID")}</SelectItem>
<SelectItem value="subscribe_id"> <SelectItem value="subscribe_id">
{t("subscription", "Subscription")} {t("subscription", "Subscription")}
+2 -1
View File
@@ -43,5 +43,6 @@ function PopoverAnchor({
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />; return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
} }
const PopoverClose = PopoverPrimitive.Close;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverClose };
+1
View File
@@ -2438,6 +2438,7 @@ declare namespace API {
group_locked: boolean; group_locked: boolean;
auth_methods: UserAuthMethod[]; auth_methods: UserAuthMethod[];
user_devices: UserDevice[]; user_devices: UserDevice[];
remark: string;
rules: string[]; rules: string[];
created_at: number; created_at: number;
updated_at: number; updated_at: number;