Authored by 毕凯

Merge branch 'feature/react'

... ... @@ -3,13 +3,13 @@ const normalMenus = [
title: 'Dashboard',
link: '/',
fa: 'fa-home',
icon: 'ios-home',
icon: 'dashboard',
isClassic: true
},
{
title: '缓存管理',
fa: 'fa-history',
icon: 'ios-cloud',
icon: 'cloud',
subMenus: [{
title: 'PageCahe清理',
link: '/page_cache/query',
... ... @@ -31,14 +31,14 @@ const normalMenus = [
{
title: '降级配置',
fa: 'fa-hand-o-down',
icon: 'ios-toggle',
icon: 'api',
link: '/degrade',
isClassic: true
},
{
title: '滥用防护',
fa: 'fa-shield',
icon: 'ios-locked',
icon: 'safety',
subMenus: [{
title: 'IP黑名单',
link: '/crawler_black/ip',
... ... @@ -78,7 +78,7 @@ const normalMenus = [
{
title: 'SEO管理',
fa: 'fa-list',
icon: 'ios-world',
icon: 'search',
subMenus: [{
title: '词根管理',
link: '/seo/rootwords',
... ... @@ -88,6 +88,9 @@ const normalMenus = [
link: '/keywords/expand',
isClassic: true
}, {
title: '热门关键词管理',
link: '/seo/hot-keyword'
}, {
title: 'TDK管理',
link: '/seo/tdk',
isClassic: true
... ... @@ -108,7 +111,7 @@ const normalMenus = [
{
title: '性能统计',
fa: 'fa-list',
icon: 'podium',
icon: 'area-chart',
subMenus: [{
title: '浏览器排行榜',
link: '/profile/sort_client',
... ... @@ -126,15 +129,6 @@ const normalMenus = [
link: '/profile/error',
isClassic: true
}]
},
{
title: '日志查询',
fa: 'fa-list',
icon: 'ios-eye',
subMenus: [{
title: 'Node 日志',
link: '/logs/node'
}]
}
]
... ... @@ -142,7 +136,7 @@ const masterMenus = [
{
title: '系统管理',
fa: 'fa-gear',
icon: 'person',
icon: 'setting',
subMenus: [{
title: '服务器配置',
link: '/servers/setting',
... ... @@ -184,4 +178,4 @@ module.exports = {
normalMenus,
masterMenus,
businessMenus
}
\ No newline at end of file
}
... ...
... ... @@ -76,12 +76,14 @@
"utility": "^1.8.0"
},
"devDependencies": {
"ada": "^0.2.9",
"ada": "^1.0.1",
"antd": "^3.5.0",
"axios": "^0.18.0",
"iview": "^2.13.0",
"nodemon": "^1.11.0",
"vue": "^2.5.16",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.16"
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-loadable": "^5.4.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2"
}
}
... ...
<template>
<Menu class="sidebar-menu" theme="light" width="auto">
<template v-for="(menu, index) in menus">
<Submenu v-if="menu.subMenus" :name="index">
<template slot="title">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</template>
<template v-for="(subMenu, subIndex) in menu.subMenus">
<MenuItem :name="`${index}-${subIndex}`">
<a v-if="subMenu.isClassic" :href="subMenu.link">
<Icon :type="subMenu.icon"></Icon>
{{subMenu.title}}
</a>
<router-link v-else :to="subMenu.link">
{{subMenu.title}}
</router-link>
</MenuItem>
</template>
</Submenu>
<MenuItem v-else :name="index">
<a v-if="menu.isClassic" :href="menu.link">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</a>
<router-link v-else :to="menu.link">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</router-link>
</MenuItem>
</template>
</Menu>
</template>
import React from 'react'
import { Link } from 'react-router-dom'
import { Layout, Menu, Icon } from 'antd'
<script>
import {
normalMenus,
masterMenus,
businessMenus
} from '../../../../config/menus' // 考虑与原有业务复用
export default {
props: ['user'],
computed: {
isMaster: function() {
return this.user.role === '1000'
},
isBusiness: function() {
return this.user.role === '3000'
},
menus: function() {
if (this.isBusiness) {
return businessMenus
}
if (this.isMaster) {
return [...normalMenus, ...masterMenus]
}
return normalMenus
}
}
const { Header, Sider } = Layout
function Title(props) {
return <div>
<Icon type={props.icon}></Icon>
{props.title}
</div>
}
</script>
<style scoped lang="scss">
a {
display: block;
color: #495060;
function Menus(props) {
const user = props.user
> i {
margin-right: 6px;
function isMaster() {
return user.role === '1000'
}
function isBusiness() {
return user.role === '3000'
}
}
.ivu-menu-light.ivu-menu-vertical .ivu-menu-item-active {
color: #495060;
const menus = (function() {
if (isBusiness()) {
return businessMenus
}
if (isMaster()) {
return [...normalMenus, ...masterMenus]
}
return normalMenus
}())
const ListItems = menus.map((menu, index) => {
if (menu.subMenus && menu.subMenus.length) {
return <Menu.SubMenu title={Title(menu)} key={index + ''}>
{menu.subMenus.map((subMenu, subIndex) => {
return <Menu.Item key={`${index}-${subIndex}`}>
{subMenu.isClassic ? <a href={subMenu.link}>
<Icon type={subMenu.icon}></Icon>
{subMenu.title}
</a> : <Link to={subMenu.link}>
<Icon type={subMenu.icon}></Icon>
{subMenu.title}
</Link>}
</Menu.Item>
})}
</Menu.SubMenu>
}
return <Menu.Item key={index + ''}>
{menu.isClassic ? <a href={menu.link}>
<Icon type={menu.icon}></Icon>
{menu.title}
</a> : <Link to={menu.link}>
<Icon type={menu.icon}></Icon>
{menu.title}
</Link>}
</Menu.Item>
// return <Item menu={menu} key={index + ''} />
})
return <div className="menus">
<Menu className="sidebar-menu" theme="light" mode="inline" width="auto">
{ListItems}
{/* <template v-for="(menu, index) in menus">
<Submenu v-if="menu.subMenus" :name="index">
<template slot="title">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</template>
<template v-for="(subMenu, subIndex) in menu.subMenus">
<MenuItem :name="`${index}-${subIndex}`">
<a v-if="subMenu.isClassic" :href="subMenu.link">
<Icon :type="subMenu.icon"></Icon>
{{subMenu.title}}
</a>
<router-link v-else :to="subMenu.link">
{{subMenu.title}}
</router-link>
</MenuItem>
</template>
</Submenu>
<MenuItem v-else :name="index">
<a v-if="menu.isClassic" :href="menu.link">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</a>
<router-link v-else :to="menu.link">
<Icon :type="menu.icon"></Icon>
{{menu.title}}
</router-link>
</MenuItem>
</template> */}
</Menu>
</div>
}
</style>
\ No newline at end of file
export default Menus
... ...
import '../../scss/common/sidebar.scss'
import axios from 'axios'
import React from 'react'
import { Menu, Avatar } from 'antd'
import Menus from './Menus'
import userImg from '../../../images/photos/user1.png'
class Sidebar extends React.Component {
constructor() {
super()
this.state = {
user: {}
}
}
componentDidMount() {
this.fetchUser()
}
render() {
return <div className="sidebar">
<div className="sidebar-user">
<Avatar icon="user" size="large" src={userImg} />
<span className="username">{this.state.user.username}</span>
</div>
<Menus user={this.state.user} />
</div>
}
async fetchUser() {
const result = await axios.get('/login/info')
if (result.status === 200) {
this.setState({
user: result.data
})
}
}
}
export default Sidebar
... ...
import '../../scss/common/topbar.scss'
import React from 'react'
import { Layout, Menu, Icon } from 'antd'
const { Header, Sider } = Layout
function Topbar() {
function handleClick() {
location.href = '/logout'
}
return (
<Header className="header">
<a className="header-logo" href="/"></a>
<Menu mode="horizontal" theme="dark" className="header-menu">
<Menu.Item className="header-nav">
<Icon type="logout"></Icon>
<span onClick={handleClick}>退出</span>
</Menu.Item>
</Menu>
</Header>
)
}
export default Topbar
... ...
import 'antd/dist/antd.css'
import 'common/index.scss'
import React from 'react'
import { Route, Link, Switch } from 'react-router-dom'
import { hot } from 'react-hot-loader'
import Loadable from 'react-loadable'
import { Layout } from 'antd'
import Topbar from 'common/Topbar'
import Sidebar from 'common/Sidebar'
const { Content, Sider } = Layout
function NoMatch() {
return <div>
Yoho Node.js 持续集成平台
</div>
}
function Loading() {
return <div>
Loading
</div>
}
const HotKeywords = Loadable({
loader: () => import('./pages/seo/HotKeywords'),
loading: Loading
})
function App () {
return (
<Layout className="layout">
<Topbar></Topbar>
<Layout className="main">
<Sider style={{background: '#fff'}}>
<Sidebar></Sidebar>
</Sider>
<Layout style={{padding: '24px'}}>
<Content style={{padding: '24px', background: '#fff'}}>
<Switch>
<Route exact path="/seo/hot-keyword" component={HotKeywords}/>
<Route component={NoMatch}/>
</Switch>
</Content>
</Layout>
</Layout>
</Layout>
)
}
export default hot(module)(App)
... ...
import 'iview/dist/styles/iview.css'
import 'common/index.scss'
import React from 'react'
import ReactDOM from 'react-dom'
import Vue from 'vue'
import VueRouter from 'vue-router'
import iView from 'iview'
import App from '../vue/App.vue'
import routes from './routes'
import { BrowserRouter } from 'react-router-dom'
Vue.use(VueRouter)
Vue.use(iView)
import App from './App.jsx'
const router = new VueRouter({
mode: 'history',
routes
})
export default new Vue({
el: '#root',
render: h => h(App),
router,
components: {
App
}
})
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
)
... ...
import React from 'react'
function HotKeywords() {
return <div>
HotKeywords
</div>
}
export default HotKeywords
... ...
const NotFound = { template: `
<div>
Yoho Node.js 持续集成平台
</div>
` }
const LogsNode = () => import('../../vue/logs/node.vue')
const routes = [
{ path: '/logs/node', component: LogsNode },
{ path: '*', component: NotFound }
]
export default routes
\ No newline at end of file
html,
body {
body,
#root {
height: 100%;
}
.layout{
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #f5f7f9;
}
.main {
flex: 1;
}
... ...
<template>
<div class="sidebar">
<div class="sidebar-user">
<Avatar icon="person" size="large" :src="userImg" />
<span class="username">{{user.username}}</span>
</div>
<Menus :user="user" />
</div>
</template>
<style scoped lang="scss">
.sidebar {
display: flex;
flex-direction: column;
height: 100%;
}
.sidebar-user {
padding: 10px 20px;
border: 1px solid #dddee1;
height: 65px;
.sidebar-user {
padding: 10px 20px;
border: 1px solid #dddee1;
height: 65px;
.username {
display: inline-block;
padding: 0 10px;
height: 40px;
font-size: 18px;
line-height: 40px;
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
.username {
display: inline-block;
padding: 0 10px;
height: 40px;
font-size: 18px;
line-height: 40px;
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
}
}
}
.sidebar-menu {
flex: 1;
overflow: auto;
}
</style>
<script>
import Menus from 'common/Menus.vue'
import userImg from '../../../images/photos/user1.png'
import axios from 'axios'
export default {
components: {
Menus
},
data() {
return {
userImg,
user: {}
}
},
async created() {
const result = await axios.get('/login/info')
if (result.status === 200) {
this.user = result.data
}
.menus {
flex: 1;
overflow: auto;
}
}
</script>
... ...
<template>
<Header>
<Menu mode="horizontal" theme="dark" active-name="1" class="header-menu">
<a class="header-logo" href="/"></a>
<div class="header-nav">
<MenuItem name="1">
<Icon type="log-out"></Icon>
<span @click="logout">退出</span>
</MenuItem>
</div>
</Menu>
</Header>
</template>
<script>
export default {
methods: {
logout() {
location.href = '/logout'
}
}
}
</script>
<style scoped lang="scss">
.header-menu {
.header {
display: flex;
justify-content: space-between;
margin-top: 2px;
}
.header-logo {
display: block;
width: 150px;
height: 60px;
background: url("../../../images/logo.png");
filter: invert(100%);
margin-left: -15px;
}
.header-logo {
display: block;
width: 150px;
height: 60px;
background: url("../../../images/logo.png");
filter: invert(100%);
margin-top: 2px;
margin-left: -15px;
}
.header-nav {
li:last-child {
padding-right: 0;
.header-nav {
height: 100%;
line-height: 64px;
li:last-child {
padding-right: 0;
}
}
}
</style>
\ No newline at end of file
... ...
<template>
<div class="layout">
<Layout>
<Topbar></Topbar>
<Layout class="main">
<Sider hide-trigger :style="{background: '#fff'}">
<Sidebar></Sidebar>
</Sider>
<Layout :style="{padding: '24px'}">
<Content :style="{padding: '24px', background: '#fff'}">
<router-view></router-view>
</Content>
</Layout>
</Layout>
</Layout>
</div>
</template>
<script>
import Topbar from 'common/Topbar.vue'
import Sidebar from 'common/Sidebar.vue'
export default {
components: {
Topbar,
Sidebar
}
}
</script>
<style scoped lang="scss">
.layout{
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #f5f7f9;
}
.main {
flex: 1;
}
</style>
<template>
<div>
<Table highlight-row ref="currentRowTable" :columns="columns" :data="logs"></Table>
</div>
</template>
<script>
import {LogService} from '../../js/services/logs';
export default {
data() {
return {
columns: [
{
title: 'topic_name',
key: 'topic_name'
},
{
title: 'topic_id',
key: 'topic_id'
},
{
title: '级别',
key: 'level'
},
{
title: '内容',
key: 'message'
},
{
title: '时间',
key: 'timestamp'
}
],
logs: []
}
},
methods: {
searchLog() {
this.logService.getLogs({limit: 20}).then(res => {
this.logs = res.results;
});
}
},
created() {
this.logService = new LogService();
this.searchLog();
}
}
</script>