Commit a9a7d704 authored by fangzhipeng's avatar fangzhipeng

初步让 ele-ui tabs 支持拖拽排序

parent b66b054a
{
"presets": [
["env", { "modules": false }],
"stage-2"
"stage-2",
"es2015"
],
"plugins": ["transform-runtime"],
"plugins": ["transform-runtime", "transform-vue-jsx"],
"comments": false,
"env": {
"test": {
......
......@@ -7,7 +7,8 @@
<script src="https://cdn.bootcss.com/jquery.scrollbar/0.2.11/jquery.scrollbar.js"></script>
<link href="https://cdn.bootcss.com/jquery.scrollbar/0.2.11/jquery.scrollbar.css" rel="stylesheet">
</head>
<body style="height: 100%; margin: 0px; background: #F9FAFC; overflow: hidden; background: #f5f5f5 url(/static/bg.jpg) no-repeat center; background-size: cover;">
<!--<body style="height: 100%; margin: 0px; background: #F9FAFC; overflow: hidden; background: #f5f5f5 url(/static/bg.jpg) no-repeat center; background-size: cover;">-->
<dody>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
......
<template>
<div>
<my-tabs type="border-card" v-model="editableTabsValue" editable @edit="handleTabsEdit">
<my-tab-pane label="用户管理">用户管理</my-tab-pane>
<my-tab-pane label="配置管理">配置管理</my-tab-pane>
<my-tab-pane label="角色管理">角色管理</my-tab-pane>
<my-tab-pane label="定时任务补偿">定时任务补偿</my-tab-pane>
</my-tabs>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import Sortable from 'sortablejs'
import MyTabs from './tabs/tabs'
import MyTabPane from './tabs/tab-pane'
export default {
name: "drag",
components: {
draggable,
Sortable,
MyTabPane,
MyTabs
},
data: function () {
return {
myArray: [{
id: 1,
name: "用户管理"
}, {
id: 1,
name: "配置管理"
}, {
id: 1,
name: "角色管理"
}, {
id: 1,
name: "定时任务补偿",
active: true
}]
}
},
methods: {
getClass: function (item) {
return "el-tabs__item is-top " + (item.active ? "is-active" : "")
},
clickTab: function (target) {
if (target.active) {
return;
}
this.myArray.forEach((item, index) => {
item.active = false;
this.$set(this.myArray, index, item);
});
console.log(this.myArray)
target.active = true;
}
}
}
</script>
<style>
* {
outline: none;
}
</style>
<template>
<div class="el-tabs__active-bar" :style="barStyle"></div>
</template>
<script>
export default {
name: 'TabBar',
props: {
tabs: Array
},
computed: {
barStyle: {
cache: false,
get() {
if (!this.$parent.$refs.tabs) return {};
let style = {};
let offset = 0;
let tabWidth = 0;
this.tabs.every((tab, index) => {
let $el = this.$parent.$refs.tabs[index];
if (!$el) { return false; }
if (!tab.active) {
offset += $el.clientWidth;
return true;
} else {
tabWidth = $el.clientWidth;
return false;
}
});
const transform = `translateX(${offset}px)`;
style.width = tabWidth + 'px';
style.transform = transform;
style.msTransform = transform;
style.webkitTransform = transform;
return style;
}
}
}
};
</script>
<script>
import TabBar from './tab-bar';
import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
import draggable from 'vuedraggable';
import Sortable from 'sortablejs';
function noop() {}
export default {
name: 'TabNav',
components: {
TabBar,
draggable,
Sortable
},
props: {
panes: Array,
currentName: String,
editable: Boolean,
onTabClick: {
type: Function,
default: noop
},
onTabRemove: {
type: Function,
default: noop
},
type: String
},
data() {
return {
scrollable: false,
navStyle: {
transform: ''
}
};
},
methods: {
scrollPrev() {
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (!currentOffset) return;
let newOffset = currentOffset > containerWidth
? currentOffset - containerWidth
: 0;
this.setOffset(newOffset);
},
scrollNext() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (navWidth - currentOffset <= containerWidth) return;
let newOffset = navWidth - currentOffset > containerWidth * 2
? currentOffset + containerWidth
: (navWidth - containerWidth);
this.setOffset(newOffset);
},
scrollToActiveTab() {
if (!this.scrollable) return;
const nav = this.$refs.nav;
const activeTab = this.$el.querySelector('.is-active');
const navScroll = this.$refs.navScroll;
const activeTabBounding = activeTab.getBoundingClientRect();
const navScrollBounding = navScroll.getBoundingClientRect();
const navBounding = nav.getBoundingClientRect();
const currentOffset = this.getCurrentScrollOffset();
let newOffset = currentOffset;
if (activeTabBounding.left < navScrollBounding.left) {
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
}
if (activeTabBounding.right > navScrollBounding.right) {
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
}
if (navBounding.right < navScrollBounding.right) {
newOffset = nav.offsetWidth - navScrollBounding.width;
}
this.setOffset(Math.max(newOffset, 0));
},
getCurrentScrollOffset() {
const { navStyle } = this;
return navStyle.transform
? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
: 0;
},
setOffset(value) {
this.navStyle.transform = `translateX(-${value}px)`;
},
update() {
const navWidth = this.$refs.nav.offsetWidth;
const containerWidth = this.$refs.navScroll.offsetWidth;
const currentOffset = this.getCurrentScrollOffset();
if (containerWidth < navWidth) {
const currentOffset = this.getCurrentScrollOffset();
this.scrollable = this.scrollable || {};
this.scrollable.prev = currentOffset;
this.scrollable.next = currentOffset + containerWidth < navWidth;
if (navWidth - currentOffset < containerWidth) {
this.setOffset(navWidth - containerWidth);
}
} else {
this.scrollable = false;
if (currentOffset > 0) {
this.setOffset(0);
}
}
}
},
updated() {
this.update();
},
render(h) {
const {
type,
panes,
editable,
onTabClick,
onTabRemove,
navStyle,
scrollable,
scrollNext,
scrollPrev
} = this;
const scrollBtn = scrollable
? [
<span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>,
<span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span>
] : null;
const tabs = this._l(panes, (pane, index) => {
let tabName = pane.name || pane.index || index;
const closable = pane.isClosable || editable;
pane.index = `${index}`;
const btnClose = closable
? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span>
: null;
const tabLabelContent = pane.$slots.label || pane.label;
return (
<div
class={{
'el-tabs__item': true,
'is-active': pane.active,
'is-disabled': pane.disabled,
'is-closable': closable
}}
ref="tabs"
refInFor
on-click={(ev) => { onTabClick(pane, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
</div>
);
});
return (
<div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '']}>
{scrollBtn}
<div class={['el-tabs__nav-scroll']} ref="navScroll">
<draggable class="el-tabs__nav" ref="nav" style={navStyle}>
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
{tabs}
</draggable>
</div>
</div>
);
},
mounted() {
addResizeListener(this.$el, this.update);
},
beforeDestroy() {
if (this.$el && this.update) removeResizeListener(this.$el, this.update);
}
};
</script>
<template>
<div class="el-tab-pane" v-show="active">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'MyTabPane',
componentName: 'MyTabPane',
props: {
label: String,
labelContent: Function,
name: String,
closable: Boolean,
disabled: Boolean
},
data() {
return {
index: null
};
},
computed: {
isClosable() {
return this.closable || this.$parent.closable;
},
active() {
return this.$parent.currentName === (this.name || this.index);
}
},
mounted() {
this.$parent.addPanes(this);
},
destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.$parent.removePanes(this);
},
watch: {
label() {
this.$parent.$forceUpdate();
}
}
};
</script>
<script>
import TabNav from './tab-nav';
export default {
name: 'MyTabs',
components: {
TabNav
},
props: {
type: String,
activeName: String,
closable: Boolean,
addable: Boolean,
value: {},
editable: Boolean
},
data() {
return {
currentName: this.value || this.activeName,
panes: []
};
},
watch: {
activeName(value) {
this.setCurrentName(value);
},
value(value) {
this.setCurrentName(value);
},
currentName(value) {
if (this.$refs.nav) {
this.$nextTick(_ => {
this.$refs.nav.scrollToActiveTab();
});
}
}
},
methods: {
handleTabClick(tab, tabName, event) {
if (tab.disabled) return;
this.setCurrentName(tabName);
this.$emit('tab-click', tab, event);
},
handleTabRemove(pane, ev) {
if (pane.disabled) return;
ev.stopPropagation();
this.$emit('edit', pane.name, 'remove');
this.$emit('tab-remove', pane.name);
},
handleTabAdd() {
this.$emit('edit', null, 'add');
this.$emit('tab-add');
},
setCurrentName(value) {
this.currentName = value;
this.$emit('input', value);
},
addPanes(item) {
const index = this.$slots.default.indexOf(item.$vnode);
this.panes.splice(index, 0, item);
},
removePanes(item) {
const panes = this.panes;
const index = panes.indexOf(item);
if (index > -1) {
panes.splice(index, 1);
}
}
},
render(h) {
let {
type,
handleTabClick,
handleTabRemove,
handleTabAdd,
currentName,
panes,
editable,
addable
} = this;
const newButton = editable || addable
? (
<span
class="el-tabs__new-tab"
on-click={ handleTabAdd }
>
<i class="el-icon-plus"></i>
</span>
)
: null;
const navData = {
props: {
currentName,
onTabClick: handleTabClick,
onTabRemove: handleTabRemove,
editable,
type,
panes
},
ref: 'nav'
};
return (
<div class={{
'el-tabs': true,
'el-tabs--card': type === 'card',
'el-tabs--border-card': type === 'border-card'
}}>
<div class="el-tabs__header">
{newButton}
<tab-nav { ...navData }></tab-nav>
</div>
<div class="el-tabs__content">
{this.$slots.default}
</div>
</div>
);
},
created() {
if (!this.currentName) {
this.setCurrentName('0');
}
}
};
</script>
......@@ -3,6 +3,7 @@ import Router from 'vue-router'
import Hello from '@/components/Hello'
import Ele from '@/components/Ele'
import ChatWin from '@/components/ChatWin'
import Drag from '@/components/Drag'
Vue.use(Router)
......@@ -22,6 +23,11 @@ export default new Router({
path: '/chatWin',
name: 'ChatWin',
component: ChatWin
},
{
path: '/drag',
name: 'Drag',
component: Drag
}
]
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment