add layout component with header, content, sider and footer
This commit is contained in:
parent
e6508e277f
commit
a2eb028782
17 changed files with 509 additions and 4 deletions
|
@ -6,13 +6,14 @@ nav { margin-bottom: 40px; }
|
|||
ul { display: flex; flex-wrap: wrap; }
|
||||
li { display: inline-block; }
|
||||
li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
|
||||
.container{ padding: 10px 40px; }
|
||||
.container{ padding: 10px 40px 0; }
|
||||
.v-link-active { color: #bbb; }
|
||||
</style>
|
||||
<template>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><router-link to="/layout">Layout</router-link></li>
|
||||
<li><router-link to="/affix">Affix</router-link></li>
|
||||
<li><router-link to="/grid">Grid</router-link></li>
|
||||
<li><router-link to="/button">Button</router-link></li>
|
||||
|
|
|
@ -17,6 +17,10 @@ Vue.config.debug = true;
|
|||
// 路由配置
|
||||
const router = new VueRouter({
|
||||
routes: [
|
||||
{
|
||||
path: '/layout',
|
||||
component: require('./routers/layout.vue')
|
||||
},
|
||||
{
|
||||
path: '/affix',
|
||||
component: require('./routers/affix.vue')
|
||||
|
|
61
examples/routers/layout.vue
Normal file
61
examples/routers/layout.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="layout-demo-con">
|
||||
<Layout :style="{minHeight: '100vh'}">
|
||||
<Sider
|
||||
v-model="isCollapsed"
|
||||
collapsed-width="0"
|
||||
breakpoint="md"
|
||||
ref="side"
|
||||
width="200">
|
||||
<Menu width="auto" theme="dark" active-name="1">
|
||||
<MenuGroup title="内容管理">
|
||||
<MenuItem name="1">
|
||||
<Icon type="document-text"></Icon>
|
||||
文章管理
|
||||
</MenuItem>
|
||||
<MenuItem name="2">
|
||||
<Icon type="chatbubbles"></Icon>
|
||||
评论管理
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="统计分析">
|
||||
<MenuItem name="3">
|
||||
<Icon type="heart"></Icon>
|
||||
用户留存
|
||||
</MenuItem>
|
||||
<MenuItem name="4">
|
||||
<Icon type="heart-broken"></Icon>
|
||||
流失用户
|
||||
</MenuItem>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
</Sider>
|
||||
<Layout class-name="test-class">
|
||||
<Header :style="{background: '#eee'}"><Button @click="toggleCollapse">菜单</Button></Header>
|
||||
<Content :style="{background:'#FFCF9E'}">
|
||||
<p v-for="i in 100" :key="i">{{ i }}</p>
|
||||
</Content>
|
||||
<Footer>sdfsdsdfsdfs</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
isCollapsed: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse () {
|
||||
this.$refs.side.toggleCollapse();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.layout-demo-con{
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
3
src/components/content/index.js
Normal file
3
src/components/content/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Content from '../layout/content.vue';
|
||||
|
||||
export default Content;
|
3
src/components/footer/index.js
Normal file
3
src/components/footer/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Footer from '../layout/footer.vue';
|
||||
|
||||
export default Footer;
|
3
src/components/header/index.js
Normal file
3
src/components/header/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Header from '../layout/header.vue';
|
||||
|
||||
export default Header;
|
28
src/components/layout/content.vue
Normal file
28
src/components/layout/content.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div :class="wrapClasses"><slot></slot></div>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-layout';
|
||||
export default {
|
||||
name: 'Content',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-content`,
|
||||
this.className
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
28
src/components/layout/footer.vue
Normal file
28
src/components/layout/footer.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div :class="wrapClasses"><slot></slot></div>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-layout';
|
||||
export default {
|
||||
name: 'Footer',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-footer`,
|
||||
this.className
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
28
src/components/layout/header.vue
Normal file
28
src/components/layout/header.vue
Normal file
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<div :class="wrapClasses"><slot></slot></div>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-layout';
|
||||
export default {
|
||||
name: 'Header',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-header`,
|
||||
this.className
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
12
src/components/layout/index.js
Normal file
12
src/components/layout/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Layout from './layout.vue';
|
||||
import Header from './header.vue';
|
||||
import Sider from './sider.vue';
|
||||
import Content from './content.vue';
|
||||
import Footer from './footer.vue';
|
||||
|
||||
Layout.Header = Header;
|
||||
Layout.Sider = Sider;
|
||||
Layout.Content = Content;
|
||||
Layout.Footer = Footer;
|
||||
|
||||
export default Layout;
|
43
src/components/layout/layout.vue
Normal file
43
src/components/layout/layout.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div :class="wrapClasses"><slot></slot></div>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-layout';
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
hasSider: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
this.className,
|
||||
{
|
||||
[`${prefixCls}-has-sider`]: this.hasSider
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findSider () {
|
||||
return this.$children.some(child => {
|
||||
return child.$options._componentTag === 'Sider';
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.hasSider = this.findSider();
|
||||
}
|
||||
};
|
||||
</script>
|
150
src/components/layout/sider.vue
Normal file
150
src/components/layout/sider.vue
Normal file
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<div
|
||||
:class="wrapClasses"
|
||||
:style="{width: siderWidth + 'px', minWidth: siderWidth + 'px', maxWidth: siderWidth + 'px', flex: '0 0 ' + siderWidth + 'px'}">
|
||||
<span v-show="showZeroTrigger" @click="toggleCollapse" :class="zeroWidthTriggerClasses">
|
||||
<i class="ivu-icon ivu-icon-navicon-round"></i>
|
||||
</span>
|
||||
<div :class="`${prefixCls}-children`">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-show="!mediaMatched && !hideTrigger" :class="triggerClasses" @click="toggleCollapse" :style="{width: siderWidth + 'px'}">
|
||||
<i :class="triggerIconClasses"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { on, off } from '../../utils/dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
const prefixCls = 'ivu-layout-sider';
|
||||
const dimensionMap = {
|
||||
xs: '480px',
|
||||
sm: '768px',
|
||||
md: '992px',
|
||||
lg: '1200px',
|
||||
xl: '1600px',
|
||||
};
|
||||
if (typeof window !== 'undefined') {
|
||||
const matchMediaPolyfill = mediaQuery => {
|
||||
return {
|
||||
media: mediaQuery,
|
||||
matches: false,
|
||||
on() {
|
||||
},
|
||||
off() {
|
||||
},
|
||||
};
|
||||
};
|
||||
window.matchMedia = window.matchMedia || matchMediaPolyfill;
|
||||
}
|
||||
export default {
|
||||
name: 'Sider',
|
||||
props: {
|
||||
value: { // if it's collpased now
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 200
|
||||
},
|
||||
collapsedWidth: {
|
||||
type: [Number, String],
|
||||
default: 64
|
||||
},
|
||||
hideTrigger: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
breakpoint: {
|
||||
type: String,
|
||||
default: '',
|
||||
validator (val) {
|
||||
return oneOf(val, ['xs', 'sm', 'md', 'lg', 'xl']);
|
||||
}
|
||||
},
|
||||
defaultCollapsed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
reverseArrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
mediaMatched: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
this.className,
|
||||
this.siderWidth ? '' : `${prefixCls}-zero-width`,
|
||||
this.value ? `${prefixCls}-collapsed` : ''
|
||||
];
|
||||
},
|
||||
triggerClasses () {
|
||||
return [
|
||||
`${prefixCls}-trigger`,
|
||||
this.value ? `${prefixCls}-trigger-collapsed` : '',
|
||||
];
|
||||
},
|
||||
zeroWidthTriggerClasses () {
|
||||
return [
|
||||
`${prefixCls}-zero-width-trigger`,
|
||||
this.reverseArrow ? `${prefixCls}-zero-width-trigger-left` : ''
|
||||
];
|
||||
},
|
||||
triggerIconClasses () {
|
||||
return [
|
||||
'ivu-icon',
|
||||
`ivu-icon-chevron-${this.reverseArrow ? 'right' : 'left'}`,
|
||||
`${prefixCls}-trigger-icon`,
|
||||
];
|
||||
},
|
||||
siderWidth () {
|
||||
return this.value ? (this.mediaMatched ? 0 : parseInt(this.collapsedWidth)) : parseInt(this.width);
|
||||
},
|
||||
showZeroTrigger () {
|
||||
return this.mediaMatched && !this.hideTrigger || (parseInt(this.collapsedWidth) === 0) && this.value && !this.hideTrigger;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse () {
|
||||
this.$emit('input', !this.value);
|
||||
this.$emit('on-collapse', !this.value);
|
||||
},
|
||||
matchMedia () {
|
||||
let matchMedia;
|
||||
if (window.matchMedia) {
|
||||
matchMedia = window.matchMedia;
|
||||
}
|
||||
let mediaMatched = this.mediaMatched;
|
||||
this.mediaMatched = matchMedia(`(max-width: ${dimensionMap[this.breakpoint]})`).matches;
|
||||
if (this.mediaMatched !== mediaMatched) {
|
||||
this.$emit('input', this.mediaMatched);
|
||||
this.$emit('on-collapse', this.mediaMatched);
|
||||
}
|
||||
},
|
||||
onWindowResize () {
|
||||
this.matchMedia();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
on(window, 'resize', this.onWindowResize);
|
||||
this.matchMedia();
|
||||
this.$emit('input', this.defaultCollapsed);
|
||||
},
|
||||
destroyed () {
|
||||
off(window, 'resize', this.onWindowResize);
|
||||
}
|
||||
};
|
||||
</script>
|
3
src/components/sider/index.js
Normal file
3
src/components/sider/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Sider from '../layout/sider.vue';
|
||||
|
||||
export default Sider;
|
15
src/index.js
15
src/index.js
|
@ -17,13 +17,17 @@ import Checkbox from './components/checkbox';
|
|||
import Circle from './components/circle';
|
||||
import Collapse from './components/collapse';
|
||||
import ColorPicker from './components/color-picker';
|
||||
import Content from './components/content';
|
||||
import DatePicker from './components/date-picker';
|
||||
import Dropdown from './components/dropdown';
|
||||
import Footer from './components/footer';
|
||||
import Form from './components/form';
|
||||
import Header from './components/header';
|
||||
import Icon from './components/icon';
|
||||
import Input from './components/input';
|
||||
import InputNumber from './components/input-number';
|
||||
import Scroll from './components/scroll';
|
||||
import Layout from './components/layout';
|
||||
import LoadingBar from './components/loading-bar';
|
||||
import Menu from './components/menu';
|
||||
import Message from './components/message';
|
||||
|
@ -34,6 +38,7 @@ import Poptip from './components/poptip';
|
|||
import Progress from './components/progress';
|
||||
import Radio from './components/radio';
|
||||
import Rate from './components/rate';
|
||||
import Sider from './components/sider';
|
||||
import Slider from './components/slider';
|
||||
import Spin from './components/spin';
|
||||
import Steps from './components/steps';
|
||||
|
@ -71,21 +76,26 @@ const components = {
|
|||
Col,
|
||||
Collapse,
|
||||
ColorPicker,
|
||||
Content: Content,
|
||||
DatePicker,
|
||||
Dropdown,
|
||||
DropdownItem: Dropdown.Item,
|
||||
DropdownMenu: Dropdown.Menu,
|
||||
Footer: Footer,
|
||||
Form,
|
||||
FormItem: Form.Item,
|
||||
Header: Header,
|
||||
Icon,
|
||||
Input,
|
||||
InputNumber,
|
||||
Scroll,
|
||||
Sider: Sider,
|
||||
Submenu: Menu.Sub,
|
||||
Layout: Layout,
|
||||
LoadingBar,
|
||||
Menu,
|
||||
MenuGroup: Menu.Group,
|
||||
MenuItem: Menu.Item,
|
||||
Submenu: Menu.Sub,
|
||||
Message,
|
||||
Modal,
|
||||
Notice,
|
||||
|
@ -122,7 +132,10 @@ const iview = {
|
|||
iButton: Button,
|
||||
iCircle: Circle,
|
||||
iCol: Col,
|
||||
iContent: Content,
|
||||
iForm: Form,
|
||||
iFooter: Footer,
|
||||
iHeader: Header,
|
||||
iInput: Input,
|
||||
iMenu: Menu,
|
||||
iOption: Option,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
@import "input-number";
|
||||
@import "scroll";
|
||||
@import "tag";
|
||||
@import "layout";
|
||||
@import "loading-bar";
|
||||
@import "progress";
|
||||
@import "timeline";
|
||||
|
|
113
src/styles/components/layout.less
Normal file
113
src/styles/components/layout.less
Normal file
|
@ -0,0 +1,113 @@
|
|||
@layout-prefix-cls: ~"@{css-prefix}layout";
|
||||
|
||||
.@{layout-prefix-cls} {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
background: @layout-body-background;
|
||||
|
||||
&&-has-sider {
|
||||
flex-direction: row;
|
||||
> .@{layout-prefix-cls},
|
||||
> .@{layout-prefix-cls}-content {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-header,
|
||||
&-footer {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&-header {
|
||||
background: @layout-header-background;
|
||||
padding: @layout-header-padding;
|
||||
height: @layout-header-height;
|
||||
line-height: @layout-header-height;
|
||||
}
|
||||
|
||||
&-sider {
|
||||
transition: all .2s @ease-in-out;
|
||||
position: relative;
|
||||
background: @layout-sider-background;
|
||||
|
||||
min-width: 0;
|
||||
|
||||
&-children {
|
||||
height: 100%;
|
||||
padding-top: 0.1px;
|
||||
margin-top: -0.1px;
|
||||
}
|
||||
|
||||
&-has-trigger {
|
||||
padding-bottom: @layout-trigger-height;
|
||||
}
|
||||
|
||||
&-trigger {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
height: @layout-trigger-height;
|
||||
line-height: @layout-trigger-height;
|
||||
color: @layout-trigger-color;
|
||||
background: @layout-sider-background;
|
||||
z-index: 1000;
|
||||
transition: all .2s @ease-in-out;
|
||||
.ivu-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
>* {
|
||||
transition: all .2s;
|
||||
}
|
||||
&-collapsed {
|
||||
.@{layout-prefix-cls}-sider-trigger-icon {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-zero-width {
|
||||
& > * {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-trigger {
|
||||
position: absolute;
|
||||
top: @layout-header-height;
|
||||
right: -@layout-zero-trigger-width;
|
||||
text-align: center;
|
||||
width: @layout-zero-trigger-width;
|
||||
height: @layout-zero-trigger-height;
|
||||
line-height: @layout-zero-trigger-height;
|
||||
background: @layout-sider-background;
|
||||
color: #fff;
|
||||
font-size: @layout-zero-trigger-width / 2;
|
||||
border-radius: 0 @border-radius-base @border-radius-base 0;
|
||||
cursor: pointer;
|
||||
transition: background .3s ease;
|
||||
|
||||
&:hover {
|
||||
background: tint(@layout-sider-background, 10%);
|
||||
}
|
||||
|
||||
&&-left {
|
||||
right: 0;
|
||||
left: -@layout-zero-trigger-width;
|
||||
border-radius: @border-radius-base 0 0 @border-radius-base;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
background: @layout-footer-background;
|
||||
padding: @layout-footer-padding;
|
||||
color: @text-color;
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
&-content {
|
||||
flex: auto;
|
||||
}
|
||||
}
|
|
@ -91,6 +91,17 @@
|
|||
// Layout and Grid
|
||||
@grid-columns : 24;
|
||||
@grid-gutter-width : 0;
|
||||
@layout-body-background : #f5f7f9;
|
||||
@layout-header-background : #495060;
|
||||
@layout-header-height : 64px;
|
||||
@layout-header-padding : 0 50px;
|
||||
@layout-footer-padding : 24px 50px;
|
||||
@layout-footer-background : @layout-body-background;
|
||||
@layout-sider-background : @layout-header-background;
|
||||
@layout-trigger-height : 48px;
|
||||
@layout-trigger-color : #fff;
|
||||
@layout-zero-trigger-width : 36px;
|
||||
@layout-zero-trigger-height : 42px;
|
||||
|
||||
// Legend
|
||||
@legend-color : #999;
|
||||
|
|
Loading…
Add table
Reference in a new issue