import { useState, type ReactNode } from "react"; import { Activity, ChevronDown, ChevronRight, CircleHelp, Database, FilePlus2, FolderPlus, FolderTree, KeyRound, LayoutGrid, Menu as MenuIcon, MoreHorizontal, Plus, Save, Search, ShieldCheck, Trash2, Users, } from "lucide-react"; import "./index.css"; type Workspace = "overview" | "domainEntities" | "menuPermissions" | "roles"; type Domain = { id: number; code: string; name: string; parentId: number | null; enabled: boolean; description: string; }; type Entity = { id: number; domainId: number; code: string; name: string; view: string; save: string; key: string; type: "table" | "view" | "query"; enabled: boolean; }; type Field = { id: number; entityId: number; code: string; name: string; type: string; nullable: boolean; enabled: boolean; }; type Relation = { id: number; entityId: number; targetEntityId: number; relationType: "one-to-one" | "one-to-many" | "many-to-one" | "many-to-many"; foreignField: string; linkField: string; ownership: "reference" | "owned"; deletePolicy: "restrict" | "set-null" | "cascade" | "archive"; enabled: boolean; }; type Menu = { id: number; parentId: number | null; code: string; name: string; type: "directory" | "page" | "data"; route: string; entityId: number | null; domainId: number | null; enabled: boolean; visible: boolean; }; type Role = { id: number; code: string; name: string; members: number }; type Power = { id: number; menuId: number; code: string; name: string; enabled: boolean; }; const initialDomains: Domain[] = [ { id: 1, code: "ocean", name: "海运业务", parentId: null, enabled: true, description: "订舱、运输节点与费用相关业务能力", }, { id: 2, code: "booking", name: "订舱管理", parentId: 1, enabled: true, description: "订舱单据及审核流程", }, { id: 3, code: "master-data", name: "基础资料", parentId: null, enabled: true, description: "客户、港口、航线等公共主数据", }, ]; const initialEntities: Entity[] = [ { id: 1, domainId: 2, code: "ocean_booking", name: "订舱单", view: "v_ocean_booking_list", save: "ocean_booking", key: "b_id", type: "view", enabled: true, }, { id: 2, domainId: 2, code: "ocean_booking_container", name: "集装箱明细", view: "ocean_booking_container", save: "ocean_booking_container", key: "b_id", type: "table", enabled: true, }, { id: 3, domainId: 3, code: "base_customer", name: "客户档案", view: "base_customer", save: "base_customer", key: "b_id", type: "table", enabled: true, }, ]; const initialFields: Field[] = [ { id: 1, entityId: 1, code: "b_ywno", name: "业务单号", type: "input", nullable: false, enabled: true, }, { id: 2, entityId: 1, code: "b_customer_id", name: "委托客户", type: "select", nullable: false, enabled: true, }, { id: 3, entityId: 1, code: "b_ywtype", name: "业务类型", type: "select", nullable: false, enabled: true, }, { id: 4, entityId: 1, code: "b_etd", name: "预计开船日", type: "date", nullable: true, enabled: true, }, { id: 5, entityId: 1, code: "b_state", name: "单据状态", type: "select", nullable: false, enabled: true, }, { id: 6, entityId: 3, code: "b_code", name: "客户编码", type: "input", nullable: false, enabled: true, }, { id: 7, entityId: 3, code: "b_name", name: "客户名称", type: "input", nullable: false, enabled: true, }, { id: 8, entityId: 2, code: "b_booking_id", name: "订舱单", type: "number", nullable: false, enabled: true, }, ]; const initialRelations: Relation[] = [ { id: 1, entityId: 1, targetEntityId: 2, relationType: "one-to-many", foreignField: "b_booking_id", linkField: "b_id", ownership: "owned", deletePolicy: "cascade", enabled: true, }, { id: 2, entityId: 1, targetEntityId: 3, relationType: "many-to-one", foreignField: "b_customer_id", linkField: "b_id", ownership: "reference", deletePolicy: "restrict", enabled: true, }, ]; const initialMenus: Menu[] = [ { id: 1, parentId: null, code: "ocean", name: "海运业务", type: "directory", route: "", entityId: null, domainId: 1, enabled: true, visible: true, }, { id: 2, parentId: 1, code: "booking", name: "订舱管理", type: "directory", route: "", entityId: null, domainId: 2, enabled: true, visible: true, }, { id: 3, parentId: 2, code: "booking-list", name: "订舱列表", type: "data", route: "/ocean/booking", entityId: 1, domainId: 2, enabled: true, visible: true, }, { id: 4, parentId: 2, code: "booking-audit", name: "订舱审核", type: "page", route: "/ocean/booking/audit", entityId: null, domainId: 2, enabled: true, visible: true, }, { id: 5, parentId: null, code: "master-data", name: "基础资料", type: "directory", route: "", entityId: null, domainId: 3, enabled: true, visible: true, }, { id: 6, parentId: 5, code: "customer", name: "客户档案", type: "data", route: "/base/customer", entityId: 3, domainId: 3, enabled: true, visible: true, }, ]; const initialRoles: Role[] = [ { id: 1, code: "ops", name: "运营专员", members: 18 }, { id: 2, code: "manager", name: "业务经理", members: 6 }, { id: 3, code: "admin", name: "系统管理员", members: 2 }, ]; const actionNames = ["view", "create", "edit", "delete", "export"]; const actionLabels: Record = { view: "查看", create: "新增", edit: "编辑", delete: "删除", export: "导出", }; const fieldTypes = [ "input", "select", "date", "datetime", "number", "textarea", ]; export default function App() { const [workspace, setWorkspace] = useState("overview"); const [domains, setDomains] = useState(initialDomains); const [entities, setEntities] = useState(initialEntities); const [fields, setFields] = useState(initialFields); const [relations, setRelations] = useState(initialRelations); const [menus, setMenus] = useState(initialMenus); const [roles] = useState(initialRoles); const [powers, setPowers] = useState(() => initialMenus .filter((menu) => menu.type !== "directory") .flatMap((menu) => actionNames.map((action, index) => ({ id: menu.id * 10 + index, menuId: menu.id, code: `${menu.code}.${action}`, name: actionLabels[action], enabled: action !== "delete", })), ), ); const [selectedDomainId, setSelectedDomainId] = useState(2); const [selectedEntityId, setSelectedEntityId] = useState(1); const [selectedMenuId, setSelectedMenuId] = useState(3); const [selectedRoleId, setSelectedRoleId] = useState(1); const [menuTab, setMenuTab] = useState<"basic" | "powers" | "scope">("basic"); const [dirty, setDirty] = useState(false); const [toast, setToast] = useState(""); const selectedDomain = domains.find((item) => item.id === selectedDomainId) ?? domains[0]; const selectedEntity = entities.find( (item) => item.id === selectedEntityId && item.domainId === selectedDomain?.id, ) ?? entities.find((item) => item.domainId === selectedDomain?.id); const selectedMenu = menus.find((item) => item.id === selectedMenuId) ?? menus[0]; const selectedRole = roles.find((item) => item.id === selectedRoleId) ?? roles[0]; const selectDomain = (id: number) => { setSelectedDomainId(id); setSelectedEntityId(0); }; const selectEntity = (id: number) => { const entity = entities.find((item) => item.id === id); if (!entity) return; setSelectedDomainId(entity.domainId); setSelectedEntityId(id); }; const markDirty = () => setDirty(true); const save = () => { setDirty(false); setToast("配置已保存到当前工作区"); window.setTimeout(() => setToast(""), 2600); }; const addDomain = (parentId: number | null) => { const id = Math.max(...domains.map((item) => item.id), 0) + 1; setDomains((items) => [ ...items, { id, code: `new-domain-${id}`, name: "新业务域", parentId, enabled: true, description: "请补充业务域说明", }, ]); setSelectedDomainId(id); setWorkspace("domainEntities"); markDirty(); }; const addEntity = () => { const id = Math.max(...entities.map((item) => item.id), 0) + 1; setEntities((items) => [ ...items, { id, domainId: selectedDomain.id, code: `new_entity_${id}`, name: "新实体", view: "", save: "", key: "b_id", type: "table", enabled: true, }, ]); setSelectedEntityId(id); setWorkspace("domainEntities"); markDirty(); }; const addMenu = (type: Menu["type"], parentId: number | null) => { const id = Math.max(...menus.map((item) => item.id), 0) + 1; const item: Menu = { id, parentId, code: `new-menu-${id}`, name: type === "directory" ? "新目录" : type === "data" ? "新数据页面" : "新页面", type, route: type === "directory" ? "" : "/new-route", entityId: type === "data" ? (selectedEntity?.id ?? null) : null, domainId: selectedDomain.id, enabled: true, visible: true, }; setMenus((items) => [...items, item]); setSelectedMenuId(id); setWorkspace("menuPermissions"); setMenuTab("basic"); if (type !== "directory") setPowers((items) => [ ...items, ...actionNames.map((action, index) => ({ id: id * 10 + index, menuId: id, code: `${item.code}.${action}`, name: actionLabels[action], enabled: action !== "delete", })), ]); markDirty(); }; return (
FMS 配置中心 业务域与实体 · 菜单与权限
生产环境
{dirty ? "有未保存修改" : "配置已同步"} 林
{workspace === "overview" && ( )} {workspace === "domainEntities" && ( { setDomains((items) => items.map((item) => item.id === selectedDomain.id ? { ...item, ...patch } : item, ), ); markDirty(); }} onEntityChange={(patch) => { if (!selectedEntity) return; setEntities((items) => items.map((item) => item.id === selectedEntity.id ? { ...item, ...patch } : item, ), ); markDirty(); }} onFieldChange={(id, patch) => { setFields((items) => items.map((item) => item.id === id ? { ...item, ...patch } : item, ), ); markDirty(); }} onRelationChange={(id, patch) => { setRelations((items) => items.map((item) => item.id === id ? { ...item, ...patch } : item, ), ); markDirty(); }} onAddField={() => { if (!selectedEntity) return; const id = Math.max(...fields.map((item) => item.id), 0) + 1; setFields((items) => [ ...items, { id, entityId: selectedEntity.id, code: `b_field_${id}`, name: "新字段", type: "input", nullable: true, enabled: true, }, ]); markDirty(); }} /> )} {workspace === "menuPermissions" && ( { setMenus((items) => items.map((item) => item.id === selectedMenu.id ? { ...item, ...patch } : item, ), ); markDirty(); }} onToggle={(id) => { setPowers((items) => items.map((item) => item.id === id ? { ...item, enabled: !item.enabled } : item, ), ); markDirty(); }} onPublish={() => { setDirty(false); setToast("菜单权限已发布"); window.setTimeout(() => setToast(""), 2600); }} /> )} {workspace === "roles" && }
{toast && (
✓ {toast}
)}
); } function SideNav({ icon, label, caption, active, onClick, }: { icon: ReactNode; label: string; caption?: string; active: boolean; onClick: () => void; }) { return ( ); } function PageHeader({ eyebrow, title, description, action, }: { eyebrow: string; title: string; description: string; action?: ReactNode; }) { return (
{eyebrow}

{title}

{description}

{action}
); } function StatCard({ icon, label, value, note, tone, }: { icon: ReactNode; label: string; value: string; note: string; tone: string; }) { return (
{icon}
{label} {value} {note}
); } function Overview({ domains, entities, menus, roles, onNavigate, }: { domains: Domain[]; entities: Entity[]; menus: Menu[]; roles: Role[]; onNavigate: (workspace: Workspace) => void; }) { return (
onNavigate("domainEntities")} > 开始配置 } />
} label="业务域" value={String(domains.length)} note="业务归属分组" tone="blue" /> } label="实体" value={String(entities.length)} note="可查询 / 可保存数据对象" tone="teal" /> } label="菜单入口" value={String( menus.filter((item) => item.type !== "directory").length, )} note="页面与数据菜单" tone="amber" /> } label="角色" value={String(roles.length)} note="授权主体" tone="violet" />

推荐维护方式

每个工作区都把高关联对象放在同一屏幕。

2 WORKSPACES
} title="业务域与实体" description="先选业务域,再在右侧维护实体、字段和页面配置。" onClick={() => onNavigate("domainEntities")} /> } title="菜单与权限" description="选中菜单后,在同一页面配置入口、动作、角色和数据范围。" onClick={() => onNavigate("menuPermissions")} /> } title="角色与用户" description="维护角色和人员归属,授权动作在菜单工作区完成。" onClick={() => onNavigate("roles")} />

底层职责仍然分离

页面聚合维护,数据模型保持清晰。

} label="业务域" detail="s_module" tone="blue" /> } label="实体" detail="s_entity" tone="teal" /> } label="菜单" detail="s_menu" tone="amber" /> } label="权限" detail="s_power" tone="violet" />
管理界面按操作关联度合并,底层仍由实体、菜单、权限表分别持久化。
); } function WorkflowStep({ number, icon, title, description, onClick, }: { number: string; icon: ReactNode; title: string; description: string; onClick: () => void; }) { return ( ); } function ModelNode({ icon, label, detail, tone, }: { icon: ReactNode; label: string; detail: string; tone: string; }) { return (
{icon} {label} {detail}
); } function DetailTitle({ icon, eyebrow, title, badge, }: { icon: ReactNode; eyebrow: string; title: string; badge: string; }) { return (
{icon}
{eyebrow}

{title}

{badge}
); } function LabeledInput({ label, value, onChange, readOnly = false, }: { label: string; value: string; onChange?: (value: string) => void; readOnly?: boolean; }) { return ( ); } function Toggle({ value, onChange }: { value: boolean; onChange: () => void }) { return ( ); } function DomainEntityWorkspace({ domains, entities, fields, relations, selectedDomain, selectedEntity, onSelectDomain, onSelectEntity, onAddDomain, onAddEntity, onDomainChange, onEntityChange, onFieldChange, onRelationChange, onAddField, }: { domains: Domain[]; entities: Entity[]; fields: Field[]; relations: Relation[]; selectedDomain: Domain; selectedEntity?: Entity; onSelectDomain: (id: number) => void; onSelectEntity: (id: number) => void; onAddDomain: (parentId: number | null) => void; onAddEntity: () => void; onDomainChange: (patch: Partial) => void; onEntityChange: (patch: Partial) => void; onFieldChange: (id: number, patch: Partial) => void; onRelationChange: (id: number, patch: Partial) => void; onAddField: () => void; }) { const visibleEntities = entities.filter( (item) => item.domainId === selectedDomain.id, ); const entityFields = selectedEntity ? fields.filter((item) => item.entityId === selectedEntity.id) : []; const entityRelations = selectedEntity ? relations.filter((item) => item.entityId === selectedEntity.id) : []; return (
} />

业务模型树

{domains.length} 个业务域 · {entities.length} 个实体

{renderBusinessTree( domains, entities, relations, null, 0, selectedDomain.id, selectedEntity?.id ?? 0, onSelectDomain, onSelectEntity, )}
{selectedDomain.name} {selectedDomain.description}

{selectedEntity ? selectedEntity.name : selectedDomain.name}

{selectedEntity ? "实体数据边界、字段与关系配置" : `${visibleEntities.length} 个直接归属实体`}

{selectedEntity ? "S_ENTITY" : "S_MODULE"}
{!selectedEntity && ( <> } eyebrow="S_MODULE" title={selectedDomain.name} badge={selectedDomain.enabled ? "启用中" : "已停用"} />
onDomainChange({ code: value })} /> onDomainChange({ name: value })} />