Commit 8979c734695c2e4e9063e622f8a0bb3accc97a5b
1 parent
273cc057
add split components
Showing
9 changed files
with
392 additions
and
0 deletions
Show diff stats
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 | }, | ... | ... |
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> | ... | ... |
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> | ... | ... |
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 './components/icon'; |
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
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 | +} | ... | ... |