some layout

This commit is contained in:
zuiidea
2016-09-11 22:55:25 +08:00
parent 8a4d961124
commit 02babf74f7
7 changed files with 433 additions and 111 deletions
+86 -80
View File
@@ -1,113 +1,119 @@
import React from 'react';
import {Link} from 'react-router';
import {Menu, Icon} from 'antd';
import Logo from '../Logo';
import Logger from '../../utils/Logger';
import items from 'menu.js'; // 由于webpack中的设置, 不用写完整路径
import React from 'react'
import { Link } from 'react-router'
import { Menu, Icon } from 'antd'
import Logo from '../Logo'
import Logger from '../../utils/Logger'
import items from 'menu.js'
const SubMenu = Menu.SubMenu;
const MenuItem = Menu.Item;
const SubMenu = Menu.SubMenu
const MenuItem = Menu.Item
const logger = Logger.getLogger('Sidebar')
const logger = Logger.getLogger('Sidebar');
/**
* 定义sidebar组件
*/
class Sidebar extends React.Component {
/**
* 将菜单项配置转换为对应的MenuItem组件
*
* @param obj sidebar菜单配置项
* @param paths 父级目录, array
* @returns {XML}
*/
const Sider = React.createClass({
getInitialState() {
let fixed=false;
let fixedText="固定";
if(window.localStorage.getItem("mgFixedSider")&&window.localStorage.getItem("mgFixedSider")=="true"){
fixed=true;
fixedText="取消";
}
return {
fixed:fixed,
collapse: true,
fixedText:fixedText
}
},
onCollapseChange() {
this.setState({
collapse: !this.state.collapse,
})
},
switchFixed(){
window.localStorage.setItem("mgFixedSider",!this.state.fixed);
this.setState({
fixed: !this.state.fixed
});
if(this.state.fixedText=="固定"){
this.setState({
fixed: !this.state.fixed,
fixedText:"取消"
});
} else {
this.setState({
fixed: !this.state.fixed,
fixedText:"固定"
});
}
},
transFormMenuItem(obj, paths) {
const parentPath = paths.join('/'); // 将各级父目录组合成完整的路径
logger.debug('transform %o to path %s', obj, parentPath);
// 这个表达式还是有点恶心的...
// JSX虽然方便, 但是很容易被滥用, ES6也是
const parentPath = paths.join('/')
logger.debug('transform %o to path %s', obj, parentPath)
return (
<MenuItem key={obj.key}>
{obj.child ? obj.name : <Link to={`/${parentPath}`}>{obj.name}</Link> }
</MenuItem>
);
}
// 在每次组件挂载的时候parse一次菜单, 不用每次render都解析
)
},
componentWillMount() {
const paths = []; // 暂存各级路径, 当作stack用
const defaultOpenKeys = [];
// 菜单项是从配置中读取的, parse过程还是有点复杂的
// map函数很好用
const paths = []
const defaultOpenKeys = []
const menu = items.map((level1) => {
// parse一级菜单
paths.push(level1.key);
paths.push(level1.key)
if (defaultOpenKeys.length === 0) {
defaultOpenKeys.push(level1.key); // 默认展开第一个菜单
defaultOpenKeys.push(level1.key)
}
// 是否有子菜单?
if (level1.child) {
const level2menu = level1.child.map((level2) => {
// parse二级菜单
paths.push(level2.key);
paths.push(level2.key)
if (level2.child) {
const level3menu = level2.child.map((level3) => {
// parse三级菜单, 不可能再有子菜单了
paths.push(level3.key);
const tmp = this.transFormMenuItem(level3, paths);
paths.pop();
return tmp;
});
paths.pop();
paths.push(level3.key)
const tmp = this.transFormMenuItem(level3, paths)
paths.pop()
return tmp
})
paths.pop()
return (
<SubMenu key={level2.key} title={level2.name}>
{level3menu}
</SubMenu>
);
)
} else {
const tmp = this.transFormMenuItem(level2, paths);
paths.pop();
return tmp;
const tmp = this.transFormMenuItem(level2, paths)
paths.pop()
return tmp
}
});
paths.pop();
})
paths.pop()
return (
<SubMenu key={level1.key} title={<span><Icon type={level1.icon} />{level1.name}</span>}>
{level2menu}
</SubMenu>
)
}
// 没有子菜单, 直接转换为MenuItem
else {
const tmp = this.transFormMenuItem(level1, paths);
paths.pop(); // return之前别忘了pop
return tmp;
const tmp = this.transFormMenuItem(level1, paths)
paths.pop()
return tmp
}
});
this.menu = menu;
this.defaultOpenKeys = defaultOpenKeys;
}
})
this.menu = menu
this.defaultOpenKeys = defaultOpenKeys
},
render() {
// 这些样式其实是在App/index.less中定义的
const collapse = this.state.collapse
return (
<aside className="ant-layout-sider">
<Logo />
<Menu theme="dark" mode="inline" defaultOpenKeys={this.defaultOpenKeys}>
{this.menu}
</Menu>
</aside>
);
<div>
<div className="ant-layout-logo"></div>
<Menu mode="inline" theme="dark" defaultSelectedKeys={['user']}>
{this.menu}
</Menu>
<div className="ant-aside-action" onClick={this.onCollapseChange}>
{collapse ? <Icon type="right" /> : <Icon type="left" />}
</div>
</div>
)
}
})
}
export default Sidebar;
module.exports = Sider
+1 -5
View File
@@ -9,11 +9,7 @@ class Welcome extends React.Component {
render() {
return (
<div>
<h1 className="welcome">
Welcome, 这里是欢迎界面, 欢迎访问我的<a target="_blank" href="http://jxy.me">blog</a>.
<br />
项目地址: <a target="_blank" href="https://github.com/jiangxy/react-antd-admin">https://github.com/jiangxy/react-antd-admin</a>
</h1>
Hello,React
</div>
);
}
-1
View File
@@ -8,7 +8,6 @@ import {Router, Route, IndexRoute, hashHistory} from 'react-router';
import './utils/index.js'; // 引入各种prototype辅助方法
// 开始引入各种自定义的组件
// import App from './components/App';
import App from './view/index';
import SignIn from './view/user/signIn';
import Welcome from './components/Welcome';
+43 -7
View File
@@ -10,9 +10,9 @@
module.exports = [
{
key: 'index', // route时url中的值
name: '菜单哈哈哈', // 在菜单中显示的名称
icon: 'smile', // 只有顶层菜单可以带图标
key: 'index',
name: '菜单一',
icon: 'user',
child: [
{
key: 'option1',
@@ -30,8 +30,8 @@ module.exports = [
},
{
key: 'daohang',
name: '导航',
icon: 'appstore',
name: '菜单二',
icon: 'setting',
child: [
{
key: '555',
@@ -63,8 +63,8 @@ module.exports = [
},
{
key: 'test',
name: '测试',
icon: 'eye',
name: '菜单三',
icon: 'laptop',
child: [
{
key: 'aaa',
@@ -100,4 +100,40 @@ module.exports = [
},
],
},
{
key: 'menu4',
name: '菜单四',
icon: 'notification',
child: [
{
key: 'menu4',
name: '菜单四一',
},
],
},
{
key: 'menu5',
name: '菜单五',
icon: 'folder',
child: [
{
key: 'menu51',
name: '菜单五一',
},
{
key: 'menu52',
name: '菜单五二',
},
{
key: 'menu53',
name: '菜单五三',
child: [
{
key: 'menu531',
name: '菜单五三一',
},
],
},
],
},
];
+23 -9
View File
@@ -5,8 +5,9 @@ import Footer from '../components/Footer'
import Sidebar from '../components/Sidebar'
import SignIn from './user/SignIn'
import Breadcrumb from '../components/Breadcrumb'
import './index.less'
import { ajax, config, logger } from '../utils/lib.js'
import { Spin, message } from 'antd'
import { Spin, message, Icon } from 'antd'
const App = React.createClass({
getInitialState() {
@@ -16,6 +17,7 @@ const App = React.createClass({
loading: false,
login: true,
userName: '未登录',
collapse: false,
}
},
componentDidMount() {
@@ -63,20 +65,32 @@ const App = React.createClass({
} else if (!this.state.login) {
return <SignIn loginSuccess={this.loginSuccess} />
}
const collapse = this.state.collapse
return (
<div className="ant-layout-aside">
<div className={collapse ? "ant-layout-aside ant-layout-aside-collapse" : "ant-layout-aside"}>
<aside className="ant-layout-sider">
<Sidebar />
<div id="main-content-div" className="ant-layout-main">
<Spin spinning={this.state.loading} size="large">
<div className="ant-aside-action" onClick={this.onCollapseChange}>
{collapse ? <Icon type="right" /> : <Icon type="left" />}
</div>
</aside>
<div className="ant-layout-main">
<div className="ant-layout-header">
<Header userName={this.state.userName} />
</div>
<div className="ant-layout-breadcrumb">
<Breadcrumb {...this.props} />
<div className="ant-layout-container">
<div className="ant-layout-content">
{this.state.loading ? '' : this.props.children}
</div>
<div className="ant-layout-container">
<div className="ant-layout-content">
<div style={{ height: 220 }}>
{this.props.children}
</div>
</div>
<Footer />
</Spin>
</div>
<div className="ant-layout-footer">
Ant Design 版权所有 © 2015 由蚂蚁金服体验技术部支持
</div>
</div>
</div>
)
+260
View File
@@ -0,0 +1,260 @@
body {
background: #f0f0f0;
height: 100%;
overflow-y: hidden;
}
// .main-wrapper {
// background: #fff;
// width: 100%;
// margin: 0 auto;
// overflow: hidden;
// position: relative;
// // min-width: 1200px;
// .aside {
// float: left;
// // height: e("calc(100vh - 48px)");
// height: 100vh;
// .ant-menu-item-selected {
// background-color: #333;
// }
// .side-menus {
// // height: e("calc(100vh - 48px)");
// height: 100vh;
// position: absolute;
// width: 60px;
// overflow: hidden;
// transition: width 0.3s ease;
// left: 0;
// z-index: 9999;
// background: #404040;
// &:hover{
// width: 200px;
// }
// &.fixed-sider{
// width: 200px;
// position: relative;
// .ant-menu-item {
// a {
// .text {
// opacity: 1;
// }
// }
// }
// }
// .ant-menu-item {
// height: 48px;
// line-height: 48px;
// text-align: center;
// padding: 0;
// transition: background-color 0.3s ease;
// &:hover {
// background-color: #474747;
// }
// a {
// width: 100%;
// text-align: center;
// .text {
// opacity: 0;
// transition: opacity 0.3s ease;
// width: 60px;
// display: inline-block;
// text-align: left;
// }
// .anticon {
// width: 60px;
// margin-right: 0;
// font-size: 14px;
// }
// }
// }
// &:hover {
// .ant-menu-item {
// a {
// .text {
// opacity: 1;
// }
// }
// }
// }
// }
// }
// .content {
// padding: 24px;
// height: e("calc(100vh - 48px)");
// margin-left: 60px;
// background: #fff;
// overflow: auto;
// .ant-table-small {
// border: none;
// }
// }
// &.no-aside {
// .content {
// margin-left: 0;
// padding: 24px;
// }
// }
// }
// .fixed-btn {
// position: absolute;
// bottom: 0;
// left: 0;
// width: 200px;
// color: #666;
// height: 48px;
// z-index: 9999;
// line-height: 48px;
// background: #474747;
// transition: all .3s ease;
// cursor: pointer;
// .text {
// width: 60px;
// display: inline-block;
// text-align: left;
// float: left;
// }
// .anticon {
// width: 60px;
// margin-right: 0;
// float: left;
// line-height: 48px;
// }
// &:hover{
// color: #2db7f5;
// background-color: #333;
// }
// }
.ant-layout-aside {
position: relative;
min-height: 100%;
}
.ant-layout-aside .ant-layout-logo {
width: 150px;
height: 32px;
background: #333;
border-radius: 6px;
margin: 16px 24px 16px 28px;
transition: all 0.3s ease;
}
.ant-layout-aside-collapse .ant-layout-logo {
width: 32px;
margin: 16px;
transition: all 0.3s ease;
}
.ant-layout-aside .ant-layout-sider {
width: 224px;
background: #404040;
position: absolute;
overflow: visible;
padding-bottom: 24px;
height: 100%;
transition: all 0.3s ease;
}
.ant-layout-aside-collapse .ant-layout-sider {
width: 64px;
transition: all 0.3s ease;
}
.ant-layout-aside .ant-layout-sider > .ant-menu {
margin-bottom: 20px;
}
.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item {
margin: 16px 0;
}
.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item .nav-text {
vertical-align: baseline;
display: inline-block;
}
.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item > .anticon {
transition: font-size .3s;
}
.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item {
transition: all 0s ease;
}
.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item > .anticon {
font-size: 16px;
display: inline-block;
}
.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item .nav-text {
display: none;
}
.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item:hover {
background: #2db7f5;
color: #fff;
transition: all 0s ease;
}
.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item:hover .nav-text {
display: inline-block;
vertical-align: top;
background: #2db7f5;
color: #fff;
padding-right: 16px;
border-radius: 0 5px 5px 0;
}
/* 实际使用中需要改成 position: fixed */
.ant-layout-aside {
.ant-layout-header{
background: #fff;
height: 64px;
border-bottom: 1px solid #e9e9e9;
}
.ant-layout-breadcrumb{
margin: 7px 0 -17px 24px;
}
.ant-layout-main{
margin-left: 224px;
transition: all 0.3s ease;
}
.ant-aside-action {
height: 42px;
width: 224px;
position: fixed;
bottom: 0;
background: #474747;
color: #fff;
text-align: center;
line-height: 42px;
cursor: pointer;
transition: all 0.3s ease;
}
.ant-layout-container{
margin: 24px 16px;
.ant-layout-content{
background: #fff;
padding: 24px;
}
}
.ant-layout-footer{
height: 64px;
line-height: 64px;
text-align: center;
font-size: 12px;
color: #999;
background: #fff;
border-top: 1px solid #e9e9e9;
width: 100%;
}
&.ant-layout-aside-collapse{
.ant-aside-action{
width: 64px;
transition: all 0.3s ease;
}
.ant-layout-main{
margin-left: 64px;
transition: all 0.3s ease;
}
}
}
+20 -9
View File
@@ -1,4 +1,6 @@
const webpack = require('webpack');
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require("extract-text-webpack-plugin")
const babelLoaderConfig = {
presets: ['es2015', 'stage-0', 'react'],
@@ -13,7 +15,7 @@ module.exports = {
output: {
path: __dirname + '/dist',
filename: 'bundle.js',
// publicPath: 'http://mycdn.com/', // require时用来生成图片的地址
// publicPath: 'http://mycdn.com/',
},
resolve: {
@@ -27,15 +29,23 @@ module.exports = {
test: /\.jsx?$/,
loaders: ['babel-loader?' + JSON.stringify(babelLoaderConfig), 'strip-loader?strip[]=logger.debug,strip[]=console.log,strip[]=console.debug'],
exclude: /node_modules/,
}, {
test: /\.css$/,
loader: 'style!css',
}, {
test: /\.less$/,
loader: 'style!css!less',
}, {
},
// {
// test: /\.css$/,
// loader: 'style!css',
// }, {
// test: /\.less$/,
// loader: 'style!css!less',
// },
{
test: /\.(png|jpg|svg)$/,
loader: 'url?limit=25000',
},{
test: /\.css$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader")
}, {
test: /\.less$/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader!less-loader")
},
],
},
@@ -50,6 +60,7 @@ module.exports = {
filename: 'index.html',
template: './src/index.html'
}),
new ExtractTextPlugin("[name].css"),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),