ソースを参照

完成,待测试

Shellmiao 3 年 前
コミット
9a90aa527a
45 ファイル変更2115 行追加156 行削除
  1. 6 1
      craco.config.js
  2. 7 1
      package.json
  3. 71 8
      src/components/Forms/ForgetPasswordForm.js
  4. 11 6
      src/components/Forms/LoginForm.js
  5. 49 0
      src/components/Forms/LogoutForm.js
  6. 20 2
      src/components/Forms/RegisterForm.js
  7. 14 0
      src/components/Forms/UserForm.less
  8. 0 23
      src/components/MyBreadCrumb/MyBreadCrumb.js
  9. 69 0
      src/components/Page/AddFolder.js
  10. 68 0
      src/components/Page/AddFolderModal.js
  11. 168 34
      src/components/Page/DiskPage.js
  12. 36 0
      src/components/Page/DiskPage.less
  13. 68 7
      src/components/Page/File.js
  14. 3 1
      src/components/Page/File.less
  15. 45 7
      src/components/Page/Folder.js
  16. 3 1
      src/components/Page/Folder.less
  17. 188 0
      src/components/Page/GroupModal.js
  18. 51 0
      src/components/Page/SettingPage.js
  19. 29 0
      src/components/Page/SettingPage.less
  20. 54 10
      src/components/Page/TeamPage.js
  21. 17 0
      src/components/Page/TeamPage.less
  22. 102 0
      src/components/Page/UploadFile.js
  23. 3 0
      src/components/Page/UploadFile.less
  24. 68 0
      src/components/Page/UploadFileModal.js
  25. 5 0
      src/components/Page/UploadFileModal.less
  26. 12 0
      src/components/Page/UserDisk.js
  27. 5 0
      src/components/Page/add-folder-modal.less
  28. 33 13
      src/components/Sider/MySider.js
  29. 3 0
      src/components/Sider/MySider.less
  30. 1 2
      src/layouts/DefaultLayout/DefaultLayout.js
  31. 1 1
      src/layouts/HomePage/HomePage.js
  32. 38 16
      src/layouts/User/UserPage.js
  33. 4 2
      src/routes/PageRouter/PageRouter.js
  34. 122 7
      src/services/API/API.js
  35. 8 5
      src/utils/Axios/Axios.js
  36. 23 0
      src/utils/Axios/UploadFile.js
  37. 30 0
      src/utils/Download/Download.js
  38. 16 0
      src/utils/deleteCookie.js
  39. 50 0
      src/utils/encrypt/aes.js
  40. 15 0
      src/utils/encrypt/encrypt.js
  41. 9 0
      src/utils/encrypt/rsa.js
  42. 18 0
      src/utils/getCookie.js
  43. 55 0
      src/utils/plugin.config.js
  44. 37 0
      src/utils/utils.js
  45. 480 9
      yarn.lock

+ 6 - 1
craco.config.js

@@ -1,4 +1,5 @@
 const CracoLessPlugin = require('craco-less');
+const createThemeColorReplacerPlugin = require("./src/utils/plugin.config");
 
 module.exports = {
     plugins: [
@@ -7,11 +8,15 @@ module.exports = {
             options: {
                 lessLoaderOptions: {
                     lessOptions: {
-                        modifyVars: { '@primary-color': '#1890ff' },
+                        modifyVars: {},
                         javascriptEnabled: true,
                     },
                 },
             },
         },
     ],
+    webpack: {
+        // 这里面是webpack的配置
+        plugins: [createThemeColorReplacerPlugin()],
+    }
 };

+ 7 - 1
package.json

@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@ant-design/colors": "^6.0.0",
     "@ant-design/icons": "^4.6.4",
     "@craco/craco": "^6.2.0",
     "@testing-library/jest-dom": "^5.11.4",
@@ -12,14 +13,19 @@
     "antd": "^4.16.13",
     "axios": "^0.21.4",
     "craco-less": "^1.20.0",
+    "crypto-js": "^4.1.1",
+    "js-file-download": "^0.4.12",
+    "jsencrypt": "2.3.1",
     "qs": "^6.10.1",
     "rc-queue-anim": "^2.0.0",
     "react": "^17.0.2",
+    "react-color": "^2.19.3",
     "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"
+    "web-vitals": "^1.0.1",
+    "webpack-theme-color-replacer": "1.3.22"
   },
   "scripts": {
     "start": "craco start",

+ 71 - 8
src/components/Forms/ForgetPasswordForm.js

@@ -1,7 +1,9 @@
 import React, {Component} from "react";
-import {Button, Form, Input, Steps, Row, Col} from "antd";
+import {Button, Form, Input, Steps, Row, Col, message} from "antd";
 import './UserForm.less'
 import {LockOutlined, MailOutlined, SecurityScanOutlined, UserOutlined} from "@ant-design/icons";
+import {apiCheck, apiConfirm, apiReset} from "../../services/API/API";
+import getCookie from "../../utils/getCookie";
 
 const {Step} = Steps;
 
@@ -10,6 +12,67 @@ class ForgetPasswordForm extends Component {
         forgetStatus: 'check',
         currentStep: 0,
         stepProcessStatus: 'process',
+        token: '',
+    }
+
+    handleSubmitCheck = values => {
+        let params = {
+            ...values
+        }
+        console.log('Received values of form: ', params);
+        apiCheck(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('验证成功');
+                this.handleCheckSuccessfully()
+            } else if (res.data.code === 303) {
+                message.error('用户不存在')
+            } else if (res.data.code === 301) {
+                message.error('邮箱错误')
+            } else if (res.data.code === 500) {
+                message.error('验证码发送失败')
+            } else {
+                message.error('发生错误')
+            }
+        })
+    }
+
+    handleSubmitConfirm = values => {
+        let params = {
+            username: getCookie('username'),
+            ...values
+        }
+        console.log('Received values of form: ', params);
+        apiConfirm(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('确认成功');
+                this.handleConfirmSuccessfully()
+                this.setState({
+                    token: values.token,
+                })
+            } else if (res.data.code === 302) {
+                message.error('用户不存在')
+            } else if (res.data.code === 303) {
+                message.error('验证码无效')
+            } else {
+                message.error('发生错误')
+            }
+        })
+    }
+
+    handleSubmitReset = values => {
+        let params = {
+            username: getCookie('username'),
+            token: this.state.token,
+            password: values.password,
+        }
+        apiReset(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('重置成功');
+                window.location.href = "";
+            } else {
+                message.error('发生错误')
+            }
+        })
     }
 
     handleCheckSuccessfully = () => {
@@ -41,10 +104,10 @@ class ForgetPasswordForm extends Component {
                 <Col span={16}>
                     {
                         forgetStatus === 'check' ?
-                            <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                            <Form onFinish={this.handleSubmitCheck} className="forget-password-form"
                                   initialValues={{remember: true}}>
                                 <Form.Item
-                                    name="account"
+                                    name="username"
                                     rules={[{required: true, message: 'Please input your Username!'}]}
                                 >
                                     <Input
@@ -74,7 +137,7 @@ class ForgetPasswordForm extends Component {
                                 </Form.Item>
                                 <Form.Item style={{marginBottom: '10px'}}>
                                     <Button size="large" type="primary" htmlType="submit" className="login-form-button"
-                                            style={{width: '100%'}} onClick={this.handleCheckSuccessfully}>
+                                            style={{width: '100%'}}>
                                         RESET YOUR PASSWORD
                                     </Button>
                                 </Form.Item>
@@ -86,10 +149,10 @@ class ForgetPasswordForm extends Component {
                                     </Button>
                                 </Form.Item>
                             </Form> : forgetStatus === 'confirm' ?
-                                <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                                <Form onFinish={this.handleSubmitConfirm} className="forget-password-form"
                                       initialValues={{remember: true}}>
                                     <Form.Item
-                                        name="VerificationCode"
+                                        name="token"
                                         rules={[{required: true, message: 'Please input your Verification Code!'}]}
                                     >
                                         <Input
@@ -100,12 +163,12 @@ class ForgetPasswordForm extends Component {
                                     </Form.Item>
                                     <Form.Item style={{marginBottom: '10px'}}>
                                         <Button size="large" type="primary" htmlType="submit" className="login-form-button"
-                                                style={{width: '100%'}} onClick={this.handleConfirmSuccessfully}>
+                                                style={{width: '100%'}}>
                                             CONFIRM
                                         </Button>
                                     </Form.Item>
                                 </Form> :
-                                <Form onSubmit={this.handleSubmitLogin} className="forget-password-form"
+                                <Form onFinish={this.handleSubmitReset} className="forget-password-form"
                                       initialValues={{remember: true}}>
                                     <Form.Item
                                         name="password"

+ 11 - 6
src/components/Forms/LoginForm.js

@@ -3,6 +3,8 @@ import {Button, Form, Input, message} from "antd";
 import {apiLogin} from "../../services/API/API";
 import {LockOutlined, UserOutlined} from "@ant-design/icons";
 import './UserForm.less'
+import deleteCookie from "../../utils/deleteCookie";
+import getCookie from "../../utils/getCookie";
 
 class LoginForm extends Component {
     handleSubmitLogin = values => {
@@ -11,19 +13,22 @@ class LoginForm extends Component {
             ...values
         }
         apiLogin(params).then(res => {
-            if (res.data.code === 1001) {
-                message.success(res.data.message);
-            } else {
-                message.error(res.data.message)
+            if (res.data.code === 200) {
+                message.success('登陆成功');
+                this.props.handleLogged()
+                deleteCookie()
+                document.cookie = 'token=' + res.data.token
+            } else if (res.data.code === 303) {
+                message.error('用户名或密码错误')
             }
         })
     };
 
     render() {
         return (
-            <Form onSubmit={this.handleSubmitLogin} className="login-form" initialValues={{remember: true}}>
+            <Form onFinish={this.handleSubmitLogin} className="login-form" initialValues={{remember: true}}>
                 <Form.Item
-                    name="account"
+                    name="username"
                     rules={[{required: true, message: 'Please input your Username or E-mail!'}]}
                 >
                     <Input

+ 49 - 0
src/components/Forms/LogoutForm.js

@@ -0,0 +1,49 @@
+import React, {Component} from "react";
+import {Button, Form, message} from "antd";
+import {apiLogout} from "../../services/API/API";
+import getCookie from "../../utils/getCookie";
+import deleteCookie from "../../utils/deleteCookie";
+import {CheckCircleTwoTone} from "@ant-design/icons";
+import './UserForm.less'
+import {Link} from "react-router-dom";
+
+class LogoutForm extends Component {
+    handleSubmitLogout = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+        }
+        apiLogout(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('登出成功');
+                deleteCookie()
+                this.props.handleLogged()
+            } else if (res.data.code === 303) {
+                message.error(res.data.error)
+            }
+        })
+    };
+
+    render() {
+        return (
+            <div className={'logged-div'}>
+                <CheckCircleTwoTone className={'logged-logo'} twoToneColor="#52c41a"/>
+                <Button className={'enter-button'} size="large" type="primary">
+                    <Link to={'/cloud/file'}>
+                        Enter STCloud
+                    </Link>
+                </Button>
+                <Form onFinish={this.handleSubmitLogout} className="login-form" initialValues={{remember: true}}>
+                    <Form.Item style={{marginBottom: '10px'}}>
+                        <Button size="large" type="primary" htmlType="submit" className="login-form-button"
+                                style={{width: '100%'}}>
+                            Logout
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        )
+    }
+}
+
+export default LogoutForm;

+ 20 - 2
src/components/Forms/RegisterForm.js

@@ -1,12 +1,30 @@
 import React, {Component} from "react";
-import {Button, Form, Input} from "antd";
+import {Button, Form, Input, message} from "antd";
 import {LockOutlined, MailOutlined, UserOutlined} from "@ant-design/icons";
 import './UserForm.less'
+import {apiRegister} from "../../services/API/API";
 
 class RegisterForm extends Component {
+    handleSubmitRegister = values => {
+        console.log('Received values of form: ', values);
+        let params = {
+            username: values.username,
+            email: values.email,
+            password: values.password,
+        }
+        apiRegister(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('注册成功');
+                this.props.handleSelectKey()
+            } else {
+                message.error(res.data.error)
+            }
+        })
+    };
+
     render() {
         return (
-            <Form onSubmit={this.handleSubmitRegister} className="register-form">
+            <Form onFinish={this.handleSubmitRegister} className="register-form">
                 <Form.Item
                     name="username"
                     rules={[{required: true, message: 'Please input your Username!'}]}

+ 14 - 0
src/components/Forms/UserForm.less

@@ -4,4 +4,18 @@
 
 #item-icon {
   color: rgba(0, 0, 0, .25)
+}
+
+.logged-div{
+  text-align: center;
+}
+
+.logged-logo{
+  font-size: 20vh;
+  margin-bottom: 5vh;
+}
+
+.enter-button{
+  width: 100%;
+  margin-bottom: 5vh;
 }

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

@@ -1,23 +0,0 @@
-import React, {Component} from "react";
-import {Breadcrumb} from "antd";
-
-class MyBreadCrumb extends Component {
-    render() {
-        return this.address.split('/').map((ele, index) => {
-            let key = this.address.split('/').slice(0, index + 1);
-            key = key.join('/');
-            // 保证最有'/'结尾
-            key = key.slice(-1) === '/' ? key : key + '/';
-            return (
-                <Breadcrumb.Item
-                    key={key}
-                    className="breadcrumb-line"
-                    onClick={(ev) => this.cdFolder(key)}>
-                    <a>{ele}</a>
-                </Breadcrumb.Item>
-            );
-        });
-    }
-}
-
-export default MyBreadCrumb;

+ 69 - 0
src/components/Page/AddFolder.js

@@ -0,0 +1,69 @@
+import React, {Component} from "react";
+import {Button, Form, Input, message} from "antd";
+import {apiAddFolder} from "../../services/API/API";
+import getCookie from "../../utils/getCookie";
+import {FolderOutlined, UserOutlined} from "@ant-design/icons";
+
+class AddFolderForm extends Component {
+    constructor(props) {
+        super(props);
+        if (props.onRef) {
+            props.onRef(this)
+        }
+    }
+
+    clickButton = () => {
+        let e = document.createEvent('MouseEvents');
+        e.initEvent('click', true, true)
+        document.getElementById('add-folder-form-button').dispatchEvent(e)
+    }
+
+    handleAddFolder = values => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            father_folder_id: this.props.fatherFolderId,
+            ...values
+        }
+        console.log(params)
+        apiAddFolder(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('添加成功');
+                this.props.cdFolder(this.props.fatherFolderId)
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else {
+                message.error('错误')
+                window.location.href = "";
+            }
+        })
+    };
+
+    render() {
+        return (
+            <div className={'add-folder-div'} style={{height: '5vh'}}>
+                <Form onFinish={this.handleAddFolder} className="login-form" initialValues={{remember: true}}>
+                    <Form.Item
+                        name="folder_name"
+                        rules={[{required: true, message: 'Please input Folder Name!'}]}
+                    >
+                        <Input
+                            prefix={<FolderOutlined id="item-icon"/>}
+                            placeholder="Folder Name"
+                            size="large"
+                        />
+                    </Form.Item>
+                    <Form.Item style={{marginBottom: '10px'}}>
+                        <Button size="large" type="primary" htmlType="submit" id="add-folder-form-button"
+                                style={{width: '100%', display: 'none', float: 'bottom', marginBottom: '100vh'}}>
+                            Add
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        )
+    }
+}
+
+export default AddFolderForm;

+ 68 - 0
src/components/Page/AddFolderModal.js

@@ -0,0 +1,68 @@
+import {Modal, Button} from 'antd';
+import React, {Component} from "react";
+import AddFolderForm from "./AddFolder";
+import './add-folder-modal.less'
+import {FolderAddOutlined} from "@ant-design/icons";
+
+class AddFolderModal extends Component {
+    state = {
+        visible: false,
+        confirmLoading: false,
+        actionName: this.props.actionName,
+    }
+
+    showModal = () => {
+        this.setState({
+            visible: true
+        })
+    }
+
+    setVisible = visible => {
+        this.setState({
+            visible: visible
+        })
+    }
+
+    setConfirmLoading = ConfirmLoading => {
+        this.setState({
+            confirmLoading: ConfirmLoading
+        })
+    }
+
+    handleOk = () => {
+        this.Child.clickButton()
+        this.setConfirmLoading(true);
+        setTimeout(() => {
+            this.setVisible(false);
+            this.setConfirmLoading(false);
+        }, 1000);
+    };
+
+    handleCancel = () => {
+        this.setVisible(false);
+    };
+
+    render() {
+        return (
+            <div className={'add-folder-modal'}>
+                <Button type="primary" onClick={this.showModal} icon={<FolderAddOutlined/>}>
+                    ADD
+                </Button>
+                <Modal
+                    title={this.state.actionName}
+                    visible={this.state.visible}
+                    onOk={this.handleOk}
+                    confirmLoading={this.state.confirmLoading}
+                    onCancel={this.handleCancel}
+                >
+                    <div>
+                        <AddFolderForm onRef={c => this.Child = c} fatherFolderId={this.props.fatherFolderId}
+                                       cdFolder={this.props.cdFolder}/>
+                    </div>
+                </Modal>
+            </div>
+        )
+    }
+}
+
+export default AddFolderModal;

+ 168 - 34
src/components/Page/DiskPage.js

@@ -1,34 +1,123 @@
 import React, {Component} from "react";
-import {Space, Slider, Button} from 'antd';
+import {Space, Slider, message, Breadcrumb} from 'antd';
 import Folder from "./Folder";
-import MyBreadCrumb from "../MyBreadCrumb/MyBreadCrumb";
 import File from "./File";
-
-const data = [
-    {
-        key: '1',
-        isFolder: true,
-        name: '高等数学',
-        tags: ['nice', 'developer'],
-    },
-    {
-        key: '2',
-        isFolder: false,
-        name: 'Jim Green',
-        tags: ['loser'],
-    },
-    {
-        key: '3',
-        isFolder: true,
-        name: 'Joe Black',
-        tags: ['cool', 'teacher'],
-    },
-];
+import {apiFolderList, apiGroupRoot, apiRootFolderId} from "../../services/API/API";
+import getCookie from "../../utils/getCookie";
+import './DiskPage.less'
+import AddFolderModal from "./AddFolderModal";
+import UploadFileModal from "./UploadFileModal";
+import GroupModal from "./GroupModal";
+import {CopyTwoTone} from "@ant-design/icons";
 
 class DiskPage extends Component {
 
     state = {
-        size: 8
+        type: this.props.type,
+        currentId: -1,
+        size: 8,
+        addressList: [],
+        data: [],
+    }
+
+    getRootFolderIdAndInter = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+        }
+        if (this.props.type === 'team') {
+            params = {
+                group_id: this.props.group_id,
+                ...params
+            }
+            apiGroupRoot(params).then(res => {
+                if (res.data.code === 200) {
+                    console.log('成功')
+                    this.setState({
+                        currentId: res.data.root_folder_id,
+                    })
+                    this.cdFolder(this.state.currentId, this.props.group_name)
+                } else if (res.data.code === 401) {
+                    message.error('未登录')
+                    window.location.href = "";
+                } else if (res.data.code === 403) {
+                    message.error('群不存在')
+                    window.location.href = "";
+                } else {
+                    message.error('错误')
+                    window.location.href = "";
+                }
+            })
+        } else {
+            apiRootFolderId(params).then(res => {
+                if (res.data.code === 200) {
+                    console.log('成功')
+                    this.setState({
+                        currentId: res.data.root_folder_id,
+                    })
+                    this.cdFolder(this.state.currentId, getCookie('username'))
+                } else if (res.data.code === 401) {
+                    message.error('未登录')
+                    window.location.href = "";
+                } else {
+                    message.error('错误')
+                    window.location.href = "";
+                }
+            })
+        }
+    }
+
+
+    cdFolder = (FolderId, FolderName) => {
+        console.log('正在进入' + FolderId)
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            folder_id: FolderId
+        }
+        apiFolderList(params).then(res => {
+            let addressList = this.state.addressList
+            let temp = -1
+            for (let i = 0; i < addressList.length; i++) {
+                if (addressList[i].id === FolderId) {
+                    temp = i
+                    break
+                }
+            }
+            if (temp === -1) {
+                addressList.push({
+                    name: FolderName,
+                    id: FolderId
+                })
+            } else {
+                addressList.splice(temp + 1, addressList.length - temp - 1)
+            }
+            if (res.data.code === 200) {
+                this.setState({
+                    currentId: FolderId,
+                    data: res.data.children,
+                    // addressList: [...this.state.addressList, {
+                    //     name: FolderName,
+                    //     id: FolderId
+                    // }]
+                    addressList: addressList
+                })
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else {
+                message.error('错误')
+                // window.location.href = "";
+            }
+        })
+    }
+
+    componentWillMount() {
+        this.getRootFolderIdAndInter()
+    }
+
+    componentDidMount() {
+
     }
 
     setSize = size => {
@@ -37,18 +126,58 @@ class DiskPage extends Component {
         })
     }
 
+    renderBread(addressList) {
+        return addressList.map((ele, index) => {
+            return (
+                <Breadcrumb.Item
+                    key={ele.name}
+                    className="breadcrumb-line"
+                    onClick={(ev) => this.cdFolder(ele.id, ele.name)}>
+                    <a>{ele.name}</a>
+                </Breadcrumb.Item>
+            );
+        });
+    }
+
     render() {
         return (
             <div>
-                {/*<MyBreadCrumb/>*/}
-                <div className="site-layout-background" style={{marginTop: 20, padding: 24, minHeight: '82vh'}}>
-                    <Slider value={this.state.size} onChange={value => this.setSize(value)}/>
-                    <Space size={this.state.size}>
+                <div className={'disk-header'}>
+                    <Breadcrumb.Item
+                        key={'root'}
+                        className="breadcrumb-line"
+                    >
+                        >
+                    </Breadcrumb.Item>
+                    {this.renderBread(this.state.addressList)}
+                    {
+                        this.state.type === 'team' ? <div className={'group-modal'}>
+                            <GroupModal group_id={this.props.group_id} getGroupList={this.props.getGroupList}/>
+                        </div> : null
+                    }
+                </div>
+                <div className="site-layout-background" style={{marginTop: 10, padding: 24, minHeight: '82vh'}}>
+                    <div>
+                        <Slider value={this.state.size} onChange={value => this.setSize(value)}
+                                className={'disk-slider'}/>
+                        <AddFolderModal actionName={'Add Folder'} fatherFolderId={this.state.currentId}
+                                        cdFolder={this.cdFolder.bind(this)} className={'add-folder-button'}/>
+                        <UploadFileModal actionName={'Upload File'} fatherFolderId={this.state.currentId}
+                                         cdFolder={this.cdFolder.bind(this)} className={'upload-file-button'}/>
+                    </div>
+                    <Space className={'disk-content'} size={this.state.size} wrap>
                         {
-                            this.renderFolderList(data)
+                            this.state.data.length === 0 ?
+                                <div className={'no-files-div'}>
+                                    <CopyTwoTone/>
+                                    NO FILES
+                                </div>
+                                : this.renderFolderList(this.state.data)
                         }
                         {
-                            this.renderFileList(data)
+                            this.state.data.length === 0 ?
+                                null
+                                : this.renderFileList(this.state.data)
                         }
                     </Space>
                 </div>
@@ -59,12 +188,15 @@ class DiskPage extends Component {
     renderFolderList(dataList) {
         return dataList.map((ele, index) => {
             return (
-                ele.isFolder ?
+                ele.type === 'folder' ?
                     <div
                         key={index}
                         // onDoubleClick={ele.isFolder ? () => this.cdFolder(ele.Key, ele.Title) : null}
                     >
-                        <Folder ele={ele}/>
+                        <Folder ele={ele}
+                                cdFolder={this.cdFolder.bind(this)}
+                                fatherFolderId={this.state.currentId}
+                        />
                     </div> : null
             );
         });
@@ -73,9 +205,11 @@ class DiskPage extends Component {
     renderFileList(dataList) {
         return dataList.map((ele, index) => {
             return (
-                ele.isFolder ? null :
+                ele.type === 'folder' ? null :
                     <div>
-                        <File ele={ele}/>
+                        <File ele={ele}
+                              fatherFolderId={this.state.currentId}
+                              cdFolder={this.cdFolder.bind(this)}/>
                     </div>
             );
         });

+ 36 - 0
src/components/Page/DiskPage.less

@@ -0,0 +1,36 @@
+.disk-slider {
+  width: 56%;
+  float: left;
+}
+
+.add-folder-button {
+  float: right;
+}
+
+.upload-file-button {
+  float: right;
+}
+
+.disk-content {
+  margin-top: 5vh;
+}
+
+.disk-header {
+  margin-top: 2vh
+}
+
+.group-modal {
+  width: 1vh;
+  height: 1vh;
+  float: right;
+  margin-right: 22vh;
+  margin-top: 4vh;
+}
+
+.no-files-div{
+  font-weight: bolder;
+  font-size: 15vh;
+  margin-top: 10vh;
+  margin-left: 25vh;
+  float: top;
+}

+ 68 - 7
src/components/Page/File.js

@@ -1,7 +1,11 @@
 import React, {Component} from "react";
 import {FileTextTwoTone, FileTwoTone} from "@ant-design/icons";
 import './File.less'
-import {Dropdown, Menu} from "antd";
+import {Button, Dropdown, Menu, message} from "antd";
+import {apiDelete, apiDownload, apiFolderList} from "../../services/API/API";
+import getCookie from "../../utils/getCookie";
+
+const fileDownload = require('js-file-download');
 
 class File extends Component {
     state = {
@@ -18,13 +22,70 @@ class File extends Component {
         })
     }
 
+    handleDownload = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            file_id: this.props.ele.file_id,
+        }
+        apiDownload(params).then(res => {
+            if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 402) {
+                message.error('文件不存在')
+                // window.location.href = "";
+            } else if (res.data.code === 404) {
+                message.error('没有下载文件的权限')
+                // window.location.href = "";
+            } else {
+                message.success('下载开始');
+                fileDownload(res.data, this.props.ele.file_name)
+                // window.location.href = "";
+            }
+        })
+    }
+
+    handleDelete = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            file_id: this.props.ele.file_id,
+        }
+        apiDelete(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('删除成功');
+                this.props.cdFolder(this.props.fatherFolderId)
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 402) {
+                message.error('文件不存在')
+                // window.location.href = "";
+            } else if (res.data.code === 404) {
+                message.error('没有删除文件的权限')
+                // window.location.href = "";
+            } else {
+                message.error('错误')
+                // window.location.href = "";
+            }
+        })
+    }
+
     render() {
         let {isOver} = this.state
         const ele = this.props.ele
         const menu = (
             <Menu>
-                <Menu.Item key="open">
-                    <a href="https://www.antgroup.com">Open Folder</a>
+                <Menu.Item key="download">
+                    <Button onClick={this.handleDownload}>
+                        DOWNLOAD
+                    </Button>
+                </Menu.Item>
+                <Menu.Item key="delete">
+                    <Button onClick={this.handleDelete}>
+                        DELETE
+                    </Button>
                 </Menu.Item>
             </Menu>
         );
@@ -43,14 +104,14 @@ class File extends Component {
                     </div>
                 }
                 {
-                    isOver ? <FileTextTwoTone className={'file-logo'}/> :
-                        <FileTwoTone className={'file-logo'}/>
+                    isOver ? <FileTextTwoTone className={'file-logo'} twoToneColor="#71B8FB"/> :
+                        <FileTwoTone className={'file-logo'} twoToneColor="#71B8FB"/>
                 }
                 <div className={'file-name'}>
-                    {ele.name}
+                    {ele.file_name}
                 </div>
                 <div className={'file-date'}>
-                    2019/8/8
+                    {ele.update_time.substring(0, 10)}
                 </div>
             </div>
         )

+ 3 - 1
src/components/Page/File.less

@@ -3,6 +3,7 @@
   height: 20vh;
   border-radius: 15px;
   text-align: center;
+  flex-wrap: wrap;
 }
 
 .file-over {
@@ -12,6 +13,7 @@
   background-color: rgba(145, 229, 255, 0.63);
   text-align: center;
   border: 3px solid rgba(80, 167, 255, 0.76);
+  flex-wrap: wrap;
 }
 
 .file-options {
@@ -30,7 +32,7 @@
 }
 
 .file-name {
-  font-size: 20px;
+  font-size: 15px;
   font-weight: bolder;
   text-align: center;
   margin-top: 1vh;

+ 45 - 7
src/components/Page/Folder.js

@@ -1,7 +1,9 @@
 import React, {Component} from "react";
 import {FolderOpenTwoTone, FolderTwoTone} from "@ant-design/icons";
 import './Folder.less';
-import {Dropdown, Menu} from "antd";
+import {Button, Dropdown, Menu, message} from "antd";
+import getCookie from "../../utils/getCookie";
+import {apiDeleteFolder} from "../../services/API/API";
 
 class Folder extends Component {
     state = {
@@ -18,19 +20,58 @@ class Folder extends Component {
         })
     }
 
+    handleOpenFolder = () => {
+        this.props.cdFolder(this.props.ele.folder_id, this.props.ele.folder_name)
+    }
+
+    handleDeleteFolder = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            folder_id: this.props.ele.folder_id,
+        }
+        apiDeleteFolder(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('删除成功');
+                this.props.cdFolder(this.props.fatherFolderId)
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 400) {
+                message.error('文件夹不存在')
+                // window.location.href = "";
+            } else if (res.data.code === 404) {
+                message.error('没有删除文件夹的权限')
+                // window.location.href = "";
+            } else {
+                message.error('错误')
+                // window.location.href = "";
+            }
+        })
+    }
+
     render() {
         let {isOver} = this.state
         const ele = this.props.ele
         const menu = (
             <Menu>
                 <Menu.Item key="open">
-                    <a href="https://www.antgroup.com">Open Folder</a>
+                    <Button onClick={this.handleOpenFolder}>
+                        OPEN
+                    </Button>
+                </Menu.Item>
+                <Menu.Item key="delete">
+                    <Button onClick={this.handleDeleteFolder}>
+                        DELETE
+                    </Button>
                 </Menu.Item>
             </Menu>
         );
         return (
             <div className={'folder' + (isOver ? '-over' : '')} onMouseEnter={this.onMouseOver}
-                 onMouseLeave={this.onMouseOut}>
+                 onMouseLeave={this.onMouseOut}
+                 onDoubleClick={this.handleOpenFolder}
+            >
                 {
                     isOver ? <div className={'folder-options'}>
                         <Dropdown overlay={menu} trigger={['click']} placement="bottomLeft" arrow>
@@ -47,10 +88,7 @@ class Folder extends Component {
                         <FolderTwoTone className={'folder-logo'}/>
                 }
                 <div className={'folder-name'}>
-                    {ele.name}
-                </div>
-                <div className={'folder-date'}>
-                    2019/8/8
+                    {ele.folder_name}
                 </div>
             </div>
         )

+ 3 - 1
src/components/Page/Folder.less

@@ -7,6 +7,7 @@
   height: 20vh;
   border-radius: 15px;
   text-align: center;
+  flex-wrap: wrap;
 }
 
 .folder-over {
@@ -16,10 +17,11 @@
   background-color: rgba(145, 229, 255, 0.63);
   text-align: center;
   border: 3px solid rgba(80, 167, 255, 0.76);
+  flex-wrap: wrap;
 }
 
 .folder-name {
-  font-size: 20px;
+  font-size: 16px;
   font-weight: bolder;
   text-align: center;
 }

+ 188 - 0
src/components/Page/GroupModal.js

@@ -0,0 +1,188 @@
+import {Modal, Button, Tabs, Form, Input, message} from 'antd';
+import React, {Component} from "react";
+import './add-folder-modal.less'
+import {SearchOutlined, UserOutlined} from "@ant-design/icons";
+import getCookie from "../../utils/getCookie";
+import {apiCreateGroup, apiJoinGroup, apiQuitGroup} from "../../services/API/API";
+
+const {TabPane} = Tabs;
+
+class GroupModal extends Component {
+    state = {
+        visible: false,
+    }
+
+    showModal = () => {
+        this.setState({
+            visible: true
+        })
+    }
+
+    setVisible = visible => {
+        this.setState({
+            visible: visible
+        })
+    }
+
+    handleOk = () => {
+        this.Child.clickButton()
+        setTimeout(() => {
+            this.setVisible(false);
+        }, 1000);
+    };
+
+    handleJoinGroup = values => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            ...values
+        }
+        apiJoinGroup(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('加入成功');
+                this.props.getGroupList()
+                this.handleCancel()
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 402) {
+                message.error('你已在群内')
+            } else if (res.data.code === 403) {
+                message.error('该群不存在')
+            } else {
+                message.error('错误')
+            }
+        })
+    }
+
+    handleCreateGroup = values => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            ...values
+        }
+        apiCreateGroup(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('建群成功');
+                this.props.getGroupList()
+                this.handleCancel()
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 500) {
+                message.error('新建失败')
+            } else {
+                message.error('错误')
+            }
+        })
+    }
+
+    handleQuitGroup = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+            group_id: this.props.group_id,
+        }
+        apiQuitGroup(params).then(res => {
+            if (res.data.code === 200) {
+                message.success('退出成功');
+                this.props.getGroupList()
+                this.handleCancel()
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else if (res.data.code === 403) {
+                message.error('该群不存在')
+            } else if (res.data.code === 421) {
+                message.error('群主不可退群')
+            } else {
+                message.error('错误')
+            }
+        })
+    }
+
+    handleCancel = () => {
+        this.setVisible(false);
+    };
+
+    render() {
+        return (
+            <div className={'group-modal'}>
+                <Button type="primary" shape="circle" icon={<SearchOutlined/>} onClick={this.showModal}/>
+                <Modal
+                    title='Join / Create Group'
+                    visible={this.state.visible}
+                    footer={null}
+                    onCancel={this.handleCancel}
+                >
+                    <div>
+                        <Tabs
+                            defaultActiveKey="1"
+                            size={'large'}
+                            animated={true}
+                            centered={true}
+                            tabBarGutter={50}
+                        >
+                            <TabPane
+                                tab='Join'
+                                key="1"
+                                style={{padding: "20px 30px 10px"}}>
+                                <Form onFinish={this.handleJoinGroup} initialValues={{remember: true}}>
+                                    <Form.Item
+                                        name="group_id"
+                                        rules={[{required: true, message: 'Please input Group Id!'}]}
+                                    >
+                                        <Input
+                                            prefix={<UserOutlined id="item-icon"/>}
+                                            placeholder="Group Id"
+                                            size="large"
+                                        />
+                                    </Form.Item>
+                                    <Form.Item style={{marginBottom: '10px'}}>
+                                        <Button size="large" type="primary" htmlType="submit"
+                                                style={{width: '100%',}}>
+                                            Join
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                            </TabPane>
+                            <TabPane tab="Create" key="2" style={{padding: "20px 30px 0"}}>
+                                <Form onFinish={this.handleCreateGroup} initialValues={{remember: true}}>
+                                    <Form.Item
+                                        name="group_name"
+                                        rules={[{required: true, message: 'Please input Group Name!'}]}
+                                    >
+                                        <Input
+                                            prefix={<UserOutlined id="item-icon"/>}
+                                            placeholder="Group Name"
+                                            size="large"
+                                        />
+                                    </Form.Item>
+                                    <Form.Item style={{marginBottom: '10px'}}>
+                                        <Button size="large" type="primary" htmlType="submit"
+                                                style={{width: '100%',}}>
+                                            Create
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                            </TabPane>
+                            <TabPane tab="Quit" key="3" style={{padding: "20px 30px 0"}}>
+                                <Form onFinish={this.handleQuitGroup}
+                                      initialValues={{remember: true}}>
+                                    <Form.Item style={{marginBottom: '10px'}}>
+                                        <Button size="large" type="primary" htmlType="submit"
+                                                style={{width: '100%',}}>
+                                            Quit Group
+                                        </Button>
+                                    </Form.Item>
+                                </Form>
+                            </TabPane>
+                        </Tabs>
+                    </div>
+                </Modal>
+            </div>
+        )
+    }
+}
+
+export default GroupModal;

+ 51 - 0
src/components/Page/SettingPage.js

@@ -0,0 +1,51 @@
+import React, {Component} from "react";
+import updateTheme from "../../utils/utils";
+import {Button, Col, Row} from "antd";
+import './SettingPage.less'
+import {SketchPicker} from "react-color";
+import UserPage from "../../layouts/User/UserPage";
+import QueueAnim from "rc-queue-anim";
+
+class SettingPage extends Component {
+    state = {
+        background: '#1890ff',
+    };
+    handleChangeComplete = (color) => {
+        this.setState({background: color.hex});
+        updateTheme(color.hex)
+    };
+
+    handleChangeCompleteHex = () => {
+        this.setState({background: '#1890ff'});
+        updateTheme('#1890ff')
+    };
+
+    render() {
+        return (
+            <div className="site-layout-background" style={{marginTop: 20, padding: 24, minHeight: '85vh'}}>
+                <div className={'edit-div'}>
+                    <div className={'edit-theme'}>
+                        Custom Theme Color
+                        <SketchPicker
+                            color={this.state.background}
+                            onChangeComplete={this.handleChangeComplete}
+                            className={'pick-theme'}
+                        />
+                        <Button type="primary" className={'default-button'} onClick={this.handleChangeCompleteHex}>
+                            Default
+                        </Button>
+                    </div>
+                </div>
+                <div className={'UserPage'}>
+                    <QueueAnim forcedReplay={false}>
+                        <div key="UserPage">
+                            <UserPage comeFrom={'setting'}/>
+                        </div>
+                    </QueueAnim>
+                </div>
+            </div>
+        )
+    }
+}
+
+export default SettingPage;

+ 29 - 0
src/components/Page/SettingPage.less

@@ -0,0 +1,29 @@
+.edit-theme {
+  text-align: center;
+  font-size: 4vh;
+  margin-top: 4vh;
+  font-weight: bolder;
+}
+
+.pick-theme {
+  margin: 4vh auto 3vh;
+}
+
+.edit-div {
+  //border: 4px solid #001529;
+  border-radius: 15px;
+  margin-top: 4vh;
+  margin-left: 10vh;
+  width: 50vh;
+  float: left;
+}
+
+.UserPage {
+  width: 74vh;
+  float: right;
+  margin-right: 5vh;
+}
+
+.default-button {
+  width: 60%;
+}

+ 54 - 10
src/components/Page/TeamPage.js

@@ -1,21 +1,65 @@
 import React, {Component} from "react";
 import DiskPage from "./DiskPage";
-import {Tabs} from "antd";
+import {message, Tabs} from "antd";
+import getCookie from "../../utils/getCookie";
+import {apiGetGroup} from "../../services/API/API";
+import './TeamPage.less'
+import GroupModal from "./GroupModal";
 
 const {TabPane} = Tabs;
 
 class TeamPage extends Component {
+    state = {
+        currentId: -1,
+        size: 8,
+        GroupList: [],
+        data: [],
+    }
+
+    componentWillMount() {
+        this.getGroupList()
+    }
+
+    getGroupList = () => {
+        let params = {
+            username: getCookie('username'),
+            token: getCookie('token'),
+        }
+        apiGetGroup(params).then(res => {
+            if (res.data.code === 200) {
+                console.log('成功')
+                this.setState({
+                    GroupList: res.data.group_list,
+                })
+            } else if (res.data.code === 401) {
+                message.error('未登录')
+                window.location.href = "";
+            } else {
+                message.error('错误')
+            }
+        })
+    }
+
     render() {
+        const {GroupList} = this.state
         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 className={'TeamDiv'}>
+                <div className={'TeamsTags'}>
+                    <Tabs
+                        tabPosition='right'
+                        centered={true}
+                    >
+
+                        {GroupList.map((ele, index) => (
+                            <TabPane tab={ele.group_name} key={ele.group_id}>
+                                <div className={'TeamPage'}>
+                                    <DiskPage group_id={ele.group_id} group_name={ele.group_name}
+                                              getGroupList={this.getGroupList.bind(this)} type={'team'}/>
+                                </div>
+                            </TabPane>
+                        ))}
+                    </Tabs>
+                </div>
             </div>
         );
     }

+ 17 - 0
src/components/Page/TeamPage.less

@@ -0,0 +1,17 @@
+.TeamsTags {
+  width: 100%;
+}
+
+.TeamModal {
+  width: 20vh;
+  //float: right;
+  //margin-right: 20vh;
+}
+
+.TeamDiv {
+  width: 100%;
+}
+
+.TeamPage{
+  margin-top: 1vh;
+}

+ 102 - 0
src/components/Page/UploadFile.js

@@ -0,0 +1,102 @@
+import React, {Component} from "react";
+import {Button, Form, message, Upload} from "antd";
+import getCookie from "../../utils/getCookie";
+import {InboxOutlined, UserOutlined} from "@ant-design/icons";
+import {apiUploadFile} from "../../services/API/API";
+import {file} from "ant-design-icons";
+import './UploadFile.less'
+
+const {Dragger} = Upload;
+
+class UploadFile extends Component {
+    state = {
+        fileList: []
+    }
+
+    constructor(props) {
+        super(props);
+        if (props.onRef) {
+            props.onRef(this)
+        }
+    }
+
+    clickButton = () => {
+        let e = document.createEvent('MouseEvents');
+        e.initEvent('click', true, true)
+        document.getElementById('upload-file-form-button').dispatchEvent(e)
+    }
+
+    handleUploadFile = () => {
+        const {fileList} = this.state
+        var formData = new FormData();
+        fileList.forEach(file => {
+            formData.append('file', file);
+        })
+        formData.append('username', getCookie('username'))
+        formData.append('token', getCookie('token'))
+        formData.append('father_folder_id', this.props.fatherFolderId)
+        // let data = {
+        //     username: getCookie('username'),
+        //     token: getCookie('token'),
+        //     father_folder_id: this.props.fatherFolderId,
+        //     fileList: fileList,
+        // }
+        apiUploadFile(formData).then(res => {
+            if (res.data.code === 200) {
+                message.success('添加成功');
+                this.props.cdFolder(this.props.fatherFolderId)
+            } else {
+                message.error(res.data.error)
+            }
+        })
+    };
+
+    beforeUpload = file => {
+        this.setState({
+            fileList: [...this.state.fileList, file]
+        })
+        return false
+    }
+
+    render() {
+        return (
+            <div className={'UploadFile-div'}>
+                <Form onFinish={this.handleUploadFile} initialValues={{remember: true}}>
+                    <Form.Item
+                        name="upload"
+                        rules={[{required: true, message: 'Please upload file!'}]}
+                    >
+                        <Dragger
+                            beforeUpload={this.beforeUpload}
+                            name={'upload'}
+                            multiple={false}
+                        >
+                            <p className="ant-upload-drag-icon">
+                                <InboxOutlined/>
+                            </p>
+                            <p className="ant-upload-text">Click or drag file to this area to upload</p>
+                            <p className="ant-upload-hint">
+                                Support for a single or bulk upload. Strictly prohibit from uploading company data or
+                                other
+                                band files
+                            </p>
+                        </Dragger>
+                    </Form.Item>
+                    <Form.Item style={{height: "1px"}}>
+                        <Button size="large" type="primary" htmlType="submit" id="upload-file-form-button"
+                                style={{
+                                    width: '100%',
+                                    display: 'none',
+                                    float: 'bottom',
+                                    marginBottom: '100vh'
+                                }}>
+                            Upload
+                        </Button>
+                    </Form.Item>
+                </Form>
+            </div>
+        )
+    }
+}
+
+export default UploadFile;

+ 3 - 0
src/components/Page/UploadFile.less

@@ -0,0 +1,3 @@
+.UploadFile-div {
+  margin-top: 2vh
+}

+ 68 - 0
src/components/Page/UploadFileModal.js

@@ -0,0 +1,68 @@
+import {Modal, Button} from 'antd';
+import React, {Component} from "react";
+import UploadFile from "./UploadFile";
+import './UploadFileModal.less'
+import {UploadOutlined} from "@ant-design/icons";
+
+class UploadFileModal extends Component {
+    state = {
+        visible: false,
+        confirmLoading: false,
+        actionName: this.props.actionName,
+    }
+
+    showModal = () => {
+        this.setState({
+            visible: true
+        })
+    }
+
+    setVisible = visible => {
+        this.setState({
+            visible: visible
+        })
+    }
+
+    setConfirmLoading = ConfirmLoading => {
+        this.setState({
+            confirmLoading: ConfirmLoading
+        })
+    }
+
+    handleOk = () => {
+        this.Child.clickButton()
+        this.setConfirmLoading(true);
+        setTimeout(() => {
+            this.setVisible(false);
+            this.setConfirmLoading(false);
+        }, 1000);
+    };
+
+    handleCancel = () => {
+        this.setVisible(false);
+    };
+
+    render() {
+        return (
+            <div className={'upload-file-modal'}>
+                <Button type="primary" onClick={this.showModal} icon={<UploadOutlined />}>
+                    UPLOAD
+                </Button>
+                <Modal
+                    title={this.state.actionName}
+                    visible={this.state.visible}
+                    onOk={this.handleOk}
+                    confirmLoading={this.state.confirmLoading}
+                    onCancel={this.handleCancel}
+                >
+                    <div>
+                        <UploadFile onRef={c => this.Child = c} cdFolder={this.props.cdFolder}
+                                    fatherFolderId={this.props.fatherFolderId}/>
+                    </div>
+                </Modal>
+            </div>
+        )
+    }
+}
+
+export default UploadFileModal;

+ 5 - 0
src/components/Page/UploadFileModal.less

@@ -0,0 +1,5 @@
+.upload-file-modal {
+  width: 8vh;
+  float: right;
+  margin-right: 8vh
+}

+ 12 - 0
src/components/Page/UserDisk.js

@@ -0,0 +1,12 @@
+import React, {Component} from "react";
+import DiskPage from "./DiskPage";
+
+class UserDisk extends Component {
+    render() {
+        return (
+            <DiskPage type={'user'}/>
+        )
+    }
+}
+
+export default UserDisk;

+ 5 - 0
src/components/Page/add-folder-modal.less

@@ -0,0 +1,5 @@
+.add-folder-modal {
+  width: 8vh;
+  float: right;
+  margin-right: 8vh
+}

+ 33 - 13
src/components/Sider/MySider.js

@@ -2,6 +2,7 @@ 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";
+import './MySider.less'
 
 const {Sider} = Layout;
 const {SubMenu} = Menu;
@@ -24,7 +25,7 @@ class MySider extends Component {
         return (
             <Sider collapsible collapsed={this.state.collapsed} onCollapse={this.onCollapse}>
                 <div className="logo"/>
-                <Menu theme="dark" defaultSelectedKeys={['file']} mode="inline">
+                <Menu theme="dark" defaultSelectedKeys={['file']} mode="inline" className={'sider-menu'}>
                     <Menu.Item key="account" icon={<UserOutlined/>}>
                         <Link to={'/cloud/account'}>
                             Account
@@ -35,23 +36,42 @@ class MySider extends Component {
                             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="team" icon={<TeamOutlined/>}>
+                        <Link to={'/cloud/teams'}>
+                            My Team
+                        </Link>
+                    </Menu.Item>
+
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
+                    <Menu.Item disabled={true}>
+
+                    </Menu.Item>
                     <Menu.Item key="setting" icon={<SettingOutlined/>}>
                         <Link to={'/cloud/setting'}>
                             Setting
                         </Link>
                     </Menu.Item>
+
                 </Menu>
             </Sider>
         )

+ 3 - 0
src/components/Sider/MySider.less

@@ -0,0 +1,3 @@
+.sider-menu{
+  margin-top: 4vh;
+}

+ 1 - 2
src/layouts/DefaultLayout/DefaultLayout.js

@@ -1,8 +1,7 @@
 import React, {Component} from "react";
-import {Layout, Breadcrumb} from 'antd';
+import {Layout, Breadcrumb, Button} 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;

+ 1 - 1
src/layouts/HomePage/HomePage.js

@@ -19,7 +19,7 @@ class HomePage extends Component {
                 <Content className={'HomeContent'}>
                     <QueueAnim forcedReplay={false}>
                         <div key="UserPage">
-                            <UserPage/>
+                            <UserPage comeFrom={'home'}/>
                         </div>
                     </QueueAnim>
                 </Content>

+ 38 - 16
src/layouts/User/UserPage.js

@@ -4,16 +4,31 @@ import {Col, Tabs} from "antd";
 import RegisterForm from "../../components/Forms/RegisterForm";
 import './UserPage.less'
 import ForgetPasswordForm from "../../components/Forms/ForgetPasswordForm";
+import LogoutForm from "../../components/Forms/LogoutForm";
 
 const {TabPane} = Tabs;
 
 class UserPage extends Component {
     state = {
         loginStatus: "login",
-        tagWidth: 8,
-        tagOffset: 12,
+        tagWidth: this.props.comeFrom === 'home' ? 8 : 20,
+        tagOffset: this.props.comeFrom === 'home' ? 12 : 2,
+        selectedKey: '1',
     };
 
+    handleLogged = () => {
+        this.setState({
+            loginStatus: this.state.loginStatus === 'login' ? 'logged' : 'login',
+        })
+    }
+
+    handleSelectKey = () => {
+        this.setState({
+            selectedKey: this.state.selectedKey === '1' ? '2' : '1',
+        })
+        this.handleLoginStatus('login')
+    }
+
     handleLoginStatus = LoginStatus => {
         this.setState(
             {
@@ -23,15 +38,15 @@ class UserPage extends Component {
         if (LoginStatus === 'forget') {
             this.setState(
                 {
-                    tagWidth: 10,
-                    tagOffset: 11,
+                    tagWidth: this.props.comeFrom === 'home' ? 10 : 24,
+                    tagOffset: this.props.comeFrom === 'home' ? 11 : 0,
                 }
             )
         } else {
             this.setState(
                 {
-                    tagWidth: 8,
-                    tagOffset: 12,
+                    tagWidth: this.props.comeFrom === 'home' ? 8 : 20,
+                    tagOffset: this.props.comeFrom === 'home' ? 12 : 2,
                 }
             )
         }
@@ -47,26 +62,33 @@ class UserPage extends Component {
         return (
             <Col span={tagWidth} offset={tagOffset} className={'UserCard'}>
                 <Tabs
-                    onChange={this.callback}
+                    onChange={this.handleSelectKey}
                     size={'large'}
                     animated={true}
                     centered={true}
                     tabBarGutter={50}
                     renderTabBar={renderTabBar}
-                    defaultActiveKey="1"
+                    activeKey={this.state.selectedKey}
                 >
-                    <TabPane tab={loginStatus === 'forget' ? "RESET PASSWORD" : "LOGIN"} key="1"
-                             style={{padding: "20px 30px 10px"}}>
+                    <TabPane
+                        tab={loginStatus === 'forget' ? "RESET PASSWORD" : loginStatus === 'login' ? "LOGIN" : 'LOGOUT'}
+                        key="1"
+                        style={{padding: "20px 30px 10px"}}>
                         {
                             loginStatus === 'login' ?
-                                <LoginForm handleLoginStatus={this.handleLoginStatus.bind(this)}/>
-                                :
-                                <ForgetPasswordForm handleLoginStatus={this.handleLoginStatus.bind(this)}/>
+                                <LoginForm handleLoginStatus={this.handleLoginStatus.bind(this)}
+                                           handleLogged={this.handleLogged.bind(this)}/>
+                                : loginStatus === 'forget' ?
+                                    <ForgetPasswordForm handleLoginStatus={this.handleLoginStatus.bind(this)}/>
+                                    : <LogoutForm handleLogged={this.handleLogged.bind(this)}/>
                         }
                     </TabPane>
-                    <TabPane tab="REGISTER" key="2" style={{padding: "20px 30px 0"}}>
-                        <RegisterForm/>
-                    </TabPane>
+                    {
+                        loginStatus === 'logged' ? null :
+                            <TabPane tab="REGISTER" key="2" style={{padding: "20px 30px 0"}}>
+                                <RegisterForm handleSelectKey={this.handleSelectKey.bind(this)}/>
+                            </TabPane>
+                    }
                 </Tabs>
             </Col>
         );

+ 4 - 2
src/routes/PageRouter/PageRouter.js

@@ -1,9 +1,10 @@
 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";
 import TestDataPage from "../../components/Page/TestDataPage";
 import Folder from "../../components/Page/Folder";
+import SettingPage from "../../components/Page/SettingPage";
+import UserDisk from "../../components/Page/UserDisk";
 
 class PageRouter extends Component {
     render() {
@@ -11,10 +12,11 @@ class PageRouter extends Component {
             <div id={'router'}>
                 <HashRouter>
                     <Switch>
-                        <Route path={'/cloud/file'} component={DiskPage}/>
+                        <Route path={'/cloud/file'} component={UserDisk}/>
                         <Route path={'/cloud/teams'} component={TeamPage}/>
                         <Route path={'/cloud/test'} component={TestDataPage}/>
                         <Route path={'/cloud/folder'} component={Folder}/>
+                        <Route path={'/cloud/setting'} component={SettingPage}/>
                     </Switch>
                 </HashRouter>
             </div>

+ 122 - 7
src/services/API/API.js

@@ -1,10 +1,11 @@
 import fetch from "../../utils/Axios/Axios";
 import Qs from 'qs'
+import instance from "../../utils/Axios/UploadFile";
 
 // example 获取数据json模拟
 export function apiRegister(params) {
     return fetch({
-        url: '/profile/register/',
+        url: '/account/register/',
         method: 'post',
         data: Qs.stringify(params),
     })
@@ -13,7 +14,7 @@ export function apiRegister(params) {
 // 登录校验
 export function apiLogin(params) {
     return fetch({
-        url: '/profile/login/',
+        url: '/account/login/',
         method: 'post',
         data: Qs.stringify(params),
     })
@@ -26,16 +27,130 @@ export function apiIsLogged() {
     })
 }
 
-export function apiLogout() {
+export function apiLogout(params) {
     return fetch({
-        url: '/profile/logout/',
-        method: 'get',
+        url: '/account/logout/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiRootFolderId(params) {
+    return fetch({
+        url: '/folder/get_root_folder/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiFolderList(params) {
+    return fetch({
+        url: '/folder/folder_list/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiAddFolder(params) {
+    return fetch({
+        url: '/folder/add_folder/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiUploadFile(params) {
+    console.log(params.get('username'))
+    return instance({
+        url: '/file/upload_file/',
+        method: 'post',
+        data: params,
+    })
+}
+
+export function apiGetGroup(params) {
+    return fetch({
+        url: '/group/group_list/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiJoinGroup(params) {
+    return fetch({
+        url: '/group/join_group/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiCreateGroup(params) {
+    return fetch({
+        url: '/group/create_group/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiGroupRoot(params) {
+    return fetch({
+        url: '/group/get_group_root_folder/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiConfirm(params) {
+    return fetch({
+        url: '/account/check_token/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiCheck(params) {
+    return fetch({
+        url: '/account/send_email_verification_code/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiReset(params) {
+    return fetch({
+        url: '/account/reset_password/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiDownload(params) {
+    return fetch({
+        url: '/file/download_file/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiDelete(params) {
+    return fetch({
+        url: '/file/delete_file/',
+        method: 'post',
+        data: Qs.stringify(params),
+    })
+}
+
+export function apiDeleteFolder(params) {
+    return fetch({
+        url: '/folder/delete_folder/',
+        method: 'post',
+        data: Qs.stringify(params),
     })
 }
 
-export function apiPostMission(params) {
+export function apiQuitGroup(params) {
     return fetch({
-        url: '/craw_keywords/page_mission/',
+        url: '/group/quit_group/',
         method: 'post',
         data: Qs.stringify(params),
     })

+ 8 - 5
src/utils/Axios/Axios.js

@@ -1,16 +1,17 @@
 import axios from 'axios'
 import qs from 'qs'
+import '../encrypt/rsa'
+import '../encrypt/aes'
 
 let fetch = axios.create({
-    // baseURL: "http://30gz758467.51vip.biz", // 这里是本地express启动的服务地址
-    baseURL: "http://localhost:8000", // 这里是本地express启动的服务地址
+    baseURL: "http://30gz758467.51vip.biz:59173/", // 这里是本地express启动的服务地址
     timeout: 5000 // request timeout
 })
-fetch.defaults.withCredentials = true
+// 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]
+    // 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)
@@ -28,8 +29,10 @@ fetch.interceptors.response.use(async data => {
         if (error.response.status === 500) {
             console.log('服务器错误,请联系管理员处理')
         }
+        console.log('1服务器错误,请联系管理员处理')
         return Promise.reject(error.response.data)
     } else {
+        console.log('2服务器错误,请联系管理员处理')
         return Promise.reject(error)
     }
 })

+ 23 - 0
src/utils/Axios/UploadFile.js

@@ -0,0 +1,23 @@
+import axios from 'axios'
+import qs from 'qs'
+
+// 实例对象
+let instance = axios.create({
+    baseURL: "http://30gz758467.51vip.biz:59173/", // 这里是本地express启动的服务地址
+    timeout: 5000, // request timeout
+    headers: {
+        // 'Content-Type': 'application/x-www-form-urlencoded'multipart/form-data
+        'Content-Type': 'multipart/form-data;charset=UTF-8'
+    }
+})
+
+// 请求拦截器
+instance.interceptors.request.use(
+    config => {
+        // config.data = qs.stringify(config.data) // 转为formdata数据格式
+        return config
+    },
+    error => Promise.error(error)
+)
+
+export default instance;

+ 30 - 0
src/utils/Download/Download.js

@@ -0,0 +1,30 @@
+export function convertRes2Blob(response) {
+    // 提取文件名
+    const fileName = response.headers['content-disposition'].match(
+        /filename=(.*)/
+    )[1]
+    // 将二进制流转为blob
+    const blob = new Blob([response.data], {type: 'application/octet-stream'})
+    if (typeof window.navigator.msSaveBlob !== 'undefined') {
+        // 兼容IE,window.navigator.msSaveBlob:以本地方式保存文件
+        window.navigator.msSaveBlob(blob, decodeURI(fileName))
+    } else {
+        // 创建新的URL并指向File对象或者Blob对象的地址
+        const blobURL = window.URL.createObjectURL(blob)
+        // 创建a标签,用于跳转至下载链接
+        const tempLink = document.createElement('a')
+        tempLink.style.display = 'none'
+        tempLink.href = blobURL
+        tempLink.setAttribute('download', decodeURI(fileName))
+        // 兼容:某些浏览器不支持HTML5的download属性
+        if (typeof tempLink.download === 'undefined') {
+            tempLink.setAttribute('target', '_blank')
+        }
+        // 挂载a标签
+        document.body.appendChild(tempLink)
+        tempLink.click()
+        document.body.removeChild(tempLink)
+        // 释放blob URL地址
+        window.URL.revokeObjectURL(blobURL)
+    }
+}

+ 16 - 0
src/utils/deleteCookie.js

@@ -0,0 +1,16 @@
+function deleteCookie() {
+    let c_name = 'username'
+    let date = new Date();
+    date.setTime(date.getTime() - 1);
+    let c_start;
+    let c_end;
+    if (document.cookie.length > 0) {
+        c_start = document.cookie.indexOf(c_name + '=')
+        if (c_start !== -1) {
+            c_end = document.cookie.length
+            document.cookie = unescape(document.cookie.substring(c_start, c_end)) + ';expire=' + date.toUTCString();
+        }
+    }
+}
+
+export default deleteCookie;

+ 50 - 0
src/utils/encrypt/aes.js

@@ -0,0 +1,50 @@
+import CryptoJS from 'crypto-js'
+
+var iv = CryptoJS.enc.Utf8.parse("16-Bytes--String");   //加密向量
+function AESEnc(key, content) {
+    var key = CryptoJS.enc.Utf8.parse(key);   //加密密钥
+    var srcs = CryptoJS.enc.Utf8.parse(content);
+    var encrypted = CryptoJS.AES.encrypt(srcs, key, {iv: iv, mode: CryptoJS.mode.CBC});
+    return encrypted.toString();
+}
+
+function AESDec(key, content) {
+    var key = CryptoJS.enc.Utf8.parse(key);   //加密密钥
+    var decrypted = CryptoJS.AES.decrypt(content, key, {iv: iv, mode: CryptoJS.mode.CBC});
+    return decrypted.toString(CryptoJS.enc.Utf8);
+}
+
+function getKey() {
+    return uuid(16, 16);
+}
+
+function uuid(len, radix) {
+    var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+    var uuid = [], i;
+    radix = radix || chars.length;
+
+    if (len) {
+        // Compact form
+        for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
+    } else {
+        // rfc4122, version 4 form
+        var r;
+
+        // rfc4122 requires these characters
+        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+        uuid[14] = '4';
+
+        // Fill in random data.  At i==19 set the high bits of clock sequence as
+        // per rfc4122, sec. 4.1.5
+        for (i = 0; i < 36; i++) {
+            if (!uuid[i]) {
+                r = 0 | Math.random() * 16;
+                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+            }
+        }
+    }
+
+    return uuid.join('');
+}
+
+

+ 15 - 0
src/utils/encrypt/encrypt.js

@@ -0,0 +1,15 @@
+import 'rsa.js'
+import 'aes.js'
+
+function encrypt() {
+    let key = getKey();
+    let encryptKey = RSA(key);
+    console.log("encryptKey: " + encryptKey);
+    let cipherText = AESEnc(key, "context");
+    let plainText = AESDec(key, cipherText);
+    console.log("密文: " + cipherText);
+    console.log("明文: " + plainText);
+    return [cipherText, encryptKey]
+}
+
+export default encrypt;

+ 9 - 0
src/utils/encrypt/rsa.js

@@ -0,0 +1,9 @@
+import JSEncrypt from 'jsencrypt'
+
+var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCRQZ5O/AOAjeYAaSFf6Rjhqovws78I716I9oGF7WxCIPmcaUa1YuyLOncCCuPsaw69+RMWjdbOBp8hd4PPM/d4mKTOVEYUE0SfxhhDTZaM5CzQEUXUyXy7icQTGR5wBjrbjU1yHCKOf5PJJZZQWB06husSFZ40TdL7FdlBpZ1u1QIDAQAB";
+var encrypt = new JSEncrypt();
+encrypt.setPublicKey(publicKey);
+
+function RSA(content) {
+    return encrypt.encrypt(content);
+}

+ 18 - 0
src/utils/getCookie.js

@@ -0,0 +1,18 @@
+function getCookie(c_name) {
+    let c_start;
+    let c_end;
+    if (document.cookie.length > 0) {
+        c_start = document.cookie.indexOf(c_name + '=')
+        if (c_start !== -1) {
+            c_start = c_start + c_name.length + 1
+            c_end = document.cookie.indexOf(';', c_start)
+            if (c_end === -1) {
+                c_end = document.cookie.length
+            }
+            return unescape(document.cookie.substring(c_start, c_end))
+        }
+    }
+    return ''
+}
+
+export default getCookie;

+ 55 - 0
src/utils/plugin.config.js

@@ -0,0 +1,55 @@
+const ThemeColorReplacer = require("webpack-theme-color-replacer");
+const generate = require("@ant-design/colors").generate;
+// generate方法和ant-design-vue获取方式的区别
+// react: require("@ant-design/colors").generate;
+// vue: require('@ant-design/colors/lib/generate').default
+
+const getAntdSerials = (color) => {
+    // 淡化(即less的tint)
+    const lightens = new Array(9).fill().map((t, i) => {
+        return ThemeColorReplacer.varyColor.lighten(color, i / 10);
+    });
+    const colorPalettes = generate(color);
+    const rgb = ThemeColorReplacer.varyColor
+        .toNum3(color.replace("#", ""))
+        .join(",");
+    return lightens.concat(colorPalettes).concat(rgb);
+};
+
+const themePluginOption = {
+    fileName: "css/theme-colors-[contenthash:8].css",
+    matchColors: getAntdSerials("#1890ff"), // 主色系列
+    // 改变样式选择器,解决样式覆盖问题
+    changeSelector(selector) {
+        switch (selector) {
+            case ".ant-calendar-today .ant-calendar-date":
+                return (
+                    ":not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)" +
+                    selector
+                );
+            case ".ant-btn:focus,.ant-btn:hover":
+                return ".ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)";
+            case ".ant-btn.active,.ant-btn:active":
+                return ".ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)";
+            case ".ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon":
+            case ".ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon":
+                return ":not(.ant-steps-item-process)" + selector;
+            case ".ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover":
+            case ".ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover":
+                return ".ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover";
+            case ".ant-menu-horizontal > .ant-menu-item-selected > a":
+            case ".ant-menu-horizontal>.ant-menu-item-selected>a":
+                return ".ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a";
+            case ".ant-menu-horizontal > .ant-menu-item > a:hover":
+            case ".ant-menu-horizontal>.ant-menu-item>a:hover":
+                return ".ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover";
+            default:
+                return selector;
+        }
+    },
+};
+
+const createThemeColorReplacerPlugin = () =>
+    new ThemeColorReplacer(themePluginOption);
+
+module.exports = createThemeColorReplacerPlugin;

+ 37 - 0
src/utils/utils.js

@@ -0,0 +1,37 @@
+import client from "webpack-theme-color-replacer/client";
+import {generate} from "@ant-design/colors";
+
+// 同样的 antd-react和antd-vue获取generate方法的路径是不同的
+
+function getAntdSerials(color) {
+    // 淡化(即less的tint)
+    const lightens = new Array(9).fill().map((t, i) => {
+        return client.varyColor.lighten(color, i / 10);
+    });
+    // colorPalette变换得到颜色值
+    const colorPalettes = generate(color);
+    const rgb = client.varyColor.toNum3(color.replace("#", "")).join(",");
+    return lightens.concat(colorPalettes).concat(rgb);
+}
+
+function changeColor(newColor) {
+    var options = {
+        newColors: getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
+        changeUrl(cssUrl) {
+            return `/${cssUrl}`; // while router is not `hash` mode, it needs absolute path
+        },
+    };
+    return client.changer.changeColor(options, Promise);
+}
+
+function updateTheme(newPrimaryColor) {
+    const hideMessage = () => console.log("正在切换主题!", 0);
+    changeColor(newPrimaryColor).finally((t) => {
+        setTimeout(() => {
+            hideMessage();
+        });
+    });
+}
+
+
+export default updateTheme;

+ 480 - 9
yarn.lock

@@ -1258,6 +1258,11 @@
   dependencies:
     "@hapi/hoek" "^8.3.0"
 
+"@icons/material@^0.2.4":
+  version "0.2.4"
+  resolved "https://registry.npm.taobao.org/@icons/material/download/@icons/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
+  integrity sha1-6QyfcXaLNzbnbX3WeD/Gwq+oi8g=
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -2272,6 +2277,15 @@ ajv@^7.0.2:
     require-from-string "^2.0.2"
     uri-js "^4.2.2"
 
+align-text@^0.1.1, align-text@^0.1.3:
+  version "0.1.4"
+  resolved "https://registry.npm.taobao.org/align-text/download/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+  integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=
+  dependencies:
+    kind-of "^3.0.2"
+    longest "^1.0.1"
+    repeat-string "^1.5.2"
+
 alphanum-sort@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@@ -2294,6 +2308,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.1:
   dependencies:
     type-fest "^0.11.0"
 
+ansi-gray@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.npm.taobao.org/ansi-gray/download/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
+  integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE=
+  dependencies:
+    ansi-wrap "0.1.0"
+
 ansi-html@0.0.7, ansi-html@^0.0.7:
   version "0.0.7"
   resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -2314,6 +2335,11 @@ ansi-regex@^5.0.0:
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
   integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
 
+ansi-styles@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.nlark.com/ansi-styles/download/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+  integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
+
 ansi-styles@^3.2.0, ansi-styles@^3.2.1:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -2333,6 +2359,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==
 
+ansi-wrap@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npm.taobao.org/ansi-wrap/download/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
+  integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
+
 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"
@@ -2441,6 +2472,11 @@ arr-union@^3.1.0:
   resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
   integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
 
+array-differ@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.nlark.com/array-differ/download/array-differ-1.0.0.tgz?cache=0&sync_timestamp=1628631380258&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farray-differ%2Fdownload%2Farray-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031"
+  integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=
+
 array-flatten@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
@@ -2479,7 +2515,7 @@ array-union@^2.1.0:
   resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
-array-uniq@^1.0.1:
+array-uniq@^1.0.1, array-uniq@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
   integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
@@ -2585,6 +2621,11 @@ async@^2.6.2:
   dependencies:
     lodash "^4.17.14"
 
+async@~0.2.6:
+  version "0.2.10"
+  resolved "https://registry.nlark.com/async/download/async-0.2.10.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fasync%2Fdownload%2Fasync-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+  integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2839,6 +2880,11 @@ bcrypt-pbkdf@^1.0.0:
   dependencies:
     tweetnacl "^0.14.3"
 
+beeper@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.nlark.com/beeper/download/beeper-1.1.1.tgz?cache=0&sync_timestamp=1620038988553&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fbeeper%2Fdownload%2Fbeeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
+  integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=
+
 bfj@^7.0.2:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/bfj/-/bfj-7.0.2.tgz#1988ce76f3add9ac2913fd8ba47aad9e651bfbb2"
@@ -3197,6 +3243,11 @@ camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
   integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
 
+camelcase@^1.0.2:
+  version "1.2.1"
+  resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-1.2.1.tgz?cache=0&sync_timestamp=1603921799543&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase%2Fdownload%2Fcamelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+  integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=
+
 camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0:
   version "6.2.0"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
@@ -3234,6 +3285,14 @@ caseless@~0.12.0:
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
   integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
 
+center-align@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.npm.taobao.org/center-align/download/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+  integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60=
+  dependencies:
+    align-text "^0.1.3"
+    lazy-cache "^1.0.3"
+
 chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3243,6 +3302,17 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
     escape-string-regexp "^1.0.5"
     supports-color "^5.3.0"
 
+chalk@^1.0.0:
+  version "1.1.3"
+  resolved "https://registry.nlark.com/chalk/download/chalk-1.1.3.tgz?cache=0&sync_timestamp=1627646697260&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fchalk%2Fdownload%2Fchalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+  integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
+  dependencies:
+    ansi-styles "^2.2.1"
+    escape-string-regexp "^1.0.2"
+    has-ansi "^2.0.0"
+    strip-ansi "^3.0.0"
+    supports-color "^2.0.0"
+
 chalk@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
@@ -3373,6 +3443,15 @@ clean-stack@^2.0.0:
   resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
   integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
 
+cliui@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npm.taobao.org/cliui/download/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+  integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=
+  dependencies:
+    center-align "^0.1.1"
+    right-align "^0.1.1"
+    wordwrap "0.0.2"
+
 cliui@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@@ -3391,6 +3470,16 @@ cliui@^6.0.0:
     strip-ansi "^6.0.0"
     wrap-ansi "^6.2.0"
 
+clone-stats@^0.0.1:
+  version "0.0.1"
+  resolved "https://registry.npm.taobao.org/clone-stats/download/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
+  integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=
+
+clone@^1.0.0:
+  version "1.0.4"
+  resolved "https://registry.npm.taobao.org/clone/download/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
+  integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
+
 co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -3450,6 +3539,11 @@ color-string@^1.5.4:
     color-name "^1.0.0"
     simple-swizzle "^0.2.2"
 
+color-support@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.npm.taobao.org/color-support/download/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
+  integrity sha1-k4NDeaHMmgxh+C9S8NBDIiUb1aI=
+
 color@^3.0.0:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
@@ -3768,6 +3862,11 @@ crypto-browserify@^3.11.0:
     randombytes "^2.0.0"
     randomfill "^1.0.3"
 
+crypto-js@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.nlark.com/crypto-js/download/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
+  integrity sha1-nkhbzwNSEEG9hYRHhrg/t2GXNs8=
+
 crypto-random-string@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
@@ -4042,11 +4141,21 @@ date-fns@2.x:
   resolved "https://registry.nlark.com/date-fns/download/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9"
   integrity sha1-TohslBZZrwz3sw+v3R6qN+iHiKk=
 
+dateformat@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.nlark.com/dateformat/download/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062"
+  integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=
+
 dayjs@1.x:
   version "1.10.6"
   resolved "https://registry.nlark.com/dayjs/download/dayjs-1.10.6.tgz#288b2aa82f2d8418a6c9d4df5898c0737ad02a63"
   integrity sha1-KIsqqC8thBimydTfWJjAc3rQKmM=
 
+deap@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/deap/download/deap-1.0.1.tgz#0646e9e1a095ffe8a9e404d68d1f76dcf57e66fb"
+  integrity sha1-Bkbp4aCV/+ip5ATWjR923PV+Zvs=
+
 debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -4068,7 +4177,7 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
   dependencies:
     ms "2.1.2"
 
-decamelize@^1.2.0:
+decamelize@^1.0.0, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -4356,6 +4465,13 @@ dotenv@8.2.0:
   resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
   integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
 
+duplexer2@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npm.taobao.org/duplexer2/download/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db"
+  integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=
+  dependencies:
+    readable-stream "~1.1.9"
+
 duplexer@^0.1.1:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@@ -4588,7 +4704,7 @@ escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
   integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
 
-escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -5028,6 +5144,16 @@ extsprintf@^1.2.0:
   resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
   integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
 
+fancy-log@^1.0.0, fancy-log@^1.1.0:
+  version "1.3.3"
+  resolved "https://registry.npm.taobao.org/fancy-log/download/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
+  integrity sha1-28GRVPVYaQFQojlToK29A1vkX8c=
+  dependencies:
+    ansi-gray "^0.1.1"
+    color-support "^1.1.3"
+    parse-node-version "^1.0.0"
+    time-stamp "^1.0.0"
+
 fast-deep-equal@^3.1.1:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -5499,6 +5625,13 @@ globby@^6.1.0:
     pify "^2.0.0"
     pinkie-promise "^2.0.0"
 
+glogg@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.nlark.com/glogg/download/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f"
+  integrity sha1-LX3XAr7aIus7/634gGltpthGMT8=
+  dependencies:
+    sparkles "^1.0.0"
+
 graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
   version "4.2.6"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@@ -5509,6 +5642,56 @@ growly@^1.3.0:
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
   integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
 
+gulp-rename@^1.2.2:
+  version "1.4.0"
+  resolved "https://registry.npm.taobao.org/gulp-rename/download/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd"
+  integrity sha1-3hxxjnxAla6GH3KW708ySGSCQL0=
+
+gulp-uglify@^1.5.3:
+  version "1.5.4"
+  resolved "https://registry.npm.taobao.org/gulp-uglify/download/gulp-uglify-1.5.4.tgz#524788d87666d09f9d0c21fb2177f90039a658c9"
+  integrity sha1-UkeI2HZm0J+dDCH7IXf5ADmmWMk=
+  dependencies:
+    deap "^1.0.0"
+    fancy-log "^1.0.0"
+    gulp-util "^3.0.0"
+    isobject "^2.0.0"
+    through2 "^2.0.0"
+    uglify-js "2.6.4"
+    uglify-save-license "^0.4.1"
+    vinyl-sourcemaps-apply "^0.2.0"
+
+gulp-util@^3.0.0:
+  version "3.0.8"
+  resolved "https://registry.nlark.com/gulp-util/download/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f"
+  integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08=
+  dependencies:
+    array-differ "^1.0.0"
+    array-uniq "^1.0.2"
+    beeper "^1.0.0"
+    chalk "^1.0.0"
+    dateformat "^2.0.0"
+    fancy-log "^1.1.0"
+    gulplog "^1.0.0"
+    has-gulplog "^0.1.0"
+    lodash._reescape "^3.0.0"
+    lodash._reevaluate "^3.0.0"
+    lodash._reinterpolate "^3.0.0"
+    lodash.template "^3.0.0"
+    minimist "^1.1.0"
+    multipipe "^0.1.2"
+    object-assign "^3.0.0"
+    replace-ext "0.0.1"
+    through2 "^2.0.0"
+    vinyl "^0.5.0"
+
+gulplog@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.nlark.com/gulplog/download/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5"
+  integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U=
+  dependencies:
+    glogg "^1.0.0"
+
 gzip-size@5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"
@@ -5540,6 +5723,13 @@ harmony-reflect@^1.4.6:
   resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9"
   integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==
 
+has-ansi@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz?cache=0&sync_timestamp=1618558073716&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhas-ansi%2Fdownload%2Fhas-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+  integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
+  dependencies:
+    ansi-regex "^2.0.0"
+
 has-flag@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -5550,6 +5740,13 @@ has-flag@^4.0.0:
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
+has-gulplog@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npm.taobao.org/has-gulplog/download/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce"
+  integrity sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=
+  dependencies:
+    sparkles "^1.0.0"
+
 has-symbols@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
@@ -6851,6 +7048,11 @@ jest@26.6.0:
     import-local "^3.0.2"
     jest-cli "^26.6.0"
 
+js-file-download@^0.4.12:
+  version "0.4.12"
+  resolved "https://registry.npm.taobao.org/js-file-download/download/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821"
+  integrity sha1-EMcO82JVmlsjzb3DvW85nD2R2CE=
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -6901,6 +7103,14 @@ jsdom@^16.4.0:
     ws "^7.2.3"
     xml-name-validator "^3.0.0"
 
+jsencrypt@2.3.1:
+  version "2.3.1"
+  resolved "https://registry.nlark.com/jsencrypt/download/jsencrypt-2.3.1.tgz?cache=0&sync_timestamp=1627585554349&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fjsencrypt%2Fdownload%2Fjsencrypt-2.3.1.tgz#64edcb781bf92035db6a95901012e91c0f42eaca"
+  integrity sha1-ZO3LeBv5IDXbapWQEBLpHA9C6so=
+  dependencies:
+    gulp-rename "^1.2.2"
+    gulp-uglify "^1.5.3"
+
 jsesc@^2.5.1:
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@@ -7065,6 +7275,11 @@ last-call-webpack-plugin@^3.0.0:
     lodash "^4.17.5"
     webpack-sources "^1.1.0"
 
+lazy-cache@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.npm.taobao.org/lazy-cache/download/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+  integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4=
+
 less-loader@^7.3.0:
   version "7.3.0"
   resolved "https://registry.nlark.com/less-loader/download/less-loader-7.3.0.tgz#f9d6d36d18739d642067a05fb5bd70c8c61317e5"
@@ -7182,21 +7397,112 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+lodash-es@^4.17.15:
+  version "4.17.21"
+  resolved "https://registry.npm.taobao.org/lodash-es/download/lodash-es-4.17.21.tgz?cache=0&sync_timestamp=1613836280924&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flodash-es%2Fdownload%2Flodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
+  integrity sha1-Q+YmxG5lkbd1C+srUBFzkMYJ4+4=
+
+lodash._basecopy@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.nlark.com/lodash._basecopy/download/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
+  integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=
+
+lodash._basetostring@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.nlark.com/lodash._basetostring/download/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5"
+  integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=
+
+lodash._basevalues@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.nlark.com/lodash._basevalues/download/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7"
+  integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=
+
+lodash._getnative@^3.0.0:
+  version "3.9.1"
+  resolved "https://registry.npm.taobao.org/lodash._getnative/download/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
+  integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=
+
+lodash._isiterateecall@^3.0.0:
+  version "3.0.9"
+  resolved "https://registry.npm.taobao.org/lodash._isiterateecall/download/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c"
+  integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=
+
+lodash._reescape@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npm.taobao.org/lodash._reescape/download/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a"
+  integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=
+
+lodash._reevaluate@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npm.taobao.org/lodash._reevaluate/download/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed"
+  integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=
+
 lodash._reinterpolate@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
   integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=
 
+lodash._root@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.npm.taobao.org/lodash._root/download/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
+  integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
+
+lodash.escape@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.npm.taobao.org/lodash.escape/download/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698"
+  integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=
+  dependencies:
+    lodash._root "^3.0.0"
+
+lodash.isarguments@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.npm.taobao.org/lodash.isarguments/download/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
+  integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=
+
+lodash.isarray@^3.0.0:
+  version "3.0.4"
+  resolved "https://registry.npm.taobao.org/lodash.isarray/download/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55"
+  integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=
+
+lodash.keys@^3.0.0:
+  version "3.1.2"
+  resolved "https://registry.npm.taobao.org/lodash.keys/download/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
+  integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=
+  dependencies:
+    lodash._getnative "^3.0.0"
+    lodash.isarguments "^3.0.0"
+    lodash.isarray "^3.0.0"
+
 lodash.memoize@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
   integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
 
+lodash.restparam@^3.0.0:
+  version "3.6.1"
+  resolved "https://registry.nlark.com/lodash.restparam/download/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
+  integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=
+
 lodash.sortby@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
   integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
 
+lodash.template@^3.0.0:
+  version "3.6.2"
+  resolved "https://registry.npm.taobao.org/lodash.template/download/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f"
+  integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=
+  dependencies:
+    lodash._basecopy "^3.0.0"
+    lodash._basetostring "^3.0.0"
+    lodash._basevalues "^3.0.0"
+    lodash._isiterateecall "^3.0.0"
+    lodash._reinterpolate "^3.0.0"
+    lodash.escape "^3.0.0"
+    lodash.keys "^3.0.0"
+    lodash.restparam "^3.0.0"
+    lodash.templatesettings "^3.0.0"
+
 lodash.template@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
@@ -7205,6 +7511,14 @@ lodash.template@^4.5.0:
     lodash._reinterpolate "^3.0.0"
     lodash.templatesettings "^4.0.0"
 
+lodash.templatesettings@^3.0.0:
+  version "3.1.1"
+  resolved "https://registry.npm.taobao.org/lodash.templatesettings/download/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5"
+  integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=
+  dependencies:
+    lodash._reinterpolate "^3.0.0"
+    lodash.escape "^3.0.0"
+
 lodash.templatesettings@^4.0.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33"
@@ -7217,7 +7531,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5:
+"lodash@>=3.5 <5", lodash@^4.0.1, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -7227,6 +7541,11 @@ loglevel@^1.6.8:
   resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.1.tgz#005fde2f5e6e47068f935ff28573e125ef72f197"
   integrity sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==
 
+longest@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/longest/download/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+  integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=
+
 loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -7301,6 +7620,11 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+material-colors@^1.2.1:
+  version "1.2.6"
+  resolved "https://registry.npm.taobao.org/material-colors/download/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
+  integrity sha1-bRlYhxEmmSzuzHL0vMTY8BCGX0Y=
+
 md5.js@^1.3.4:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -7468,7 +7792,7 @@ minimatch@3.0.4, minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
@@ -7595,6 +7919,13 @@ multicast-dns@^6.0.1:
     dns-packet "^1.3.1"
     thunky "^1.0.2"
 
+multipipe@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.npm.taobao.org/multipipe/download/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b"
+  integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=
+  dependencies:
+    duplexer2 "0.0.2"
+
 nan@^2.12.1:
   version "2.14.2"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@@ -7810,6 +8141,11 @@ oauth-sign@~0.9.0:
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
   integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
 
+object-assign@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.nlark.com/object-assign/download/object-assign-3.0.0.tgz?cache=0&sync_timestamp=1618846992533&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fobject-assign%2Fdownload%2Fobject-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
+  integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=
+
 object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -8141,7 +8477,7 @@ 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:
+parse-node-version@^1.0.0, 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=
@@ -9091,7 +9427,7 @@ prompts@2.4.0, prompts@^2.0.1:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
-prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -9626,6 +9962,19 @@ react-app-polyfill@^2.0.0:
     regenerator-runtime "^0.13.7"
     whatwg-fetch "^3.4.1"
 
+react-color@^2.19.3:
+  version "2.19.3"
+  resolved "https://registry.nlark.com/react-color/download/react-color-2.19.3.tgz#ec6c6b4568312a3c6a18420ab0472e146aa5683d"
+  integrity sha1-7GxrRWgxKjxqGEIKsEcuFGqlaD0=
+  dependencies:
+    "@icons/material" "^0.2.4"
+    lodash "^4.17.15"
+    lodash-es "^4.17.15"
+    material-colors "^1.2.1"
+    prop-types "^15.5.10"
+    reactcss "^1.2.0"
+    tinycolor2 "^1.4.1"
+
 react-dev-utils@^11.0.3:
   version "11.0.4"
   resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a"
@@ -9788,6 +10137,13 @@ react@^17.0.2:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
 
+reactcss@^1.2.0:
+  version "1.2.3"
+  resolved "https://registry.npm.taobao.org/reactcss/download/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
+  integrity sha1-wAATh15Vexzw39mjaKHD2rO1SN0=
+  dependencies:
+    lodash "^4.0.1"
+
 read-pkg-up@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@@ -9846,6 +10202,16 @@ readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0:
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
 
+readable-stream@~1.1.9:
+  version "1.1.14"
+  resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+  integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+  dependencies:
+    core-util-is "~1.0.0"
+    inherits "~2.0.1"
+    isarray "0.0.1"
+    string_decoder "~0.10.x"
+
 readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -9982,11 +10348,16 @@ repeat-element@^1.1.2:
   resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
   integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
 
-repeat-string@^1.6.1:
+repeat-string@^1.5.2, repeat-string@^1.6.1:
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
   integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
 
+replace-ext@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.npm.taobao.org/replace-ext/download/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924"
+  integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=
+
 request-promise-core@1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
@@ -10163,6 +10534,13 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
+right-align@^0.1.1:
+  version "0.1.3"
+  resolved "https://registry.npm.taobao.org/right-align/download/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+  integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8=
+  dependencies:
+    align-text "^0.1.1"
+
 rimraf@^2.5.4, rimraf@^2.6.3:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -10658,7 +11036,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
-source-map@^0.5.0, source-map@^0.5.6:
+source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.6, source-map@~0.5.1:
   version "0.5.7"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
   integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@@ -10673,6 +11051,11 @@ sourcemap-codec@^1.4.4:
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 
+sparkles@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.npm.taobao.org/sparkles/download/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
+  integrity sha1-AI22XtzmxQ7sDF4ijhlFBh3QQ3w=
+
 spdx-correct@^3.0.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@@ -10907,6 +11290,11 @@ string_decoder@^1.0.0, string_decoder@^1.1.1:
   dependencies:
     safe-buffer "~5.2.0"
 
+string_decoder@~0.10.x:
+  version "0.10.31"
+  resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+  integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
 string_decoder@~1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -11006,6 +11394,11 @@ stylehacks@^4.0.0:
     postcss "^7.0.0"
     postcss-selector-parser "^3.0.0"
 
+supports-color@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.nlark.com/supports-color/download/supports-color-2.0.0.tgz?cache=0&sync_timestamp=1626703400240&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fsupports-color%2Fdownload%2Fsupports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+  integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -11208,6 +11601,11 @@ thunky@^1.0.2:
   resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
   integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
 
+time-stamp@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.npm.taobao.org/time-stamp/download/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
+  integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
+
 timers-browserify@^2.0.4:
   version "2.0.12"
   resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee"
@@ -11230,6 +11628,11 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3:
   resolved "https://registry.npm.taobao.org/tiny-warning/download/tiny-warning-1.0.3.tgz?cache=0&sync_timestamp=1562635535811&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftiny-warning%2Fdownload%2Ftiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha1-lKMNtFPfTGQ9D9VmBg1gqHXYR1Q=
 
+tinycolor2@^1.4.1:
+  version "1.4.2"
+  resolved "https://registry.npm.taobao.org/tinycolor2/download/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803"
+  integrity sha1-P2pNEHGtB2dtf6Ry4frECnGdiAM=
+
 tmpl@1.0.x:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
@@ -11458,6 +11861,26 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
+uglify-js@2.6.4:
+  version "2.6.4"
+  resolved "https://registry.nlark.com/uglify-js/download/uglify-js-2.6.4.tgz?cache=0&sync_timestamp=1631026584603&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fuglify-js%2Fdownload%2Fuglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf"
+  integrity sha1-ZeovswWck5RpLxX+2HwrNsFrmt8=
+  dependencies:
+    async "~0.2.6"
+    source-map "~0.5.1"
+    uglify-to-browserify "~1.0.0"
+    yargs "~3.10.0"
+
+uglify-save-license@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.npm.taobao.org/uglify-save-license/download/uglify-save-license-0.4.1.tgz#95726c17cc6fd171c3617e3bf4d8d82aa8c4cce1"
+  integrity sha1-lXJsF8xv0XHDYX479NjYKqjEzOE=
+
+uglify-to-browserify@~1.0.0:
+  version "1.0.2"
+  resolved "https://registry.npm.taobao.org/uglify-to-browserify/download/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+  integrity sha1-bgkk1r2mta/jSeOabWMoUKD4grc=
+
 unicode-canonical-property-names-ecmascript@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -11700,6 +12123,22 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vinyl-sourcemaps-apply@^0.2.0:
+  version "0.2.1"
+  resolved "https://registry.npm.taobao.org/vinyl-sourcemaps-apply/download/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705"
+  integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU=
+  dependencies:
+    source-map "^0.5.1"
+
+vinyl@^0.5.0:
+  version "0.5.3"
+  resolved "https://registry.npm.taobao.org/vinyl/download/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde"
+  integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=
+  dependencies:
+    clone "^1.0.0"
+    clone-stats "^0.0.1"
+    replace-ext "0.0.1"
+
 vm-browserify@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@@ -11848,6 +12287,11 @@ webpack-merge@^4.2.2:
   dependencies:
     lodash "^4.17.15"
 
+webpack-sources@*:
+  version "3.2.0"
+  resolved "https://registry.nlark.com/webpack-sources/download/webpack-sources-3.2.0.tgz#b16973bcf844ebcdb3afde32eda1c04d0b90f89d"
+  integrity sha1-sWlzvPhE682zr94y7aHATQuQ+J0=
+
 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"
@@ -11856,6 +12300,13 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
+webpack-theme-color-replacer@1.3.22:
+  version "1.3.22"
+  resolved "https://registry.nlark.com/webpack-theme-color-replacer/download/webpack-theme-color-replacer-1.3.22.tgz#d77febb02b433859226dd4b9d754111cd320e559"
+  integrity sha1-13/rsCtDOFkibdS511QRHNMg5Vk=
+  dependencies:
+    webpack-sources "*"
+
 webpack@4.44.2:
   version "4.44.2"
   resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72"
@@ -11944,11 +12395,21 @@ which@^2.0.1, which@^2.0.2:
   dependencies:
     isexe "^2.0.0"
 
+window-size@0.1.0:
+  version "0.1.0"
+  resolved "https://registry.npm.taobao.org/window-size/download/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+  integrity sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=
+
 word-wrap@^1.2.3, word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
+wordwrap@0.0.2:
+  version "0.0.2"
+  resolved "https://registry.npm.taobao.org/wordwrap/download/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+  integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
+
 workbox-background-sync@^5.1.4:
   version "5.1.4"
   resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-5.1.4.tgz#5ae0bbd455f4e9c319e8d827c055bb86c894fd12"
@@ -12245,6 +12706,16 @@ yargs@^15.4.1:
     y18n "^4.0.0"
     yargs-parser "^18.1.2"
 
+yargs@~3.10.0:
+  version "3.10.0"
+  resolved "https://registry.nlark.com/yargs/download/yargs-3.10.0.tgz?cache=0&sync_timestamp=1628889089720&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fyargs%2Fdownload%2Fyargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+  integrity sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=
+  dependencies:
+    camelcase "^1.0.2"
+    cliui "^2.1.0"
+    decamelize "^1.0.0"
+    window-size "0.1.0"
+
 yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"