377 lines
12 KiB
TypeScript
377 lines
12 KiB
TypeScript
|
|
import React, { useState } from 'react'
|
|||
|
|
import {
|
|||
|
|
Card,
|
|||
|
|
Form,
|
|||
|
|
Input,
|
|||
|
|
Button,
|
|||
|
|
Typography,
|
|||
|
|
Alert,
|
|||
|
|
Space,
|
|||
|
|
Steps,
|
|||
|
|
Row,
|
|||
|
|
Col,
|
|||
|
|
message
|
|||
|
|
} from 'antd'
|
|||
|
|
import {
|
|||
|
|
UserOutlined,
|
|||
|
|
LockOutlined,
|
|||
|
|
CheckCircleOutlined,
|
|||
|
|
SettingOutlined,
|
|||
|
|
SafetyOutlined
|
|||
|
|
} from '@ant-design/icons'
|
|||
|
|
import apiClient from '../services/apiClient'
|
|||
|
|
|
|||
|
|
const { Title, Text, Paragraph } = Typography
|
|||
|
|
const { Step } = Steps
|
|||
|
|
|
|||
|
|
interface InstallPageProps {
|
|||
|
|
onInstallComplete: () => void
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 系统安装页面组件
|
|||
|
|
* 用于首次运行时设置管理员账号和密码
|
|||
|
|
*/
|
|||
|
|
const InstallPage: React.FC<InstallPageProps> = ({ onInstallComplete }) => {
|
|||
|
|
const [form] = Form.useForm()
|
|||
|
|
const [loading, setLoading] = useState(false)
|
|||
|
|
const [currentStep, setCurrentStep] = useState(0)
|
|||
|
|
const [error, setError] = useState<string | null>(null)
|
|||
|
|
const [lockFilePath, setLockFilePath] = useState<string>('')
|
|||
|
|
|
|||
|
|
const handleInstall = async (values: { username: string; password: string; confirmPassword: string }) => {
|
|||
|
|
if (values.password !== values.confirmPassword) {
|
|||
|
|
setError('两次输入的密码不一致')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setLoading(true)
|
|||
|
|
setError(null)
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await apiClient.post<any>('/api/auth/initialize', {
|
|||
|
|
username: values.username,
|
|||
|
|
password: values.password
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (result.success) {
|
|||
|
|
setCurrentStep(2)
|
|||
|
|
message.success('系统初始化成功!')
|
|||
|
|
|
|||
|
|
// 获取初始化信息以显示锁文件路径
|
|||
|
|
try {
|
|||
|
|
const checkResult = await apiClient.get<any>('/api/auth/check-initialization')
|
|||
|
|
if (checkResult.success && checkResult.lockFilePath) {
|
|||
|
|
setLockFilePath(checkResult.lockFilePath)
|
|||
|
|
}
|
|||
|
|
} catch (infoError) {
|
|||
|
|
console.warn('获取初始化信息失败:', infoError)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 延迟跳转到登录页面
|
|||
|
|
setTimeout(() => {
|
|||
|
|
onInstallComplete()
|
|||
|
|
}, 3000)
|
|||
|
|
} else {
|
|||
|
|
setError(result.message || '初始化失败')
|
|||
|
|
}
|
|||
|
|
} catch (error: any) {
|
|||
|
|
setError(error.message || '初始化失败,请稍后重试')
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validateUsername = (_: any, value: string) => {
|
|||
|
|
if (!value) {
|
|||
|
|
return Promise.reject(new Error('请输入用户名'))
|
|||
|
|
}
|
|||
|
|
if (value.length < 3) {
|
|||
|
|
return Promise.reject(new Error('用户名至少需要3个字符'))
|
|||
|
|
}
|
|||
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
|
|||
|
|
return Promise.reject(new Error('用户名只能包含字母、数字、下划线和横线'))
|
|||
|
|
}
|
|||
|
|
return Promise.resolve()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validatePassword = (_: any, value: string) => {
|
|||
|
|
if (!value) {
|
|||
|
|
return Promise.reject(new Error('请输入密码'))
|
|||
|
|
}
|
|||
|
|
if (value.length < 6) {
|
|||
|
|
return Promise.reject(new Error('密码至少需要6个字符'))
|
|||
|
|
}
|
|||
|
|
return Promise.resolve()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const steps = [
|
|||
|
|
{
|
|||
|
|
title: '欢迎',
|
|||
|
|
icon: <SettingOutlined />,
|
|||
|
|
description: '系统初始化向导'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '设置账号',
|
|||
|
|
icon: <UserOutlined />,
|
|||
|
|
description: '创建管理员账号'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '完成',
|
|||
|
|
icon: <CheckCircleOutlined />,
|
|||
|
|
description: '初始化完成'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div style={{
|
|||
|
|
minHeight: '100vh',
|
|||
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|||
|
|
display: 'flex',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
justifyContent: 'center',
|
|||
|
|
padding: '20px'
|
|||
|
|
}}>
|
|||
|
|
<Card
|
|||
|
|
style={{
|
|||
|
|
width: '100%',
|
|||
|
|
maxWidth: '500px',
|
|||
|
|
borderRadius: '16px',
|
|||
|
|
boxShadow: '0 8px 32px rgba(0,0,0,0.12)',
|
|||
|
|
border: 'none'
|
|||
|
|
}}
|
|||
|
|
styles={{ body: { padding: '40px' } }}
|
|||
|
|
>
|
|||
|
|
<div style={{ textAlign: 'center', marginBottom: '32px' }}>
|
|||
|
|
<div style={{
|
|||
|
|
fontSize: '48px',
|
|||
|
|
marginBottom: '16px',
|
|||
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|||
|
|
WebkitBackgroundClip: 'text',
|
|||
|
|
WebkitTextFillColor: 'transparent',
|
|||
|
|
backgroundClip: 'text'
|
|||
|
|
}}>
|
|||
|
|
🚀
|
|||
|
|
</div>
|
|||
|
|
<Title level={2} style={{ margin: 0, color: '#1a1a1a' }}>
|
|||
|
|
远程控制系统
|
|||
|
|
</Title>
|
|||
|
|
<Text style={{ color: '#666', fontSize: '16px' }}>
|
|||
|
|
系统初始化向导
|
|||
|
|
</Text>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<Steps
|
|||
|
|
current={currentStep}
|
|||
|
|
style={{ marginBottom: '32px' }}
|
|||
|
|
size="small"
|
|||
|
|
>
|
|||
|
|
{steps.map((step, index) => (
|
|||
|
|
<Step
|
|||
|
|
key={index}
|
|||
|
|
title={step.title}
|
|||
|
|
description={step.description}
|
|||
|
|
icon={step.icon}
|
|||
|
|
/>
|
|||
|
|
))}
|
|||
|
|
</Steps>
|
|||
|
|
|
|||
|
|
{currentStep === 0 && (
|
|||
|
|
<div style={{ textAlign: 'center' }}>
|
|||
|
|
<Alert
|
|||
|
|
message="欢迎使用远程控制系统"
|
|||
|
|
description={
|
|||
|
|
<div>
|
|||
|
|
<Paragraph style={{ marginBottom: '16px' }}>
|
|||
|
|
这是您首次运行本系统,需要进行初始化设置。
|
|||
|
|
</Paragraph>
|
|||
|
|
<Paragraph style={{ marginBottom: '16px' }}>
|
|||
|
|
请为系统创建一个管理员账号,该账号将用于登录和管理系统。
|
|||
|
|
</Paragraph>
|
|||
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|||
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|||
|
|
<SafetyOutlined style={{ color: '#52c41a' }} />
|
|||
|
|
<Text>安全的密码保护</Text>
|
|||
|
|
</div>
|
|||
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|||
|
|
<UserOutlined style={{ color: '#1890ff' }} />
|
|||
|
|
<Text>个性化用户名</Text>
|
|||
|
|
</div>
|
|||
|
|
</Space>
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
type="info"
|
|||
|
|
showIcon
|
|||
|
|
style={{ marginBottom: '24px', textAlign: 'left' }}
|
|||
|
|
/>
|
|||
|
|
<Button
|
|||
|
|
type="primary"
|
|||
|
|
size="large"
|
|||
|
|
onClick={() => setCurrentStep(1)}
|
|||
|
|
style={{
|
|||
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: '8px',
|
|||
|
|
height: '48px',
|
|||
|
|
fontSize: '16px'
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
开始设置
|
|||
|
|
</Button>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{currentStep === 1 && (
|
|||
|
|
<Form
|
|||
|
|
form={form}
|
|||
|
|
layout="vertical"
|
|||
|
|
onFinish={handleInstall}
|
|||
|
|
size="large"
|
|||
|
|
>
|
|||
|
|
<Alert
|
|||
|
|
message="创建管理员账号"
|
|||
|
|
description="请设置您的管理员用户名和密码,建议使用强密码以确保系统安全。"
|
|||
|
|
type="warning"
|
|||
|
|
showIcon
|
|||
|
|
style={{ marginBottom: '24px' }}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{error && (
|
|||
|
|
<Alert
|
|||
|
|
message={error}
|
|||
|
|
type="error"
|
|||
|
|
closable
|
|||
|
|
onClose={() => setError(null)}
|
|||
|
|
style={{ marginBottom: '16px' }}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<Form.Item
|
|||
|
|
name="username"
|
|||
|
|
label="管理员用户名"
|
|||
|
|
rules={[{ validator: validateUsername }]}
|
|||
|
|
>
|
|||
|
|
<Input
|
|||
|
|
prefix={<UserOutlined />}
|
|||
|
|
placeholder="请输入管理员用户名"
|
|||
|
|
style={{ borderRadius: '8px' }}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
|
|||
|
|
<Form.Item
|
|||
|
|
name="password"
|
|||
|
|
label="密码"
|
|||
|
|
rules={[{ validator: validatePassword }]}
|
|||
|
|
>
|
|||
|
|
<Input.Password
|
|||
|
|
prefix={<LockOutlined />}
|
|||
|
|
placeholder="请输入密码(至少6个字符)"
|
|||
|
|
style={{ borderRadius: '8px' }}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
|
|||
|
|
<Form.Item
|
|||
|
|
name="confirmPassword"
|
|||
|
|
label="确认密码"
|
|||
|
|
rules={[
|
|||
|
|
{ required: true, message: '请确认密码' },
|
|||
|
|
({ getFieldValue }) => ({
|
|||
|
|
validator(_, value) {
|
|||
|
|
if (!value || getFieldValue('password') === value) {
|
|||
|
|
return Promise.resolve()
|
|||
|
|
}
|
|||
|
|
return Promise.reject(new Error('两次输入的密码不一致'))
|
|||
|
|
},
|
|||
|
|
}),
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
<Input.Password
|
|||
|
|
prefix={<LockOutlined />}
|
|||
|
|
placeholder="请再次输入密码"
|
|||
|
|
style={{ borderRadius: '8px' }}
|
|||
|
|
/>
|
|||
|
|
</Form.Item>
|
|||
|
|
|
|||
|
|
<Row gutter={16} style={{ marginTop: '32px' }}>
|
|||
|
|
<Col span={12}>
|
|||
|
|
<Button
|
|||
|
|
block
|
|||
|
|
size="large"
|
|||
|
|
onClick={() => setCurrentStep(0)}
|
|||
|
|
style={{ borderRadius: '8px' }}
|
|||
|
|
>
|
|||
|
|
上一步
|
|||
|
|
</Button>
|
|||
|
|
</Col>
|
|||
|
|
<Col span={12}>
|
|||
|
|
<Button
|
|||
|
|
type="primary"
|
|||
|
|
htmlType="submit"
|
|||
|
|
block
|
|||
|
|
size="large"
|
|||
|
|
loading={loading}
|
|||
|
|
style={{
|
|||
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|||
|
|
border: 'none',
|
|||
|
|
borderRadius: '8px'
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{loading ? '初始化中...' : '完成设置'}
|
|||
|
|
</Button>
|
|||
|
|
</Col>
|
|||
|
|
</Row>
|
|||
|
|
</Form>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{currentStep === 2 && (
|
|||
|
|
<div style={{ textAlign: 'center' }}>
|
|||
|
|
<div style={{ fontSize: '64px', marginBottom: '24px' }}>
|
|||
|
|
✅
|
|||
|
|
</div>
|
|||
|
|
<Alert
|
|||
|
|
message="初始化完成!"
|
|||
|
|
description={
|
|||
|
|
<div>
|
|||
|
|
<Paragraph style={{ marginBottom: '16px' }}>
|
|||
|
|
系统已成功初始化,管理员账号创建完成。
|
|||
|
|
</Paragraph>
|
|||
|
|
<Paragraph style={{ marginBottom: '16px' }}>
|
|||
|
|
即将跳转到登录页面,请使用刚才设置的账号密码登录系统。
|
|||
|
|
</Paragraph>
|
|||
|
|
{lockFilePath && (
|
|||
|
|
<div style={{
|
|||
|
|
background: '#f6f6f6',
|
|||
|
|
padding: '12px',
|
|||
|
|
borderRadius: '6px',
|
|||
|
|
marginTop: '16px',
|
|||
|
|
textAlign: 'left'
|
|||
|
|
}}>
|
|||
|
|
<Text strong style={{ color: '#666' }}>💡 重要提示:</Text>
|
|||
|
|
<br />
|
|||
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>
|
|||
|
|
系统已创建初始化锁文件,防止重复初始化。
|
|||
|
|
</Text>
|
|||
|
|
<br />
|
|||
|
|
<Text code style={{ fontSize: '11px', wordBreak: 'break-all' }}>
|
|||
|
|
{lockFilePath}
|
|||
|
|
</Text>
|
|||
|
|
<br />
|
|||
|
|
<Text style={{ fontSize: '12px', color: '#666' }}>
|
|||
|
|
如需重新初始化,请先删除此文件。
|
|||
|
|
</Text>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
}
|
|||
|
|
type="success"
|
|||
|
|
showIcon
|
|||
|
|
style={{ textAlign: 'left' }}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</Card>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default InstallPage
|