diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000..d16fc32 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,7 @@ + +待实现 +迁移管理页面,在正式环境中,需要验证env中配置的密码参数才能打开 + +2025.05.13 0.1.0 +首页添加了迁移管理入口按钮, 无需登录即可访问 +打开迁移管理页面时,将迁移历史读取出来 \ No newline at end of file diff --git a/client/migrations/migrations_app.tsx b/client/migrations/migrations_app.tsx new file mode 100644 index 0000000..8a98f46 --- /dev/null +++ b/client/migrations/migrations_app.tsx @@ -0,0 +1,171 @@ +import React, { useState } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Button, Space, Alert, Spin, Typography, Table } from 'antd'; +import axios from 'axios'; +import dayjs from 'dayjs'; +import { + QueryClient, + QueryClientProvider, + useQuery, +} from '@tanstack/react-query'; + +const { Title } = Typography; + +// 创建QueryClient实例 +const queryClient = new QueryClient(); + +interface MigrationResponse { + success: boolean; + error?: string; + failedResult?: any; +} + +interface MigrationHistory { + id: string; + name: string; + status: string; + timestamp: string; + batch: string; +} + +const MigrationsApp: React.FC = () => { + const [loading, setLoading] = useState(false); + const [migrationResult, setMigrationResult] = useState(null); + + const { data: historyData, isLoading: isHistoryLoading, error: historyError } = useQuery({ + queryKey: ['migrations-history'], + queryFn: async () => { + const response = await axios.get('/api/migrations/history'); + return response.data.history; + } + }); + + const runMigrations = async () => { + try { + setLoading(true); + setMigrationResult(null); + + const response = await axios.get('/api/migrations'); + setMigrationResult(response.data); + } catch (error: any) { + setMigrationResult({ + success: false, + error: error.response?.data?.error || '数据库迁移失败', + failedResult: error.response?.data?.failedResult + }); + } finally { + setLoading(false); + } + }; + + const columns = [ + { + title: '迁移名称', + dataIndex: 'name', + key: 'name', + sorter: (a: MigrationHistory, b: MigrationHistory) => a.name.localeCompare(b.name), + }, + { + title: '批次', + dataIndex: 'batch', + key: 'batch', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: string) => ( + + {status === 'completed' ? '已完成' : '失败'} + + ) + }, + { + title: '时间', + dataIndex: 'timestamp', + key: 'timestamp', + render: (timestamp: string) => dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss') + }, + ]; + + return ( +
+ 数据库迁移管理 + + + + + {loading && } + + {migrationResult && ( + migrationResult.success ? ( + + ) : ( + +

{migrationResult.error}

+ {migrationResult.failedResult && ( +
+                      {JSON.stringify(migrationResult.failedResult, null, 2)}
+                    
+ )} + + } + type="error" + showIcon + /> + ) + )} + + 迁移历史记录 + + {isHistoryLoading ? ( + + ) : historyError ? ( + + ) : ( + `共 ${total} 条记录`, + }} + bordered + className="migration-history-table" + style={{ marginTop: 16 }} + /> + )} + + + ); +}; + +// 渲染应用 +const root = createRoot(document.getElementById('root') as HTMLElement); +root.render( + + + +); \ No newline at end of file diff --git a/server/app.tsx b/server/app.tsx index e9f0740..adcfb67 100644 --- a/server/app.tsx +++ b/server/app.tsx @@ -354,6 +354,15 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) { > 进入移动端 + + {/* 迁移管理入口按钮 */} + + 数据库迁移 + + @@ -366,21 +375,25 @@ export default function({ apiClient, app, moduleDir }: ModuleParams) { const createHtmlWithConfig = (scriptConfig: EsmScriptConfig, title = '应用Starter') => { return (c: HonoContext) => { const isProd = GLOBAL_CONFIG.ENV === 'production'; + const isLocalDeploy = Deno.env.get('IS_LOCAL_DEPLOY') === 'true'; return c.html( + {isProd && } {title} - {isProd ? ( + {isLocalDeploy ? ( ) : ( - + )} - {isProd ? () : ()} + {isLocalDeploy ? () : ()}