feat: 节点/服务器批量编辑 Sheet、Server 表单协议合并修复、设备类型解析
Build and Release / Build (push) Has been cancelled

- 新增 NodeBatchSheet 与 ServerBatchSheet,支持批量选中后一键编辑公共字段
- Server 表单:引入 mergeProtocol / buildSanitizedProtocols,过滤空字符串避免 Zod 枚举校验失败;renderGroupCard 改为具名导出供批量 Sheet 复用;优化嵌套错误提示递归取第一条
- 新增 parseDeviceType 工具函数,从 User-Agent 解析设备平台(iPhone/Android/Mac 等)
- 用户列表:登录标识旁展示设备平台 Badge
- 注册安全设置表单:引入 Textarea 组件并完善字段布局
- 类型定义补充及 i18n 本地化更新

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 05:17:01 -07:00
parent de87133061
commit 7400137b3c
19 changed files with 1422 additions and 142 deletions
+10 -2
View File
@@ -55,6 +55,7 @@ import {
getUserList,
updateUserBasicInfo,
} from "@workspace/ui/services/admin/user";
import { parseDeviceType } from "@workspace/ui/utils/device";
import { useCallback, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -252,16 +253,23 @@ export default function User() {
const method = row.original.auth_methods?.[0];
const identifier = method?.auth_identifier || "";
const isDevice = method?.auth_type === "device";
const deviceNo = (row.original.user_devices?.[0] as any)?.device_no;
const firstDevice = row.original.user_devices?.[0] as any;
const deviceNo = firstDevice?.device_no;
const deviceType = parseDeviceType(firstDevice?.user_agent || "");
const display = isDevice ? deviceNo || identifier : identifier;
return (
<div>
<div className="flex items-center">
<Badge
className="mr-1 uppercase"
title={method?.verified ? t("verified", "Verified") : ""}
>
{method?.auth_type}
</Badge>
{deviceType && (
<Badge className="mr-1" variant="secondary">
{deviceType}
</Badge>
)}
<span title={isDevice ? display : undefined}>{display}</span>
</div>
);
+6 -6
View File
@@ -59,8 +59,8 @@ export default function UserForm<T extends Record<string, any>>({
referral_percentage: z.number().optional(),
only_first_purchase: z.boolean().optional(),
is_admin: z.boolean().optional(),
balance: z.number().optional(),
gift_amount: z.number().optional(),
// balance: z.number().optional(),
// gift_amount: z.number().optional(),
commission: z.number().optional(),
});
const form = useForm({
@@ -282,7 +282,7 @@ export default function UserForm<T extends Record<string, any>>({
</FormItem>
)}
/>
<FormField
{/* <FormField
control={form.control}
name="balance"
render={({ field }) => (
@@ -309,8 +309,8 @@ export default function UserForm<T extends Record<string, any>>({
<FormMessage />
</FormItem>
)}
/>
<FormField
/> */}
{/* <FormField
control={form.control}
name="gift_amount"
render={({ field }) => (
@@ -340,7 +340,7 @@ export default function UserForm<T extends Record<string, any>>({
<FormMessage />
</FormItem>
)}
/>
/> */}
<FormField
control={form.control}
name="commission"