Commit 8979c734695c2e4e9063e622f8a0bb3accc97a5b

Authored by zhigang.li
1 parent 273cc057

add split components

examples/app.vue
... ... @@ -16,6 +16,7 @@ nav {
16 16 <div class="container">
17 17 <nav>
18 18 <ul>
  19 + <li><router-link to="/split">Split</router-link></li>
19 20 <li><router-link to="/layout">Layout</router-link></li>
20 21 <li><router-link to="/affix">Affix</router-link></li>
21 22 <li><router-link to="/grid">Grid</router-link></li>
... ...
examples/main.js
... ... @@ -20,6 +20,10 @@ const router = new VueRouter({
20 20 esModule: false,
21 21 routes: [
22 22 {
  23 + path: '/split',
  24 + component: (resolve) => require(['./routers/split.vue'], resolve)
  25 + },
  26 + {
23 27 path: '/layout',
24 28 component: (resolve) => require(['./routers/layout.vue'], resolve)
25 29 },
... ...
examples/routers/split.vue 0 → 100644
  1 +<template>
  2 + <div class="split-pane-page-wrapper">
  3 + <Split v-model="offset" @on-moving="handleMoving">
  4 + <div slot="left" class="pane left-pane">
  5 + <Split v-model="offsetVertical" mode="vertical" @on-moving="handleMoving">
  6 + <div slot="top" class="pane top-pane"></div>
  7 + <div slot="bottom" class="pane bottom-pane"></div>
  8 + <div slot="trigger" class="custom-trigger">
  9 + <Icon class="trigger-icon" :size="22" type="android-more-vertical" color="#000000"/>
  10 + </div>
  11 + </Split>
  12 + </div>
  13 + <div slot="right" class="pane right-pane"></div>
  14 + </Split>
  15 + </div>
  16 +</template>
  17 +
  18 +<script>
  19 +export default {
  20 + name: 'split_pane_page',
  21 + data () {
  22 + return {
  23 + offset: 0.6,
  24 + offsetVertical: '250px'
  25 + }
  26 + },
  27 + methods: {
  28 + handleMoving (e) {
  29 + console.log(e.atMin, e.atMax)
  30 + }
  31 + }
  32 +}
  33 +</script>
  34 +
  35 +<style lang="less">
  36 +.center-middle{
  37 + position: absolute;
  38 + left: 50%;
  39 + top: 50%;
  40 + transform: translate(-50%, -50%);
  41 +}
  42 +.split-pane-page-wrapper{
  43 + height: 600px;
  44 + .pane{
  45 + width: 100%;
  46 + height: 100%;
  47 + &.left-pane{
  48 + background: sandybrown;
  49 + }
  50 + &.right-pane{
  51 + background: palevioletred;
  52 + }
  53 + &.top-pane{
  54 + background: sandybrown;
  55 + }
  56 + &.bottom-pane{
  57 + background: palevioletred;
  58 + }
  59 + }
  60 + .custom-trigger{
  61 + width: 20px;
  62 + height: 20px;
  63 + border-radius: 50%;
  64 + background: #fff;
  65 + position: absolute;
  66 + .center-middle;
  67 + box-shadow: 0 0 6px 0 rgba(28, 36, 56, 0.4);
  68 + cursor: row-resize;
  69 + i.trigger-icon{
  70 + .center-middle;
  71 + }
  72 + }
  73 +}
  74 +</style>
... ...
src/components/split/index.js 0 → 100644
  1 +import Split from './split.vue'
  2 +export default Split
... ...
src/components/split/split.vue 0 → 100644
  1 +<template>
  2 + <div ref="outerWrapper" :class="wrapperClasses">
  3 + <div v-if="isHorizontal" :class="`${prefix}-horizontal`">
  4 + <div :style="{right: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'left-pane']"><slot name="left"/></div>
  5 + <div :class="`${prefix}-trigger-con`" :style="{left: `${offset}%`}" @mousedown="handleMousedown">
  6 + <slot name="trigger">
  7 + <trigger mode="vertical"/>
  8 + </slot>
  9 + </div>
  10 + <div :style="{left: `${offset}%`}" :class="[`${prefix}-pane`, 'right-pane']"><slot name="right"/></div>
  11 + </div>
  12 + <div v-else :class="`${prefix}-vertical`">
  13 + <div :style="{bottom: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'top-pane']"><slot name="top"/></div>
  14 + <div :class="`${prefix}-trigger-con`" :style="{top: `${offset}%`}" @mousedown="handleMousedown">
  15 + <slot name="trigger">
  16 + <trigger mode="horizontal"/>
  17 + </slot>
  18 + </div>
  19 + <div :style="{top: `${offset}%`}" :class="[`${prefix}-pane`, 'bottom-pane']"><slot name="bottom"/></div>
  20 + </div>
  21 + </div>
  22 +</template>
  23 +
  24 +<script>
  25 +import { oneOf } from '../../utils/assist';
  26 +import { on, off } from '../../utils/dom';
  27 +import Trigger from './trigger.vue'
  28 +export default {
  29 + name: 'SplitPane',
  30 + components: {
  31 + Trigger
  32 + },
  33 + props: {
  34 + value: {
  35 + type: [Number, String],
  36 + default: 0.5
  37 + },
  38 + mode: {
  39 + validator (value) {
  40 + return oneOf(value, ['horizontal', 'vertical'])
  41 + },
  42 + default: 'horizontal'
  43 + },
  44 + min: {
  45 + type: [Number, String],
  46 + default: '40px'
  47 + },
  48 + max: {
  49 + type: [Number, String],
  50 + default: '40px'
  51 + }
  52 + },
  53 + /**
  54 + * Events
  55 + * @on-move-start
  56 + * @on-moving 返回值:事件对象,但是在事件对象中加入了两个参数:atMin(当前是否在最小值处), atMax(当前是否在最大值处)
  57 + * @on-move-end
  58 + */
  59 + data () {
  60 + return {
  61 + prefix: 'ivu-split',
  62 + offset: 0,
  63 + oldOffset: 0,
  64 + isMoving: false
  65 + }
  66 + },
  67 + computed: {
  68 + wrapperClasses () {
  69 + return [
  70 + `${this.prefix}-wrapper`,
  71 + this.isMoving ? 'no-select' : ''
  72 + ]
  73 + },
  74 + isHorizontal () {
  75 + return this.mode === 'horizontal'
  76 + },
  77 + anotherOffset () {
  78 + return 100 - this.offset
  79 + },
  80 + valueIsPx () {
  81 + return typeof this.value === 'string'
  82 + },
  83 + offsetSize () {
  84 + return this.isHorizontal ? 'offsetWidth' : 'offsetHeight'
  85 + },
  86 + computedMin () {
  87 + return this.getComputedThresholdValue('min')
  88 + },
  89 + computedMax () {
  90 + return this.getComputedThresholdValue('max')
  91 + }
  92 + },
  93 + methods: {
  94 + px2percent (numerator, denominator) {
  95 + return parseFloat(numerator) / parseFloat(denominator)
  96 + },
  97 + getComputedThresholdValue (type) {
  98 + let size = this.$refs.outerWrapper[this.offsetSize]
  99 + if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type]
  100 + else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type]
  101 + },
  102 + getMin (value1, value2) {
  103 + if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`
  104 + else return Math.min(value1, value2)
  105 + },
  106 + getMax (value1, value2) {
  107 + if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`
  108 + else return Math.max(value1, value2)
  109 + },
  110 + getAnotherOffset (value) {
  111 + let res = 0
  112 + if (this.valueIsPx) res = `${this.$refs.outerWrapper[this.offsetSize] - parseFloat(value)}px`
  113 + else res = 1 - value
  114 + return res
  115 + },
  116 + handleMove (e) {
  117 + let pageOffset = this.isHorizontal ? e.pageX : e.pageY
  118 + let offset = pageOffset - this.initOffset
  119 + let outerWidth = this.$refs.outerWrapper[this.offsetSize]
  120 + let value = this.valueIsPx ? `${parseFloat(this.oldOffset) + offset}px` : (this.px2percent(outerWidth * this.oldOffset + offset, outerWidth))
  121 + let anotherValue = this.getAnotherOffset(value)
  122 + if (parseFloat(value) <= parseFloat(this.computedMin)) value = this.getMax(value, this.computedMin)
  123 + if (parseFloat(anotherValue) <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax))
  124 + e.atMin = this.value === this.computedMin
  125 + e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : this.getAnotherOffset(this.value).toFixed(5) === this.computedMax.toFixed(5)
  126 + this.$emit('input', value)
  127 + this.$emit('on-moving', e)
  128 + },
  129 + handleUp () {
  130 + this.isMoving = false
  131 + off(document, 'mousemove', this.handleMove)
  132 + off(document, 'mouseup', this.handleUp)
  133 + this.$emit('on-move-end')
  134 + },
  135 + handleMousedown (e) {
  136 + this.initOffset = this.isHorizontal ? e.pageX : e.pageY
  137 + this.oldOffset = this.value
  138 + this.isMoving = true
  139 + on(document, 'mousemove', this.handleMove)
  140 + on(document, 'mouseup', this.handleUp)
  141 + this.$emit('on-move-start')
  142 + }
  143 + },
  144 + watch: {
  145 + value () {
  146 + this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
  147 + }
  148 + },
  149 + mounted () {
  150 + this.$nextTick(() => {
  151 + this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
  152 + })
  153 + }
  154 +}
  155 +</script>
... ...
src/components/split/trigger.vue 0 → 100644
  1 +<template>
  2 + <div :class="classes">
  3 + <div :class="barConClasses">
  4 + <i :class="`${prefix}-bar`" v-once v-for="i in 8" :key="`trigger-${i}`"></i>
  5 + </div>
  6 + </div>
  7 +</template>
  8 +
  9 +<script>
  10 +export default {
  11 + name: 'Trigger',
  12 + props: {
  13 + mode: String
  14 + },
  15 + data () {
  16 + return {
  17 + prefix: 'ivu-split-trigger',
  18 + initOffset: 0
  19 + }
  20 + },
  21 + computed: {
  22 + isVertical () {
  23 + return this.mode === 'vertical'
  24 + },
  25 + classes () {
  26 + return [
  27 + this.prefix,
  28 + this.isVertical ? `${this.prefix}-vertical` : `${this.prefix}-horizontal`
  29 + ]
  30 + },
  31 + barConClasses () {
  32 + return [
  33 + `${this.prefix}-bar-con`,
  34 + this.isVertical ? 'vertical' : 'horizontal'
  35 + ]
  36 + }
  37 + }
  38 +}
  39 +</script>
... ...
src/index.js
... ... @@ -23,6 +23,7 @@ import Icon from &#39;./components/icon&#39;;
23 23 import Input from './components/input';
24 24 import InputNumber from './components/input-number';
25 25 import Scroll from './components/scroll';
  26 +import Split from './components/split';
26 27 import Layout from './components/layout';
27 28 import LoadingBar from './components/loading-bar';
28 29 import Menu from './components/menu';
... ... @@ -86,6 +87,7 @@ const components = {
86 87 InputNumber,
87 88 Scroll,
88 89 Sider: Sider,
  90 + Split,
89 91 Submenu: Menu.Sub,
90 92 Layout: Layout,
91 93 LoadingBar,
... ...
src/styles/components/index.less
... ... @@ -24,6 +24,7 @@
24 24 @import "modal";
25 25 @import "select";
26 26 @import "select-dropdown";
  27 +@import "split";
27 28 @import "tooltip";
28 29 @import "poptip";
29 30 @import "input";
... ...
src/styles/components/split.less 0 → 100644
  1 +@split-prefix-cls: ~"@{css-prefix}split";
  2 +@box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.4);
  3 +@trigger-bar-background: rgba(23, 35, 61, 0.25);
  4 +@trigger-background: #F8F8F9;
  5 +@trigger-width: 6px;
  6 +@trigger-bar-width: 4px;
  7 +@trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2;
  8 +@trigger-bar-interval: 3px;
  9 +@trigger-bar-weight: 1px;
  10 +@trigger-bar-con-height: (@trigger-bar-weight + @trigger-bar-interval) * 8;
  11 +
  12 +.@{split-prefix-cls}{
  13 + &-wrapper{
  14 + position: relative;
  15 + width: 100%;
  16 + height: 100%;
  17 + }
  18 + &-pane{
  19 + position: absolute;
  20 + &.left-pane, &.right-pane{
  21 + top: 0px;
  22 + bottom: 0px;
  23 + }
  24 + &.left-pane{
  25 + left: 0px;
  26 + }
  27 + &.right-pane{
  28 + right: 0px;
  29 + }
  30 + &.top-pane, &.bottom-pane{
  31 + left: 0px;
  32 + right: 0px;
  33 + }
  34 + &.top-pane{
  35 + top: 0px;
  36 + }
  37 + &.bottom-pane{
  38 + bottom: 0px;
  39 + }
  40 + }
  41 + &-trigger{
  42 + &-con{
  43 + position: absolute;
  44 + transform: translate(-50%, -50%);
  45 + z-index: 10;
  46 + }
  47 + &-bar-con{
  48 + position: absolute;
  49 + overflow: hidden;
  50 + &.vertical{
  51 + left: @trigger-bar-offset;
  52 + top: 50%;
  53 + height: @trigger-bar-con-height;
  54 + transform: translate(0, -50%);
  55 + }
  56 + &.horizontal{
  57 + left: 50%;
  58 + top: @trigger-bar-offset;
  59 + width: @trigger-bar-con-height;
  60 + transform: translate(-50%, 0);
  61 + }
  62 + }
  63 + &-vertical{
  64 + width: @trigger-width;
  65 + height: 100%;
  66 + background: @trigger-background;
  67 + box-shadow: @box-shadow;
  68 + cursor: col-resize;
  69 + .@{split-prefix-cls}-trigger-bar{
  70 + width: @trigger-bar-width;
  71 + height: 1px;
  72 + background: @trigger-bar-background;
  73 + float: left;
  74 + margin-top: @trigger-bar-interval;
  75 + }
  76 + }
  77 + &-horizontal{
  78 + height: @trigger-width;
  79 + width: 100%;
  80 + background: @trigger-background;
  81 + box-shadow: @box-shadow;
  82 + cursor: row-resize;
  83 + .@{split-prefix-cls}-trigger-bar{
  84 + height: @trigger-bar-width;
  85 + width: 1px;
  86 + background: @trigger-bar-background;
  87 + float: left;
  88 + margin-right: @trigger-bar-interval;
  89 + }
  90 + }
  91 + }
  92 + &-horizontal{
  93 + .@{split-prefix-cls}-trigger-con{
  94 + top: 50%;
  95 + height: 100%;
  96 + width: 0;
  97 + }
  98 + }
  99 + &-vertical{
  100 + .@{split-prefix-cls}-trigger-con{
  101 + left: 50%;
  102 + height: 0;
  103 + width: 100%;
  104 + }
  105 + }
  106 + .no-select{
  107 + -webkit-touch-callout: none;
  108 + -webkit-user-select: none;
  109 + -khtml-user-select: none;
  110 + -moz-user-select: none;
  111 + -ms-user-select: none;
  112 + user-select: none;
  113 + }
  114 +}
... ...