Commit dfe23efe3d262c0e57943525db9e17171aab49d1

Authored by Aresn
Committed by GitHub
2 parents e6508e27 76db49cd

Merge pull request #2646 from lison16/layout

 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 + collapsible
  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 + computed: {
  9 + wrapClasses () {
  10 + return `${prefixCls}-content`;
  11 + }
  12 + }
  13 + };
  14 +</script>
0 15 \ 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 + computed: {
  9 + wrapClasses () {
  10 + return `${prefixCls}-footer`;
  11 + }
  12 + }
  13 + };
  14 +</script>
0 15 \ 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 + computed: {
  9 + wrapClasses () {
  10 + return `${prefixCls}-header`;
  11 + }
  12 + }
  13 + };
  14 +</script>
0 15 \ 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 + data () {
  10 + return {
  11 + hasSider: false
  12 + };
  13 + },
  14 + computed: {
  15 + wrapClasses () {
  16 + return [
  17 + `${prefixCls}`,
  18 + {
  19 + [`${prefixCls}-has-sider`]: this.hasSider
  20 + }
  21 + ];
  22 + }
  23 + },
  24 + methods: {
  25 + findSider () {
  26 + return this.$children.some(child => {
  27 + return child.$options.name === 'Sider';
  28 + });
  29 + }
  30 + },
  31 + mounted () {
  32 + this.hasSider = this.findSider();
  33 + }
  34 + };
  35 +</script>
0 36 \ No newline at end of file
... ...
src/components/layout/sider.vue 0 → 100644
  1 +<template>
  2 + <div
  3 + :class="wrapClasses"
  4 + :style="wrapStyles">
  5 + <span v-show="showZeroTrigger" @click="toggleCollapse" :class="zeroWidthTriggerClasses">
  6 + <i class="ivu-icon ivu-icon-navicon-round"></i>
  7 + </span>
  8 + <div :class="childClasses">
  9 + <slot></slot>
  10 + </div>
  11 + <div v-show="showBottomTrigger" :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, dimensionMap, setMatchMedia } from '../../utils/assist';
  19 + const prefixCls = 'ivu-layout-sider';
  20 + setMatchMedia();
  21 + export default {
  22 + name: 'Sider',
  23 + props: {
  24 + value: { // if it's collpased now
  25 + type: Boolean,
  26 + default: false
  27 + },
  28 + width: {
  29 + type: [Number, String],
  30 + default: 200
  31 + },
  32 + collapsedWidth: {
  33 + type: [Number, String],
  34 + default: 64
  35 + },
  36 + hideTrigger: {
  37 + type: Boolean,
  38 + default: false
  39 + },
  40 + breakpoint: {
  41 + type: String,
  42 + default: 'md',
  43 + validator (val) {
  44 + return oneOf(val, ['xs', 'sm', 'md', 'lg', 'xl']);
  45 + }
  46 + },
  47 + collapsible: {
  48 + type: Boolean,
  49 + default: false
  50 + },
  51 + defaultCollapsed: {
  52 + type: Boolean,
  53 + default: false
  54 + },
  55 + reverseArrow: {
  56 + type: Boolean,
  57 + default: false
  58 + }
  59 + },
  60 + data () {
  61 + return {
  62 + prefixCls: prefixCls,
  63 + mediaMatched: false,
  64 + isCollapsed: false
  65 + };
  66 + },
  67 + computed: {
  68 + wrapClasses () {
  69 + return [
  70 + `${prefixCls}`,
  71 + this.siderWidth ? '' : `${prefixCls}-zero-width`,
  72 + this.isCollapsed ? `${prefixCls}-collapsed` : ''
  73 + ];
  74 + },
  75 + wrapStyles () {
  76 + return {
  77 + width: `${this.siderWidth}px`,
  78 + minWidth: `${this.siderWidth}px`,
  79 + maxWidth: `${this.siderWidth}px`,
  80 + flex: `0 0 ${this.siderWidth}px`
  81 + };
  82 + },
  83 + triggerClasses () {
  84 + return [
  85 + `${prefixCls}-trigger`,
  86 + this.isCollapsed ? `${prefixCls}-trigger-collapsed` : '',
  87 + ];
  88 + },
  89 + childClasses () {
  90 + return `${this.prefixCls}-children`;
  91 + },
  92 + zeroWidthTriggerClasses () {
  93 + return [
  94 + `${prefixCls}-zero-width-trigger`,
  95 + this.reverseArrow ? `${prefixCls}-zero-width-trigger-left` : ''
  96 + ];
  97 + },
  98 + triggerIconClasses () {
  99 + return [
  100 + 'ivu-icon',
  101 + `ivu-icon-chevron-${this.reverseArrow ? 'right' : 'left'}`,
  102 + `${prefixCls}-trigger-icon`,
  103 + ];
  104 + },
  105 + siderWidth () {
  106 + return this.collapsible ? (this.isCollapsed ? (this.mediaMatched ? 0 : parseInt(this.collapsedWidth)) : parseInt(this.width)) : this.width;
  107 + },
  108 + showZeroTrigger () {
  109 + return this.collapsible ? (this.mediaMatched && !this.hideTrigger || (parseInt(this.collapsedWidth) === 0) && this.isCollapsed && !this.hideTrigger) : false;
  110 + },
  111 + showBottomTrigger () {
  112 + return this.collapsible ? !this.mediaMatched && !this.hideTrigger : false;
  113 + }
  114 + },
  115 + methods: {
  116 + toggleCollapse () {
  117 + this.isCollapsed = this.collapsible ? !this.isCollapsed : false;
  118 + this.$emit('input', !this.isCollapsed);
  119 + this.$emit('on-collapse', !this.isCollapsed);
  120 + },
  121 + matchMedia () {
  122 + let matchMedia;
  123 + if (window.matchMedia) {
  124 + matchMedia = window.matchMedia;
  125 + }
  126 + let mediaMatched = this.mediaMatched;
  127 + this.mediaMatched = matchMedia(`(max-width: ${dimensionMap[this.breakpoint]})`).matches;
  128 +
  129 + if (this.mediaMatched !== mediaMatched) {
  130 + this.isCollapsed = this.collapsible ? this.mediaMatched : false;
  131 + this.$emit('input', this.mediaMatched);
  132 + this.$emit('on-collapse', this.mediaMatched);
  133 + }
  134 + },
  135 + onWindowResize () {
  136 + this.matchMedia();
  137 + }
  138 + },
  139 + mounted () {
  140 + on(window, 'resize', this.onWindowResize);
  141 + this.matchMedia();
  142 + this.$emit('input', this.defaultCollapsed);
  143 + if (this.defaultCollapsed) {
  144 + this.isCollapsed = true;
  145 + }
  146 + },
  147 + destroyed () {
  148 + off(window, 'resize', this.onWindowResize);
  149 + }
  150 + };
  151 +</script>
0 152 \ 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;
... ...
src/utils/assist.js
... ... @@ -278,3 +278,25 @@ export function removeClass(el, cls) {
278 278 el.className = trim(curClass);
279 279 }
280 280 }
  281 +
  282 +export const dimensionMap = {
  283 + xs: '480px',
  284 + sm: '768px',
  285 + md: '992px',
  286 + lg: '1200px',
  287 + xl: '1600px',
  288 +};
  289 +
  290 +export function setMatchMedia () {
  291 + if (typeof window !== 'undefined') {
  292 + const matchMediaPolyfill = mediaQuery => {
  293 + return {
  294 + media: mediaQuery,
  295 + matches: false,
  296 + on() {},
  297 + off() {},
  298 + };
  299 + };
  300 + window.matchMedia = window.matchMedia || matchMediaPolyfill;
  301 + }
  302 +}
281 303 \ No newline at end of file
... ...