/* eslint-disable max-lines-per-function, complexity -- 页面组件集中编排筛选、表单与弹层,拆散会割裂数据流;纯逻辑已抽到 *-utils.ts */
import { Button, RadioGroup, Select, Tag } from '@douyinfe/semi-ui-19';
import {
DICT_GROUPS,
type DictItem,
type DictItemListResponse,
type DictTypeDetail,
} from '@lingxi/shared';
import { ErrorState, PageEmpty } from '@lingxi/ui';
import { IconAdd, IconAlert, IconEdit, IconPause, IconPlay, IconView } from '@lingxi/ui/icons';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import {
dictItemViewStats,
dictScopeChips,
dictTypeKindLabel,
formatDictDateTime,
} from './dict-utils';
import { DictGroupIcon } from './DictGroupIcon';
import { DictItemsView } from './DictItemsView';
export interface DictScopeOption {
value: string | null;
label: string;
}
const EMPTY_ITEMS: DictItemListResponse = {
items: [],
next_cursor: null,
layers: [],
effectiveCount: 0,
overriddenCount: 0,
disabledCount: 0,
};
function groupNameOf(groupKey: DictTypeDetail['group']): string {
return DICT_GROUPS.find((group) => group.key === groupKey)?.name ?? groupKey;
}
function DetailSkeleton(): ReactNode {
return (
{Array.from({ length: 6 }, (_value, index) => (
))}
);
}
function DetailHeader({
type,
readOnly,
typeDisabled,
onAddItem,
onEditType,
onToggleType,
}: {
type: DictTypeDetail;
readOnly: boolean;
typeDisabled: boolean;
onAddItem: () => void;
onEditType: () => void;
onToggleType: () => void;
}): ReactNode {
return (
{type.name}
{type.builtin ? '内置' : '自定义'}
{dictTypeKindLabel(type.kind)}
{type.industryScoped ? '行业作用域' : '全平台'}
{type.status === 'active' ? '启用' : '停用'}
{type.code}
·
{groupNameOf(type.group)}
·
{type.itemCount} 个条目
·
{type.industryCount} 个行业覆盖
排序 {type.sortOrder}
·
最近更新 {type.updatedBy ?? '—'} {formatDictDateTime(type.updatedAt)}
{type.description.trim() === '' ? null : (
{type.description}
)}
{readOnly ? (
只读查看
) : (
<>
}
disabled={typeDisabled}
onClick={onAddItem}
>
新增条目
}
disabled={typeDisabled}
onClick={onEditType}
>
编辑类型
{typeDisabled ? (
} onClick={onToggleType}>
启用类型
) : (
}
onClick={onToggleType}
>
停用类型
)}
>
)}
);
}
function ScopeBar({
type,
industryId,
options,
items,
onIndustryChange,
}: {
type: DictTypeDetail;
industryId: string | null;
options: readonly DictScopeOption[];
items: DictItemListResponse;
onIndustryChange: (industryId: string | null) => void;
}): ReactNode {
if (!type.industryScoped) {
return (
行业视角
全平台通用 · 该类型不支持行业扩展
通用 {items.effectiveCount}
行业包只覆盖差异项
);
}
const chips = dictScopeChips(items, { scoped: industryId !== null });
return (
行业视角
);
}
function ViewRow({
view,
items,
onViewChange,
}: {
view: 'tree' | 'list';
items: DictItemListResponse;
onViewChange: (view: 'tree' | 'list') => void;
}): ReactNode {
const stats = dictItemViewStats(items);
return (
{
const next = String(event.target.value);
if (next === 'tree' || next === 'list') onViewChange(next);
}}
/>
生效 {stats.active} 项 · 继承 {stats.inherited} · 覆盖 {stats.overridden}
{stats.disabled > 0 ? ` · 停用 ${stats.disabled}` : ''}
{stats.overridden > 0 ? (
{stats.overridden} 项被更具体层覆盖
) : null}
);
}
function EmptyItems({
type,
readOnly,
industryLabel,
onAddItem,
}: {
type: DictTypeDetail;
readOnly: boolean;
industryLabel: string;
onAddItem: () => void;
}): ReactNode {
return (
} onClick={onAddItem}>
新增条目
)
}
/>
);
}
export interface DictDetailPanelProps {
type: DictTypeDetail | undefined;
typeLoading: boolean;
typeError: Error | null;
items: DictItemListResponse | undefined;
itemsLoading: boolean;
itemsError: Error | null;
view: 'tree' | 'list';
industryId: string | null;
industryOptions: readonly DictScopeOption[];
readOnly: boolean;
onRetry: () => void;
onViewChange: (view: 'tree' | 'list') => void;
onIndustryChange: (industryId: string | null) => void;
onAddItem: () => void;
onEditType: () => void;
onToggleType: () => void;
onViewItem: (item: DictItem) => void;
onEditItem: (item: DictItem) => void;
onToggleItem: (item: DictItem) => void;
}
/** 右栏:类型头 + 行业作用域 + 树/词表切换 + 条目区 + 底部 R-10 提示 */
export function DictDetailPanel({
type,
typeLoading,
typeError,
items,
itemsLoading,
itemsError,
view,
industryId,
industryOptions,
readOnly,
onRetry,
onViewChange,
onIndustryChange,
onAddItem,
onEditType,
onToggleType,
onViewItem,
onEditItem,
onToggleItem,
}: DictDetailPanelProps): ReactNode {
const response = items ?? EMPTY_ITEMS;
const industryLabel = useMemo(() => {
if (industryId === null) return '全平台';
return industryOptions.find((option) => option.value === industryId)?.label ?? industryId;
}, [industryId, industryOptions]);
if (typeLoading) {
return (
);
}
if (typeError !== null) {
return (
);
}
if (type === undefined) {
return (
);
}
const typeDisabled = type.status === 'disabled';
return (
{typeDisabled ? (
该字典类型已停用
停用后新数据不可选该类型条目;历史引用保留快照,历史统计按快照解释(R-10)。
) : null}
{readOnly ? (
当前账号无字典写权限(dict:manage)
仅可查看类型、切换视图与行业视角,不可新增或修改条目。
) : null}
{itemsLoading ? : null}
{!itemsLoading && itemsError !== null ? (
) : null}
{!itemsLoading && itemsError === null && response.items.length === 0 ? (
) : null}
{!itemsLoading && itemsError === null && response.items.length > 0 ? (
) : null}
编码创建后不可修改
·
变更只影响新数据,历史统计按快照解释(R-10)
生效 {response.effectiveCount} 项 · 停用 {response.disabledCount} 项
);
}