306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
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>
|
||
)
|
||
}
|