commit
83cc2fd235
9 changed files with 392 additions and 0 deletions
|
@ -16,6 +16,7 @@ nav {
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><router-link to="/split">Split</router-link></li>
|
||||||
<li><router-link to="/layout">Layout</router-link></li>
|
<li><router-link to="/layout">Layout</router-link></li>
|
||||||
<li><router-link to="/affix">Affix</router-link></li>
|
<li><router-link to="/affix">Affix</router-link></li>
|
||||||
<li><router-link to="/anchor">Anchor</router-link></li>
|
<li><router-link to="/anchor">Anchor</router-link></li>
|
||||||
|
|
|
@ -19,6 +19,10 @@ Vue.config.debug = true;
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
esModule: false,
|
esModule: false,
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/split',
|
||||||
|
component: (resolve) => require(['./routers/split.vue'], resolve)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/layout',
|
path: '/layout',
|
||||||
component: (resolve) => require(['./routers/layout.vue'], resolve)
|
component: (resolve) => require(['./routers/layout.vue'], resolve)
|
||||||
|
|
74
examples/routers/split.vue
Normal file
74
examples/routers/split.vue
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<template>
|
||||||
|
<div class="split-pane-page-wrapper">
|
||||||
|
<Split v-model="offset" @on-moving="handleMoving">
|
||||||
|
<div slot="left" class="pane left-pane">
|
||||||
|
<Split v-model="offsetVertical" mode="vertical" @on-moving="handleMoving">
|
||||||
|
<div slot="top" class="pane top-pane"></div>
|
||||||
|
<div slot="bottom" class="pane bottom-pane"></div>
|
||||||
|
<div slot="trigger" class="custom-trigger">
|
||||||
|
<Icon class="trigger-icon" :size="22" type="android-more-vertical" color="#000000"/>
|
||||||
|
</div>
|
||||||
|
</Split>
|
||||||
|
</div>
|
||||||
|
<div slot="right" class="pane right-pane"></div>
|
||||||
|
</Split>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'split_pane_page',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
offset: 0.6,
|
||||||
|
offsetVertical: '250px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleMoving (e) {
|
||||||
|
console.log(e.atMin, e.atMax)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.center-middle{
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
.split-pane-page-wrapper{
|
||||||
|
height: 600px;
|
||||||
|
.pane{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
&.left-pane{
|
||||||
|
background: sandybrown;
|
||||||
|
}
|
||||||
|
&.right-pane{
|
||||||
|
background: palevioletred;
|
||||||
|
}
|
||||||
|
&.top-pane{
|
||||||
|
background: sandybrown;
|
||||||
|
}
|
||||||
|
&.bottom-pane{
|
||||||
|
background: palevioletred;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.custom-trigger{
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
position: absolute;
|
||||||
|
.center-middle;
|
||||||
|
box-shadow: 0 0 6px 0 rgba(28, 36, 56, 0.4);
|
||||||
|
cursor: row-resize;
|
||||||
|
i.trigger-icon{
|
||||||
|
.center-middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
2
src/components/split/index.js
Normal file
2
src/components/split/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Split from './split.vue'
|
||||||
|
export default Split
|
155
src/components/split/split.vue
Normal file
155
src/components/split/split.vue
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<template>
|
||||||
|
<div ref="outerWrapper" :class="wrapperClasses">
|
||||||
|
<div v-if="isHorizontal" :class="`${prefix}-horizontal`">
|
||||||
|
<div :style="{right: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'left-pane']"><slot name="left"/></div>
|
||||||
|
<div :class="`${prefix}-trigger-con`" :style="{left: `${offset}%`}" @mousedown="handleMousedown">
|
||||||
|
<slot name="trigger">
|
||||||
|
<trigger mode="vertical"/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div :style="{left: `${offset}%`}" :class="[`${prefix}-pane`, 'right-pane']"><slot name="right"/></div>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="`${prefix}-vertical`">
|
||||||
|
<div :style="{bottom: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'top-pane']"><slot name="top"/></div>
|
||||||
|
<div :class="`${prefix}-trigger-con`" :style="{top: `${offset}%`}" @mousedown="handleMousedown">
|
||||||
|
<slot name="trigger">
|
||||||
|
<trigger mode="horizontal"/>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
<div :style="{top: `${offset}%`}" :class="[`${prefix}-pane`, 'bottom-pane']"><slot name="bottom"/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { oneOf } from '../../utils/assist';
|
||||||
|
import { on, off } from '../../utils/dom';
|
||||||
|
import Trigger from './trigger.vue'
|
||||||
|
export default {
|
||||||
|
name: 'SplitPane',
|
||||||
|
components: {
|
||||||
|
Trigger
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0.5
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
validator (value) {
|
||||||
|
return oneOf(value, ['horizontal', 'vertical'])
|
||||||
|
},
|
||||||
|
default: 'horizontal'
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: '40px'
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: '40px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Events
|
||||||
|
* @on-move-start
|
||||||
|
* @on-moving 返回值:事件对象,但是在事件对象中加入了两个参数:atMin(当前是否在最小值处), atMax(当前是否在最大值处)
|
||||||
|
* @on-move-end
|
||||||
|
*/
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
prefix: 'ivu-split',
|
||||||
|
offset: 0,
|
||||||
|
oldOffset: 0,
|
||||||
|
isMoving: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
wrapperClasses () {
|
||||||
|
return [
|
||||||
|
`${this.prefix}-wrapper`,
|
||||||
|
this.isMoving ? 'no-select' : ''
|
||||||
|
]
|
||||||
|
},
|
||||||
|
isHorizontal () {
|
||||||
|
return this.mode === 'horizontal'
|
||||||
|
},
|
||||||
|
anotherOffset () {
|
||||||
|
return 100 - this.offset
|
||||||
|
},
|
||||||
|
valueIsPx () {
|
||||||
|
return typeof this.value === 'string'
|
||||||
|
},
|
||||||
|
offsetSize () {
|
||||||
|
return this.isHorizontal ? 'offsetWidth' : 'offsetHeight'
|
||||||
|
},
|
||||||
|
computedMin () {
|
||||||
|
return this.getComputedThresholdValue('min')
|
||||||
|
},
|
||||||
|
computedMax () {
|
||||||
|
return this.getComputedThresholdValue('max')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
px2percent (numerator, denominator) {
|
||||||
|
return parseFloat(numerator) / parseFloat(denominator)
|
||||||
|
},
|
||||||
|
getComputedThresholdValue (type) {
|
||||||
|
let size = this.$refs.outerWrapper[this.offsetSize]
|
||||||
|
if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type]
|
||||||
|
else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type]
|
||||||
|
},
|
||||||
|
getMin (value1, value2) {
|
||||||
|
if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`
|
||||||
|
else return Math.min(value1, value2)
|
||||||
|
},
|
||||||
|
getMax (value1, value2) {
|
||||||
|
if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`
|
||||||
|
else return Math.max(value1, value2)
|
||||||
|
},
|
||||||
|
getAnotherOffset (value) {
|
||||||
|
let res = 0
|
||||||
|
if (this.valueIsPx) res = `${this.$refs.outerWrapper[this.offsetSize] - parseFloat(value)}px`
|
||||||
|
else res = 1 - value
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
handleMove (e) {
|
||||||
|
let pageOffset = this.isHorizontal ? e.pageX : e.pageY
|
||||||
|
let offset = pageOffset - this.initOffset
|
||||||
|
let outerWidth = this.$refs.outerWrapper[this.offsetSize]
|
||||||
|
let value = this.valueIsPx ? `${parseFloat(this.oldOffset) + offset}px` : (this.px2percent(outerWidth * this.oldOffset + offset, outerWidth))
|
||||||
|
let anotherValue = this.getAnotherOffset(value)
|
||||||
|
if (parseFloat(value) <= parseFloat(this.computedMin)) value = this.getMax(value, this.computedMin)
|
||||||
|
if (parseFloat(anotherValue) <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax))
|
||||||
|
e.atMin = this.value === this.computedMin
|
||||||
|
e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : this.getAnotherOffset(this.value).toFixed(5) === this.computedMax.toFixed(5)
|
||||||
|
this.$emit('input', value)
|
||||||
|
this.$emit('on-moving', e)
|
||||||
|
},
|
||||||
|
handleUp () {
|
||||||
|
this.isMoving = false
|
||||||
|
off(document, 'mousemove', this.handleMove)
|
||||||
|
off(document, 'mouseup', this.handleUp)
|
||||||
|
this.$emit('on-move-end')
|
||||||
|
},
|
||||||
|
handleMousedown (e) {
|
||||||
|
this.initOffset = this.isHorizontal ? e.pageX : e.pageY
|
||||||
|
this.oldOffset = this.value
|
||||||
|
this.isMoving = true
|
||||||
|
on(document, 'mousemove', this.handleMove)
|
||||||
|
on(document, 'mouseup', this.handleUp)
|
||||||
|
this.$emit('on-move-start')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value () {
|
||||||
|
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
39
src/components/split/trigger.vue
Normal file
39
src/components/split/trigger.vue
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<template>
|
||||||
|
<div :class="classes">
|
||||||
|
<div :class="barConClasses">
|
||||||
|
<i :class="`${prefix}-bar`" v-once v-for="i in 8" :key="`trigger-${i}`"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'Trigger',
|
||||||
|
props: {
|
||||||
|
mode: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
prefix: 'ivu-split-trigger',
|
||||||
|
initOffset: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isVertical () {
|
||||||
|
return this.mode === 'vertical'
|
||||||
|
},
|
||||||
|
classes () {
|
||||||
|
return [
|
||||||
|
this.prefix,
|
||||||
|
this.isVertical ? `${this.prefix}-vertical` : `${this.prefix}-horizontal`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
barConClasses () {
|
||||||
|
return [
|
||||||
|
`${this.prefix}-bar-con`,
|
||||||
|
this.isVertical ? 'vertical' : 'horizontal'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -25,6 +25,7 @@ import Icon from './components/icon';
|
||||||
import Input from './components/input';
|
import Input from './components/input';
|
||||||
import InputNumber from './components/input-number';
|
import InputNumber from './components/input-number';
|
||||||
import Scroll from './components/scroll';
|
import Scroll from './components/scroll';
|
||||||
|
import Split from './components/split';
|
||||||
import Layout from './components/layout';
|
import Layout from './components/layout';
|
||||||
import LoadingBar from './components/loading-bar';
|
import LoadingBar from './components/loading-bar';
|
||||||
import Menu from './components/menu';
|
import Menu from './components/menu';
|
||||||
|
@ -91,6 +92,7 @@ const components = {
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Scroll,
|
Scroll,
|
||||||
Sider: Sider,
|
Sider: Sider,
|
||||||
|
Split,
|
||||||
Submenu: Menu.Sub,
|
Submenu: Menu.Sub,
|
||||||
Layout: Layout,
|
Layout: Layout,
|
||||||
LoadingBar,
|
LoadingBar,
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
@import "modal";
|
@import "modal";
|
||||||
@import "select";
|
@import "select";
|
||||||
@import "select-dropdown";
|
@import "select-dropdown";
|
||||||
|
@import "split";
|
||||||
@import "tooltip";
|
@import "tooltip";
|
||||||
@import "poptip";
|
@import "poptip";
|
||||||
@import "input";
|
@import "input";
|
||||||
|
|
114
src/styles/components/split.less
Normal file
114
src/styles/components/split.less
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
@split-prefix-cls: ~"@{css-prefix}split";
|
||||||
|
@box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.4);
|
||||||
|
@trigger-bar-background: rgba(23, 35, 61, 0.25);
|
||||||
|
@trigger-background: #F8F8F9;
|
||||||
|
@trigger-width: 6px;
|
||||||
|
@trigger-bar-width: 4px;
|
||||||
|
@trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2;
|
||||||
|
@trigger-bar-interval: 3px;
|
||||||
|
@trigger-bar-weight: 1px;
|
||||||
|
@trigger-bar-con-height: (@trigger-bar-weight + @trigger-bar-interval) * 8;
|
||||||
|
|
||||||
|
.@{split-prefix-cls}{
|
||||||
|
&-wrapper{
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
&-pane{
|
||||||
|
position: absolute;
|
||||||
|
&.left-pane, &.right-pane{
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
&.left-pane{
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
&.right-pane{
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
&.top-pane, &.bottom-pane{
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
&.top-pane{
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
&.bottom-pane{
|
||||||
|
bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-trigger{
|
||||||
|
&-con{
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
&-bar-con{
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
&.vertical{
|
||||||
|
left: @trigger-bar-offset;
|
||||||
|
top: 50%;
|
||||||
|
height: @trigger-bar-con-height;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
&.horizontal{
|
||||||
|
left: 50%;
|
||||||
|
top: @trigger-bar-offset;
|
||||||
|
width: @trigger-bar-con-height;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-vertical{
|
||||||
|
width: @trigger-width;
|
||||||
|
height: 100%;
|
||||||
|
background: @trigger-background;
|
||||||
|
box-shadow: @box-shadow;
|
||||||
|
cursor: col-resize;
|
||||||
|
.@{split-prefix-cls}-trigger-bar{
|
||||||
|
width: @trigger-bar-width;
|
||||||
|
height: 1px;
|
||||||
|
background: @trigger-bar-background;
|
||||||
|
float: left;
|
||||||
|
margin-top: @trigger-bar-interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-horizontal{
|
||||||
|
height: @trigger-width;
|
||||||
|
width: 100%;
|
||||||
|
background: @trigger-background;
|
||||||
|
box-shadow: @box-shadow;
|
||||||
|
cursor: row-resize;
|
||||||
|
.@{split-prefix-cls}-trigger-bar{
|
||||||
|
height: @trigger-bar-width;
|
||||||
|
width: 1px;
|
||||||
|
background: @trigger-bar-background;
|
||||||
|
float: left;
|
||||||
|
margin-right: @trigger-bar-interval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-horizontal{
|
||||||
|
.@{split-prefix-cls}-trigger-con{
|
||||||
|
top: 50%;
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-vertical{
|
||||||
|
.@{split-prefix-cls}-trigger-con{
|
||||||
|
left: 50%;
|
||||||
|
height: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.no-select{
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue