diff --git a/web/client/src/components/assets/AssetGallery.tsx b/web/client/src/components/assets/AssetGallery.tsx
index fb0dee6..07227a9 100644
--- a/web/client/src/components/assets/AssetGallery.tsx
+++ b/web/client/src/components/assets/AssetGallery.tsx
@@ -1,3 +1,97 @@
+import { useState, useEffect } from 'react';
+import { Trash2, RefreshCw } from 'lucide-react';
+import { useAssets } from '@/hooks/useAssets';
+import { AssetPreview } from './AssetPreview';
+import { Button } from '@/components/ui/button';
+import type { Asset } from '@/types';
+
export function AssetGallery() {
- return
资产画廊
;
+ const [accountFilter, setAccountFilter] = useState('');
+ const [typeFilter, setTypeFilter] = useState('');
+ const [accounts, setAccounts] = useState<{ id: string; name: string }[]>([]);
+ const { assets, loading, remove } = useAssets({
+ accountId: accountFilter || undefined,
+ type: typeFilter || undefined,
+ });
+ const [previewAsset, setPreviewAsset] = useState(null);
+
+ useEffect(() => {
+ fetch('/api/accounts').then((r) => r.json()).then(setAccounts).catch(() => {});
+ }, []);
+
+ const handleScan = async () => {
+ await fetch('/api/assets/scan', { method: 'POST' });
+ window.location.reload();
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {loading ? (
+
加载中...
+ ) : assets.length === 0 ? (
+
暂无资产,点击"扫描"导入
+ ) : (
+
+ {assets.map((asset) => (
+
setPreviewAsset(asset)}
+ >
+ {asset.type === 'image' ? (
+
}`})
+ ) : (
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+ {previewAsset &&
setPreviewAsset(null)} />}
+
+ );
}
diff --git a/web/client/src/components/assets/AssetPreview.tsx b/web/client/src/components/assets/AssetPreview.tsx
new file mode 100644
index 0000000..30ff18b
--- /dev/null
+++ b/web/client/src/components/assets/AssetPreview.tsx
@@ -0,0 +1,35 @@
+import { X } from 'lucide-react';
+import type { Asset } from '@/types';
+
+export function AssetPreview({ asset, onClose }: { asset: Asset; onClose: () => void }) {
+ return (
+
+
+
+
e.stopPropagation()}>
+ {asset.type === 'image' ? (
+
}`})
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/web/client/src/hooks/useAssets.ts b/web/client/src/hooks/useAssets.ts
new file mode 100644
index 0000000..971f5cc
--- /dev/null
+++ b/web/client/src/hooks/useAssets.ts
@@ -0,0 +1,19 @@
+import { useState, useEffect, useCallback } from 'react';
+import { api } from '@/lib/api';
+import type { Asset } from '@/types';
+
+export function useAssets(params?: { accountId?: string; type?: string }) {
+ const [assets, setAssets] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const refresh = useCallback(() => {
+ setLoading(true);
+ api.listAssets(params).then(setAssets).finally(() => setLoading(false));
+ }, [params?.accountId, params?.type]);
+
+ useEffect(() => { refresh(); }, [refresh]);
+
+ const remove = (id: string) => api.deleteAsset(id).then(refresh);
+
+ return { assets, loading, refresh, remove };
+}