mirror of
https://github.com/zuiidea/antd-admin
synced 2026-10-30 05:13:43 +00:00
some layout
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user