增加初始化过程

This commit is contained in:
hanhh
2021-10-03 20:58:55 +08:00
parent bd1ca7e975
commit 14b20873c7
18 changed files with 714 additions and 223 deletions
+1 -5
View File
@@ -1,11 +1,7 @@
import { PageLoading } from '@ant-design/pro-layout';
const NewPageLoading = () => {
return (
<div style={{ margin: '-24px -24px 0' }}>
<PageLoading delay={1}></PageLoading>
</div>
);
return <PageLoading delay={1}></PageLoading>;
};
export default NewPageLoading;
+6
View File
@@ -18,6 +18,12 @@ export default {
hideInMenu: true,
component: '@/pages/login/index',
},
{
name: '初始化',
path: '/initialization',
hideInMenu: true,
component: '@/pages/initialization/index',
},
{
path: '/crontab',
name: '定时任务',
+4
View File
@@ -247,4 +247,8 @@
::placeholder {
opacity: 0.5 !important;
}
.ant-select-selection-placeholder {
opacity: 0.5 !important;
}
}
+32 -8
View File
@@ -22,6 +22,7 @@ export default function (props: any) {
const theme = useTheme();
const [user, setUser] = useState<any>();
const [loading, setLoading] = useState<boolean>(true);
const [systemInfo, setSystemInfo] = useState<{ isInitialized: boolean }>();
const logout = () => {
request.post(`${config.apiPrefix}logout`).then(() => {
@@ -30,6 +31,22 @@ export default function (props: any) {
});
};
const getSystemInfo = () => {
request.get(`${config.apiPrefix}system`).then(({ code, data }) => {
if (code === 200) {
setSystemInfo(data);
if (!data.isInitialized) {
history.push('/initialization');
setLoading(false);
} else {
getUser();
}
} else {
message.error(data);
}
});
};
const getUser = (needLoading = true) => {
needLoading && setLoading(true);
request.get(`${config.apiPrefix}user`).then(({ code, data }) => {
@@ -63,19 +80,21 @@ export default function (props: any) {
};
useEffect(() => {
const isAuth = localStorage.getItem(config.authKey);
if (!isAuth) {
history.push('/login');
}
vhCheck();
}, []);
useEffect(() => {
if (!user) {
if (systemInfo && systemInfo.isInitialized && !user) {
getUser();
}
}, [props.location.pathname]);
useEffect(() => {
if (!systemInfo) {
getSystemInfo();
}
}, [systemInfo]);
useEffect(() => {
setTheme();
}, [theme.theme]);
@@ -92,8 +111,13 @@ export default function (props: any) {
}
}, []);
if (props.location.pathname === '/login') {
return props.children;
if (['/login', '/initialization'].includes(props.location.pathname)) {
document.title = `${
(config.documentTitleMap as any)[props.location.pathname]
} - 控制面板`;
if (systemInfo) {
return props.children;
}
}
const isFirefox = navigator.userAgent.includes('Firefox');
@@ -148,7 +172,7 @@ export default function (props: any) {
];
}}
pageTitleRender={(props, pageName, info) => {
if (info) {
if (info && typeof info.pageName === 'string') {
return `${info.pageName} - 控制面板`;
}
return '控制面板';
+72
View File
@@ -0,0 +1,72 @@
@import '~antd/es/style/themes/default.less';
.container {
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
height: calc(100vh - var(--vh-offset, 0px));
overflow: auto;
background: @layout-body-background;
padding-top: 70px;
}
@media (min-width: @screen-md-min) {
.container {
background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
background-repeat: no-repeat;
background-position: center 110px;
background-size: 100%;
}
}
.top {
text-align: center;
}
.header {
display: flex;
align-items: center;
flex-direction: column;
}
.logo {
width: 48px;
display: block;
margin-bottom: 24px;
margin-top: 20px;
}
.title {
font-size: 20px;
margin-bottom: 16px;
}
.desc {
margin-top: 12px;
margin-bottom: 40px;
color: @text-color-secondary;
font-size: @font-size-base;
}
.main {
padding: 20px;
border-radius: 6px;
background-color: #f6f8fa;
border: 1px solid #ebedef;
display: flex;
width: 500px;
height: 500px;
.ant-steps {
width: 150px;
}
.steps-container {
flex: 1;
overflow-y: auto;
padding-right: 20px;
}
}
.extra {
margin-top: 20px;
}
+237
View File
@@ -0,0 +1,237 @@
import React, { Fragment, useEffect, useState } from 'react';
import {
Button,
Row,
Input,
Form,
message,
notification,
Steps,
Select,
} from 'antd';
import config from '@/utils/config';
import { history, Link } from 'umi';
import styles from './index.less';
import { request } from '@/utils/http';
const FormItem = Form.Item;
const { Step } = Steps;
const { Option } = Select;
const Login = () => {
const [loading, setLoading] = useState(false);
const [current, setCurrent] = React.useState(0);
const [fields, setFields] = useState<any[]>([]);
const next = () => {
setCurrent(current + 1);
};
const prev = () => {
setCurrent(current - 1);
};
const submitAccountSetting = (values: any) => {
setLoading(true);
request
.put(`${config.apiPrefix}init/user`, {
data: {
username: values.username,
password: values.password,
},
})
.then((data) => {
if (data.code === 200) {
next();
} else {
message.error(data.message);
}
})
.finally(() => setLoading(false));
};
const submitNotification = (values: any) => {
request
.put(`${config.apiPrefix}init/notification`, {
data: {
...values,
},
})
.then((_data: any) => {
if (_data && _data.code === 200) {
next();
} else {
message.error(_data.data);
}
})
.finally(() => setLoading(false));
};
const notificationModeChange = (value: string) => {
const _fields = (config.notificationModeMap as any)[value];
setFields(_fields || []);
};
const steps = [
{
title: '欢迎使用',
content: (
<div className={styles.top} style={{ marginTop: 120 }}>
<div className={styles.header}>
<span className={styles.title}>使</span>
</div>
<div className={styles.action}>
<Button
type="primary"
onClick={() => {
next();
}}
>
</Button>
</div>
</div>
),
},
{
title: '账户设置',
content: (
<Form onFinish={submitAccountSetting} layout="vertical">
<Form.Item
label="用户名"
name="username"
rules={[{ required: true }]}
style={{ maxWidth: 350 }}
>
<Input placeholder="用户名" />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true }]}
hasFeedback
style={{ maxWidth: 350 }}
>
<Input type="password" placeholder="密码" />
</Form.Item>
<Form.Item
name="confirm"
label="确认密码"
dependencies={['password']}
hasFeedback
style={{ maxWidth: 350 }}
rules={[
{
required: true,
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('您输入的两个密码不匹配!'));
},
}),
]}
>
<Input.Password placeholder="确认密码" />
</Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
</Form>
),
},
{
title: '通知设置',
content: (
<Form onFinish={submitNotification} layout="vertical">
<Form.Item
label="通知方式"
name="type"
rules={[{ required: true, message: '请选择通知方式' }]}
style={{ maxWidth: 350 }}
>
<Select
onChange={notificationModeChange}
placeholder="请选择通知方式"
>
{config.notificationModes
.filter((x) => x.value !== 'closed')
.map((x) => (
<Option value={x.value}>{x.label}</Option>
))}
</Select>
</Form.Item>
{fields.map((x) => (
<Form.Item
label={x.label}
name={x.label}
extra={x.tip}
rules={[{ required: x.required }]}
style={{ maxWidth: 400 }}
>
<Input.TextArea
autoSize={true}
placeholder={`请输入${x.label}`}
/>
</Form.Item>
))}
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
<Button type="link" htmlType="button" onClick={() => next()}>
</Button>
</Form>
),
},
{
title: '完成安装',
content: (
<div className={styles.top} style={{ marginTop: 120 }}>
<div className={styles.header}>
<span className={styles.title}></span>
</div>
<div className={styles.action}>
<Button
type="primary"
onClick={() => {
history.push('/login');
}}
>
</Button>
</div>
</div>
),
},
];
return (
<div className={styles.container}>
<div className={styles.top}>
<div className={styles.header}>
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
<span className={styles.title}></span>
</div>
</div>
<div className={styles.main}>
<Steps
current={current}
direction="vertical"
className={styles['ant-steps']}
>
{steps.map((item) => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<div className={styles['steps-container']}>
{steps[current].content}
</div>
</div>
</div>
);
};
export default Login;
+3 -19
View File
@@ -3,29 +3,12 @@
.container {
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
height: calc(100vh - var(--vh-offset, 0px));
overflow: auto;
background: @layout-body-background;
}
.lang {
width: 100%;
height: 40px;
line-height: 44px;
text-align: right;
:global(.ant-dropdown-trigger) {
margin-right: 24px;
}
}
.content {
position: absolute;
top: 86px;
left: 50%;
margin-left: -170px;
width: 340px;
padding: 0 16px;
padding-top: 70px;
}
@media (min-width: @screen-md-min) {
@@ -70,6 +53,7 @@
border-radius: 6px;
background-color: #f6f8fa;
border: 1px solid #ebedef;
width: 340px;
@media screen and (max-width: @screen-sm) {
width: 95%;
+82 -90
View File
@@ -122,97 +122,89 @@ const Login = () => {
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<img
alt="logo"
className={styles.logo}
src="/images/qinglong.png"
/>
<span className={styles.title}>
{twoFactor ? '两步验证' : config.siteName}
</span>
<div className={styles.top}>
<div className={styles.header}>
<img alt="logo" className={styles.logo} src="/images/qinglong.png" />
<span className={styles.title}>
{twoFactor ? '两步验证' : config.siteName}
</span>
</div>
</div>
<div className={styles.main}>
{twoFactor ? (
<Form layout="vertical" onFinish={completeTowFactor}>
<FormItem
name="code"
label="验证码"
rules={[
{
pattern: /^[0-9]{6}$/,
message: '验证码为6位数字',
validateTrigger: 'onBlur',
},
]}
hasFeedback
>
<Input placeholder="6位数字" autoComplete="off" />
</FormItem>
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={verifing}
>
</Button>
</Form>
) : (
<Form layout="vertical" onFinish={handleOk}>
<FormItem name="username" label="用户名" hasFeedback>
<Input placeholder="用户名" autoFocus />
</FormItem>
<FormItem name="password" label="密码" hasFeedback>
<Input type="password" placeholder="密码" />
</FormItem>
<Row>
{waitTime ? (
<Button type="primary" style={{ width: '100%' }} disabled>
<Countdown
valueStyle={{
color:
theme === 'vs'
? 'rgba(0,0,0,.25)'
: 'rgba(232, 230, 227, 0.25)',
}}
className="inline-countdown"
onFinish={() => setWaitTime(null)}
format="ss"
value={Date.now() + 1000 * waitTime}
/>
</Button>
) : (
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={loading}
>
</Button>
)}
</Row>
</Form>
)}
</div>
<div className={styles.extra}>
{twoFactor ? (
<div style={{ paddingLeft: 20, position: 'relative' }}>
<MobileOutlined style={{ position: 'absolute', left: 0, top: 4 }} />
</div>
</div>
<div className={styles.main}>
{twoFactor ? (
<Form layout="vertical" onFinish={completeTowFactor}>
<FormItem
name="code"
label="验证码"
rules={[
{
pattern: /^[0-9]{6}$/,
message: '验证码为6位数字',
validateTrigger: 'onBlur',
},
]}
hasFeedback
>
<Input placeholder="6位数字" autoComplete="off" />
</FormItem>
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={verifing}
>
</Button>
</Form>
) : (
<Form layout="vertical" onFinish={handleOk}>
<FormItem name="username" label="用户名" hasFeedback>
<Input placeholder="用户名" autoFocus />
</FormItem>
<FormItem name="password" label="密码" hasFeedback>
<Input type="password" placeholder="密码" />
</FormItem>
<Row>
{waitTime ? (
<Button type="primary" style={{ width: '100%' }} disabled>
<Countdown
valueStyle={{
color:
theme === 'vs'
? 'rgba(0,0,0,.25)'
: 'rgba(232, 230, 227, 0.25)',
}}
className="inline-countdown"
onFinish={() => setWaitTime(null)}
format="ss"
value={Date.now() + 1000 * waitTime}
/>
</Button>
) : (
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={loading}
>
</Button>
)}
</Row>
</Form>
)}
</div>
<div className={styles.extra}>
{twoFactor ? (
<div style={{ paddingLeft: 20, position: 'relative' }}>
<MobileOutlined
style={{ position: 'absolute', left: 0, top: 4 }}
/>
</div>
) : (
''
)}
</div>
) : (
''
)}
</div>
</div>
);
+1 -1
View File
@@ -17,7 +17,7 @@ const SecuritySettings = ({ user, userChange }: any) => {
const handleOk = (values: any) => {
request
.post(`${config.apiPrefix}user`, {
.put(`${config.apiPrefix}user`, {
data: {
username: values.username,
password: values.password,
+11
View File
@@ -174,4 +174,15 @@ export default {
{ label: 'emailPass', tip: '邮箱SMTP授权码', required: true },
],
},
documentTitleMap: {
'/login': '登录',
'/initialization': '初始化',
'/cron': '定时任务',
'/env': '环境变量',
'/config': '配置文件',
'/script': '脚本管理',
'/diff': '对比工具',
'/log': '任务日志',
'/setting': '系统设置',
},
};