Authored by 毕凯

Merge branch 'feature/react'

@@ -3,13 +3,13 @@ const normalMenus = [ @@ -3,13 +3,13 @@ const normalMenus = [
3 title: 'Dashboard', 3 title: 'Dashboard',
4 link: '/', 4 link: '/',
5 fa: 'fa-home', 5 fa: 'fa-home',
6 - icon: 'ios-home', 6 + icon: 'dashboard',
7 isClassic: true 7 isClassic: true
8 }, 8 },
9 { 9 {
10 title: '缓存管理', 10 title: '缓存管理',
11 fa: 'fa-history', 11 fa: 'fa-history',
12 - icon: 'ios-cloud', 12 + icon: 'cloud',
13 subMenus: [{ 13 subMenus: [{
14 title: 'PageCahe清理', 14 title: 'PageCahe清理',
15 link: '/page_cache/query', 15 link: '/page_cache/query',
@@ -31,14 +31,14 @@ const normalMenus = [ @@ -31,14 +31,14 @@ const normalMenus = [
31 { 31 {
32 title: '降级配置', 32 title: '降级配置',
33 fa: 'fa-hand-o-down', 33 fa: 'fa-hand-o-down',
34 - icon: 'ios-toggle', 34 + icon: 'api',
35 link: '/degrade', 35 link: '/degrade',
36 isClassic: true 36 isClassic: true
37 }, 37 },
38 { 38 {
39 title: '滥用防护', 39 title: '滥用防护',
40 fa: 'fa-shield', 40 fa: 'fa-shield',
41 - icon: 'ios-locked', 41 + icon: 'safety',
42 subMenus: [{ 42 subMenus: [{
43 title: 'IP黑名单', 43 title: 'IP黑名单',
44 link: '/crawler_black/ip', 44 link: '/crawler_black/ip',
@@ -78,7 +78,7 @@ const normalMenus = [ @@ -78,7 +78,7 @@ const normalMenus = [
78 { 78 {
79 title: 'SEO管理', 79 title: 'SEO管理',
80 fa: 'fa-list', 80 fa: 'fa-list',
81 - icon: 'ios-world', 81 + icon: 'search',
82 subMenus: [{ 82 subMenus: [{
83 title: '词根管理', 83 title: '词根管理',
84 link: '/seo/rootwords', 84 link: '/seo/rootwords',
@@ -88,6 +88,9 @@ const normalMenus = [ @@ -88,6 +88,9 @@ const normalMenus = [
88 link: '/keywords/expand', 88 link: '/keywords/expand',
89 isClassic: true 89 isClassic: true
90 }, { 90 }, {
  91 + title: '热门关键词管理',
  92 + link: '/seo/hot-keyword'
  93 + }, {
91 title: 'TDK管理', 94 title: 'TDK管理',
92 link: '/seo/tdk', 95 link: '/seo/tdk',
93 isClassic: true 96 isClassic: true
@@ -108,7 +111,7 @@ const normalMenus = [ @@ -108,7 +111,7 @@ const normalMenus = [
108 { 111 {
109 title: '性能统计', 112 title: '性能统计',
110 fa: 'fa-list', 113 fa: 'fa-list',
111 - icon: 'podium', 114 + icon: 'area-chart',
112 subMenus: [{ 115 subMenus: [{
113 title: '浏览器排行榜', 116 title: '浏览器排行榜',
114 link: '/profile/sort_client', 117 link: '/profile/sort_client',
@@ -126,15 +129,6 @@ const normalMenus = [ @@ -126,15 +129,6 @@ const normalMenus = [
126 link: '/profile/error', 129 link: '/profile/error',
127 isClassic: true 130 isClassic: true
128 }] 131 }]
129 - },  
130 - {  
131 - title: '日志查询',  
132 - fa: 'fa-list',  
133 - icon: 'ios-eye',  
134 - subMenus: [{  
135 - title: 'Node 日志',  
136 - link: '/logs/node'  
137 - }]  
138 } 132 }
139 ] 133 ]
140 134
@@ -142,7 +136,7 @@ const masterMenus = [ @@ -142,7 +136,7 @@ const masterMenus = [
142 { 136 {
143 title: '系统管理', 137 title: '系统管理',
144 fa: 'fa-gear', 138 fa: 'fa-gear',
145 - icon: 'person', 139 + icon: 'setting',
146 subMenus: [{ 140 subMenus: [{
147 title: '服务器配置', 141 title: '服务器配置',
148 link: '/servers/setting', 142 link: '/servers/setting',
@@ -76,12 +76,14 @@ @@ -76,12 +76,14 @@
76 "utility": "^1.8.0" 76 "utility": "^1.8.0"
77 }, 77 },
78 "devDependencies": { 78 "devDependencies": {
79 - "ada": "^0.2.9", 79 + "ada": "^1.0.1",
  80 + "antd": "^3.5.0",
80 "axios": "^0.18.0", 81 "axios": "^0.18.0",
81 - "iview": "^2.13.0",  
82 "nodemon": "^1.11.0", 82 "nodemon": "^1.11.0",
83 - "vue": "^2.5.16",  
84 - "vue-router": "^3.0.1",  
85 - "vue-template-compiler": "^2.5.16" 83 + "react": "^16.3.2",
  84 + "react-dom": "^16.3.2",
  85 + "react-loadable": "^5.4.0",
  86 + "react-router": "^4.2.0",
  87 + "react-router-dom": "^4.2.2"
86 } 88 }
87 } 89 }
1 -<template>  
2 - <Menu class="sidebar-menu" theme="light" width="auto">  
3 - <template v-for="(menu, index) in menus"> 1 +import React from 'react'
  2 +import { Link } from 'react-router-dom'
  3 +import { Layout, Menu, Icon } from 'antd'
  4 +
  5 +import {
  6 + normalMenus,
  7 + masterMenus,
  8 + businessMenus
  9 +} from '../../../../config/menus' // 考虑与原有业务复用
  10 +
  11 +const { Header, Sider } = Layout
  12 +
  13 +function Title(props) {
  14 + return <div>
  15 + <Icon type={props.icon}></Icon>
  16 + {props.title}
  17 + </div>
  18 +}
  19 +
  20 +function Menus(props) {
  21 + const user = props.user
  22 +
  23 + function isMaster() {
  24 + return user.role === '1000'
  25 + }
  26 +
  27 + function isBusiness() {
  28 + return user.role === '3000'
  29 + }
  30 +
  31 + const menus = (function() {
  32 + if (isBusiness()) {
  33 + return businessMenus
  34 + }
  35 + if (isMaster()) {
  36 + return [...normalMenus, ...masterMenus]
  37 + }
  38 + return normalMenus
  39 + }())
  40 +
  41 + const ListItems = menus.map((menu, index) => {
  42 + if (menu.subMenus && menu.subMenus.length) {
  43 + return <Menu.SubMenu title={Title(menu)} key={index + ''}>
  44 + {menu.subMenus.map((subMenu, subIndex) => {
  45 + return <Menu.Item key={`${index}-${subIndex}`}>
  46 + {subMenu.isClassic ? <a href={subMenu.link}>
  47 + <Icon type={subMenu.icon}></Icon>
  48 + {subMenu.title}
  49 + </a> : <Link to={subMenu.link}>
  50 + <Icon type={subMenu.icon}></Icon>
  51 + {subMenu.title}
  52 + </Link>}
  53 + </Menu.Item>
  54 + })}
  55 + </Menu.SubMenu>
  56 + }
  57 +
  58 + return <Menu.Item key={index + ''}>
  59 + {menu.isClassic ? <a href={menu.link}>
  60 + <Icon type={menu.icon}></Icon>
  61 + {menu.title}
  62 + </a> : <Link to={menu.link}>
  63 + <Icon type={menu.icon}></Icon>
  64 + {menu.title}
  65 + </Link>}
  66 + </Menu.Item>
  67 + // return <Item menu={menu} key={index + ''} />
  68 + })
  69 +
  70 + return <div className="menus">
  71 + <Menu className="sidebar-menu" theme="light" mode="inline" width="auto">
  72 + {ListItems}
  73 + {/* <template v-for="(menu, index) in menus">
4 <Submenu v-if="menu.subMenus" :name="index"> 74 <Submenu v-if="menu.subMenus" :name="index">
5 <template slot="title"> 75 <template slot="title">
6 <Icon :type="menu.icon"></Icon> 76 <Icon :type="menu.icon"></Icon>
@@ -28,50 +98,9 @@ @@ -28,50 +98,9 @@
28 {{menu.title}} 98 {{menu.title}}
29 </router-link> 99 </router-link>
30 </MenuItem> 100 </MenuItem>
31 - </template> 101 + </template> */}
32 </Menu> 102 </Menu>
33 -</template>  
34 -  
35 -<script>  
36 -import {  
37 - normalMenus,  
38 - masterMenus,  
39 - businessMenus  
40 -} from '../../../../config/menus' // 考虑与原有业务复用  
41 -  
42 -export default {  
43 - props: ['user'],  
44 - computed: {  
45 - isMaster: function() {  
46 - return this.user.role === '1000'  
47 - },  
48 - isBusiness: function() {  
49 - return this.user.role === '3000'  
50 - },  
51 - menus: function() {  
52 - if (this.isBusiness) {  
53 - return businessMenus  
54 - }  
55 - if (this.isMaster) {  
56 - return [...normalMenus, ...masterMenus]  
57 - }  
58 - return normalMenus  
59 - }  
60 - } 103 + </div>
61 } 104 }
62 -</script>  
63 105
64 -<style scoped lang="scss">  
65 -a {  
66 - display: block;  
67 - color: #495060;  
68 -  
69 - > i {  
70 - margin-right: 6px;  
71 - }  
72 -}  
73 -  
74 -.ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active {  
75 - color: #495060;  
76 -}  
77 -</style>  
  106 +export default Menus
  1 +import '../../scss/common/sidebar.scss'
  2 +
  3 +import axios from 'axios'
  4 +import React from 'react'
  5 +import { Menu, Avatar } from 'antd'
  6 +
  7 +import Menus from './Menus'
  8 +
  9 +import userImg from '../../../images/photos/user1.png'
  10 +
  11 +class Sidebar extends React.Component {
  12 + constructor() {
  13 + super()
  14 + this.state = {
  15 + user: {}
  16 + }
  17 + }
  18 +
  19 + componentDidMount() {
  20 + this.fetchUser()
  21 + }
  22 +
  23 + render() {
  24 + return <div className="sidebar">
  25 + <div className="sidebar-user">
  26 + <Avatar icon="user" size="large" src={userImg} />
  27 + <span className="username">{this.state.user.username}</span>
  28 + </div>
  29 + <Menus user={this.state.user} />
  30 + </div>
  31 + }
  32 +
  33 + async fetchUser() {
  34 + const result = await axios.get('/login/info')
  35 +
  36 + if (result.status === 200) {
  37 + this.setState({
  38 + user: result.data
  39 + })
  40 + }
  41 + }
  42 +}
  43 +
  44 +export default Sidebar
  1 +import '../../scss/common/topbar.scss'
  2 +
  3 +import React from 'react'
  4 +import { Layout, Menu, Icon } from 'antd'
  5 +
  6 +const { Header, Sider } = Layout
  7 +
  8 +function Topbar() {
  9 + function handleClick() {
  10 + location.href = '/logout'
  11 + }
  12 +
  13 + return (
  14 + <Header className="header">
  15 + <a className="header-logo" href="/"></a>
  16 + <Menu mode="horizontal" theme="dark" className="header-menu">
  17 + <Menu.Item className="header-nav">
  18 + <Icon type="logout"></Icon>
  19 + <span onClick={handleClick}>退出</span>
  20 + </Menu.Item>
  21 + </Menu>
  22 + </Header>
  23 + )
  24 +}
  25 +
  26 +export default Topbar
  1 +import 'antd/dist/antd.css'
  2 +import 'common/index.scss'
  3 +
  4 +import React from 'react'
  5 +import { Route, Link, Switch } from 'react-router-dom'
  6 +import { hot } from 'react-hot-loader'
  7 +import Loadable from 'react-loadable'
  8 +import { Layout } from 'antd'
  9 +
  10 +import Topbar from 'common/Topbar'
  11 +import Sidebar from 'common/Sidebar'
  12 +
  13 +const { Content, Sider } = Layout
  14 +
  15 +function NoMatch() {
  16 + return <div>
  17 + Yoho Node.js 持续集成平台
  18 + </div>
  19 +}
  20 +
  21 +function Loading() {
  22 + return <div>
  23 + Loading
  24 + </div>
  25 +}
  26 +
  27 +const HotKeywords = Loadable({
  28 + loader: () => import('./pages/seo/HotKeywords'),
  29 + loading: Loading
  30 +})
  31 +
  32 +function App () {
  33 + return (
  34 + <Layout className="layout">
  35 + <Topbar></Topbar>
  36 + <Layout className="main">
  37 + <Sider style={{background: '#fff'}}>
  38 + <Sidebar></Sidebar>
  39 + </Sider>
  40 + <Layout style={{padding: '24px'}}>
  41 + <Content style={{padding: '24px', background: '#fff'}}>
  42 + <Switch>
  43 + <Route exact path="/seo/hot-keyword" component={HotKeywords}/>
  44 + <Route component={NoMatch}/>
  45 + </Switch>
  46 + </Content>
  47 + </Layout>
  48 + </Layout>
  49 + </Layout>
  50 + )
  51 +}
  52 +
  53 +export default hot(module)(App)
1 -import 'iview/dist/styles/iview.css'  
2 -import 'common/index.scss' 1 +import React from 'react'
  2 +import ReactDOM from 'react-dom'
3 3
4 -import Vue from 'vue'  
5 -import VueRouter from 'vue-router'  
6 -import iView from 'iview'  
7 4
8 -import App from '../vue/App.vue'  
9 -import routes from './routes' 5 +import { BrowserRouter } from 'react-router-dom'
10 6
11 -Vue.use(VueRouter)  
12 -Vue.use(iView) 7 +import App from './App.jsx'
13 8
14 -const router = new VueRouter({  
15 - mode: 'history',  
16 - routes  
17 -})  
18 -  
19 -export default new Vue({  
20 - el: '#root',  
21 - render: h => h(App),  
22 - router,  
23 - components: {  
24 - App  
25 - }  
26 -}) 9 +ReactDOM.render(
  10 + <BrowserRouter>
  11 + <App />
  12 + </BrowserRouter>,
  13 + document.getElementById('root')
  14 +)
  1 +import React from 'react'
  2 +
  3 +function HotKeywords() {
  4 + return <div>
  5 + HotKeywords
  6 + </div>
  7 +}
  8 +
  9 +export default HotKeywords
1 -const NotFound = { template: `  
2 - <div>  
3 - Yoho Node.js 持续集成平台  
4 - </div>  
5 - ` }  
6 -  
7 -const LogsNode = () => import('../../vue/logs/node.vue')  
8 -  
9 -const routes = [  
10 - { path: '/logs/node', component: LogsNode },  
11 - { path: '*', component: NotFound }  
12 -]  
13 -  
14 -export default routes  
1 html, 1 html,
2 -body { 2 +body,
  3 +#root {
3 height: 100%; 4 height: 100%;
4 } 5 }
  6 +
  7 +.layout{
  8 + display: flex;
  9 + flex-direction: column;
  10 + position: relative;
  11 + width: 100%;
  12 + height: 100%;
  13 + overflow: hidden;
  14 + background: #f5f7f9;
  15 +}
  16 +
  17 +.main {
  18 + flex: 1;
  19 +}
1 -<template>  
2 - <div class="sidebar">  
3 - <div class="sidebar-user">  
4 - <Avatar icon="person" size="large" :src="userImg" />  
5 - <span class="username">{{user.username}}</span>  
6 - </div>  
7 - <Menus :user="user" />  
8 - </div>  
9 -</template>  
10 -  
11 -<style scoped lang="scss">  
12 .sidebar { 1 .sidebar {
13 display: flex; 2 display: flex;
14 flex-direction: column; 3 flex-direction: column;
15 height: 100%; 4 height: 100%;
16 -}  
17 5
18 -.sidebar-user { 6 + .sidebar-user {
19 padding: 10px 20px; 7 padding: 10px 20px;
20 border: 1px solid #dddee1; 8 border: 1px solid #dddee1;
21 height: 65px; 9 height: 65px;
@@ -31,35 +19,10 @@ @@ -31,35 +19,10 @@
31 text-overflow: ellipsis; 19 text-overflow: ellipsis;
32 vertical-align: middle; 20 vertical-align: middle;
33 } 21 }
34 -} 22 + }
35 23
36 -.sidebar-menu { 24 + .menus {
37 flex: 1; 25 flex: 1;
38 overflow: auto; 26 overflow: auto;
39 -}  
40 -</style>  
41 -  
42 -<script>  
43 -import Menus from 'common/Menus.vue'  
44 -import userImg from '../../../images/photos/user1.png'  
45 -import axios from 'axios'  
46 -  
47 -export default {  
48 - components: {  
49 - Menus  
50 - },  
51 - data() {  
52 - return {  
53 - userImg,  
54 - user: {}  
55 - }  
56 - },  
57 - async created() {  
58 - const result = await axios.get('/login/info')  
59 -  
60 - if (result.status === 200) {  
61 - this.user = result.data  
62 - }  
63 } 27 }
64 } 28 }
65 -</script>  
1 -<template>  
2 - <Header>  
3 - <Menu mode="horizontal" theme="dark" active-name="1" class="header-menu">  
4 - <a class="header-logo" href="/"></a>  
5 - <div class="header-nav">  
6 - <MenuItem name="1">  
7 - <Icon type="log-out"></Icon>  
8 - <span @click="logout">退出</span>  
9 - </MenuItem>  
10 - </div>  
11 - </Menu>  
12 - </Header>  
13 -</template>  
14 -  
15 -<script>  
16 -export default {  
17 - methods: {  
18 - logout() {  
19 - location.href = '/logout'  
20 - }  
21 - }  
22 -}  
23 -</script>  
24 -  
25 -<style scoped lang="scss">  
26 -.header-menu { 1 +.header {
27 display: flex; 2 display: flex;
28 justify-content: space-between; 3 justify-content: space-between;
29 - margin-top: 2px;  
30 -}  
31 4
32 -.header-logo { 5 + .header-logo {
33 display: block; 6 display: block;
34 width: 150px; 7 width: 150px;
35 height: 60px; 8 height: 60px;
36 background: url("../../../images/logo.png"); 9 background: url("../../../images/logo.png");
37 filter: invert(100%); 10 filter: invert(100%);
  11 + margin-top: 2px;
38 margin-left: -15px; 12 margin-left: -15px;
39 -} 13 + }
  14 +
  15 + .header-nav {
  16 + height: 100%;
  17 + line-height: 64px;
40 18
41 -.header-nav {  
42 li:last-child { 19 li:last-child {
43 padding-right: 0; 20 padding-right: 0;
44 } 21 }
  22 + }
45 } 23 }
46 -</style>  
1 -<template>  
2 - <div class="layout">  
3 - <Layout>  
4 - <Topbar></Topbar>  
5 - <Layout class="main">  
6 - <Sider hide-trigger :style="{background: '#fff'}">  
7 - <Sidebar></Sidebar>  
8 - </Sider>  
9 - <Layout :style="{padding: '24px'}">  
10 - <Content :style="{padding: '24px', background: '#fff'}">  
11 - <router-view></router-view>  
12 - </Content>  
13 - </Layout>  
14 - </Layout>  
15 - </Layout>  
16 - </div>  
17 -</template>  
18 -<script>  
19 -import Topbar from 'common/Topbar.vue'  
20 -import Sidebar from 'common/Sidebar.vue'  
21 -  
22 -export default {  
23 - components: {  
24 - Topbar,  
25 - Sidebar  
26 - }  
27 -}  
28 -</script>  
29 -<style scoped lang="scss">  
30 -.layout{  
31 - display: flex;  
32 - flex-direction: column;  
33 - position: relative;  
34 - width: 100%;  
35 - height: 100%;  
36 - overflow: hidden;  
37 - background: #f5f7f9;  
38 -}  
39 -  
40 -.main {  
41 - flex: 1;  
42 -}  
43 -</style>  
1 -<template>  
2 - <div>  
3 - <Table highlight-row ref="currentRowTable" :columns="columns" :data="logs"></Table>  
4 - </div>  
5 -</template>  
6 -  
7 -<script>  
8 - import {LogService} from '../../js/services/logs';  
9 -  
10 - export default {  
11 - data() {  
12 - return {  
13 - columns: [  
14 - {  
15 - title: 'topic_name',  
16 - key: 'topic_name'  
17 - },  
18 - {  
19 - title: 'topic_id',  
20 - key: 'topic_id'  
21 - },  
22 - {  
23 - title: '级别',  
24 - key: 'level'  
25 - },  
26 - {  
27 - title: '内容',  
28 - key: 'message'  
29 - },  
30 - {  
31 - title: '时间',  
32 - key: 'timestamp'  
33 - }  
34 - ],  
35 - logs: []  
36 - }  
37 - },  
38 - methods: {  
39 - searchLog() {  
40 - this.logService.getLogs({limit: 20}).then(res => {  
41 - this.logs = res.results;  
42 - });  
43 - }  
44 - },  
45 - created() {  
46 - this.logService = new LogService();  
47 - this.searchLog();  
48 - }  
49 - }  
50 -</script>