├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── config-overrides.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── ArticleStatistics │ │ ├── ArticleStatistics.js │ │ ├── articleStatistic.css │ │ ├── articleStatistic.css.map │ │ └── articleStatistic.scss │ ├── GlobalConfig │ │ └── GlobalConfig.js │ ├── ProgramExperience │ │ └── ProgramExperience.js │ ├── SecurityCenter │ │ └── SecurityCenter.js │ ├── ServerStatus │ │ ├── ServerStatus.js │ │ ├── serverStatus.css │ │ ├── serverStatus.css.map │ │ └── serverStatus.scss │ ├── UserDrop │ │ └── UserDrop.js │ ├── UserInfo │ │ └── UserInfo.js │ └── WorkExperience │ │ └── WorkExperience.js ├── containers │ ├── About │ │ └── About.js │ ├── Article │ │ ├── Article.js │ │ ├── ArticleDetail.js │ │ ├── articleDetail.css │ │ ├── articleDetail.css.map │ │ └── articleDetail.scss │ ├── CV │ │ ├── CV.css │ │ ├── CV.css.map │ │ ├── CV.js │ │ └── CV.scss │ ├── Exception │ │ ├── 404.svg │ │ └── Exception.js │ ├── Home │ │ ├── Announcement │ │ │ └── Announcement.js │ │ ├── Cover │ │ │ └── Cover.js │ │ ├── Motto │ │ │ └── Motto.js │ │ └── Project │ │ │ └── Project.js │ ├── Music │ │ ├── FeaturedRecord │ │ │ └── FeaturedRecord.js │ │ ├── LiveTour │ │ │ └── LiveTour.js │ │ ├── Player │ │ │ ├── Player.js │ │ │ ├── player.css │ │ │ ├── player.css.map │ │ │ └── player.scss │ │ └── YanceyMusic │ │ │ └── YanceyMusic.js │ ├── Overview │ │ ├── OverView.js │ │ ├── overview.css │ │ ├── overview.css.map │ │ └── overview.scss │ ├── Page │ │ └── Login │ │ │ ├── Login.js │ │ │ ├── login.css │ │ │ ├── login.css.map │ │ │ ├── login.scss │ │ │ ├── wew.png │ │ │ └── yancey-official-blog-logo.png │ └── Setting │ │ └── Setting.js ├── history.js ├── http │ ├── AboutApi.js │ ├── AnnouncementApi.js │ ├── ArticleApi.js │ ├── CoverApi.js │ ├── FeaturedRecordApi.js │ ├── GlobalApi.js │ ├── LiveTourApi.js │ ├── LoginApi.js │ ├── MottoApi.js │ ├── PlayerApi.js │ ├── ProgramExperienceApi.js │ ├── ProjectApi.js │ ├── ServerStatusApi.js │ ├── UploadApi.js │ ├── UserInfoApi.js │ ├── WorkExperienceApi.js │ ├── YanceyMusicApi.js │ └── index.js ├── index.css ├── index.js ├── layouts │ ├── Layouts.js │ ├── layouts.css │ ├── layouts.css.map │ └── layouts.scss ├── registerServiceWorker.js ├── stores │ ├── AboutStore.js │ ├── AnnouncementStore.js │ ├── ArticleDetailStore.js │ ├── ArticleStore.js │ ├── CoverStore.js │ ├── FeaturedRecordStore.js │ ├── GlobalStore.js │ ├── LiveTourStore.js │ ├── LoginStore.js │ ├── ModifyPasswordStore.js │ ├── MottoStore.js │ ├── PlayerStore.js │ ├── ProgramExperienceStore.js │ ├── ProjectStore.js │ ├── ServerStatusStore.js │ ├── UserInfoStore.js │ ├── WorkExperienceStore.js │ ├── YanceyMusicStore.js │ └── index.js └── util │ ├── axios.js │ ├── baseUrl.js │ └── tools.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | 3 | /node_modules 4 | 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "env": { 7 | "browser": true, 8 | "node": true, 9 | "jquery": true 10 | }, 11 | "parser": "babel-eslint", 12 | "rules": { 13 | "eqeqeq": 0, 14 | "semi": [ 15 | "error", 16 | "always" 17 | ], 18 | "import/first": 0, 19 | "max-len": [ 20 | 1, 21 | { 22 | "code": 200 23 | } 24 | ], 25 | "no-unused-vars": 1, 26 | "global-require": 0, 27 | "prefer-destructuring": 0, 28 | "class-methods-use-this": 0, 29 | "react/no-unused-state": 1, 30 | "react/prop-types": 0, 31 | "react/jsx-filename-extension": [ 32 | 1, 33 | { 34 | "extensions": [ 35 | ".js", 36 | ".jsx" 37 | ] 38 | } 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # misc 10 | .DS_Store 11 | .env.local 12 | .env.development.local 13 | .env.test.local 14 | .env.production.local 15 | 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | # IDE 21 | .idea 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yancey Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | 这是Blog2.0的后台管理系统,1.0版本直接用的是Django Admin,可塑性确实不太高。 4 | 5 | 因为工作中有用Vue独立编写后台的经验,所以这次自己用React手撸了一个后台,用了react + react-router-4 + mobx + Google reCAPTCHA + Ant Design,说实话比起前端JS和CSS的代码量各占50%,这个后台收获还是很大的。 6 | 7 | **后期会放出一个lite版本,供各位大佬们批评指正,也就是不能上传、不能修改密码,别的都能干。** 8 | 9 | 因为刚毕业不久,工作时间也不多,感觉做的项目还稍显稚嫩,因此决定开源出来接受大佬们的意见。 10 | 11 | [GitHub](https://github.com/Yancey-Blog/BLOG_CMS) 12 | 13 | [前端线上地址](https://www.yanceyleo.com/) 14 | 15 | [前端文档戳这里](https://github.com/Yancey-Blog/BLOG_FE) 16 | 17 | ## Detail 18 | 19 | ### Login 20 | 21 | ![Login](https://user-gold-cdn.xitu.io/2018/10/20/166921256c985480?w=640&h=400&f=gif&s=1909357) 22 | 23 | 因为确实没有注册的必要,所以直接把用户名和加密后的密码存在了数据库,当然后期心情好不排除弄个注册。 24 | 25 | 用到了Google reCAPTCHA,用同事大神的话来说就是装逼给自己看😂,不过用这个东西也对后面填request库和request-promise库打下了坚实的基础,嗯。 26 | 27 | 虽说是给自己用的后台,但还是考虑到一些非法操作,比如这个页面,只有email、password、Google reCAPTCHA有值才能点击登录按钮(写到这里,突然发现忘了写email、password格式校验);点击按钮之后立即将按钮设为`disabled`来避免多次点击。 28 | 29 | ### 增删改查 30 | 31 | ![增删改查](https://user-gold-cdn.xitu.io/2018/10/21/166962128cd16227?w=640&h=400&f=gif&s=1424453) 32 | 33 | 因为大部分模块都是类似上面这张图的架构,所以挑一个来写,就以`player`模块为例: 34 | 35 | - 表格渲染数据,采用分页的方式; 36 | - 通过增删改查和批量删除按钮来进行数据的操作; 37 | - 上面说到后台是从用户的角度来写的,因此当用户未填写全信息将禁止点击按钮提交; 38 | - 封装了一下Ali OSS的上传接口 39 | - 点击表格中的图片(如果有),会放大显示; 40 | - 删除和批量删除会提前弹出`是否确认删除tip`; 41 | 42 | ### Article 43 | 44 | ![Article](https://user-gold-cdn.xitu.io/2018/10/18/16685d91609a1604?w=3840&h=2400&f=jpeg&s=193346) 45 | 46 | 这是Blog的核心功能区,除了常规的增删改查批量删除,还增加了模糊查找和按时间段查找的功能。 47 | 48 | ### Article Editor 49 | 50 | ![Article Editor](https://user-gold-cdn.xitu.io/2018/10/18/16685d916603471c?w=3840&h=2400&f=jpeg&s=227828) 51 | 52 | 这是Blog的核核核核核心功能区了,用来编写文章的Header Cover, Title, Summary, Content, Tags,当然我设置的这些都是必填项,所以mobx检测到如果有的项为空,也是无法提交的。 53 | 54 | Markdown编辑器用的toast ui的,说实话用起来真蛋疼,魔改了好多地方才适合自己使用。 55 | 56 | 着重说一下上面这张图没截到的部分😂,是两个按钮,用来提交。 57 | 58 | - 左边的按钮是`保存并留在当前页面`,实际上就是起到一个暂存的功能,而且我还加了一个功能,当点击这个按钮时会弹出一个popup,询问你是否立即发布,也就是说: 59 | 60 | 当点击`保存并留在当前页面`后在点击yes,文章将会被保存到数据库,而且会被发布,并且留在当前编辑页面 61 | 当点击`保存并留在当前页面`后在点击no,文章将会被保存到数据库,不会被发布,并且留在当前编辑页面 62 | 63 | - 右边的按钮是纯粹的`保存`,点击后文章将会保存,然后发布,最后跳转到Article List页面。 64 | 65 | ### CV 66 | 67 | ![CV](https://user-gold-cdn.xitu.io/2018/10/18/16685d916cc79f3b?w=3840&h=2400&f=jpeg&s=212396) 68 | 69 | 这个模块其实完全可以写成表格的形式,但确实写表格写烦了,所以换个口味,实际这就跟很多招聘网站的效果差不多了,其实也是增删改查,当然没去设计批量删除,感觉没意义。 70 | 71 | ### Global Setting 72 | 73 | ![Global Setting](https://user-gold-cdn.xitu.io/2018/10/18/16685d916dfeb0c9?w=3840&h=2400&f=jpeg&s=106681) 74 | 75 | 这里分三个模块: 76 | 77 | - 第一个是个人基本信息,实际上有两个作用,一个是后台右上角那个地方(~~要不太空了~~😂),另一个用途是前端简历页面个人信息那个部分; 78 | - 第二个是修改密码,我估计自己基本也不会用到这个地方,但是既然想写,那就去做好了; 79 | - 第三个是全局配置,当然现在只有一个接口,就是全站置灰效果,后台一旦设置了置灰,那前端全站就会变成灰色,用于一些特殊的哀悼日子。 80 | 81 | ## 最后 82 | 83 | 因为最近忙着找工作,文章迁移、英文文档撰写都会在忙过这段时间之后再去实施,如果有好的工作机会可以联系我哈~ 84 | 85 | 以上、よろしく。 86 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { injectBabelPlugin } = require('react-app-rewired'); /* eslint-disable-line */ 2 | const rewireMobX = require('react-app-rewire-mobx'); 3 | 4 | module.exports = function override(config, env) { 5 | config = injectBabelPlugin( /* eslint-disable-line */ 6 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], 7 | config, 8 | ); 9 | config = rewireMobX(config, env); /* eslint-disable-line */ 10 | return config; 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-cms", 3 | "version": "1.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^3.9.2", 7 | "axios": "^0.18.0", 8 | "babel-eslint": "^9.0.0", 9 | "babel-loader": "^8.0.2", 10 | "babel-plugin-import": "^1.8.0", 11 | "chart.js": "^2.7.2", 12 | "codemirror": "^5.40.2", 13 | "dayjs": "^1.7.8", 14 | "eslint-loader": "^2.1.0", 15 | "highlightjs": "^9.10.0", 16 | "mobx": "^5.1.1", 17 | "mobx-react": "^5.2.8", 18 | "moment": "^2.22.2", 19 | "react": "^16.5.0", 20 | "react-app-rewire-mobx": "^1.0.9", 21 | "react-calendar-heatmap": "^1.7.0", 22 | "react-chartjs-2": "^2.7.4", 23 | "react-dom": "^16.5.0", 24 | "react-google-recaptcha": "^1.0.4", 25 | "react-router-dom": "^4.3.1", 26 | "react-scripts": "1.1.5", 27 | "react-tooltip": "^3.8.4", 28 | "tui-editor": "^1.2.6" 29 | }, 30 | "scripts": { 31 | "start": "PORT=3002 react-app-rewired start", 32 | "build": "react-app-rewired build", 33 | "test": "react-app-rewired test --env=jsdom", 34 | "eject": "react-scripts eject" 35 | }, 36 | "devDependencies": { 37 | "eslint": "^5.5.0", 38 | "eslint-config-airbnb": "^17.1.0", 39 | "eslint-plugin-import": "^2.14.0", 40 | "eslint-plugin-jsx-a11y": "^6.1.1", 41 | "eslint-plugin-react": "^7.11.1", 42 | "react-app-rewired": "^1.6.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yancey-Blog/BLOG_CMS/6ce26e1a910c03b4914ef0d522632f2bfdb90adf/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Yancey Blog CMS | Yancey Inc. 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .ant-upload img { 2 | width: 102px; 3 | height: 102px; 4 | object-fit: cover; 5 | } 6 | 7 | .wrapper { 8 | margin: 24px 24px 0 24px; 9 | padding: 24px; 10 | background: #fff; 11 | min-height: 900px; 12 | } 13 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'mobx-react'; 3 | import { Router, Route, Switch } from 'react-router-dom'; 4 | import history from './history'; 5 | import Layouts from './layouts/Layouts'; 6 | import Login from './containers/Page/Login/Login'; 7 | import stores from './stores/index'; 8 | import './App.css'; 9 | 10 | class App extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = {}; 14 | } 15 | 16 | render() { 17 | return ( 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/ArticleStatistics/ArticleStatistics.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import dayjs from 'dayjs'; 4 | import ReactTooltip from 'react-tooltip'; 5 | import CalendarHeatmap from 'react-calendar-heatmap'; 6 | import 'react-calendar-heatmap/dist/styles.css'; 7 | import './articleStatistic.css'; 8 | 9 | @inject('articleStore') 10 | @observer 11 | class ArticleStatistics extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = {}; 15 | } 16 | 17 | componentWillMount() {} 18 | 19 | componentDidMount() { 20 | const { articleStore } = this.props; 21 | Promise.all([articleStore.getDataByDay(), articleStore.getDataByPV()]); 22 | } 23 | 24 | componentWillUnmount() {} 25 | 26 | render() { 27 | const { articleStore } = this.props; 28 | const startDay = dayjs(new Date()) 29 | .subtract(1, 'years') 30 | .format('YYYY-MM-DD'); 31 | const endDay = new Date(); 32 | const rankNumberStyle = { 33 | backgroundColor: 'rgb(49, 70, 89)', 34 | color: 'rgb(255, 255, 255)' 35 | }; 36 | return ( 37 |
38 |
39 |

Heat Map

40 | { 45 | if (!value) { 46 | return 'color-empty'; 47 | } 48 | return `color-github-${value.count}`; 49 | }} 50 | tooltipDataAttrs={value => ({ 51 | 'data-tip': value.date 52 | ? `${value.date} you post ${value.count} ${ 53 | value.count > 1 ? 'articles' : 'article' 54 | }` 55 | : 'no articles on this day' 56 | })} 57 | showWeekdayLabels 58 | /> 59 | 60 |
61 |
62 |

Top 7 Articles

63 |
    64 | {Object.keys(articleStore.PVDataSource).map(key => ( 65 |
  • 66 | 67 | 71 | {parseInt(key, 10) + 1} 72 | 73 | 74 | {articleStore.PVDataSource[key].title} 75 | 76 | 77 | 78 | {articleStore.PVDataSource[key].pv_count} PV 79 | 80 |
  • 81 | ))} 82 |
83 |
84 |
85 | ); 86 | } 87 | } 88 | 89 | export default ArticleStatistics; 90 | -------------------------------------------------------------------------------- /src/components/ArticleStatistics/articleStatistic.css: -------------------------------------------------------------------------------- 1 | .article_statistics_wrapper { 2 | display: grid; 3 | grid-template-columns: 70% 30%; 4 | margin-top: 24px; 5 | padding: 24px 24px 24px 6px; 6 | background: #fff; } 7 | .article_statistics_wrapper h4 { 8 | margin-bottom: 25px; } 9 | .article_statistics_wrapper .heat_map_header { 10 | margin-left: 22px; } 11 | .article_statistics_wrapper .rank_articles_wrapper { 12 | margin-left: 100px; } 13 | .article_statistics_wrapper ul { 14 | padding: 0; } 15 | .article_statistics_wrapper li { 16 | display: flex; 17 | justify-content: space-between; 18 | list-style-type: none; 19 | margin-top: 16px; } 20 | .article_statistics_wrapper li .rank_number_wrapper { 21 | display: inline-flex; } 22 | .article_statistics_wrapper li .rank_number { 23 | display: inline-block; 24 | font-size: 12px; 25 | font-weight: 600; 26 | margin-right: 16px; 27 | height: 20px; 28 | line-height: 20px; 29 | width: 20px; 30 | text-align: center; 31 | margin-top: 1.5px; 32 | border-radius: 20px; 33 | background-color: whitesmoke; } 34 | .article_statistics_wrapper li .article_title { 35 | display: inline-block; 36 | max-width: 200px; 37 | min-width: 200px; 38 | overflow: hidden; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; } 41 | 42 | /*# sourceMappingURL=articleStatistic.css.map */ 43 | -------------------------------------------------------------------------------- /src/components/ArticleStatistics/articleStatistic.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAAA,2BAA4B;EAC1B,OAAO,EAAE,IAAI;EACb,qBAAqB,EAAE,OAAO;EAC9B,UAAU,EAAE,IAAI;EAChB,OAAO,EAAE,kBAAkB;EAC3B,UAAU,EAAE,IAAI;EAChB,8BAAG;IACD,aAAa,EAAE,IAAI;EAErB,4CAAiB;IACf,WAAW,EAAE,IAAI;EAEnB,kDAAuB;IACrB,WAAW,EAAE,KAAK;EAEpB,8BAAG;IACD,OAAO,EAAE,CAAC;EAEZ,8BAAG;IACD,OAAO,EAAE,IAAI;IACb,eAAe,EAAE,aAAa;IAC9B,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,IAAI;IAChB,mDAAqB;MACnB,OAAO,EAAE,WAAW;IAEtB,2CAAa;MACX,OAAO,EAAE,YAAY;MACrB,SAAS,EAAE,IAAI;MACf,WAAW,EAAE,GAAG;MAChB,YAAY,EAAE,IAAI;MAClB,MAAM,EAAE,IAAI;MACZ,WAAW,EAAE,IAAI;MACjB,KAAK,EAAE,IAAI;MACX,UAAU,EAAE,MAAM;MAClB,UAAU,EAAE,KAAK;MACjB,aAAa,EAAE,IAAI;MACnB,gBAAgB,EAAE,UAAkB;IAEtC,6CAAe;MACb,OAAO,EAAE,YAAY;MACrB,SAAS,EAAE,KAAK;MAChB,SAAS,EAAE,KAAK;MAChB,QAAQ,EAAE,MAAM;MAChB,aAAa,EAAE,QAAQ;MACvB,WAAW,EAAE,MAAM", 4 | "sources": ["articleStatistic.scss"], 5 | "names": [], 6 | "file": "articleStatistic.css" 7 | } -------------------------------------------------------------------------------- /src/components/ArticleStatistics/articleStatistic.scss: -------------------------------------------------------------------------------- 1 | .article_statistics_wrapper { 2 | display: grid; 3 | grid-template-columns: 70% 30%; 4 | margin-top: 24px; 5 | padding: 24px 24px 24px 6px; 6 | background: #fff; 7 | h4 { 8 | margin-bottom: 25px; 9 | } 10 | .heat_map_header { 11 | margin-left: 22px; 12 | } 13 | .rank_articles_wrapper { 14 | margin-left: 100px; 15 | } 16 | ul { 17 | padding: 0; 18 | } 19 | li { 20 | display: flex; 21 | justify-content: space-between; 22 | list-style-type: none; 23 | margin-top: 16px; 24 | .rank_number_wrapper { 25 | display: inline-flex; 26 | } 27 | .rank_number { 28 | display: inline-block; 29 | font-size: 12px; 30 | font-weight: 600; 31 | margin-right: 16px; 32 | height: 20px; 33 | line-height: 20px; 34 | width: 20px; 35 | text-align: center; 36 | margin-top: 1.5px; 37 | border-radius: 20px; 38 | background-color: rgb(245, 245, 245); 39 | } 40 | .article_title { 41 | display: inline-block; 42 | max-width: 200px; 43 | min-width: 200px; 44 | overflow: hidden; 45 | text-overflow: ellipsis; 46 | white-space: nowrap; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/GlobalConfig/GlobalConfig.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import { List, Switch, Icon } from 'antd'; 4 | 5 | @inject('globalStore') 6 | @observer 7 | class GlobalConfig extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | componentDidMount() { 14 | const { globalStore } = this.props; 15 | globalStore.getData(); 16 | } 17 | 18 | render() { 19 | const { globalStore } = this.props; 20 | const listData = [ 21 | { 22 | title: 'Mourning Mode', 23 | description: 'To set filter: grayscale(100%) for full site.', 24 | }, 25 | ]; 26 | return ( 27 |
30 |

31 | Global Config 32 |

33 | ( 37 | } 40 | unCheckedChildren={} 41 | defaultChecked={globalStore.fullSiteGrayStatus} 42 | onChange={checked => globalStore.onFullSiteGray(checked)} /* eslint-disable-line */ 43 | />, 44 | ]} 45 | > 46 | 50 | 51 | )} 52 | /> 53 |
54 | ); 55 | } 56 | } 57 | 58 | 59 | export default GlobalConfig; 60 | -------------------------------------------------------------------------------- /src/components/ProgramExperience/ProgramExperience.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { observer, inject } from 'mobx-react'; 3 | import { 4 | Modal, Input, Icon, Row, Col, Tag, Tooltip, 5 | } from 'antd'; 6 | 7 | const { TextArea } = Input; 8 | 9 | @inject('programExperienceStore') 10 | @observer 11 | class ProgramExperience extends Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = {}; 15 | } 16 | 17 | componentDidMount() { 18 | const { programExperienceStore } = this.props; 19 | programExperienceStore.getData(); 20 | } 21 | 22 | render() { 23 | const { programExperienceStore } = this.props; 24 | const tagColorList = ['magenta', 'red', 'volcano', 'orange', 'gold', 'lime', 'green', 'cyan', 'blue', 'geekblue', 'purple']; 25 | return ( 26 | 29 | 35 | {programExperienceStore.modalType === 'add' ? 'Add new' : 'Update the'} 36 | {' '} 37 | Program Experience 38 | 39 | )} 40 | width={600} 41 | wrapClassName="reset_modal" 42 | closable={false} 43 | destroyOnClose 44 | visible={programExperienceStore.showModal} 45 | okButtonProps={{ disabled: !programExperienceStore.isFilled }} 46 | okText="" 47 | onOk={programExperienceStore.modalType === 'add' ? () => programExperienceStore.insertData() : () => programExperienceStore.modifyData()} 48 | onCancel={programExperienceStore.closeModal} 49 | > 50 | 51 | 52 | 53 | * 54 | {' '} 55 | 56 | 57 | Program Name: 58 | 59 | 60 | 61 | programExperienceStore.onProgramNameChange(event)} 65 | /> 66 | 67 | 68 | 69 | * 70 | {' '} 71 | 72 | 73 | Program Url: 74 | 75 | 76 | 77 | programExperienceStore.onProgramUrlChange(event)} 81 | /> 82 | 83 | 84 | 85 | * 86 | {' '} 87 | 88 | 89 | Program Content: 90 | 91 | 92 | 93 |