Explorar el Código

完成了项目整体结构,cloud大致布局,登录注册相关表单,接下来细化网盘的前端组件

Shellmiao hace 3 años
padre
commit
bd5b353c82

+ 5 - 0
.idea/.gitignore

@@ -0,0 +1,5 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/

+ 12 - 0
.idea/STCloudFrontEnd.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/STCloudFrontEnd.iml" filepath="$PROJECT_DIR$/.idea/STCloudFrontEnd.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 17 - 0
craco.config.js

@@ -0,0 +1,17 @@
+const CracoLessPlugin = require('craco-less');
+
+module.exports = {
+    plugins: [
+        {
+            plugin: CracoLessPlugin,
+            options: {
+                lessLoaderOptions: {
+                    lessOptions: {
+                        modifyVars: { '@primary-color': '#1890ff' },
+                        javascriptEnabled: true,
+                    },
+                },
+            },
+        },
+    ],
+};

+ 10 - 3
package.json

@@ -3,20 +3,27 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@ant-design/icons": "^4.6.4",
+    "@craco/craco": "^6.2.0",
     "@testing-library/jest-dom": "^5.11.4",
     "@testing-library/react": "^11.1.0",
     "@testing-library/user-event": "^12.1.10",
+    "ant-design-icons": "^1.3.3",
     "antd": "^4.16.13",
+    "axios": "^0.21.4",
+    "craco-less": "^1.20.0",
+    "qs": "^6.10.1",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
     "react-router": "^5.2.1",
+    "react-router-dom": "^5.3.0",
     "react-scripts": "4.0.3",
     "web-vitals": "^1.0.1"
   },
   "scripts": {
-    "start": "react-scripts start",
-    "build": "react-scripts build",
-    "test": "react-scripts test",
+    "start": "craco start",
+    "build": "craco build",
+    "test": "craco test",
     "eject": "react-scripts eject"
   },
   "eslintConfig": {

+ 0 - 38
src/App.css

@@ -1,38 +0,0 @@
-.App {
-  text-align: center;
-}
-
-.App-logo {
-  height: 40vmin;
-  pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
-  .App-logo {
-    animation: App-logo-spin infinite 20s linear;
-  }
-}
-
-.App-header {
-  background-color: #282c34;
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: calc(10px + 2vmin);
-  color: white;
-}
-
-.App-link {
-  color: #61dafb;
-}
-
-@keyframes App-logo-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}

+ 12 - 22
src/App.js

@@ -1,25 +1,15 @@
-import logo from './logo.svg';
-import './App.css';
+import React, {Component} from "react";
+import RouterWrap from "./routes/router";
+import './App.less';
 
-function App() {
-  return (
-    <div className="App">
-      <header className="App-header">
-        <img src={logo} className="App-logo" alt="logo" />
-        <p>
-          Edit <code>src/App.js</code> and save to reload.
-        </p>
-        <a
-          className="App-link"
-          href="https://reactjs.org"
-          target="_blank"
-          rel="noopener noreferrer"
-        >
-          Learn React
-        </a>
-      </header>
-    </div>
-  );
+class App extends Component {
+    render() {
+        return (
+            <div className={'App'}>
+                <RouterWrap/>
+            </div>
+        )
+    }
 }
 
-export default App;
+export default App;

+ 1 - 0
src/App.less

@@ -0,0 +1 @@
+@import '~antd/dist/antd.less';

+ 163 - 0
src/components/Forms/ForgetPasswordForm.js

@@ -0,0 +1,163 @@
+import React, {Component} from "react";
+import {Button, Form, Input, Steps, Row, Col} from "antd";
+import './Form.less'
+import {LockOutlined, MailOutlined, SecurityScanOutlined, UserOutlined} from "@ant-design/icons";
+
+const {Step} = Steps;
+
+class ForgetPasswordForm extends Component {
+    state = {
+        forgetStatus: 'check',
+        currentStep: 0,
+        stepProcessStatus: 'process',
+    }
+
+    handleCheckSuccessfully = () => {
+        this.setState({
+            forgetStatus: 'confirm',
+            currentStep: 1,
+        })
+    }
+
+    handleConfirmSuccessfully = () => {
+        this.setState({
+            forgetStatus: 'reset',
+            currentStep: 2,
+        })
+    }
+
+    render() {
+        let {forgetStatus, currentStep, stepProcessStatus} = this.state;
+
+        return (
+            <Row>
+                <Col span={8}>
+                    <Steps direction="vertical" size={'small'} current={currentStep} status={stepProcessStatus}>
+                        <Step title="Check Identity"/>
+                        <Step title="Confirm Code"/>
+                        <Step title="Reset Password"/>
+                    </Steps>
+                </Col>
+                <Col span={16}>
+                    {
+                        forgetStatus === 'check' ?
+                            <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                                  initialValues={{remember: true}}>
+                                <Form.Item
+                                    name="account"
+                                    rules={[{required: true, message: 'Please input your Username!'}]}
+                                >
+                                    <Input
+                                        prefix={<UserOutlined className="site-form-item-icon"/>}
+                                        placeholder="Username"
+                                        size="large"
+                                    />
+                                </Form.Item>
+                                <Form.Item
+                                    name="email"
+                                    rules={[
+                                        {
+                                            type: 'email',
+                                            message: 'The input is not valid E-mail!',
+                                        },
+                                        {
+                                            required: true,
+                                            message: 'Please input your E-mail!',
+                                        },
+                                    ]}
+                                >
+                                    <Input
+                                        prefix={<MailOutlined className="site-form-item-icon"/>}
+                                        placeholder="E-mail"
+                                        size="large"
+                                    />
+                                </Form.Item>
+                                <Form.Item style={{marginBottom: '10px'}}>
+                                    <Button size="large" type="primary" htmlType="submit" className="login-form-button"
+                                            style={{width: '100%'}} onClick={this.handleCheckSuccessfully}>
+                                        RESET YOUR PASSWORD
+                                    </Button>
+                                </Form.Item>
+                                <Form.Item style={{marginBottom: '0'}}>
+                                    <Button type="link" block style={{textAlign: 'right'}} onClick={() => {
+                                        this.props.handleLoginStatus('login')
+                                    }}>
+                                        LOGIN
+                                    </Button>
+                                </Form.Item>
+                            </Form> : forgetStatus === 'confirm' ?
+                                <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                                      initialValues={{remember: true}}>
+                                    <Form.Item
+                                        name="VerificationCode"
+                                        rules={[{required: true, message: 'Please input your Verification Code!'}]}
+                                    >
+                                        <Input
+                                            prefix={<SecurityScanOutlined className="site-form-item-icon"/>}
+                                            placeholder="Verification Code"
+                                            size="large"
+                                        />
+                                    </Form.Item>
+                                    <Form.Item style={{marginBottom: '10px'}}>
+                                        <Button size="large" type="primary" htmlType="submit" className="login-form-button"
+                                                style={{width: '100%'}} onClick={this.handleConfirmSuccessfully}>
+                                            CONFIRM
+                                        </Button>
+                                    </Form.Item>
+                                </Form> :
+                                <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                                      initialValues={{remember: true}}>
+                                    <Form.Item
+                                        name="password"
+                                        rules={[
+                                            {
+                                                required: true,
+                                                message: 'Please input your password!',
+                                            },
+                                        ]}
+                                        hasFeedback
+                                    >
+                                        <Input.Password placeholder="Password"
+                                                        prefix={<LockOutlined className="site-form-item-icon"/>}
+                                                        size="large"
+                                        />
+                                    </Form.Item>
+                                    <Form.Item
+                                        name="confirm"
+                                        dependencies={['password']}
+                                        hasFeedback
+                                        rules={[
+                                            {
+                                                required: true,
+                                                message: 'Please confirm your password!',
+                                            },
+                                            ({getFieldValue}) => ({
+                                                validator(_, value) {
+                                                    if (!value || getFieldValue('password') === value) {
+                                                        return Promise.resolve();
+                                                    }
+                                                    return Promise.reject(new Error('The two passwords that you entered do not match!'));
+                                                },
+                                            }),
+                                        ]}
+                                    >
+                                        <Input.Password prefix={<LockOutlined className="site-form-item-icon"/>}
+                                                        placeholder="Confirm Password"
+                                                        size="large"
+                                        />
+                                    </Form.Item>
+                                    <Form.Item style={{marginBottom: '10px'}}>
+                                        <Button size="large" type="primary" htmlType="submit" className="login-form-button"
+                                                style={{width: '100%'}}>
+                                            RESET PASSWORD
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                    }
+                </Col>
+            </Row>
+        )
+    }
+}
+
+export default ForgetPasswordForm;

+ 3 - 0
src/components/Forms/Form.less

@@ -0,0 +1,3 @@
+.site-form-item-icon {
+  color: rgba(0, 0, 0, .25)
+}

+ 64 - 0
src/components/Forms/LoginForm.js

@@ -0,0 +1,64 @@
+import React, {Component} from "react";
+import {Button, Form, Input, message} from "antd";
+import {apiLogin} from "../../services/API/API";
+import {LockOutlined, UserOutlined} from "@ant-design/icons";
+import './Form.less'
+
+class LoginForm extends Component {
+    handleSubmitLogin = values => {
+        console.log('Received values of form: ', values);
+        let params = {
+            ...values
+        }
+        apiLogin(params).then(res => {
+            if (res.data.code === 1001) {
+                message.success(res.data.message);
+            } else {
+                message.error(res.data.message)
+            }
+        })
+    };
+
+    render() {
+        return (
+            <Form onSubmit={this.handleSubmitLogin} className="login-form" initialValues={{remember: true}}>
+                <Form.Item
+                    name="account"
+                    rules={[{required: true, message: 'Please input your Username or E-mail!'}]}
+                >
+                    <Input
+                        prefix={<UserOutlined className="site-form-item-icon"/>}
+                        placeholder="Username or E-mail"
+                        size="large"
+                    />
+                </Form.Item>
+                <Form.Item
+                    name="password"
+                    rules={[{required: true, message: 'Please input your Password!'}]}
+                >
+                    <Input
+                        prefix={<LockOutlined className="site-form-item-icon"/>}
+                        type="password"
+                        placeholder="Password"
+                        size="large"
+                    />
+                </Form.Item>
+                <Form.Item style={{marginBottom: '10px'}}>
+                    <Button size="large" type="primary" htmlType="submit" className="login-form-button"
+                            style={{width: '100%'}}>
+                        LOGIN
+                    </Button>
+                </Form.Item>
+                <Form.Item style={{marginBottom: '0'}}>
+                    <Button type="link" block style={{textAlign: 'right'}} onClick={() => {
+                        this.props.handleLoginStatus('forget')
+                    }}>
+                        FORGET YOUR PASSWORD?
+                    </Button>
+                </Form.Item>
+            </Form>
+        )
+    }
+}
+
+export default LoginForm;

+ 89 - 0
src/components/Forms/RegisterForm.js

@@ -0,0 +1,89 @@
+import React, {Component} from "react";
+import {Button, Form, Input} from "antd";
+import {LockOutlined, MailOutlined, UserOutlined} from "@ant-design/icons";
+import './Form.less'
+
+class RegisterForm extends Component {
+    render() {
+        return (
+            <Form onSubmit={this.handleSubmitRegister} className="register-form">
+                <Form.Item
+                    name="username"
+                    rules={[{required: true, message: 'Please input your Username!'}]}
+                >
+                    <Input
+                        prefix={<UserOutlined className="site-form-item-icon"/>}
+                        placeholder="Username"
+                        size="large"
+                    />
+                </Form.Item>
+                <Form.Item
+                    name="email"
+                    rules={[
+                        {
+                            type: 'email',
+                            message: 'The input is not valid E-mail!',
+                        },
+                        {
+                            required: true,
+                            message: 'Please input your E-mail!',
+                        },
+                    ]}
+                >
+                    <Input
+                        prefix={<MailOutlined className="site-form-item-icon"/>}
+                        placeholder="E-mail"
+                        size="large"
+                    />
+                </Form.Item>
+                <Form.Item
+                    name="password"
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Please input your password!',
+                        },
+                    ]}
+                    hasFeedback
+                >
+                    <Input.Password placeholder="Password"
+                                    prefix={<LockOutlined className="site-form-item-icon"/>}
+                                    size="large"
+                    />
+                </Form.Item>
+                <Form.Item
+                    name="confirm"
+                    dependencies={['password']}
+                    hasFeedback
+                    rules={[
+                        {
+                            required: true,
+                            message: 'Please confirm your password!',
+                        },
+                        ({getFieldValue}) => ({
+                            validator(_, value) {
+                                if (!value || getFieldValue('password') === value) {
+                                    return Promise.resolve();
+                                }
+                                return Promise.reject(new Error('The two passwords that you entered do not match!'));
+                            },
+                        }),
+                    ]}
+                >
+                    <Input.Password prefix={<LockOutlined className="site-form-item-icon"/>}
+                                    placeholder="Confirm Password"
+                                    size="large"
+                    />
+                </Form.Item>
+                <Form.Item>
+                    <Button size="large" type="primary" htmlType="submit"
+                            className="register-form-button" style={{width: '100%'}}>
+                        REGISTER
+                    </Button>
+                </Form.Item>
+            </Form>
+        )
+    }
+}
+
+export default RegisterForm;

+ 15 - 0
src/components/MyBreadCrumb/MyBreadCrumb.js

@@ -0,0 +1,15 @@
+import React, {Component} from "react";
+import {Breadcrumb} from "antd";
+
+class MyBreadCrumb extends Component {
+    render() {
+        return (
+            <Breadcrumb style={{margin: '16px 0'}}>
+                <Breadcrumb.Item>User</Breadcrumb.Item>
+                <Breadcrumb.Item>Bill</Breadcrumb.Item>
+            </Breadcrumb>
+        )
+    }
+}
+
+export default MyBreadCrumb;

+ 159 - 0
src/components/Page/DiskPage.js

@@ -0,0 +1,159 @@
+import React, {Component} from "react";
+import {Table, Tag, Space} from 'antd';
+import {FileTextOutlined, FolderOpenOutlined} from "@ant-design/icons";
+
+const columns = [
+    {
+        title: 'Type',
+        dataIndex: 'type',
+        key: 'type',
+        width: '15%',
+        render: text => {
+            if (text === 'file') {
+                return (
+                    <div>
+                        <FileTextOutlined/>
+                        &nbsp; FILE
+                    </div>
+                )
+            } else if (text === 'folder') {
+                return (
+                    <div>
+                        <FolderOpenOutlined/>
+                        &nbsp; FOLDER
+                    </div>
+                )
+            }
+        }
+    },
+    {
+        title: 'Name',
+        dataIndex: 'name',
+        key: 'name',
+        render: (text, record) => {
+            if (record.type === 'file') {
+                return (
+                    <div>
+                        {text}
+                    </div>
+                )
+            } else {
+                return (
+                    <a>{text}</a>
+                )
+            }
+        },
+    },
+    {
+        title: 'Tags',
+        key: 'tags',
+        dataIndex: 'tags',
+        render: tags => (
+            <>
+                {tags.map(tag => {
+                    let color = tag.length > 5 ? 'geekblue' : 'green';
+                    if (tag === 'loser') {
+                        color = 'volcano';
+                    }
+                    return (
+                        <Tag color={color} key={tag}>
+                            {tag.toUpperCase()}
+                        </Tag>
+                    );
+                })}
+            </>
+        ),
+    },
+    {
+        title: 'Action',
+        key: 'action',
+        render: (text, record) => {
+            if (record.type === 'file') {
+                return (
+                    <Space size="middle">
+                        <a>Download {record.name}</a>
+                        <a>Delete</a>
+                    </Space>
+                )
+            } else {
+                return (
+                    <a>Get In</a>
+                )
+            }
+        }
+    },
+];
+
+const data = [
+    {
+        key: '1',
+        type: 'file',
+        name: '高等数学',
+        tags: ['nice', 'developer'],
+    },
+    {
+        key: '2',
+        type: 'file',
+        name: 'Jim Green',
+        tags: ['loser'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+    {
+        key: '3',
+        type: 'folder',
+        name: 'Joe Black',
+        tags: ['cool', 'teacher'],
+    },
+
+];
+
+class DiskPage extends Component {
+    render() {
+        return (
+            <Table columns={columns} dataSource={data}/>
+        )
+    }
+}
+
+export default DiskPage;

+ 24 - 0
src/components/Page/TeamPage.js

@@ -0,0 +1,24 @@
+import React, {Component} from "react";
+import DiskPage from "./DiskPage";
+import {Tabs} from "antd";
+
+const {TabPane} = Tabs;
+
+class TeamPage extends Component {
+    render() {
+        return (
+            <div>
+                <Tabs defaultActiveKey="1" tabPosition='top' style={{height: 220}}>
+                    {[...Array.from({length: 30}, (v, i) => i)].map(i => (
+                        <TabPane tab={`Tab-${i}`} key={i} disabled={i === 28}>
+                            Content of tab {i}
+                        </TabPane>
+                    ))}
+                </Tabs>
+                <DiskPage/>
+            </div>
+        );
+    }
+}
+
+export default TeamPage;

+ 61 - 0
src/components/Sider/MySider.js

@@ -0,0 +1,61 @@
+import React, {Component} from "react";
+import {Layout, Menu} from "antd";
+import {FolderOutlined, SettingOutlined, TeamOutlined, UserOutlined} from "@ant-design/icons";
+import {Link} from "react-router-dom";
+
+const {Sider} = Layout;
+const {SubMenu} = Menu;
+
+class MySider extends Component {
+    constructor(props) {
+        super(props);
+        this.state = {
+            collapsed: true,
+            isLogged: false,
+        }
+    }
+
+    onCollapse = collapsed => {
+        console.log(collapsed);
+        this.setState({collapsed});
+    };
+
+    render() {
+        return (
+            <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
+                <div className="logo"/>
+                <Menu theme="dark" defaultSelectedKeys={['file']} mode="inline">
+                    <Menu.Item key="account" icon={<UserOutlined/>}>
+                        <Link to={'/cloud/account'}>
+                            Account
+                        </Link>
+                    </Menu.Item>
+                    <Menu.Item key="file" icon={<FolderOutlined/>}>
+                        <Link to={'/cloud/file'}>
+                            My Files
+                        </Link>
+                    </Menu.Item>
+                    <SubMenu key="team" icon={<TeamOutlined/>} title="Team">
+                        <Menu.Item key="my_team">
+                            <Link to={'/cloud/teams'}>
+                                My Team
+                            </Link>
+                        </Menu.Item>
+                        <Menu.Item key="join_team">
+                            <Link to={'/cloud/join'}>
+                                Join Team
+                            </Link>
+                        </Menu.Item>
+                    </SubMenu>
+                    <Menu.Item key="setting" icon={<SettingOutlined/>}>
+                        <Link to={'/cloud/setting'}>
+                            Setting
+                        </Link>
+                    </Menu.Item>
+                </Menu>
+            </Sider>
+        )
+    }
+}
+
+export default MySider;

+ 29 - 0
src/layouts/DefaultLayout/DefaultLayout.js

@@ -0,0 +1,29 @@
+import React, {Component} from "react";
+import {Layout, Breadcrumb} from 'antd';
+import './DefaultLayout.less'
+import MySider from "../../components/Sider/MySider";
+import MyBreadCrumb from "../../components/MyBreadCrumb/MyBreadCrumb";
+import PageRouter from "../../routes/PageRouter/PageRouter";
+
+const {Content, Footer} = Layout;
+
+class DefaultLayout extends Component {
+    render() {
+        return (
+            <Layout style={{minHeight: '100vh'}}>
+                <MySider/>
+                <Layout className="site-layout">
+                    <Content style={{margin: '0 16px'}}>
+                        {/*<MyBreadCrumb/>*/}
+                        <div className="site-layout-background" style={{marginTop: 20, padding: 24, minHeight: '80vh'}}>
+                            <PageRouter/>
+                        </div>
+                    </Content>
+                    <Footer style={{textAlign: 'center'}}>STCloud ©2021 Created by STeam</Footer>
+                </Layout>
+            </Layout>
+        )
+    }
+}
+
+export default DefaultLayout;

+ 9 - 0
src/layouts/DefaultLayout/DefaultLayout.less

@@ -0,0 +1,9 @@
+.logo {
+  height: 32px;
+  margin: 16px;
+  background: rgba(255, 255, 255, 0.3);
+}
+
+.site-layout .site-layout-background {
+  background: #fff;
+}

+ 27 - 0
src/layouts/HomePage/HomePage.js

@@ -0,0 +1,27 @@
+import React, {Component} from "react";
+import {Layout, Row, Col} from 'antd';
+import {CloudServerOutlined} from "@ant-design/icons";
+import './HomePage.less'
+import UserPage from "../User/UserPage";
+
+const {Header, Footer, Content} = Layout;
+
+class HomePage extends Component {
+
+    render() {
+        return (
+            <Layout>
+                <Header id={'HomeHeader'}>
+                    <CloudServerOutlined/>
+                    &nbsp;STCloud
+                </Header>
+                <Content className={'HomeContent'}>
+                    <UserPage/>
+                </Content>
+                <Footer style={{textAlign: 'center'}}>STCloud ©2021 Created by STeam</Footer>
+            </Layout>
+        )
+    }
+}
+
+export default HomePage;

+ 12 - 0
src/layouts/HomePage/HomePage.less

@@ -0,0 +1,12 @@
+#HomeHeader {
+  color: white;
+  font-weight: bolder;
+  font-size: 4vh;
+}
+
+.HomeContent {
+  height: 81vh;
+  background-size: cover;
+  background-position: center center;
+  background-image: linear-gradient(rgba(169, 169, 169, 0.3), rgba(169, 169, 169, 0.3)), url(https://shellmia0-blog-1304113800.cos.ap-beijing.myqcloud.com/back.png);
+}

+ 76 - 0
src/layouts/User/UserPage.js

@@ -0,0 +1,76 @@
+import React, {Component} from "react";
+import LoginForm from "../../components/Forms/LoginForm";
+import {Col, Tabs} from "antd";
+import RegisterForm from "../../components/Forms/RegisterForm";
+import './UserPage.less'
+import ForgetPasswordForm from "../../components/Forms/ForgetPasswordForm";
+
+const {TabPane} = Tabs;
+
+class UserPage extends Component {
+    state = {
+        loginStatus: "login",
+        tagWidth: 8,
+        tagOffset: 12,
+    };
+
+    handleLoginStatus = LoginStatus => {
+        this.setState(
+            {
+                loginStatus: LoginStatus,
+            }
+        )
+        if (LoginStatus === 'forget') {
+            this.setState(
+                {
+                    tagWidth: 10,
+                    tagOffset: 11,
+                }
+            )
+        } else {
+            this.setState(
+                {
+                    tagWidth: 8,
+                    tagOffset: 12,
+                }
+            )
+        }
+    }
+
+    render() {
+        let {loginStatus, tagWidth, tagOffset} = this.state;
+        const renderTabBar = (props, DefaultTabBar) => (
+
+            <DefaultTabBar {...props} style={{width: '100%'}}/>
+
+        );
+        return (
+            <Col span={tagWidth} offset={tagOffset} className={'UserCard'}>
+                <Tabs
+                    onChange={this.callback}
+                    size={'large'}
+                    animated={true}
+                    centered={true}
+                    tabBarGutter={50}
+                    renderTabBar={renderTabBar}
+                    defaultActiveKey="1"
+                >
+                    <TabPane tab={loginStatus === 'forget' ? "重置密码" : "登录"} key="1"
+                             style={{padding: "20px 30px 10px"}}>
+                        {
+                            loginStatus === 'login' ?
+                                <LoginForm handleLoginStatus={this.handleLoginStatus.bind(this)}/>
+                                :
+                                <ForgetPasswordForm handleLoginStatus={this.handleLoginStatus.bind(this)}/>
+                        }
+                    </TabPane>
+                    <TabPane tab="注册" key="2" style={{padding: "20px 30px 0"}}>
+                        <RegisterForm/>
+                    </TabPane>
+                </Tabs>
+            </Col>
+        );
+    }
+}
+
+export default UserPage;

+ 8 - 0
src/layouts/User/UserPage.less

@@ -0,0 +1,8 @@
+.UserTab {
+  width: 100%;
+}
+.UserCard{
+  margin-top: 12vh;
+  background-color: #ffffff;
+  border-radius: 25px;
+}

+ 21 - 0
src/routes/PageRouter/PageRouter.js

@@ -0,0 +1,21 @@
+import React, {Component} from "react";
+import {HashRouter, Route, Switch} from "react-router-dom";
+import DiskPage from "../../components/Page/DiskPage";
+import TeamPage from "../../components/Page/TeamPage";
+
+class PageRouter extends Component {
+    render() {
+        return (
+            <div id={'router'}>
+                <HashRouter>
+                    <Switch>
+                        <Route path={'/cloud/file'} component={DiskPage}/>
+                        <Route path={'/cloud/teams'} component={TeamPage}/>
+                    </Switch>
+                </HashRouter>
+            </div>
+        )
+    }
+}
+
+export default PageRouter;

+ 21 - 0
src/routes/router.js

@@ -0,0 +1,21 @@
+import React, {Component} from "react";
+import {HashRouter, Route, Switch} from "react-router-dom";
+import DefaultLayout from "../layouts/DefaultLayout/DefaultLayout";
+import HomePage from "../layouts/HomePage/HomePage";
+
+class RouterWrap extends Component {
+    render() {
+        return (
+            <div id={'router'}>
+                <HashRouter>
+                    <Switch>
+                        <Route path={'/cloud'} component={DefaultLayout}/>
+                        <Route path={'/'} component={HomePage} exact/>
+                    </Switch>
+                </HashRouter>
+            </div>
+        )
+    }
+}
+
+export default RouterWrap;

+ 42 - 0
src/services/API/API.js

@@ -0,0 +1,42 @@
+import fetch from "../../utils/Axios/Axios";
+import Qs from 'qs'
+
+// example 获取数据json模拟
+export function apiRegister(params) {
+    return fetch({
+        url: '/profile/register/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+// 登录校验
+export function apiLogin(params) {
+    return fetch({
+        url: '/profile/login/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiIsLogged() {
+    return fetch({
+        url: '/profile/islogged/',
+        method: 'get',
+    })
+}
+
+export function apiLogout() {
+    return fetch({
+        url: '/profile/logout/',
+        method: 'get',
+    })
+}
+
+export function apiPostMission(params) {
+    return fetch({
+        url: '/craw_keywords/page_mission/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}

+ 37 - 0
src/utils/Axios/Axios.js

@@ -0,0 +1,37 @@
+import axios from 'axios'
+import qs from 'qs'
+
+let fetch = axios.create({
+    // baseURL: "http://30gz758467.51vip.biz", // 这里是本地express启动的服务地址
+    baseURL: "http://localhost:8000", // 这里是本地express启动的服务地址
+    timeout: 5000 // request timeout
+})
+fetch.defaults.withCredentials = true
+fetch.interceptors.request.use(config => {
+    config.headers['X-Requested-With'] = 'XMLHttpRequest'
+    let regex = /.*csrftoken=([^;.]*).*$/
+    config.headers['X-CSRFToken'] = document.cookie.match(regex) === null ? null : document.cookie.match(regex)[1]
+    if (config.method === 'post' || config.method === 'put' || config.method === 'delete') {
+        if (typeof (config.data) !== 'string' && config.headers['Content-Type'] !== 'multipart/form-data') {
+            config.data = qs.stringify(config.data)
+        }
+    }
+    return config
+}, error => {
+    Promise.reject(error)
+})
+
+fetch.interceptors.response.use(async data => {
+    return data
+}, error => {
+    if (error.response) {
+        if (error.response.status === 500) {
+            console.log('服务器错误,请联系管理员处理')
+        }
+        return Promise.reject(error.response.data)
+    } else {
+        return Promise.reject(error)
+    }
+})
+
+export default fetch

+ 127 - 8
yarn.lock

@@ -14,7 +14,7 @@
   resolved "https://registry.npm.taobao.org/@ant-design/icons-svg/download/@ant-design/icons-svg-4.1.0.tgz#480b025f4b20ef7fe8f47d4a4846e4fee84ea06c"
   integrity sha1-SAsCX0sg73/o9H1KSEbk/uhOoGw=
 
-"@ant-design/icons@^4.6.3":
+"@ant-design/icons@^4.6.3", "@ant-design/icons@^4.6.4":
   version "4.6.4"
   resolved "https://registry.nlark.com/@ant-design/icons/download/@ant-design/icons-4.6.4.tgz#21b037dbb90ee1bb7c632cca057006e57d992fd9"
   integrity sha1-IbA327kO4bt8YyzKBXAG5X2ZL9k=
@@ -1185,6 +1185,16 @@
     exec-sh "^0.3.2"
     minimist "^1.2.0"
 
+"@craco/craco@^6.2.0":
+  version "6.2.0"
+  resolved "https://registry.nlark.com/@craco/craco/download/@craco/craco-6.2.0.tgz#93847ae20899f5e810359443f2055bcf2b1a584e"
+  integrity sha1-k4R64giZ9egQNZRD8gVbzysaWE4=
+  dependencies:
+    cross-spawn "^7.0.0"
+    lodash "^4.17.15"
+    semver "^7.3.2"
+    webpack-merge "^4.2.2"
+
 "@csstools/convert-colors@^1.4.0":
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
@@ -2323,6 +2333,11 @@ ansi-styles@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
   integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
 
+ant-design-icons@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.nlark.com/ant-design-icons/download/ant-design-icons-1.3.3.tgz#88f50bee831cfc8ca04be5d8bf2fa737fb59d300"
+  integrity sha1-iPUL7oMc/IygS+XYvy+nN/tZ0wA=
+
 antd@^4.16.13:
   version "4.16.13"
   resolved "https://registry.nlark.com/antd/download/antd-4.16.13.tgz?cache=0&sync_timestamp=1630937009033&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fantd%2Fdownload%2Fantd-4.16.13.tgz#e9b9b4a590db28747aae1cab98981649a35880af"
@@ -2613,6 +2628,13 @@ axe-core@^4.0.2:
   resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224"
   integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg==
 
+axios@^0.21.4:
+  version "0.21.4"
+  resolved "https://registry.nlark.com/axios/download/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575"
+  integrity sha1-xnuQ3AVo5cHPKwuFjEO6KOLtpXU=
+  dependencies:
+    follow-redirects "^1.14.0"
+
 axobject-query@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -3579,6 +3601,13 @@ cookie@0.4.0:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
   integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
 
+copy-anything@^2.0.1:
+  version "2.0.3"
+  resolved "https://registry.nlark.com/copy-anything/download/copy-anything-2.0.3.tgz#842407ba02466b0df844819bbe3baebbe5d45d87"
+  integrity sha1-hCQHugJGaw34RIGbvjuuu+XUXYc=
+  dependencies:
+    is-what "^3.12.0"
+
 copy-concurrently@^1.0.0:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -3663,6 +3692,14 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     yaml "^1.10.0"
 
+craco-less@^1.20.0:
+  version "1.20.0"
+  resolved "https://registry.nlark.com/craco-less/download/craco-less-1.20.0.tgz#7f68679b84c7756f785f9c5ee4e4f795662acad1"
+  integrity sha1-f2hnm4THdW94X5xe5OT3lWYqytE=
+  dependencies:
+    less "^4.1.1"
+    less-loader "^7.3.0"
+
 create-ecdh@^4.0.0:
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -4428,7 +4465,7 @@ entities@^2.0.0:
   resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
-errno@^0.1.3, errno@~0.1.7:
+errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
   version "0.1.8"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
   integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
@@ -5155,6 +5192,11 @@ follow-redirects@^1.0.0:
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147"
   integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==
 
+follow-redirects@^1.14.0:
+  version "1.14.3"
+  resolved "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e"
+  integrity sha1-atp4EY2NJMruWVWVrM3ArGq9Ai4=
+
 for-in@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -5751,7 +5793,7 @@ human-signals@^1.1.1:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
   integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==
 
-iconv-lite@0.4.24:
+iconv-lite@0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -5792,6 +5834,11 @@ ignore@^5.1.4:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
+image-size@~0.5.0:
+  version "0.5.5"
+  resolved "https://registry.npm.taobao.org/image-size/download/image-size-0.5.5.tgz?cache=0&sync_timestamp=1618422657851&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fimage-size%2Fdownload%2Fimage-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
+  integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=
+
 immer@8.0.1:
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656"
@@ -6232,6 +6279,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
   resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
   integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
 
+is-what@^3.12.0:
+  version "3.14.1"
+  resolved "https://registry.npm.taobao.org/is-what/download/is-what-3.14.1.tgz?cache=0&sync_timestamp=1615170680697&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-what%2Fdownload%2Fis-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
+  integrity sha1-4SIvRt3ahd6tD9HJ3xMXYOd3VcE=
+
 is-windows@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
@@ -6986,6 +7038,32 @@ last-call-webpack-plugin@^3.0.0:
     lodash "^4.17.5"
     webpack-sources "^1.1.0"
 
+less-loader@^7.3.0:
+  version "7.3.0"
+  resolved "https://registry.nlark.com/less-loader/download/less-loader-7.3.0.tgz#f9d6d36d18739d642067a05fb5bd70c8c61317e5"
+  integrity sha1-+dbTbRhznWQgZ6Bftb1wyMYTF+U=
+  dependencies:
+    klona "^2.0.4"
+    loader-utils "^2.0.0"
+    schema-utils "^3.0.0"
+
+less@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.npm.taobao.org/less/download/less-4.1.1.tgz#15bf253a9939791dc690888c3ff424f3e6c7edba"
+  integrity sha1-Fb8lOpk5eR3GkIiMP/Qk8+bH7bo=
+  dependencies:
+    copy-anything "^2.0.1"
+    parse-node-version "^1.0.1"
+    tslib "^1.10.0"
+  optionalDependencies:
+    errno "^0.1.1"
+    graceful-fs "^4.1.2"
+    image-size "~0.5.0"
+    make-dir "^2.1.0"
+    mime "^1.4.1"
+    needle "^2.5.2"
+    source-map "~0.6.0"
+
 leven@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -7162,7 +7240,7 @@ magic-string@^0.25.0, magic-string@^0.25.7:
   dependencies:
     sourcemap-codec "^1.4.4"
 
-make-dir@^2.0.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
   integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -7308,7 +7386,7 @@ mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.19,
   dependencies:
     mime-db "1.46.0"
 
-mime@1.6.0:
+mime@1.6.0, mime@^1.4.1:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@@ -7529,6 +7607,15 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
+needle@^2.5.2:
+  version "2.9.1"
+  resolved "https://registry.nlark.com/needle/download/needle-2.9.1.tgz?cache=0&sync_timestamp=1630675856853&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fneedle%2Fdownload%2Fneedle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684"
+  integrity sha1-ItHf++NJDCuD4wH3cJtnNs2PJoQ=
+  dependencies:
+    debug "^3.2.6"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 negotiator@0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -8027,6 +8114,11 @@ parse-json@^5.0.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
+parse-node-version@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/parse-node-version/download/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
+  integrity sha1-4rXb7eAOf6m8NjYH9TMn6LBzGJs=
+
 parse5@5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
@@ -9061,6 +9153,13 @@ qs@6.7.0:
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
   integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
 
+qs@^6.10.1:
+  version "6.10.1"
+  resolved "https://registry.nlark.com/qs/download/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
+  integrity sha1-STFIL6jWR6Wqt5nFJx0hM7mB+2o=
+  dependencies:
+    side-channel "^1.0.4"
+
 qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -9551,7 +9650,20 @@ react-refresh@^0.8.3:
   resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
   integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
 
-react-router@^5.2.1:
+react-router-dom@^5.3.0:
+  version "5.3.0"
+  resolved "https://registry.nlark.com/react-router-dom/download/react-router-dom-5.3.0.tgz?cache=0&sync_timestamp=1630706407844&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-router-dom%2Fdownload%2Freact-router-dom-5.3.0.tgz#da1bfb535a0e89a712a93b97dd76f47ad1f32363"
+  integrity sha1-2hv7U1oOiacSqTuX3Xb0etHzI2M=
+  dependencies:
+    "@babel/runtime" "^7.12.13"
+    history "^4.9.0"
+    loose-envify "^1.3.1"
+    prop-types "^15.6.2"
+    react-router "5.2.1"
+    tiny-invariant "^1.0.2"
+    tiny-warning "^1.0.0"
+
+react-router@5.2.1, react-router@^5.2.1:
   version "5.2.1"
   resolved "https://registry.nlark.com/react-router/download/react-router-5.2.1.tgz#4d2e4e9d5ae9425091845b8dbc6d9d276239774d"
   integrity sha1-TS5OnVrpQlCRhFuNvG2dJ2I5d00=
@@ -10145,7 +10257,7 @@ sass-loader@^10.0.5:
     schema-utils "^3.0.0"
     semver "^7.3.2"
 
-sax@~1.2.4:
+sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@@ -11164,7 +11276,7 @@ tsconfig-paths@^3.9.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
-tslib@^1.8.1, tslib@^1.9.0:
+tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
   version "1.14.1"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -11650,6 +11762,13 @@ webpack-manifest-plugin@2.2.0:
     object.entries "^1.1.0"
     tapable "^1.0.0"
 
+webpack-merge@^4.2.2:
+  version "4.2.2"
+  resolved "https://registry.nlark.com/webpack-merge/download/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d"
+  integrity sha1-onxS6ng9E5iv0gh/VH17nS9DY00=
+  dependencies:
+    lodash "^4.17.15"
+
 webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"