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: "查詢失敗", 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-04Vue 動(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-12Vue.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-03vue打包后出現(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-07Vue3計(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-02vue.js實(shí)現(xiàn)選項(xiàng)卡切換
這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)選項(xiàng)卡切換功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03vue后端傳文件流轉(zhuǎn)化成blob對(duì)象,前端點(diǎn)擊下載返回undefined問(wèn)題
這篇文章主要介紹了vue后端傳文件流轉(zhuǎn)化成blob對(duì)象,前端點(diǎn)擊下載返回undefined問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12Vite+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