Commit a2eb028782a1da2da589999a176c89bb72d70b85

Authored by zhigang.li
1 parent e6508e27

add layout component with header, content, sider and footer

examples/app.vue
... ... @@ -6,13 +6,14 @@ nav { margin-bottom: 40px; }
6 6 ul { display: flex; flex-wrap: wrap; }
7 7 li { display: inline-block; }
8 8 li + li { border-left: solid 1px #bbb; padding-left: 10px; margin-left: 10px; }
9   -.container{ padding: 10px 40px; }
  9 +.container{ padding: 10px 40px 0; }
10 10 .v-link-active { color: #bbb; }
11 11 </style>
12 12 <template>
13 13 <div class="container">
14 14 <nav>
15 15 <ul>
  16 + <li><router-link to="/layout">Layout</router-link></li>
16 17 <li><router-link to="/affix">Affix</router-link></li>
17 18 <li><router-link to="/grid">Grid</router-link></li>
18 19 <li><router-link to="/button">Button</router-link></li>
... ...
examples/main.js
... ... @@ -18,6 +18,10 @@ Vue.config.debug = true;
18 18 const router = new VueRouter({
19 19 routes: [
20 20 {
  21 + path: '/layout',
  22 + component: require('./routers/layout.vue')
  23 + },
  24 + {
21 25 path: '/affix',
22 26 component: require('./routers/affix.vue')
23 27 },
... ...
examples/routers/layout.vue 0 → 100644
  1 +<template>
  2 + <div class="layout-demo-con">
  3 + <Layout :style="{minHeight: '100vh'}">
  4 + <Sider
  5 + v-model="isCollapsed"
  6 + collapsed-width="0"
  7 + breakpoint="md"
  8 + ref="side"
  9 + width="200">
  10 + <Menu width="auto" theme="dark" active-name="1">
  11 + <MenuGroup title="内容管理">
  12 + <MenuItem name="1">
  13 + <Icon type="document-text"></Icon>
  14 + 文章管理
  15 + </MenuItem>
  16 + <MenuItem name="2">
  17 + <Icon type="chatbubbles"></Icon>
  18 + 评论管理
  19 + </MenuItem>
  20 + </MenuGroup>
  21 + <MenuGroup title="统计分析">
  22 + <MenuItem name="3">
  23 + <Icon type="heart"></Icon>
  24 + 用户留存
  25 + </MenuItem>
  26 + <MenuItem name="4">
  27 + <Icon type="heart-broken"></Icon>
  28 + 流失用户
  29 + </MenuItem>
  30 + </MenuGroup>
  31 + </Menu>
  32 + </Sider>
  33 + <Layout class-name="test-class">
  34 + <Header :style="{background: '#eee'}"><Button @click="toggleCollapse">菜单</Button></Header>
  35 + <Content :style="{background:'#FFCF9E'}">
  36 + <p v-for="i in 100" :key="i">{{ i }}</p>
  37 + </Content>
  38 + <Footer>sdfsdsdfsdfs</Footer>
  39 + </Layout>
  40 + </Layout>
  41 + </div>
  42 +</template>
  43 +<script>
  44 +export default {
  45 + data () {
  46 + return {
  47 + isCollapsed: false
  48 + };
  49 + },
  50 + methods: {
  51 + toggleCollapse () {
  52 + this.$refs.side.toggleCollapse();
  53 + }
  54 + }
  55 +};
  56 +</script>
  57 +<style lang="less" scoped>
  58 + .layout-demo-con{
  59 + height: 100%;
  60 + }
  61 +</style>
0 62 \ No newline at end of file
... ...
src/components/content/index.js 0 → 100644
  1 +import Content from '../layout/content.vue';
  2 +
  3 +export default Content;
0 4 \ No newline at end of file
... ...
src/components/footer/index.js 0 → 100644
  1 +import Footer from '../layout/footer.vue';
  2 +
  3 +export default Footer;
0 4 \ No newline at end of file
... ...
src/components/header/index.js 0 → 100644
  1 +import Header from '../layout/header.vue';
  2 +
  3 +export default Header;
0 4 \ No newline at end of file
... ...
src/components/layout/content.vue 0 → 100644
  1 +<template>
  2 + <div :class="wrapClasses"><slot></slot></div>
  3 +</template>
  4 +<script>
  5 + const prefixCls = 'ivu-layout';
  6 + export default {
  7 + name: 'Content',
  8 + props: {
  9 + className: {
  10 + type: String,
  11 + default: ''
  12 + }
  13 + },
  14 + data () {
  15 + return {
  16 + prefixCls: prefixCls
  17 + };
  18 + },
  19 + computed: {
  20 + wrapClasses () {
  21 + return [
  22 + `${prefixCls}-content`,
  23 + this.className
  24 + ];
  25 + }
  26 + }
  27 + };
  28 +</script>
0 29 \ No newline at end of file
... ...
src/components/layout/footer.vue 0 → 100644
  1 +<template>
  2 + <div :class="wrapClasses"><slot></slot></div>
  3 +</template>
  4 +<script>
  5 + const prefixCls = 'ivu-layout';
  6 + export default {
  7 + name: 'Footer',
  8 + props: {
  9 + className: {
  10 + type: String,
  11 + default: ''
  12 + }
  13 + },
  14 + data () {
  15 + return {
  16 + prefixCls: prefixCls
  17 + };
  18 + },
  19 + computed: {
  20 + wrapClasses () {
  21 + return [
  22 + `${prefixCls}-footer`,
  23 + this.className
  24 + ];
  25 + }
  26 + }
  27 + };
  28 +</script>
0 29 \ No newline at end of file
... ...
src/components/layout/header.vue 0 → 100644
  1 +<template>
  2 + <div :class="wrapClasses"><slot></slot></div>
  3 +</template>
  4 +<script>
  5 + const prefixCls = 'ivu-layout';
  6 + export default {
  7 + name: 'Header',
  8 + props: {
  9 + className: {
  10 + type: String,
  11 + default: ''
  12 + }
  13 + },
  14 + data () {
  15 + return {
  16 + prefixCls: prefixCls
  17 + };
  18 + },
  19 + computed: {
  20 + wrapClasses () {
  21 + return [
  22 + `${prefixCls}-header`,
  23 + this.className
  24 + ];
  25 + }
  26 + }
  27 + };
  28 +</script>
0 29 \ No newline at end of file
... ...
src/components/layout/index.js 0 → 100644
  1 +import Layout from './layout.vue';
  2 +import Header from './header.vue';
  3 +import Sider from './sider.vue';
  4 +import Content from './content.vue';
  5 +import Footer from './footer.vue';
  6 +
  7 +Layout.Header = Header;
  8 +Layout.Sider = Sider;
  9 +Layout.Content = Content;
  10 +Layout.Footer = Footer;
  11 +
  12 +export default Layout;
0 13 \ No newline at end of file
... ...
src/components/layout/layout.vue 0 → 100644
  1 +<template>
  2 + <div :class="wrapClasses"><slot></slot></div>
  3 + </template>
  4 + <script>
  5 + const prefixCls = 'ivu-layout';
  6 +
  7 + export default {
  8 + name: 'Layout',
  9 + props: {
  10 + className: {
  11 + type: String,
  12 + default: ''
  13 + }
  14 + },
  15 + data () {
  16 + return {
  17 + prefixCls: prefixCls,
  18 + hasSider: false
  19 + };
  20 + },
  21 + computed: {
  22 + wrapClasses () {
  23 + return [
  24 + `${prefixCls}`,
  25 + this.className,
  26 + {
  27 + [`${prefixCls}-has-sider`]: this.hasSider
  28 + }
  29 + ];
  30 + }
  31 + },
  32 + methods: {
  33 + findSider () {
  34 + return this.$children.some(child => {
  35 + return child.$options._componentTag === 'Sider';
  36 + });
  37 + }
  38 + },
  39 + mounted () {
  40 + this.hasSider = this.findSider();
  41 + }
  42 + };
  43 + </script>
0 44 \ No newline at end of file
... ...
src/components/layout/sider.vue 0 → 100644
  1 +<template>
  2 + <div
  3 + :class="wrapClasses"
  4 + :style="{width: siderWidth + 'px', minWidth: siderWidth + 'px', maxWidth: siderWidth + 'px', flex: '0 0 ' + siderWidth + 'px'}">
  5 + <span v-show="showZeroTrigger" @click="toggleCollapse" :class="zeroWidthTriggerClasses">
  6 + <i class="ivu-icon ivu-icon-navicon-round"></i>
  7 + </span>
  8 + <div :class="`${prefixCls}-children`">
  9 + <slot></slot>
  10 + </div>
  11 + <div v-show="!mediaMatched && !hideTrigger" :class="triggerClasses" @click="toggleCollapse" :style="{width: siderWidth + 'px'}">
  12 + <i :class="triggerIconClasses"></i>
  13 + </div>
  14 + </div>
  15 +</template>
  16 +<script>
  17 + import { on, off } from '../../utils/dom';
  18 + import { oneOf } from '../../utils/assist';
  19 + const prefixCls = 'ivu-layout-sider';
  20 + const dimensionMap = {
  21 + xs: '480px',
  22 + sm: '768px',
  23 + md: '992px',
  24 + lg: '1200px',
  25 + xl: '1600px',
  26 + };
  27 + if (typeof window !== 'undefined') {
  28 + const matchMediaPolyfill = mediaQuery => {
  29 + return {
  30 + media: mediaQuery,
  31 + matches: false,
  32 + on() {
  33 + },
  34 + off() {
  35 + },
  36 + };
  37 + };
  38 + window.matchMedia = window.matchMedia || matchMediaPolyfill;
  39 + }
  40 + export default {
  41 + name: 'Sider',
  42 + props: {
  43 + value: { // if it's collpased now
  44 + type: Boolean,
  45 + default: false
  46 + },
  47 + className: {
  48 + type: String,
  49 + default: ''
  50 + },
  51 + width: {
  52 + type: [Number, String],
  53 + default: 200
  54 + },
  55 + collapsedWidth: {
  56 + type: [Number, String],
  57 + default: 64
  58 + },
  59 + hideTrigger: {
  60 + type: Boolean,
  61 + default: false
  62 + },
  63 + breakpoint: {
  64 + type: String,
  65 + default: '',
  66 + validator (val) {
  67 + return oneOf(val, ['xs', 'sm', 'md', 'lg', 'xl']);
  68 + }
  69 + },
  70 + defaultCollapsed: {
  71 + type: Boolean,
  72 + default: false
  73 + },
  74 + reverseArrow: {
  75 + type: Boolean,
  76 + default: false
  77 + }
  78 + },
  79 + data () {
  80 + return {
  81 + prefixCls: prefixCls,
  82 + mediaMatched: false
  83 + };
  84 + },
  85 + computed: {
  86 + wrapClasses () {
  87 + return [
  88 + `${prefixCls}`,
  89 + this.className,
  90 + this.siderWidth ? '' : `${prefixCls}-zero-width`,
  91 + this.value ? `${prefixCls}-collapsed` : ''
  92 + ];
  93 + },
  94 + triggerClasses () {
  95 + return [
  96 + `${prefixCls}-trigger`,
  97 + this.value ? `${prefixCls}-trigger-collapsed` : '',
  98 + ];
  99 + },
  100 + zeroWidthTriggerClasses () {
  101 + return [
  102 + `${prefixCls}-zero-width-trigger`,
  103 + this.reverseArrow ? `${prefixCls}-zero-width-trigger-left` : ''
  104 + ];
  105 + },
  106 + triggerIconClasses () {
  107 + return [
  108 + 'ivu-icon',
  109 + `ivu-icon-chevron-${this.reverseArrow ? 'right' : 'left'}`,
  110 + `${prefixCls}-trigger-icon`,
  111 + ];
  112 + },
  113 + siderWidth () {
  114 + return this.value ? (this.mediaMatched ? 0 : parseInt(this.collapsedWidth)) : parseInt(this.width);
  115 + },
  116 + showZeroTrigger () {
  117 + return this.mediaMatched && !this.hideTrigger || (parseInt(this.collapsedWidth) === 0) && this.value && !this.hideTrigger;
  118 + }
  119 + },
  120 + methods: {
  121 + toggleCollapse () {
  122 + this.$emit('input', !this.value);
  123 + this.$emit('on-collapse', !this.value);
  124 + },
  125 + matchMedia () {
  126 + let matchMedia;
  127 + if (window.matchMedia) {
  128 + matchMedia = window.matchMedia;
  129 + }
  130 + let mediaMatched = this.mediaMatched;
  131 + this.mediaMatched = matchMedia(`(max-width: ${dimensionMap[this.breakpoint]})`).matches;
  132 + if (this.mediaMatched !== mediaMatched) {
  133 + this.$emit('input', this.mediaMatched);
  134 + this.$emit('on-collapse', this.mediaMatched);
  135 + }
  136 + },
  137 + onWindowResize () {
  138 + this.matchMedia();
  139 + }
  140 + },
  141 + mounted () {
  142 + on(window, 'resize', this.onWindowResize);
  143 + this.matchMedia();
  144 + this.$emit('input', this.defaultCollapsed);
  145 + },
  146 + destroyed () {
  147 + off(window, 'resize', this.onWindowResize);
  148 + }
  149 + };
  150 +</script>
0 151 \ No newline at end of file
... ...
src/components/sider/index.js 0 → 100644
  1 +import Sider from '../layout/sider.vue';
  2 +
  3 +export default Sider;
0 4 \ No newline at end of file
... ...
src/index.js
... ... @@ -17,13 +17,17 @@ import Checkbox from &#39;./components/checkbox&#39;;
17 17 import Circle from './components/circle';
18 18 import Collapse from './components/collapse';
19 19 import ColorPicker from './components/color-picker';
  20 +import Content from './components/content';
20 21 import DatePicker from './components/date-picker';
21 22 import Dropdown from './components/dropdown';
  23 +import Footer from './components/footer';
22 24 import Form from './components/form';
  25 +import Header from './components/header';
23 26 import Icon from './components/icon';
24 27 import Input from './components/input';
25 28 import InputNumber from './components/input-number';
26 29 import Scroll from './components/scroll';
  30 +import Layout from './components/layout';
27 31 import LoadingBar from './components/loading-bar';
28 32 import Menu from './components/menu';
29 33 import Message from './components/message';
... ... @@ -34,6 +38,7 @@ import Poptip from &#39;./components/poptip&#39;;
34 38 import Progress from './components/progress';
35 39 import Radio from './components/radio';
36 40 import Rate from './components/rate';
  41 +import Sider from './components/sider';
37 42 import Slider from './components/slider';
38 43 import Spin from './components/spin';
39 44 import Steps from './components/steps';
... ... @@ -71,21 +76,26 @@ const components = {
71 76 Col,
72 77 Collapse,
73 78 ColorPicker,
  79 + Content: Content,
74 80 DatePicker,
75 81 Dropdown,
76 82 DropdownItem: Dropdown.Item,
77 83 DropdownMenu: Dropdown.Menu,
  84 + Footer: Footer,
78 85 Form,
79 86 FormItem: Form.Item,
  87 + Header: Header,
80 88 Icon,
81 89 Input,
82 90 InputNumber,
83 91 Scroll,
  92 + Sider: Sider,
  93 + Submenu: Menu.Sub,
  94 + Layout: Layout,
84 95 LoadingBar,
85 96 Menu,
86 97 MenuGroup: Menu.Group,
87 98 MenuItem: Menu.Item,
88   - Submenu: Menu.Sub,
89 99 Message,
90 100 Modal,
91 101 Notice,
... ... @@ -122,7 +132,10 @@ const iview = {
122 132 iButton: Button,
123 133 iCircle: Circle,
124 134 iCol: Col,
  135 + iContent: Content,
125 136 iForm: Form,
  137 + iFooter: Footer,
  138 + iHeader: Header,
126 139 iInput: Input,
127 140 iMenu: Menu,
128 141 iOption: Option,
... ...
src/styles/components/index.less
... ... @@ -15,6 +15,7 @@
15 15 @import "input-number";
16 16 @import "scroll";
17 17 @import "tag";
  18 +@import "layout";
18 19 @import "loading-bar";
19 20 @import "progress";
20 21 @import "timeline";
... ...
src/styles/components/layout.less 0 → 100644
  1 +@layout-prefix-cls: ~"@{css-prefix}layout";
  2 +
  3 +.@{layout-prefix-cls} {
  4 + display: flex;
  5 + flex-direction: column;
  6 + flex: auto;
  7 + background: @layout-body-background;
  8 +
  9 + &&-has-sider {
  10 + flex-direction: row;
  11 + > .@{layout-prefix-cls},
  12 + > .@{layout-prefix-cls}-content {
  13 + overflow-x: hidden;
  14 + }
  15 + }
  16 +
  17 + &-header,
  18 + &-footer {
  19 + flex: 0 0 auto;
  20 + }
  21 +
  22 + &-header {
  23 + background: @layout-header-background;
  24 + padding: @layout-header-padding;
  25 + height: @layout-header-height;
  26 + line-height: @layout-header-height;
  27 + }
  28 +
  29 + &-sider {
  30 + transition: all .2s @ease-in-out;
  31 + position: relative;
  32 + background: @layout-sider-background;
  33 +
  34 + min-width: 0;
  35 +
  36 + &-children {
  37 + height: 100%;
  38 + padding-top: 0.1px;
  39 + margin-top: -0.1px;
  40 + }
  41 +
  42 + &-has-trigger {
  43 + padding-bottom: @layout-trigger-height;
  44 + }
  45 +
  46 + &-trigger {
  47 + position: fixed;
  48 + bottom: 0;
  49 + text-align: center;
  50 + cursor: pointer;
  51 + height: @layout-trigger-height;
  52 + line-height: @layout-trigger-height;
  53 + color: @layout-trigger-color;
  54 + background: @layout-sider-background;
  55 + z-index: 1000;
  56 + transition: all .2s @ease-in-out;
  57 + .ivu-icon {
  58 + font-size: 16px;
  59 + }
  60 + >* {
  61 + transition: all .2s;
  62 + }
  63 + &-collapsed {
  64 + .@{layout-prefix-cls}-sider-trigger-icon {
  65 + transform: rotateZ(180deg);
  66 + }
  67 + }
  68 + }
  69 +
  70 + &-zero-width {
  71 + & > * {
  72 + overflow: hidden;
  73 + }
  74 +
  75 + &-trigger {
  76 + position: absolute;
  77 + top: @layout-header-height;
  78 + right: -@layout-zero-trigger-width;
  79 + text-align: center;
  80 + width: @layout-zero-trigger-width;
  81 + height: @layout-zero-trigger-height;
  82 + line-height: @layout-zero-trigger-height;
  83 + background: @layout-sider-background;
  84 + color: #fff;
  85 + font-size: @layout-zero-trigger-width / 2;
  86 + border-radius: 0 @border-radius-base @border-radius-base 0;
  87 + cursor: pointer;
  88 + transition: background .3s ease;
  89 +
  90 + &:hover {
  91 + background: tint(@layout-sider-background, 10%);
  92 + }
  93 +
  94 + &&-left {
  95 + right: 0;
  96 + left: -@layout-zero-trigger-width;
  97 + border-radius: @border-radius-base 0 0 @border-radius-base;
  98 + }
  99 + }
  100 + }
  101 + }
  102 +
  103 + &-footer {
  104 + background: @layout-footer-background;
  105 + padding: @layout-footer-padding;
  106 + color: @text-color;
  107 + font-size: @font-size-base;
  108 + }
  109 +
  110 + &-content {
  111 + flex: auto;
  112 + }
  113 +}
0 114 \ No newline at end of file
... ...
src/styles/custom.less
... ... @@ -89,8 +89,19 @@
89 89 @btn-circle-size-small : 24px;
90 90  
91 91 // Layout and Grid
92   -@grid-columns : 24;
93   -@grid-gutter-width : 0;
  92 +@grid-columns : 24;
  93 +@grid-gutter-width : 0;
  94 +@layout-body-background : #f5f7f9;
  95 +@layout-header-background : #495060;
  96 +@layout-header-height : 64px;
  97 +@layout-header-padding : 0 50px;
  98 +@layout-footer-padding : 24px 50px;
  99 +@layout-footer-background : @layout-body-background;
  100 +@layout-sider-background : @layout-header-background;
  101 +@layout-trigger-height : 48px;
  102 +@layout-trigger-color : #fff;
  103 +@layout-zero-trigger-width : 36px;
  104 +@layout-zero-trigger-height : 42px;
94 105  
95 106 // Legend
96 107 @legend-color : #999;
... ...