Files
Go-Web-Template/web-admin/src/features/auth/InitPage.tsx
2026-04-10 17:57:48 +08:00

306 lines
11 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.

import { useEffect, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Alert, Button, Card, Form, Input, Result, Select, Space, Spin, Typography, message } from 'antd'
import { initApi } from '@/lib/api'
import { useAuthStore } from '@/store/auth'
import type { InitCheckResult, InitDBPayload } from '@/types/system'
const dbTypeOptions: Array<{ label: string; value: InitDBPayload['dbType'] }> = [
{ label: 'MySQL', value: 'mysql' },
{ label: 'PostgreSQL', value: 'pgsql' },
{ label: 'SQLite', value: 'sqlite' },
{ label: 'MSSQL', value: 'mssql' },
]
type InitState = 'checking' | 'required' | 'ready'
const dbDefaults: Record<InitDBPayload['dbType'], Partial<InitDBPayload>> = {
mysql: {
host: '127.0.0.1',
port: '3306',
},
pgsql: {
host: '127.0.0.1',
port: '5432',
template: 'template1',
},
sqlite: {
dbPath: 'db',
},
mssql: {
host: '127.0.0.1',
port: '1433',
},
}
function applyDbDefaults(currentValues: InitDBPayload, nextDbType: InitDBPayload['dbType']): InitDBPayload {
return {
...currentValues,
...dbDefaults[nextDbType],
dbType: nextDbType,
}
}
export function InitPage() {
const navigate = useNavigate()
const token = useAuthStore((state) => state.token)
const [form] = Form.useForm<InitDBPayload>()
const [state, setState] = useState<InitState>('checking')
const [checkingError, setCheckingError] = useState<string | null>(null)
const [submitting, setSubmitting] = useState(false)
const dbType = Form.useWatch('dbType', form) ?? 'mysql'
const checkInitialization = async () => {
try {
const response = await initApi.checkDB()
const data: InitCheckResult = response.data
if (data.needInit) {
setState('required')
} else {
setState('ready')
}
setCheckingError(null)
} catch (error) {
const messageText = error instanceof Error ? error.message : '初始化状态检测失败'
setCheckingError(messageText)
}
}
useEffect(() => {
void checkInitialization()
}, [])
const helperText = useMemo(() => {
switch (dbType) {
case 'mysql':
return '将创建数据库并写回 MySQL 连接配置。'
case 'pgsql':
return '默认使用 PostgreSQL 公共库建库,可按需指定 template。'
case 'sqlite':
return '将创建本地 SQLite 数据库文件,并写回 sqlite 配置。'
case 'mssql':
return '将按当前连接信息接入 MSSQL并写回 mssql 配置。'
default:
return ''
}
}, [dbType])
const onDbTypeChange = (nextDbType: InitDBPayload['dbType']) => {
const currentValues = form.getFieldsValue()
form.setFieldsValue(applyDbDefaults(currentValues, nextDbType))
}
const submit = async () => {
const values = await form.validateFields()
setSubmitting(true)
try {
await initApi.initDB(values)
message.success('初始化完成,请使用 admin 账户登录')
navigate('/login', { replace: true })
} finally {
setSubmitting(false)
}
}
if (state === 'checking') {
return (
<div className="login-shell">
<section className="login-hero">
<span className="capsule">Project Bootstrap</span>
<div style={{ maxWidth: 620, marginTop: 72 }}>
<Typography.Title style={{ color: 'rgba(255,255,255,0.96)', fontSize: 52, marginBottom: 18 }}>
<br />
</Typography.Title>
<Typography.Paragraph style={{ color: 'rgba(255,255,255,0.78)', fontSize: 18, lineHeight: 1.8 }}>
使
</Typography.Paragraph>
</div>
</section>
<section className="login-form-wrap">
<Card className="glass-panel login-card" bordered={false}>
<div className="fullscreen-status" style={{ minHeight: 360 }}>
<Spin size="large" />
<span>...</span>
{checkingError ? (
<Alert
type="warning"
showIcon
message="检测失败"
description={checkingError}
action={
<Button size="small" onClick={() => void checkInitialization()}>
</Button>
}
/>
) : null}
</div>
</Card>
</section>
</div>
)
}
if (state === 'ready') {
return (
<div className="login-shell">
<section className="login-hero">
<span className="capsule">Project Bootstrap</span>
<div style={{ maxWidth: 620, marginTop: 72 }}>
<Typography.Title style={{ color: 'rgba(255,255,255,0.96)', fontSize: 52, marginBottom: 18 }}>
<br />
</Typography.Title>
<Typography.Paragraph style={{ color: 'rgba(255,255,255,0.78)', fontSize: 18, lineHeight: 1.8 }}>
</Typography.Paragraph>
</div>
</section>
<section className="login-form-wrap">
<Card className="glass-panel login-card" bordered={false}>
<Result
status="success"
title="无需再次初始化"
subTitle="如果需要调整系统配置,请登录后台后在系统配置页修改。"
extra={[
<Button key="login" type="primary" onClick={() => navigate(token ? '/' : '/login', { replace: true })}>
{token ? '进入后台' : '前往登录'}
</Button>,
<Button key="check" onClick={() => void checkInitialization()}>
</Button>,
]}
/>
</Card>
</section>
</div>
)
}
return (
<div className="login-shell">
<section className="login-hero">
<span className="capsule">Project Bootstrap</span>
<div style={{ maxWidth: 620, marginTop: 72 }}>
<Typography.Title style={{ color: 'rgba(255,255,255,0.96)', fontSize: 52, marginBottom: 18 }}>
<br />
</Typography.Title>
<Typography.Paragraph style={{ color: 'rgba(255,255,255,0.78)', fontSize: 18, lineHeight: 1.8 }}>
</Typography.Paragraph>
</div>
</section>
<section className="login-form-wrap">
<Card className="glass-panel login-card" bordered={false}>
<Typography.Title level={2} style={{ marginBottom: 8 }}>
</Typography.Title>
<Typography.Paragraph className="text-muted" style={{ marginBottom: 20 }}>
`admin`使
</Typography.Paragraph>
{checkingError ? (
<Alert
type="warning"
showIcon
message="状态检测存在异常"
description={checkingError}
style={{ marginBottom: 16 }}
/>
) : null}
<Form
form={form}
layout="vertical"
initialValues={{
adminPassword: '',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
dbName: 'go_web_template',
userName: 'root',
dbPath: 'db',
template: 'template1',
}}
onFinish={submit}
>
<Form.Item name="dbType" label="数据库类型" rules={[{ required: true, message: '请选择数据库类型' }]}>
<Select options={dbTypeOptions} onChange={onDbTypeChange} />
</Form.Item>
<Typography.Paragraph className="text-muted" style={{ marginTop: -8, marginBottom: 16 }}>
{helperText}
</Typography.Paragraph>
<div className="init-form-grid">
{dbType === 'sqlite' ? (
<Form.Item
name="dbPath"
label="数据库目录"
rules={[{ required: true, message: '请输入 SQLite 数据库目录' }]}
>
<Input placeholder="例如 db" />
</Form.Item>
) : (
<>
<Form.Item name="host" label="数据库地址" rules={[{ required: true, message: '请输入数据库地址' }]}>
<Input placeholder="例如 127.0.0.1" />
</Form.Item>
<Form.Item name="port" label="数据库端口" rules={[{ required: true, message: '请输入数据库端口' }]}>
<Input placeholder="例如 3306" />
</Form.Item>
<Form.Item
name="userName"
label="数据库用户名"
rules={[{ required: true, message: '请输入数据库用户名' }]}
>
<Input placeholder="例如 root" />
</Form.Item>
<Form.Item name="password" label="数据库密码">
<Input.Password placeholder="请输入数据库密码" />
</Form.Item>
</>
)}
<Form.Item name="dbName" label="数据库名" rules={[{ required: true, message: '请输入数据库名' }]}>
<Input placeholder={dbType === 'sqlite' ? '例如 go_web_template' : '例如 go_web_template'} />
</Form.Item>
{dbType === 'pgsql' ? (
<Form.Item name="template" label="建库模板">
<Input placeholder="默认 template1" />
</Form.Item>
) : (
<div />
)}
<Form.Item
name="adminPassword"
label="管理员密码"
rules={[
{ required: true, message: '请输入管理员密码' },
{ min: 6, message: '管理员密码至少 6 位' },
]}
>
<Input.Password placeholder="初始化后 admin 账户使用此密码登录" />
</Form.Item>
</div>
<Alert
type="info"
showIcon
style={{ marginBottom: 20 }}
message="初始化完成后,后端会立即持有数据库连接,并将连接配置写回当前配置文件。"
/>
<Space wrap>
<Button type="primary" htmlType="submit" loading={submitting}>
</Button>
<Button onClick={() => void checkInitialization()}></Button>
<Button onClick={() => navigate('/login', { replace: true })}></Button>
</Space>
</Form>
</Card>
</section>
</div>
)
}