fix: 修复订阅跳转引号问题 + 设备组显示设备类型 + 统一用户名显示
Some checks failed
Build and Release / Build (push) Has been cancelled

- 去掉所有 Link search 中的 String() 包装(7处),修复 URL 引号问题
- View Owner 改用 useNavigate 强制导航,解决同页面跳转不生效
- 设备组详情成员列表添加 auth_type Badge 和 device_type Badge
- 设备组列表 owner 列统一为 [AUTH_TYPE Badge] + identifier 格式
- UserDetail 组件统一设备标识逻辑,去掉 shortenDeviceIdentifier

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
shanshanzhong 2026-03-26 21:36:12 -07:00
parent 5019f76a73
commit 5a493fce67
7 changed files with 66 additions and 38 deletions

Binary file not shown.

View File

@ -261,6 +261,11 @@ export function FamilyDetailSheet({
<Badge variant="outline">
ID: {member.user_id}
</Badge>
{member.auth_type ? (
<Badge className="uppercase">
{member.auth_type}
</Badge>
) : null}
{member.device_no ? (
<Badge variant="outline">
<span className="font-mono">
@ -268,7 +273,16 @@ export function FamilyDetailSheet({
</span>
</Badge>
) : null}
<span>{member.identifier}</span>
{member.device_type ? (
<Badge variant="secondary">
{member.device_type}
</Badge>
) : null}
<span>
{member.auth_type === "device"
? member.device_no || member.identifier
: member.identifier}
</span>
<Badge>
{getFamilyRoleLabel(t, member.role_name)}
</Badge>

View File

@ -54,8 +54,19 @@ export default function FamilyManagement({
{
accessorKey: "owner_identifier",
header: t("owner", "Owner"),
cell: ({ row }) =>
`${row.original.owner_identifier} (ID: ${row.original.owner_user_id})`,
cell: ({ row }) => (
<div className="flex items-center gap-1">
{row.original.owner_auth_type ? (
<Badge className="uppercase">
{row.original.owner_auth_type}
</Badge>
) : null}
<span>{row.original.owner_identifier}</span>
<span className="text-muted-foreground text-xs">
(ID: {row.original.owner_user_id})
</span>
</div>
),
},
{
accessorKey: "status",

View File

@ -163,42 +163,30 @@ export default function User() {
<DropdownMenuContent align="end">
<InviteStatsMenuItem userId={row.id} />
<DropdownMenuItem asChild>
<Link
search={{ user_id: String(row.id) }}
to="/dashboard/order"
>
<Link search={{ user_id: row.id }} to="/dashboard/order">
{t("orderList", "Order List")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
search={{ user_id: String(row.id) }}
to="/dashboard/log/login"
>
<Link search={{ user_id: row.id }} to="/dashboard/log/login">
{t("loginLogs", "Login Logs")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
search={{ user_id: String(row.id) }}
to="/dashboard/log/balance"
>
<Link search={{ user_id: row.id }} to="/dashboard/log/balance">
{t("balanceLogs", "Balance Logs")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
search={{ user_id: String(row.id) }}
search={{ user_id: row.id }}
to="/dashboard/log/commission"
>
{t("commissionLogs", "Commission Logs")}
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link
search={{ user_id: String(row.id) }}
to="/dashboard/log/gift"
>
<Link search={{ user_id: row.id }} to="/dashboard/log/gift">
{t("giftLogs", "Gift Logs")}
</Link>
</DropdownMenuItem>

View File

@ -12,7 +12,6 @@ import {
getUserDetail,
getUserSubscribeById,
} from "@workspace/ui/services/admin/user";
import { shortenDeviceIdentifier } from "@workspace/ui/utils/device";
import { formatBytes } from "@workspace/ui/utils/formatting";
import { useTranslation } from "react-i18next";
import { Display } from "@/components/display";
@ -230,21 +229,29 @@ export function UserDetail({ id }: { id: number }) {
if (!id) return "--";
const emailMethod = data?.auth_methods.find((m) => m.auth_type === "email");
const firstMethod = data?.auth_methods[0];
const rawIdentifier =
emailMethod?.auth_identifier || firstMethod?.auth_identifier || "";
const isDevice = !emailMethod && firstMethod?.auth_type === "device";
const identifier = isDevice
? shortenDeviceIdentifier(rawIdentifier)
: rawIdentifier;
const isDevice = firstMethod?.auth_type === "device";
const deviceNo = (data as any)?.user_devices?.[0]?.device_no;
const rawIdentifier = firstMethod?.auth_identifier || "";
const identifier = isDevice ? deviceNo || rawIdentifier : rawIdentifier;
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button asChild className="p-0" variant="link">
<Link search={{ user_id: id }} to="/dashboard/user">
{identifier || t("loading", "Loading...")}
{data ? (
<>
{firstMethod && (
<span className="mr-1 inline-flex items-center rounded border px-1 py-0.5 font-mono text-xs uppercase">
{firstMethod.auth_type}
</span>
)}
{identifier}
</>
) : (
t("loading", "Loading...")
)}
</Link>
</Button>
</HoverCardTrigger>

View File

@ -1,4 +1,4 @@
import { Link } from "@tanstack/react-router";
import { Link, useNavigate } from "@tanstack/react-router";
import { Alert, AlertDescription } from "@workspace/ui/components/alert";
import { Badge } from "@workspace/ui/components/badge";
import { Button } from "@workspace/ui/components/button";
@ -42,6 +42,7 @@ export default function UserSubscription({ userId }: { userId: number }) {
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
const [sharedInfo, setSharedInfo] = useState<SharedInfo | null>(null);
const navigate = useNavigate();
const request = useCallback(
async (pagination: { page: number; size: number }) => {
@ -116,19 +117,23 @@ export default function UserSubscription({ userId }: { userId: number }) {
<span className="flex gap-2">
<Button asChild size="sm" variant="outline">
<Link
search={{ user_id: String(sharedInfo.ownerUserId) }}
search={{ user_id: sharedInfo.ownerUserId }}
to="/dashboard/family"
>
{t("viewDeviceGroup", "View Device Group")}
</Link>
</Button>
<Button asChild size="sm" variant="outline">
<Link
search={{ user_id: String(sharedInfo.ownerUserId) }}
to="/dashboard/user"
>
{t("viewOwner", "View Owner")}
</Link>
<Button
onClick={() => {
navigate({
to: "/dashboard/user",
search: { user_id: sharedInfo.ownerUserId },
});
}}
size="sm"
variant="outline"
>
{t("viewOwner", "View Owner")}
</Button>
</span>
</AlertDescription>

View File

@ -2607,7 +2607,9 @@ declare namespace API {
type FamilyMemberItem = {
user_id: number;
identifier: string;
auth_type: string;
device_no: string;
device_type: string;
role: number;
role_name: string;
status: number;
@ -2621,6 +2623,7 @@ declare namespace API {
family_id: number;
owner_user_id: number;
owner_identifier: string;
owner_auth_type: string;
status: string;
active_member_count: number;
max_members: number;