style(code): 格式化代码缩进和布局样式

- 统一调整 admin.ts 文件中的接口定义缩进格式
- 优化 animated-theme-toggler.tsx 组件的代码结构和缩进
- 规范 app-config-modal.tsx 中 JSX 元素的嵌套格式
- 整理 app-providers.tsx 中的组件层级缩进
- 标准化 app-theme.ts 中的对象属性缩进格式
This commit is contained in:
HouYunFei
2026-05-22 23:19:25 +08:00
parent cee08cb5a8
commit e319481976
93 changed files with 11406 additions and 10424 deletions
@@ -12,254 +12,251 @@ import { fetchAssetLibrary, type AssetLibraryItem } from "@/services/api/assets"
export type AssetPickerTab = "my-assets" | "library";
export type InsertAssetPayload =
| { kind: "text"; content: string; title: string }
| { kind: "image"; dataUrl: string; title: string; storageKey?: string };
export type InsertAssetPayload = { kind: "text"; content: string; title: string } | { kind: "image"; dataUrl: string; title: string; storageKey?: string };
type Props = {
open: boolean;
defaultTab?: AssetPickerTab;
onInsert: (payload: InsertAssetPayload) => void;
onClose: () => void;
open: boolean;
defaultTab?: AssetPickerTab;
onInsert: (payload: InsertAssetPayload) => void;
onClose: () => void;
};
export function AssetPickerModal({ open, defaultTab = "my-assets", onInsert, onClose }: Props) {
const [activeTab, setActiveTab] = useState<AssetPickerTab>(defaultTab);
const [activeTab, setActiveTab] = useState<AssetPickerTab>(defaultTab);
useEffect(() => {
if (open) setActiveTab(defaultTab);
}, [open, defaultTab]);
useEffect(() => {
if (open) setActiveTab(defaultTab);
}, [open, defaultTab]);
return (
<Modal
title="选择素材"
open={open}
onCancel={onClose}
footer={null}
width={860}
destroyOnHidden
styles={{ body: { padding: "0 24px 24px", minHeight: 480 } }}
>
<Tabs
activeKey={activeTab}
onChange={(key) => setActiveTab(key as AssetPickerTab)}
items={[
{ key: "my-assets", label: "我的素材", children: <MyAssetsTab onInsert={onInsert} /> },
{ key: "library", label: "素材库", children: <LibraryTab onInsert={onInsert} /> },
]}
/>
</Modal>
);
return (
<Modal title="选择素材" open={open} onCancel={onClose} footer={null} width={860} destroyOnHidden styles={{ body: { padding: "0 24px 24px", minHeight: 480 } }}>
<Tabs
activeKey={activeTab}
onChange={(key) => setActiveTab(key as AssetPickerTab)}
items={[
{ key: "my-assets", label: "我的素材", children: <MyAssetsTab onInsert={onInsert} /> },
{ key: "library", label: "素材库", children: <LibraryTab onInsert={onInsert} /> },
]}
/>
</Modal>
);
}
const PAGE_SIZE = 8;
const kindOptions = [
{ label: "全部", value: "all" },
{ label: "文本", value: "text" },
{ label: "图片", value: "image" },
{ label: "全部", value: "all" },
{ label: "文本", value: "text" },
{ label: "图片", value: "image" },
];
function LibraryTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => void }) {
const { message } = App.useApp();
const [keyword, setKeyword] = useState("");
const [kindFilter, setKindFilter] = useState("");
const [page, setPage] = useState(1);
const [inserting, setInserting] = useState<string | null>(null);
const { message } = App.useApp();
const [keyword, setKeyword] = useState("");
const [kindFilter, setKindFilter] = useState("");
const [page, setPage] = useState(1);
const [inserting, setInserting] = useState<string | null>(null);
const query = useQuery({
queryKey: ["asset-picker-library", keyword, kindFilter, page],
queryFn: () => fetchAssetLibrary({ keyword, type: kindFilter, page, pageSize: PAGE_SIZE }),
retry: false,
});
const query = useQuery({
queryKey: ["asset-picker-library", keyword, kindFilter, page],
queryFn: () => fetchAssetLibrary({ keyword, type: kindFilter, page, pageSize: PAGE_SIZE }),
retry: false,
});
const items = query.data?.items || [];
const total = query.data?.total || 0;
const items = query.data?.items || [];
const total = query.data?.total || 0;
const handleInsert = async (asset: AssetLibraryItem) => {
try {
setInserting(asset.id);
if (asset.type === "text") {
onInsert({ kind: "text", content: asset.content, title: asset.title });
} else {
const dataUrl = await remoteImageToDataUrl(asset.url);
onInsert({ kind: "image", dataUrl, title: asset.title });
}
} catch {
message.error("插入失败");
} finally {
setInserting(null);
}
};
const handleInsert = async (asset: AssetLibraryItem) => {
try {
setInserting(asset.id);
if (asset.type === "text") {
onInsert({ kind: "text", content: asset.content, title: asset.title });
} else {
const dataUrl = await remoteImageToDataUrl(asset.url);
onInsert({ kind: "image", dataUrl, title: asset.title });
}
} catch {
message.error("插入失败");
} finally {
setInserting(null);
}
};
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-3">
<Input
className="w-56"
size="small"
prefix={<Search className="size-3.5 text-stone-400" />}
placeholder="搜索素材"
value={keyword}
allowClear
onChange={(e) => { setPage(1); setKeyword(e.target.value); }}
/>
<div className="flex gap-1.5">
{[{ label: "全部", value: "" }, { label: "文本", value: "text" }, { label: "图片", value: "image" }].map((opt) => (
<Tag.CheckableTag
key={opt.value || "all"}
checked={kindFilter === opt.value}
className={cn("prompt-filter-tag", kindFilter === opt.value && "is-active")}
onChange={() => { setPage(1); setKindFilter(opt.value); }}
>
{opt.label}
</Tag.CheckableTag>
))}
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-3">
<Input
className="w-56"
size="small"
prefix={<Search className="size-3.5 text-stone-400" />}
placeholder="搜索素材"
value={keyword}
allowClear
onChange={(e) => {
setPage(1);
setKeyword(e.target.value);
}}
/>
<div className="flex gap-1.5">
{[
{ label: "全部", value: "" },
{ label: "文本", value: "text" },
{ label: "图片", value: "image" },
].map((opt) => (
<Tag.CheckableTag
key={opt.value || "all"}
checked={kindFilter === opt.value}
className={cn("prompt-filter-tag", kindFilter === opt.value && "is-active")}
onChange={() => {
setPage(1);
setKindFilter(opt.value);
}}
>
{opt.label}
</Tag.CheckableTag>
))}
</div>
</div>
{query.isLoading ? (
<div className="flex justify-center py-16">
<Spin />
</div>
) : items.length ? (
<div className="grid grid-cols-4 gap-3">
{items.map((asset) => (
<PickerCard key={asset.id} title={asset.title} kind={asset.type} cover={asset.coverUrl} loading={inserting === asset.id} onClick={() => void handleInsert(asset)} />
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="没有素材" className="py-12" />
)}
{total > PAGE_SIZE && (
<div className="flex justify-center">
<Pagination size="small" current={page} pageSize={PAGE_SIZE} total={total} onChange={setPage} showSizeChanger={false} />
</div>
)}
</div>
</div>
{query.isLoading ? (
<div className="flex justify-center py-16"><Spin /></div>
) : items.length ? (
<div className="grid grid-cols-4 gap-3">
{items.map((asset) => (
<PickerCard
key={asset.id}
title={asset.title}
kind={asset.type}
cover={asset.coverUrl}
loading={inserting === asset.id}
onClick={() => void handleInsert(asset)}
/>
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="没有素材" className="py-12" />
)}
{total > PAGE_SIZE && (
<div className="flex justify-center">
<Pagination size="small" current={page} pageSize={PAGE_SIZE} total={total} onChange={setPage} showSizeChanger={false} />
</div>
)}
</div>
);
);
}
function PickerCard({ title, kind, cover, loading, onClick }: { title: string; kind: string; cover: string; loading?: boolean; onClick: () => void }) {
return (
<button
type="button"
className="group relative cursor-pointer overflow-hidden rounded-lg border border-stone-200 bg-white text-left transition hover:border-stone-400 hover:shadow-md dark:border-stone-700 dark:bg-stone-900 dark:hover:border-stone-500"
onClick={onClick}
disabled={loading}
>
{cover ? (
<img src={cover} alt={title} className="aspect-[4/3] w-full object-cover" />
) : (
<div className="flex aspect-[4/3] items-center justify-center bg-stone-100 p-3 text-center text-xs leading-5 text-stone-500 dark:bg-stone-800 dark:text-stone-400">
{title}
</div>
)}
<div className="p-2.5">
<div className="flex items-center justify-between gap-2">
<span className="line-clamp-1 text-xs font-medium text-stone-800 dark:text-stone-200">{title}</span>
<Tag className="m-0 shrink-0 text-[10px]">{kind === "image" ? "图片" : "文本"}</Tag>
</div>
</div>
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/60 dark:bg-stone-900/60">
<Spin size="small" />
</div>
)}
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-stone-950/0 text-sm font-medium text-white opacity-0 transition group-hover:bg-stone-950/55 group-hover:opacity-100">
</div>
</button>
);
return (
<button
type="button"
className="group relative cursor-pointer overflow-hidden rounded-lg border border-stone-200 bg-white text-left transition hover:border-stone-400 hover:shadow-md dark:border-stone-700 dark:bg-stone-900 dark:hover:border-stone-500"
onClick={onClick}
disabled={loading}
>
{cover ? (
<img src={cover} alt={title} className="aspect-[4/3] w-full object-cover" />
) : (
<div className="flex aspect-[4/3] items-center justify-center bg-stone-100 p-3 text-center text-xs leading-5 text-stone-500 dark:bg-stone-800 dark:text-stone-400">{title}</div>
)}
<div className="p-2.5">
<div className="flex items-center justify-between gap-2">
<span className="line-clamp-1 text-xs font-medium text-stone-800 dark:text-stone-200">{title}</span>
<Tag className="m-0 shrink-0 text-[10px]">{kind === "image" ? "图片" : "文本"}</Tag>
</div>
</div>
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/60 dark:bg-stone-900/60">
<Spin size="small" />
</div>
)}
<div className="pointer-events-none absolute inset-0 flex items-center justify-center bg-stone-950/0 text-sm font-medium text-white opacity-0 transition group-hover:bg-stone-950/55 group-hover:opacity-100"></div>
</button>
);
}
async function remoteImageToDataUrl(url: string) {
const response = await axios.get(url, { responseType: "blob" });
const blob = response.data as Blob;
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = () => reject(new Error("读取图片失败"));
reader.readAsDataURL(blob);
});
const response = await axios.get(url, { responseType: "blob" });
const blob = response.data as Blob;
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = () => reject(new Error("读取图片失败"));
reader.readAsDataURL(blob);
});
}
function MyAssetsTab({ onInsert }: { onInsert: (payload: InsertAssetPayload) => void }) {
const assets = useAssetStore((state) => state.assets);
const [keyword, setKeyword] = useState("");
const [kindFilter, setKindFilter] = useState("all");
const [page, setPage] = useState(1);
const assets = useAssetStore((state) => state.assets);
const [keyword, setKeyword] = useState("");
const [kindFilter, setKindFilter] = useState("all");
const [page, setPage] = useState(1);
const filtered = useMemo(() => {
const query = keyword.trim().toLowerCase();
return assets
.filter((a) => a.kind === "text" || a.kind === "image")
.filter((a) => kindFilter === "all" || a.kind === kindFilter)
.filter((a) => !query || [a.title, ...(a.tags || [])].join(" ").toLowerCase().includes(query));
}, [assets, keyword, kindFilter]);
const filtered = useMemo(() => {
const query = keyword.trim().toLowerCase();
return assets
.filter((a) => a.kind === "text" || a.kind === "image")
.filter((a) => kindFilter === "all" || a.kind === kindFilter)
.filter((a) => !query || [a.title, ...(a.tags || [])].join(" ").toLowerCase().includes(query));
}, [assets, keyword, kindFilter]);
const visible = useMemo(() => filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [filtered, page]);
const visible = useMemo(() => filtered.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE), [filtered, page]);
useEffect(() => {
const maxPage = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
setPage((v) => Math.min(v, maxPage));
}, [filtered.length]);
useEffect(() => {
const maxPage = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
setPage((v) => Math.min(v, maxPage));
}, [filtered.length]);
const handleInsert = (asset: Asset) => {
if (asset.kind === "text") {
onInsert({ kind: "text", content: asset.data.content, title: asset.title });
} else {
onInsert({ kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title });
}
};
const handleInsert = (asset: Asset) => {
if (asset.kind === "text") {
onInsert({ kind: "text", content: asset.data.content, title: asset.title });
} else {
onInsert({ kind: "image", dataUrl: asset.data.dataUrl, storageKey: asset.data.storageKey, title: asset.title });
}
};
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-3">
<Input
className="w-56"
size="small"
prefix={<Search className="size-3.5 text-stone-400" />}
placeholder="搜索素材"
value={keyword}
allowClear
onChange={(e) => { setPage(1); setKeyword(e.target.value); }}
/>
<div className="flex gap-1.5">
{kindOptions.map((opt) => (
<Tag.CheckableTag
key={opt.value}
checked={kindFilter === opt.value}
className={cn("prompt-filter-tag", kindFilter === opt.value && "is-active")}
onChange={() => { setPage(1); setKindFilter(opt.value); }}
>
{opt.label}
</Tag.CheckableTag>
))}
return (
<div className="space-y-4">
<div className="flex flex-wrap items-center gap-3">
<Input
className="w-56"
size="small"
prefix={<Search className="size-3.5 text-stone-400" />}
placeholder="搜索素材"
value={keyword}
allowClear
onChange={(e) => {
setPage(1);
setKeyword(e.target.value);
}}
/>
<div className="flex gap-1.5">
{kindOptions.map((opt) => (
<Tag.CheckableTag
key={opt.value}
checked={kindFilter === opt.value}
className={cn("prompt-filter-tag", kindFilter === opt.value && "is-active")}
onChange={() => {
setPage(1);
setKindFilter(opt.value);
}}
>
{opt.label}
</Tag.CheckableTag>
))}
</div>
</div>
{visible.length ? (
<div className="grid grid-cols-4 gap-3">
{visible.map((asset) => (
<PickerCard key={asset.id} title={asset.title} kind={asset.kind} cover={asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : "")} onClick={() => handleInsert(asset)} />
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="没有素材" className="py-12" />
)}
{filtered.length > PAGE_SIZE && (
<div className="flex justify-center">
<Pagination size="small" current={page} pageSize={PAGE_SIZE} total={filtered.length} onChange={setPage} showSizeChanger={false} />
</div>
)}
</div>
</div>
{visible.length ? (
<div className="grid grid-cols-4 gap-3">
{visible.map((asset) => (
<PickerCard key={asset.id} title={asset.title} kind={asset.kind} cover={asset.coverUrl || (asset.kind === "image" ? asset.data.dataUrl : "")} onClick={() => handleInsert(asset)} />
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="没有素材" className="py-12" />
)}
{filtered.length > PAGE_SIZE && (
<div className="flex justify-center">
<Pagination size="small" current={page} pageSize={PAGE_SIZE} total={filtered.length} onChange={setPage} showSizeChanger={false} />
</div>
)}
</div>
);
);
}