mosaicmap/app/admin/common/page.tsx
2025-08-17 20:28:13 +08:00

182 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useEffect, useMemo, useState } from "react";
import { AdminPanel } from "./panel";
import { createDynamicAdminConfig } from "./dynamic-admin-config";
import {
useConfigs,
useConfigUpdater,
useConfigValidation,
flattenConfigObject,
unflattenConfigObject
} from "@/hooks/use-site-config";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Loader2, AlertCircle, CheckCircle, RefreshCw } from "lucide-react";
import { toast } from "sonner";
import { ScrollArea } from "@/components/ui/scroll-area";
// 配置管理页面内容组件
export default function AdminPage() {
const { configs, loading: loadingConfigs, error: errorConfigs, refetch: refetchConfigs } = useConfigs();
const { validation, loading: validationLoading, refetch: refetchValidation } = useConfigValidation();
const { updateConfigs, updating } = useConfigUpdater();
const [lastSaved, setLastSaved] = useState<Date | null>(null);
// 将 configs 列表转换为初始键值
const initialValuesFromConfigs = useMemo(() => {
const parseValue = (value: string | null | undefined, valueType: string) => {
if (value == null) return "";
const vt = (valueType || '').toLowerCase();
if (vt === 'number' || vt === 'int' || vt === 'integer') return Number(value);
if (vt === 'float' || vt === 'double') return parseFloat(value);
if (vt === 'bool' || vt === 'boolean') return value === 'true' || value === '1';
// 对于 json/object/array保留原字符串便于在 textarea 中编辑
return value;
};
const entries = configs.map((item) => ({ key: item.key, value: parseValue(item.value ?? undefined, item.valueType) }));
return unflattenConfigObject(entries);
}, [configs]);
// 处理配置保存
const handleSave = async (values: Record<string, any>) => {
try {
// 将表单值转换为配置更新格式
const configUpdates = flattenConfigObject(values);
const result = await updateConfigs(configUpdates);
setLastSaved(new Date());
toast.success(`配置保存成功${result.failedKeys?.length ? `,但有 ${result.failedKeys.length} 项失败` : ''}`);
// 刷新配置数据
refetchConfigs();
refetchValidation();
} catch (error) {
console.error('Save config error:', error);
toast.error('配置保存失败,请重试');
}
};
// 处理配置导出
const handleExport = async () => {
try {
const exportData = {
config: initialValuesFromConfigs,
timestamp: new Date().toISOString(),
version: '1.0'
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `config-export-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('配置导出成功');
} catch (error) {
console.error('Export error:', error);
toast.error('配置导出失败');
}
};
// 处理配置导入
const handleImport = async (file: File) => {
try {
const text = await file.text();
const importData = JSON.parse(text);
if (importData.config) {
const configUpdates = flattenConfigObject(importData.config);
const result = await updateConfigs(configUpdates);
if (result.success) {
toast.success('配置导入成功');
refetchConfigs();
refetchValidation();
} else {
toast.error(result.message || '配置导入失败');
}
} else {
toast.error('无效的配置文件格式');
}
} catch (error) {
console.error('Import error:', error);
toast.error('配置导入失败,请检查文件格式');
}
};
// 权限检查函数
const hasPermission = (permission: string) => {
// 这里应该实现实际的权限检查逻辑
const userPermissions = ["admin", "settings.read", "settings.write"];
return userPermissions.includes(permission);
};
if (loadingConfigs) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex items-center gap-2">
<Loader2 className="h-6 w-6 animate-spin" />
<span>...</span>
</div>
</div>
);
}
if (errorConfigs) {
return (
<Card className="border-destructive">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-destructive">
<AlertCircle className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground mb-4">
{(errorConfigs?.message) || '无法加载配置数据,请检查网络连接或联系管理员'}
</p>
<Button
onClick={() => { refetchConfigs(); }}
variant="outline"
size="sm"
className="gap-2"
>
<RefreshCw className="h-4 w-4" />
</Button>
</CardContent>
</Card>
);
}
// 创建动态配置
const adminConfig = createDynamicAdminConfig(
undefined,
handleSave,
handleExport,
handleImport,
configs
);
return (
<AdminPanel
config={adminConfig}
initialValues={initialValuesFromConfigs}
onSubmit={handleSave}
hasPermission={hasPermission}
className="min-h-screen"
/>
);
}