feat: 优化web
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
'use client';
|
||||
|
||||
import * as Select from '@radix-ui/react-select';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { iamTenant } from '@/lib/api/iam';
|
||||
import type { IamTenant } from '@/lib/api/types/tenant';
|
||||
import { useTenantStore } from '@/stores/tenant-store';
|
||||
import { useTabStore } from '@/stores/tab-store';
|
||||
|
||||
/** Radix Select 不允许空字符串作 value,用哨兵表示「默认租户」 */
|
||||
const DEFAULT_TENANT_VALUE = '__tenant_default__';
|
||||
|
||||
export function TenantSwitcher() {
|
||||
const tenantId = useTenantStore((s) => s.tenantId);
|
||||
const setTenantId = useTenantStore((s) => s.setTenantId);
|
||||
const resetTabsForTenantSwitch = useTabStore((s) => s.resetForTenantSwitch);
|
||||
const [rows, setRows] = useState<IamTenant[] | null>(null);
|
||||
const [loadErr, setLoadErr] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const data = await iamTenant.list();
|
||||
setRows(data.items ?? []);
|
||||
setLoadErr(false);
|
||||
} catch {
|
||||
setLoadErr(true);
|
||||
setRows([]);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
const short = tenantId ? (tenantId.length > 10 ? `${tenantId.slice(0, 8)}…` : tenantId) : '默认';
|
||||
|
||||
if (loadErr || !rows || rows.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className="hidden max-w-[160px] truncate text-xs text-neutral-500 md:block"
|
||||
title={tenantId ?? ''}
|
||||
>
|
||||
租户 {short}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="hidden items-center gap-1 md:flex">
|
||||
<span className="text-xs text-neutral-500" id="tenant-switcher-label">
|
||||
租户
|
||||
</span>
|
||||
<Select.Root
|
||||
value={tenantId ?? DEFAULT_TENANT_VALUE}
|
||||
onValueChange={(v) => {
|
||||
setTenantId(v === DEFAULT_TENANT_VALUE ? null : v);
|
||||
resetTabsForTenantSwitch();
|
||||
}}
|
||||
>
|
||||
<Select.Trigger
|
||||
className="inline-flex max-w-[140px] items-center justify-between gap-1 truncate rounded border border-neutral-200 bg-white px-1.5 py-0.5 text-xs text-neutral-800 outline-none hover:bg-neutral-50 focus-visible:ring-2 focus-visible:ring-neutral-400"
|
||||
aria-labelledby="tenant-switcher-label"
|
||||
title={tenantId ?? '默认'}
|
||||
>
|
||||
<Select.Value placeholder="默认" />
|
||||
<Select.Icon aria-hidden>
|
||||
<span className="text-[10px] text-neutral-500">▾</span>
|
||||
</Select.Icon>
|
||||
</Select.Trigger>
|
||||
<Select.Portal>
|
||||
<Select.Content
|
||||
className="z-[200] max-h-[min(320px,70vh)] overflow-hidden rounded-md border border-neutral-200 bg-white shadow-lg"
|
||||
position="popper"
|
||||
sideOffset={4}
|
||||
align="start"
|
||||
>
|
||||
<Select.Viewport className="p-1">
|
||||
<Select.Item
|
||||
value={DEFAULT_TENANT_VALUE}
|
||||
className="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-xs text-neutral-800 outline-none data-highlighted:bg-neutral-100"
|
||||
>
|
||||
<Select.ItemText>默认</Select.ItemText>
|
||||
</Select.Item>
|
||||
{rows.map((r) => (
|
||||
<Select.Item
|
||||
key={r.id}
|
||||
value={r.id}
|
||||
className="relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-xs text-neutral-800 outline-none data-highlighted:bg-neutral-100"
|
||||
>
|
||||
<Select.ItemText className="truncate">
|
||||
{r.tenant_name || r.tenant_code || r.id}
|
||||
</Select.ItemText>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user