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',
@@ -184,4 +178,4 @@ module.exports = { @@ -184,4 +178,4 @@ module.exports = {
184 normalMenus, 178 normalMenus,
185 masterMenus, 179 masterMenus,
186 businessMenus 180 businessMenus
187 -}  
  181 +}
@@ -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">  
4 - <Submenu v-if="menu.subMenus" :name="index">  
5 - <template slot="title">  
6 - <Icon :type="menu.icon"></Icon>  
7 - {{menu.title}}  
8 - </template>  
9 - <template v-for="(subMenu, subIndex) in menu.subMenus">  
10 - <MenuItem :name="`${index}-${subIndex}`">  
11 - <a v-if="subMenu.isClassic" :href="subMenu.link">  
12 - <Icon :type="subMenu.icon"></Icon>  
13 - {{subMenu.title}}  
14 - </a>  
15 - <router-link v-else :to="subMenu.link">  
16 - {{subMenu.title}}  
17 - </router-link>  
18 - </MenuItem>  
19 - </template>  
20 - </Submenu>  
21 - <MenuItem v-else :name="index">  
22 - <a v-if="menu.isClassic" :href="menu.link">  
23 - <Icon :type="menu.icon"></Icon>  
24 - {{menu.title}}  
25 - </a>  
26 - <router-link v-else :to="menu.link">  
27 - <Icon :type="menu.icon"></Icon>  
28 - {{menu.title}}  
29 - </router-link>  
30 - </MenuItem>  
31 - </template>  
32 - </Menu>  
33 -</template> 1 +import React from 'react'
  2 +import { Link } from 'react-router-dom'
  3 +import { Layout, Menu, Icon } from 'antd'
34 4
35 -<script>  
36 import { 5 import {
37 normalMenus, 6 normalMenus,
38 masterMenus, 7 masterMenus,
39 businessMenus 8 businessMenus
40 } from '../../../../config/menus' // 考虑与原有业务复用 9 } from '../../../../config/menus' // 考虑与原有业务复用
41 10
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 - } 11 +const { Header, Sider } = Layout
  12 +
  13 +function Title(props) {
  14 + return <div>
  15 + <Icon type={props.icon}></Icon>
  16 + {props.title}
  17 + </div>
61 } 18 }
62 -</script>  
63 19
64 -<style scoped lang="scss">  
65 -a {  
66 - display: block;  
67 - color: #495060; 20 +function Menus(props) {
  21 + const user = props.user
68 22
69 - > i {  
70 - margin-right: 6px; 23 + function isMaster() {
  24 + return user.role === '1000'
  25 + }
  26 +
  27 + function isBusiness() {
  28 + return user.role === '3000'
71 } 29 }
72 -}  
73 30
74 -.ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active {  
75 - color: #495060; 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">
  74 + <Submenu v-if="menu.subMenus" :name="index">
  75 + <template slot="title">
  76 + <Icon :type="menu.icon"></Icon>
  77 + {{menu.title}}
  78 + </template>
  79 + <template v-for="(subMenu, subIndex) in menu.subMenus">
  80 + <MenuItem :name="`${index}-${subIndex}`">
  81 + <a v-if="subMenu.isClassic" :href="subMenu.link">
  82 + <Icon :type="subMenu.icon"></Icon>
  83 + {{subMenu.title}}
  84 + </a>
  85 + <router-link v-else :to="subMenu.link">
  86 + {{subMenu.title}}
  87 + </router-link>
  88 + </MenuItem>
  89 + </template>
  90 + </Submenu>
  91 + <MenuItem v-else :name="index">
  92 + <a v-if="menu.isClassic" :href="menu.link">
  93 + <Icon :type="menu.icon"></Icon>
  94 + {{menu.title}}
  95 + </a>
  96 + <router-link v-else :to="menu.link">
  97 + <Icon :type="menu.icon"></Icon>
  98 + {{menu.title}}
  99 + </router-link>
  100 + </MenuItem>
  101 + </template> */}
  102 + </Menu>
  103 + </div>
76 } 104 }
77 -</style>  
  105 +
  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 {  
19 - padding: 10px 20px;  
20 - border: 1px solid #dddee1;  
21 - height: 65px; 6 + .sidebar-user {
  7 + padding: 10px 20px;
  8 + border: 1px solid #dddee1;
  9 + height: 65px;
22 10
23 - .username {  
24 - display: inline-block;  
25 - padding: 0 10px;  
26 - height: 40px;  
27 - font-size: 18px;  
28 - line-height: 40px;  
29 - max-width: 110px;  
30 - overflow: hidden;  
31 - text-overflow: ellipsis;  
32 - vertical-align: middle; 11 + .username {
  12 + display: inline-block;
  13 + padding: 0 10px;
  14 + height: 40px;
  15 + font-size: 18px;
  16 + line-height: 40px;
  17 + max-width: 110px;
  18 + overflow: hidden;
  19 + text-overflow: ellipsis;
  20 + vertical-align: middle;
  21 + }
33 } 22 }
34 -}  
35 23
36 -.sidebar-menu {  
37 - flex: 1;  
38 - 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 - } 24 + .menus {
  25 + flex: 1;
  26 + overflow: auto;
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 {  
33 - display: block;  
34 - width: 150px;  
35 - height: 60px;  
36 - background: url("../../../images/logo.png");  
37 - filter: invert(100%);  
38 - margin-left: -15px;  
39 -} 5 + .header-logo {
  6 + display: block;
  7 + width: 150px;
  8 + height: 60px;
  9 + background: url("../../../images/logo.png");
  10 + filter: invert(100%);
  11 + margin-top: 2px;
  12 + margin-left: -15px;
  13 + }
40 14
41 -.header-nav {  
42 - li:last-child {  
43 - padding-right: 0; 15 + .header-nav {
  16 + height: 100%;
  17 + line-height: 64px;
  18 +
  19 + li:last-child {
  20 + padding-right: 0;
  21 + }
44 } 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>