vue實(shí)現(xiàn)自定義樹(shù)形組件的示例代碼
效果展示:

近期的一個(gè)功能需求,實(shí)現(xiàn)一個(gè)樹(shù)形結(jié)構(gòu):可點(diǎn)擊,可拖拽,右側(cè)數(shù)據(jù)可以拖拽到對(duì)應(yīng)的節(jié)點(diǎn)內(nèi),可創(chuàng)建文件夾、可創(chuàng)建文件、編輯文件名、可刪除等等;
渲染列表數(shù)據(jù)的時(shí)候,列表的子項(xiàng)還是列表。針對(duì)多層級(jí)的列表,我們采用tree的方式,從根節(jié)點(diǎn)一次創(chuàng)建綁定子節(jié)點(diǎn)的方式,可以遞歸式的調(diào)用本身,對(duì)我們的樹(shù)形結(jié)構(gòu)進(jìn)行展示;并且支持多余的樹(shù)形拓展;
代碼區(qū)域
1、創(chuàng)建TreeList文件夾,其中創(chuàng)建:fonts文件夾、index.js文件、tools.js文件、Tree.js文件、VueTreeList.vue文件;
2、fonts文件夾主要用來(lái)存放icon圖標(biāo)的,這里就不展示了,依據(jù)項(xiàng)目在阿里矢量圖標(biāo)內(nèi)新增,然后在VueTreeList.vue內(nèi)進(jìn)行替換
使用
<vue-tree-list
ref="VueTreeList"
:model="treeData" // 初識(shí)數(shù)據(jù)源,treeData: new Tree([]),
:activeId="activeId" // 選中的id及背景色
default-leaf-node-name="新建文件" // 默認(rèn)創(chuàng)建文件名稱(chēng)
default-tree-node-name="新建目錄" // 默認(rèn)創(chuàng)建文件夾名稱(chēng)
:default-expanded="isExpanded" // 默認(rèn)是否展開(kāi)文件夾
@click="handleOnClick" // 點(diǎn)擊當(dāng)前節(jié)點(diǎn)
@moveGraph="moveGraph" // 右側(cè)數(shù)據(jù)拖拽至當(dāng)前節(jié)點(diǎn)觸發(fā),可刪除不使用
@add-node="handleOnAddNode" // 點(diǎn)擊創(chuàng)建節(jié)點(diǎn)
@end-edit="handleOnChangeNameEnd" // 點(diǎn)擊編輯當(dāng)前節(jié)點(diǎn)的名稱(chēng)
@delete-node="deleteNode" // 點(diǎn)擊刪除當(dāng)前節(jié)點(diǎn)
@drop="handleDrop" // 拖拽上下節(jié)點(diǎn)我已注釋掉,依據(jù)需求自身放開(kāi)
@drop-before="hadnleDropBefore" // 開(kāi)始拖拽之前的觸發(fā)函數(shù)
@drop-after="handleDropAfter" // 結(jié)束拖拽完之后的觸發(fā)函數(shù)
loadDataApi="/api/tree-dir" // 點(diǎn)擊左側(cè)icon,觸發(fā)遠(yuǎn)程加載填充子數(shù)據(jù)Api接口
>
</vue-tree-list>1.1 創(chuàng)建index.js文件,也是往外暴露的入口文件;(文章并未按照思路排序)
/**
* Created by ayou on 17/7/21.
*/
import VueTreeList from './VueTreeList'
import { Tree, TreeNode } from './Tree'
VueTreeList.install = Vue => {
Vue.component(VueTreeList.name, VueTreeList)
}
export default VueTreeList
export { Tree, TreeNode, VueTreeList }1.2 創(chuàng)建tools.js文件;
/**
* Created by ayou on 18/2/6.
*/
var handlerCache
export const addHandler = function(element, type, handler) {
handlerCache = handler
if (element.addEventListener) {
element.addEventListener(type, handler, false)
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler)
} else {
element['on' + type] = handler
}
}
export const removeHandler = function(element, type) {
if (element.removeEventListener) {
element.removeEventListener(type, handlerCache, false)
} else if (element.detachEvent) {
element.detachEvent('on' + type, handlerCache)
} else {
element['on' + type] = null
}
}
// depth first search
export const traverseTree = (root) => {
const { children, parent, ...newRoot } = root;
if (children && children.length > 0) {
newRoot.children = children.map(traverseTree);
}
return newRoot;
};1.2 創(chuàng)建Tree.js文件;
import { traverseTree } from './tools'
export class TreeNode {
constructor(data) {
const { id, isLeaf, editNode } = data
this.id = typeof id !== 'undefined' ? id : Math.floor(new Date().valueOf() * (Math.random() + 1))
this.parent = null
this.children = null
this.isLeaf = !!isLeaf
this.editNode = editNode || false
for (const key in data) {
if (key !== 'id' && key !== 'children' && key !== 'isLeaf') {
this[key] = data[key]
}
}
}
changeName(name) {
this.name = name
}
changeNodeId(id) {
this.id = id
}
addChildren(children) {
if (!this.children) {
this.children = []
}
if (Array.isArray(children)) {
children.forEach(child => {
child.parent = this
child.pid = this.id
})
this.children.push(...children)
} else {
const child = children
child.parent = this
child.pid = this.id
this.children.push(child)
}
}
// remove self
remove() {
const parent = this.parent
const index = parent.children.findIndex(child => child === this)
parent.children.splice(index, 1)
}
// remove child
_removeChild(child, bool) {
const index = this.children.findIndex(c => bool ? c.id === child.id : c === child)
if (index !== -1) {
this.children.splice(index, 1)
}
}
isTargetChild(target) {
let parent = target.parent
while (parent) {
if (parent === this) {
return true
}
parent = parent.parent
}
return false
}
moveInto(target) {
if (this.name === 'root' || this === target) {
return
}
if (this.isTargetChild(target)) {
return
}
if (target.isLeaf) {
return
}
this.parent.removeChild(this)
this.parent = target
this.pid = target.id
if (!target.children) {
target.children = []
}
target.children.unshift(this)
}
findChildIndex(child) {
return this.children.findIndex(c => c === child)
}
_canInsert(target) {
if (this.name === 'root' || this === target) {
return false
}
if (this.isTargetChild(target)) {
return false
}
this.parent.removeChild(this)
this.parent = target.parent
this.pid = target.parent.id
return true
}
insertBefore(target) {
if (!this._canInsert(target)) return
const pos = target.parent.findChildIndex(target)
target.parent.children.splice(pos, 0, this)
}
insertAfter(target) {
if (!this._canInsert(target)) return
const pos = target.parent.findChildIndex(target)
target.parent.children.splice(pos + 1, 0, this)
}
toString() {
return JSON.stringify(traverseTree(this))
}
}
export class Tree {
constructor(data) {
this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
this.initNode(this.root, data)
return this.root
}
initNode(node, data) {
data.forEach(_data => {
const child = new TreeNode(_data)
if (_data.children && _data.children.length > 0) {
this.initNode(child, _data.children)
}
node.addChildren(child)
})
}
}1.3 創(chuàng)建VueTreeList.vue文件;
說(shuō)明:支持點(diǎn)擊創(chuàng)建遠(yuǎn)程數(shù)據(jù),loadDataAjax方法,需要自己研究功能小編已實(shí)現(xiàn);現(xiàn)有代碼基本功能已經(jīng)完善,需要依賴(lài)自己的項(xiàng)目進(jìn)行變更和更改;treeNode可以直接訪問(wèn)和修改數(shù)據(jù)源的,需要讀者自己發(fā)掘;
<template>
<div :class="['vtl', isMobile && 'isMobile']">
<div
v-if="model.name !== 'root'"
:id="model.id"
class="vtl-node"
:class="{ 'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf }"
>
<div class="vtl-border vtl-up" :class="{ 'vtl-active': isDragEnterUp }" />
<div
:class="['vtl-node-main', { 'vtl-active': isDragEnterNode }]"
:style="{ fontSize: '10px' }"
@drop="drop"
@mouseover="mouseOver"
@mouseout="mouseOut"
@click.stop="handleCurClick"
@dragover="dragOver"
@dragenter="dragEnter"
@dragleave="dragLeave"
>
<span
v-if="!model.children"
class="vtl-caret vtl-is-small"
>
<i
class="vtl-icon"
style="cursor: pointer; width: 11px;"
></i>
</span>
<span
v-if="model.children"
class="vtl-caret vtl-is-small"
>
<i
class="vtl-icon"
:class="caretClass"
style="cursor: pointer"
@click.prevent.stop="toggle"
></i>
<i
v-if="isRemoteLoading"
class="Custom_demo-spin-icon-load ivu-icon ivu-icon-ios-loading"
style="font-size: 16px; margin-right: 3px; margin-top: -2px"
></i>
</span>
<span v-if="model.isLeaf">
<slot
name="leafNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i
style="cursor: pointer"
class="vtl-icon vtl-menu-icon vtl-icon-file"
></i>
</slot>
</span>
<span v-else>
<slot
name="treeNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<img
class="custom_img"
style="width:15px;margin-right: 3px"
src="../../../static/img/folder.png"
alt=""
/>
</slot>
</span>
<div
v-if="!editable"
:class="[
'vtl-node-content',
isShowClickBackg,
{ custom_class_hiddle: isHover, custom_class_click: model.isLeaf }
]"
:style="{
color: model.matched ? '#D9262C' : null,
cursor: 'pointer'
}"
>
<slot
name="leafNameDisplay"
:expanded="expanded"
:model="model"
:root="rootNode"
>
{{ model.name }}
</slot>
</div>
<input
v-if="editable || handleInitEditable(model)"
class="vtl-input"
type="text"
ref="nodeInput"
:value="model.name"
@input="updateName"
@blur="setUnEditable"
@keyup.enter="setUnEditable"
/>
<div class="vtl-operation" v-show="isHover">
<!-- 新增設(shè)備 -->
<span
title="新增設(shè)備"
v-btn-key="rolespermiss.addDevice"
@click.stop.prevent="createChild"
v-if="
(!model.isDevice || model.isDir === false) &&
$route.path != '/autoMonitorBoard'
"
>
<slot
name="addLeafNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-plus"></i>
</slot>
</span>
<!-- 編輯名稱(chēng) -->
<span
title="編輯名稱(chēng)"
v-btn-key="rolespermiss.editFolder"
@click.stop.prevent="setEditable(true)"
v-if="
!model.editNodeDisabled &&
!model.isDevice &&
$route.path != '/autoMonitorBoard'
"
>
<slot
name="editNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-edit"></i>
</slot>
</span>
<!-- 刪除節(jié)點(diǎn) -->
<span
title="刪除節(jié)點(diǎn)"
@click.stop.prevent="delNode"
style="line-height: 14px;"
v-if="$route.path != '/autoMonitorBoard'"
v-btn-key="rolespermiss.deleteFolder"
>
<slot
name="delNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-trash"></i>
</slot>
</span>
<!-- 創(chuàng)建子目錄 -->
<span
:title="defaultAddTreeNodeTitle"
@click.stop.prevent="addChild(false)"
v-btn-key="rolespermiss.createFolder"
v-if="
!model.addTreeNodeDisabled &&
!model.isLeaf &&
!model.isDevice &&
$route.path != '/autoMonitorBoard'
"
>
<slot
name="addTreeNodeIcon"
:expanded="expanded"
:model="model"
:root="rootNode"
>
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
</slot>
</span>
<!-- 詳情按鈕 -->
<span
title="設(shè)備詳情"
style="margin-top: -1px"
@click.stop="handleViewDetail"
v-btn-key="rolespermiss.folderDetail"
v-if="!model.addTreeNodeDisabled && model.isLeaf && !model.isDevice"
>
<Icon style="color: #d9262c" type="ios-paper-outline" />
</span>
</div>
</div>
<div
v-if="
model.children &&
model.children.length > 0 &&
(expanded || model.expanded)
"
class="vtl-border vtl-bottom"
:class="{ 'vtl-active': isDragEnterBottom }"
></div>
</div>
<div
:class="{ 'vtl-tree-margin': model.name !== 'root' }"
v-if="isFolder && (model.name === 'root' || expanded || model.expanded)"
>
<item
:model="model"
:title="model.name"
v-for="model in model.children"
:key="model.id"
:activeId="activeId"
:loadDataApi="loadDataApi"
:rolespermiss="rolespermiss"
:requestHeader="requestHeader"
:default-tree-node-name="defaultTreeNodeName"
:default-leaf-node-name="defaultLeafNodeName"
:default-expanded="defaultExpanded"
>
<template v-slot:leafNameDisplay="slotProps">
<slot name="leafNameDisplay" v-bind="slotProps" />
</template>
<template v-slot:addTreeNodeIcon="slotProps">
<slot name="addTreeNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:addLeafNodeIcon="slotProps">
<slot name="addLeafNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:editNodeIcon="slotProps">
<slot name="editNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:delNodeIcon="slotProps">
<slot name="delNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:leafNodeIcon="slotProps">
<slot name="leafNodeIcon" v-bind="slotProps" />
</template>
<template v-slot:treeNodeIcon="slotProps">
<slot name="treeNodeIcon" v-bind="slotProps" />
</template>
</item>
</div>
</div>
</template>
<script>
import { request } from "@/axios/index";
import { TreeNode } from "./Tree.js";
import { removeHandler } from "./tools.js";
import { isShowMobile } from "@/storage/storeutil";
let compInOperation = null;
export default {
name: "vue-tree-list",
props: {
model: {
type: Object
},
activeId: Number,
rolespermiss: Object,
loadDataApi: String,
requestHeader: Object,
defaultLeafNodeName: {
type: String,
default: "新建"
},
defaultTreeNodeName: {
type: String,
default: "新建"
},
defaultAddTreeNodeTitle: {
type: String,
default: "新建"
},
defaultExpanded: {
type: Boolean,
default: true
}
},
data() {
return {
isHover: false,
editable: false,
isDragEnterUp: false,
isDragEnterBottom: false,
isDragEnterNode: false,
isRemoteLoading: false,
expanded: this.defaultExpanded,
clickEditIcon: false
};
},
computed: {
rootNode() {
var node = this.$parent;
while (node._props.model.name !== "root") {
node = node.$parent;
}
return node;
},
caretClass() {
return this.model.expanded
? "vtl-icon-caret-down"
: this.expanded
? "vtl-icon-caret-down"
: "vtl-icon-caret-right";
},
isFolder() {
return this.model.children && this.model.children.length;
},
isShowClickBackg() {
const {
model: { id }
} = this;
return { activeItem: id === this.activeId };
},
isMobile() {
return isShowMobile();
}
// treeNodeClass() {
// const {
// model: { dragDisabled, disabled },
// } = this;
// return {
// "vtl-drag-disabled": dragDisabled,
// "vtl-disabled": disabled,
// };
// },
},
methods: {
updateName(e) {
var oldName = this.model.name;
this.model.changeName(e.target.value);
this.rootNode.$emit("change-name", {
id: this.model.id,
oldName: oldName,
newName: e.target.value,
node: this.model
});
},
// 點(diǎn)擊左側(cè)箭頭異步加載子節(jié)點(diǎn)數(shù)據(jù)
toggle() {
if (this.model.expanded) {
this.expanded = false;
} else {
this.expanded = !this.expanded;
}
this.model.expanded = false;
},
// 刪除節(jié)點(diǎn)
delNode() {
this.rootNode.$emit("delete-node", this.model);
},
setEditable(bool) {
this.clickEditIcon = bool || false;
this.editable = true;
this.$nextTick(() => {
const $input = this.$refs.nodeInput;
$input.focus();
this.handleCurClick();
});
},
setUnEditable(e) {
if (this.editable === false) return;
this.editable = false;
this.model.editNode = false;
var oldName = this.model.name;
this.model.changeName(e.target.value);
this.rootNode.$emit(
"change-name",
{
id: this.model.id,
oldName: oldName,
newName: e.target.value,
eventType: "blur"
},
this.model
);
this.rootNode.$emit(
"end-edit",
{
id: this.model.id,
oldName: oldName,
newName: e.target.value
},
this.model,
this.clickEditIcon
);
this.clickEditIcon = false;
},
// 新建目錄
handleInitEditable(row) {
if (row.editNode) {
this.setEditable();
}
},
// 異步請(qǐng)求數(shù)據(jù)
async loadDataAjax(Refresh) {
if (Refresh) {
this.model.isLeaf = true;
}
const { method, params, httpApi } = this.requestHeader || {};
const httpUrl = this.model.isLeaf ? httpApi : this.loadDataApi;
const requestParams = this.model.isLeaf
? { treeDirId: this.model.id, ...(params || {}) }
: { id: this.model.id, ...(params || {}) };
try {
this.isRemoteLoading = true;
const { code, data, message } = await request(
method || "GET",
httpUrl,
requestParams
);
if (code !== 0) {
return (
(this.expanded = false),
(this.isRemoteLoading = false),
this.$Message.error("失敗," + message || "請(qǐng)求失敗")
);
}
const dataSource = this.model.isLeaf ? data.deviceList : data.data;
if (!dataSource) {
return (this.expanded = false), (this.isRemoteLoading = false);
}
if (Array.isArray(dataSource) && dataSource.length) {
dataSource.forEach(item => {
const node = new TreeNode(item);
if (Refresh && this.expanded) {
this.model._removeChild(node, true);
}
this.model.addChildren(node, true);
});
this.expanded = true;
}
this.isRemoteLoading = false;
} catch (err) {
this.expanded = false;
this.isRemoteLoading = false;
throw new Error(err);
}
},
mouseOver() {
if (this.model.disabled) return;
this.isHover = true;
},
mouseOut() {
this.isHover = false;
},
// 點(diǎn)擊當(dāng)前節(jié)點(diǎn)
handleCurClick() {
this.rootNode.$emit(
"click",
{
toggle: this.toggle,
...this.model
},
this.editable
);
if (this.$route.path=='/autoMonitorBoard') {
this.toggle()
}
},
// 查看詳情
handleViewDetail() {
this.rootNode.$emit("viewDetail", {
...this.model
});
},
// 新增子節(jié)點(diǎn)
async addChild(isLeaf) {
if (!this.expanded) {
await this.loadDataAjax();
this.handleAddChildren(isLeaf);
} else {
this.handleAddChildren(isLeaf);
}
},
handleAddChildren(isLeaf) {
const name = isLeaf ? this.defaultLeafNodeName : this.defaultTreeNodeName;
this.expanded = true;
var node = new TreeNode({ name, isLeaf, isDir: false });
this.model.addChildren(node, true);
this.rootNode.$emit("add-node", node);
},
createChild() {
this.rootNode.$emit("create-child", {
...this.model,
loadDataAjax: this.loadDataAjax
});
},
// dragStart(e) {
// if (!(this.model.dragDisabled || this.model.disabled)) {
// compInOperation = this;
// e.dataTransfer.setData("data", "data");
// e.dataTransfer.effectAllowed = "move";
// return true;
// }
// return false;
// },
// dragEnd() {
// compInOperation = null;
// },
dragOver(e) {
e.preventDefault();
return true;
},
dragEnter(ev) {
this.isDragEnterNode = true;
},
dragLeave() {
this.isDragEnterNode = false;
},
drop(ev) {
if (ev.dataTransfer && ev.dataTransfer.getData("data")) {
const data = JSON.parse(ev.dataTransfer.getData("data"));
this.isDragEnterNode = false;
this.$Modal.confirm({
title: "提示",
content: `是否確定要移入【${this.model.title}】目錄中?`,
closable: true,
maskClosable: true,
onOk: () => {
this.axios
.request("POST", "/api/move", {
id: data.id,
treeDirId: this.model.id
})
.then(response => {
if (+response.code === 0) {
this.$Message.success("移動(dòng)成功!");
this.rootNode.$emit("moveGraph");
} else {
// 提示錯(cuò)誤
this.$Notice.error({
title: "查詢(xún)失敗",
desc: response.message || "請(qǐng)求失敗",
duration: 5
});
}
});
}
});
return;
}
if (!compInOperation) return;
const oldParent = compInOperation.model.parent;
compInOperation.model.moveInto(this.model);
this.isDragEnterNode = false;
this.rootNode.$emit("drop", {
target: this.model,
node: compInOperation.model,
src: oldParent
});
}
// dragEnterUp() {
// if (!compInOperation) return;
// this.isDragEnterUp = true;
// },
// dragOverUp(e) {
// e.preventDefault();
// return true;
// },
// dragLeaveUp() {
// if (!compInOperation) return;
// this.isDragEnterUp = false;
// },
// dropBefore() {
// if (!compInOperation) return;
// const oldParent = compInOperation.model.parent;
// compInOperation.model.insertBefore(this.model);
// this.isDragEnterUp = false;
// this.rootNode.$emit("drop-before", {
// target: this.model,
// node: compInOperation.model,
// src: oldParent,
// });
// },
// dragEnterBottom() {
// if (!compInOperation) return;
// this.isDragEnterBottom = true;
// },
// dragOverBottom(e) {
// e.preventDefault();
// return true;
// },
// dragLeaveBottom() {
// if (!compInOperation) return;
// this.isDragEnterBottom = false;
// },
// dropAfter() {
// if (!compInOperation) return;
// const oldParent = compInOperation.model.parent;
// compInOperation.model.insertAfter(this.model);
// this.isDragEnterBottom = false;
// this.rootNode.$emit("drop-after", {
// target: this.model,
// node: compInOperation.model,
// src: oldParent,
// });
// },
},
beforeCreate() {
this.$options.components.item = require("./VueTreeList").default;
},
beforeDestroy() {
removeHandler(window, "keyup");
}
};
</script>
<style lang="less">
@font-face {
font-family: "icomoon";
src: url("fonts/icomoon.eot?ui1hbx");
src: url("fonts/icomoon.eot?ui1hbx#iefix") format("embedded-opentype"),
url("fonts/icomoon.ttf?ui1hbx") format("truetype"),
url("fonts/icomoon.woff?ui1hbx") format("woff"),
url("fonts/icomoon.svg?ui1hbx#icomoon") format("svg");
font-weight: normal;
font-style: normal;
}
.vtl-icon {
font-family: "icomoon" !important;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&.vtl-menu-icon {
margin-right: 4px;
&:hover {
color: inherit;
}
}
&:hover {
color: #d9262c;
}
}
.vtl-icon-file:before {
content: "\e906";
}
.vtl-icon-folder:before {
content: "\e907";
}
.vtl-icon-caret-down:before {
font-size: 16px;
content: "\e901";
}
.vtl-icon-caret-right:before {
font-size: 16px;
content: "\e900";
}
.vtl-icon-edit:before {
content: "\e902";
font-size: 18px;
}
.vtl-icon-folder-plus-e:before {
content: "\e903";
}
.vtl-icon-plus:before {
content: "\e904";
font-size: 16px;
}
.vtl-icon-trash:before {
content: "\e905";
}
.vtl {
cursor: default;
margin-left: -3px;
}
.vtl-border {
height: 5px;
&.vtl-up {
margin-top: -5px;
background-color: transparent;
}
&.vtl-bottom {
background-color: transparent;
}
&.vtl-active {
border-bottom: 2px dashed pink;
}
}
.vtl-node-main {
display: flex;
align-items: center;
margin: 2.5px auto 2.5px -1px;
.vtl-input {
border: none;
min-width: 200px;
border-bottom: 1px solid blue;
}
&:hover {
background-color: #f0f0f0;
}
&.vtl-active {
outline: 1.5px dashed #d9262c;
}
.vtl-operation {
display: flex;
margin-left: 1rem;
height: 18px;
letter-spacing: 1px;
span {
margin-right: 10px;
}
.vtl-icon {
color: #d9262c;
vertical-align: sub;
}
}
}
.vtl-node-content {
white-space: nowrap;
padding: 1px 0px;
}
.activeItem {
background: #ccc;
}
.custom_class_click {
cursor: pointer;
}
.custom_class_hiddle {
overflow: hidden;
text-overflow: ellipsis;
}
.vtl-item {
cursor: pointer;
}
.vtl-tree-margin {
margin-left: 2em;
}
.Custom_demo-spin-icon-load {
font-size: 18px;
color: #d9262c;
animation: ani-demo-spin 1s linear infinite;
}
@keyframes ani-demo-spin {
from {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
to {
transform: rotate(360deg);
}
}
.demo-spin-col {
height: 100px;
position: relative;
border: 1px solid #eee;
}
.vtl-caret {
display: flex;
.vtl-icon {
width: 28px;
text-align: right;
}
}
.isMobile {
.vtl {
margin-left: 3px;
}
.vtl-node-content {
white-space: nowrap;
padding: 1px 0px;
font-size: 2.6em;
}
.custom_img {
width: 2.5em !important;
}
.vtl-icon-caret-down:before,
.vtl-icon-caret-right:before,
.vtl-icon-plus:before,
.vtl-icon-edit:before,
.vtl-icon-trash:before,
.vtl-icon-folder-plus-e:before {
font-size: 30px;
}
.vtl-node-main .vtl-operation {
height: auto;
}
}
</style>到此這篇關(guān)于vue實(shí)現(xiàn)自定義樹(shù)形組件的文章就介紹到這了,更多相關(guān)vue自定義樹(shù)形組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- vue遞歸實(shí)現(xiàn)樹(shù)形組件
- Vue組件庫(kù)ElementUI實(shí)現(xiàn)表格加載樹(shù)形數(shù)據(jù)教程
- Vue遞歸組件+Vuex開(kāi)發(fā)樹(shù)形組件Tree--遞歸組件的簡(jiǎn)單實(shí)現(xiàn)
- vue用遞歸組件寫(xiě)樹(shù)形控件的實(shí)例代碼
- 用 Vue.js 遞歸組件實(shí)現(xiàn)可折疊的樹(shù)形菜單(demo)
- Vue.js遞歸組件構(gòu)建樹(shù)形菜單
- vuejs使用遞歸組件實(shí)現(xiàn)樹(shù)形目錄的方法
- 基于 Vue 的樹(shù)形選擇組件的示例代碼
- Vue組件模板形式實(shí)現(xiàn)對(duì)象數(shù)組數(shù)據(jù)循環(huán)為樹(shù)形結(jié)構(gòu)(實(shí)例代碼)
- Vue組件tree實(shí)現(xiàn)樹(shù)形菜單
相關(guān)文章
vue如何使用watch監(jiān)聽(tīng)指定數(shù)據(jù)的變化
這篇文章主要介紹了vue如何使用watch監(jiān)聽(tīng)指定數(shù)據(jù)的變化,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue 動(dòng)態(tài)添加表單實(shí)現(xiàn)動(dòng)態(tài)雙向綁定
動(dòng)態(tài)表單是一個(gè)常見(jiàn)的需求,本文詳細(xì)介紹了Vue.js中實(shí)現(xiàn)動(dòng)態(tài)表單的創(chuàng)建,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Vue.js響應(yīng)式數(shù)據(jù)的簡(jiǎn)單實(shí)現(xiàn)方法(一看就會(huì))
Vue最巧妙的特性之一是其響應(yīng)式系統(tǒng),下面這篇文章主要給大家介紹了關(guān)于Vue.js響應(yīng)式數(shù)據(jù)的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
vue打包后出現(xiàn)空白頁(yè)的原因及解決方式詳解
在項(xiàng)目中很多時(shí)候需要用到vue打包成html不需要放在服務(wù)器上就能瀏覽,根據(jù)官網(wǎng)打包出來(lái)的html直接打開(kāi)是顯示空白,下面這篇文章主要給大家介紹了關(guān)于vue打包后出現(xiàn)空白頁(yè)的原因及解決方式的相關(guān)資料,需要的朋友可以參考下2022-07-07
Vue3計(jì)算屬性computed和監(jiān)聽(tīng)屬性watch區(qū)別解析
計(jì)算屬性適用于對(duì)已有的數(shù)據(jù)進(jìn)行計(jì)算,派生新的數(shù)據(jù),并在模板中使用;而監(jiān)聽(tīng)屬性適用于監(jiān)聽(tīng)數(shù)據(jù)的變化,并執(zhí)行一些特定的操作,根據(jù)具體的需求和場(chǎng)景,選擇適合的機(jī)制這篇文章主要介紹了Vue3計(jì)算屬性computed和監(jiān)聽(tīng)屬性watch,需要的朋友可以參考下2024-02-02
vue.js實(shí)現(xiàn)選項(xiàng)卡切換
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)選項(xiàng)卡切換功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
vue后端傳文件流轉(zhuǎn)化成blob對(duì)象,前端點(diǎn)擊下載返回undefined問(wèn)題
這篇文章主要介紹了vue后端傳文件流轉(zhuǎn)化成blob對(duì)象,前端點(diǎn)擊下載返回undefined問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
Vite+Vue3使用MockJS的實(shí)現(xiàn)示例
寫(xiě)一些純前端的項(xiàng)目時(shí),自己造數(shù)據(jù)有些麻煩,于是我們可以利用mock造一些簡(jiǎn)單的數(shù)據(jù),來(lái)滿足我們的需求,本文主要介紹了Vite+Vue3使用MockJS的實(shí)現(xiàn)示例,感興趣的可以了解一下2024-01-01

