20260812221200

This commit is contained in:
oneao committed 2026-08-12 22:12:01 +08:00
1 parent 87b2d3c482
commit 4bd730f649
47 files changed
+6286 -1881

No files matched your search

+151
View File
@@ -0,0 +1,151 @@
import { useState, useCallback, useRef } from "react";
import type { ModuleField, FieldGroup, EditMode, FieldDataSource } from "./types";
import { defaultFields, defaultGroups, makeField } from "./data/moduleData";
import ModuleSettings from "./components/ModuleSettings";
import FieldConfigList from "./components/FieldConfigList";
import DataSourceDrawer from "./components/DataSourceDrawer";
export default function App() {
const [fields, setFields] = useState<ModuleField[]>(defaultFields);
const [groups, setGroups] = useState<FieldGroup[]>(defaultGroups);
const [editMode, setEditMode] = useState<EditMode>("form");
const [activeId, setActiveId] = useState<number | null>(null);
const [dsDrawerFieldId, setDsDrawerFieldId] = useState<number | null>(null);
const nextGroupId = useRef(10);
const dsDrawerField = dsDrawerFieldId != null ? fields.find((f) => f.id === dsDrawerFieldId) ?? null : null;
// ======== Field mutations ========
const updateField = useCallback((id: number, key: string, value: string | number) => {
setFields((prev) => prev.map((f) => (f.id === id ? { ...f, [key]: value } : f)));
}, []);
const toggleField = useCallback((id: number, key: string) => {
setFields((prev) => prev.map((f) => (f.id === id ? { ...f, [key]: !(f as any)[key] } : f)));
}, []);
const onTypeChange = useCallback((id: number, newType: string) => {
setFields((prev) =>
prev.map((f) => {
if (f.id !== id) return f;
let ds = f.ds;
if (newType === "select" && !ds) {
ds = { type: "view", view: "", vf: "b_id", lf: "b_name" };
}
if (newType !== "select") ds = null;
return { ...f, type: newType, ds };
})
);
}, []);
const addField = useCallback(() => {
const firstGroup = groups[0]?.id ?? null;
const order = (fields.length + 1) * 10;
const f = makeField(firstGroup, "input", "新字段");
f.order = order;
setFields((prev) => [...prev, f]);
setActiveId(f.id);
}, [fields.length, groups]);
const deleteField = useCallback(
(id: number) => {
const f = fields.find((f) => f.id === id);
if (!f || !window.confirm(`删除「${f.label}」(${f.code})?`)) return;
setFields((prev) => prev.filter((f) => f.id !== id));
if (activeId === id) setActiveId(null);
},
[fields, activeId]
);
// ======== Drag reorder ========
const reorderFields = useCallback((activeId: number, overId: number) => {
setFields((prev) => {
const src = prev.find((f) => f.id === activeId);
const tgt = prev.find((f) => f.id === overId);
if (!src || !tgt) return prev;
const tmp = src.order;
return prev.map((f) => {
if (f.id === activeId) return { ...f, order: tgt.order };
if (f.id === overId) return { ...f, order: tmp };
return f;
});
});
}, []);
// ======== Data source ========
const updateDs = useCallback((ds: FieldDataSource | null) => {
if (dsDrawerFieldId == null) return;
setFields((prev) => prev.map((f) => (f.id === dsDrawerFieldId ? { ...f, ds } : f)));
}, [dsDrawerFieldId]);
// ======== Group ========
const addGroup = useCallback(() => {
const name = window.prompt("分组名称:");
if (!name) return;
const id = `g${nextGroupId.current++}`;
setGroups((prev) => [...prev, { id, name }]);
}, []);
const renameGroup = useCallback((id: string) => {
const g = groups.find((g) => g.id === id);
if (!g) return;
const name = window.prompt("新名称:", g.name);
if (name) {
setGroups((prev) => prev.map((g) => (g.id === id ? { ...g, name } : g)));
}
}, [groups]);
const activateField = useCallback(
(id: number) => {
setActiveId((prev) => (prev === id ? null : id));
},
[]
);
return (
<div className="min-h-screen flex flex-col">
<div className="flex-1 flex flex-col max-w-[1280px] mx-auto w-full p-5 gap-4">
{/* Module Settings */}
<ModuleSettings editMode={editMode} onEditModeChange={setEditMode} />
{/* Grouped field table (single column) */}
<div className="card flex flex-col min-h-0 flex-1">
<div className="flex items-center justify-between px-4 py-2.5 border-b border-slate-100 shrink-0">
<span className="text-xs font-semibold text-slate-500 uppercase tracking-wide">
字段配置 <span className="text-slate-300 font-normal ml-1">{fields.length}</span>
</span>
<div className="flex items-center gap-1.5">
<button className="btn btn-primary text-[10px] py-1 px-2.5" onClick={addField}>
+ 新增
</button>
<button className="btn btn-ghost text-[10px] py-1 px-2" onClick={addGroup}>
+ 分组
</button>
</div>
</div>
<FieldConfigList
fields={fields}
groups={groups}
activeId={activeId}
onActivate={activateField}
onToggle={toggleField}
onUpdate={updateField}
onTypeChange={onTypeChange}
onReorder={reorderFields}
onGroupRename={renameGroup}
onDelete={deleteField}
onOpenDs={setDsDrawerFieldId}
/>
</div>
</div>
{/* Data Source Drawer */}
<DataSourceDrawer
field={dsDrawerField}
open={dsDrawerFieldId != null}
onClose={() => setDsDrawerFieldId(null)}
onUpdate={updateDs}
/>
</div>
);
}