html做的网页怎么变成网站,一键优化大师,咸阳网站建设费用,西安市发布最新消息欢迎点击关注-前端面试进阶指南#xff1a;前端登顶之巅-最全面的前端知识点梳理总结
*分享一个使用比较久的#x1fa9c;
效果展示#xff1a; 近期的一个功能需求#xff0c;实现一个树形结构#xff1a;可点击#xff0c;可拖拽#xff0c;右侧数据可以拖拽到对应的…欢迎点击关注-前端面试进阶指南前端登顶之巅-最全面的前端知识点梳理总结
*分享一个使用比较久的
效果展示 近期的一个功能需求实现一个树形结构可点击可拖拽右侧数据可以拖拽到对应的节点内可创建文件夹、可创建文件、编辑文件名、可删除等等 渲染列表数据的时候列表的子项还是列表。针对多层级的列表我们采用tree的方式从根节点一次创建绑定子节点的方式可以递归式的调用本身对我们的树形结构进行展示并且支持多余的树形拓展 代码区域 1、创建TreeList文件夹其中创建fonts文件夹、index.js文件、tools.js文件、Tree.js文件、VueTreeList.vue文件 2、fonts文件夹主要用来存放icon图标的这里就不展示了依据项目在阿里矢量图标内新增然后在VueTreeList.vue内进行替换 使用
源代码地址 vue-tree-listrefVueTreeList:modeltreeData // 初识数据源treeData: new Tree([]),:activeIdactiveId // 选中的id及背景色default-leaf-node-name新建文件 // 默认创建文件名称default-tree-node-name新建目录 // 默认创建文件夹名称:default-expandedisExpanded // 默认是否展开文件夹clickhandleOnClick // 点击当前节点moveGraphmoveGraph // 右侧数据拖拽至当前节点触发可删除不使用add-nodehandleOnAddNode // 点击创建节点end-edithandleOnChangeNameEnd // 点击编辑当前节点的名称delete-nodedeleteNode // 点击删除当前节点drophandleDrop // 拖拽上下节点我已注释掉依据需求自身放开drop-beforehadnleDropBefore // 开始拖拽之前的触发函数drop-afterhandleDropAfter // 结束拖拽完之后的触发函数loadDataApi/api/tree-dir // 点击左侧icon触发远程加载填充子数据Api接口/vue-tree-list1.1 创建index.js文件也是往外暴露的入口文件(文章并未按照思路排序)
/*** Created by ayou on 17/7/21.*/import VueTreeList from ./VueTreeList
import { Tree, TreeNode } from ./TreeVueTreeList.install Vue {Vue.component(VueTreeList.name, VueTreeList)
}export default VueTreeListexport { Tree, TreeNode, VueTreeList }
1.2 创建tools.js文件
/*** Created by ayou on 18/2/6.*/var handlerCacheexport const addHandler function(element, type, handler) {handlerCache handlerif (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 创建Tree.js文件
import { traverseTree } from ./toolsexport class TreeNode {constructor(data) {const { id, isLeaf, editNode } datathis.id typeof id ! undefined ? id : Math.floor(new Date().valueOf() * (Math.random() 1))this.parent nullthis.children nullthis.isLeaf !!isLeafthis.editNode editNode || falsefor (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 thischild.pid this.id})this.children.push(...children)} else {const child childrenchild.parent thischild.pid this.idthis.children.push(child)}}// remove selfremove() {const parent this.parentconst 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.parentwhile (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 targetthis.pid target.idif (!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.parentthis.pid target.parent.idreturn true}insertBefore(target) {if (!this._canInsert(target)) returnconst pos target.parent.findChildIndex(target)target.parent.children.splice(pos, 0, this)}insertAfter(target) {if (!this._canInsert(target)) returnconst 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 创建VueTreeList.vue文件 说明支持点击创建远程数据loadDataAjax方法需要自己研究功能小编已实现现有代码基本功能已经完善需要依赖自己的项目进行变更和更改treeNode可以直接访问和修改数据源的需要读者自己发掘 templatediv :class[vtl, isMobile isMobile]divv-ifmodel.name ! root:idmodel.idclassvtl-node:class{ vtl-leaf-node: model.isLeaf, vtl-tree-node: !model.isLeaf }div classvtl-border vtl-up :class{ vtl-active: isDragEnterUp } /div:class[vtl-node-main, { vtl-active: isDragEnterNode }]:style{ fontSize: 10px }dropdropmouseovermouseOvermouseoutmouseOutclick.stophandleCurClickdragoverdragOverdragenterdragEnterdragleavedragLeavespanv-if!model.childrenclassvtl-caret vtl-is-smalliclassvtl-iconstylecursor: pointer; width: 11px;/i/spanspanv-ifmodel.childrenclassvtl-caret vtl-is-smalliclassvtl-icon:classcaretClassstylecursor: pointerclick.prevent.stoptoggle/iiv-ifisRemoteLoadingclassCustom_demo-spin-icon-load ivu-icon ivu-icon-ios-loadingstylefont-size: 16px; margin-right: 3px; margin-top: -2px/i/spanspan v-ifmodel.isLeafslotnameleafNodeIcon:expandedexpanded:modelmodel:rootrootNodeistylecursor: pointerclassvtl-icon vtl-menu-icon vtl-icon-file/i/slot/spanspan v-elseslotnametreeNodeIcon:expandedexpanded:modelmodel:rootrootNodeimgclasscustom_imgstylewidth:15px;margin-right: 3pxsrc../../../static/img/folder.pngalt//slot/spandivv-if!editable:class[vtl-node-content,isShowClickBackg,{ custom_class_hiddle: isHover, custom_class_click: model.isLeaf }]:style{color: model.matched ? #D9262C : null,cursor: pointer}slotnameleafNameDisplay:expandedexpanded:modelmodel:rootrootNode{{ model.name }}/slot/divinputv-ifeditable || handleInitEditable(model)classvtl-inputtypetextrefnodeInput:valuemodel.nameinputupdateNameblursetUnEditablekeyup.entersetUnEditable/div classvtl-operation v-showisHover!-- 新增设备 --spantitle新增设备v-btn-keyrolespermiss.addDeviceclick.stop.preventcreateChildv-if(!model.isDevice || model.isDir false) $route.path ! /autoMonitorBoardslotnameaddLeafNodeIcon:expandedexpanded:modelmodel:rootrootNodei classvtl-icon vtl-icon-plus/i/slot/span!-- 编辑名称 --spantitle编辑名称v-btn-keyrolespermiss.editFolderclick.stop.preventsetEditable(true)v-if!model.editNodeDisabled !model.isDevice $route.path ! /autoMonitorBoardslotnameeditNodeIcon:expandedexpanded:modelmodel:rootrootNodei classvtl-icon vtl-icon-edit/i/slot/span!-- 删除节点 --spantitle删除节点click.stop.preventdelNodestyleline-height: 14px;v-if$route.path ! /autoMonitorBoardv-btn-keyrolespermiss.deleteFolderslotnamedelNodeIcon:expandedexpanded:modelmodel:rootrootNodei classvtl-icon vtl-icon-trash/i/slot/span!-- 创建子目录 --span:titledefaultAddTreeNodeTitleclick.stop.preventaddChild(false)v-btn-keyrolespermiss.createFolderv-if!model.addTreeNodeDisabled !model.isLeaf !model.isDevice $route.path ! /autoMonitorBoardslotnameaddTreeNodeIcon:expandedexpanded:modelmodel:rootrootNodei classvtl-icon vtl-icon-folder-plus-e/i/slot/span!-- 详情按钮 --spantitle设备详情stylemargin-top: -1pxclick.stophandleViewDetailv-btn-keyrolespermiss.folderDetailv-if!model.addTreeNodeDisabled model.isLeaf !model.isDeviceIcon stylecolor: #d9262c typeios-paper-outline //span/div/divdivv-ifmodel.children model.children.length 0 (expanded || model.expanded)classvtl-border vtl-bottom:class{ vtl-active: isDragEnterBottom }/div/divdiv:class{ vtl-tree-margin: model.name ! root }v-ifisFolder (model.name root || expanded || model.expanded)item:modelmodel:titlemodel.namev-formodel in model.children:keymodel.id:activeIdactiveId:loadDataApiloadDataApi:rolespermissrolespermiss:requestHeaderrequestHeader:default-tree-node-namedefaultTreeNodeName:default-leaf-node-namedefaultLeafNodeName:default-expandeddefaultExpandedtemplate v-slot:leafNameDisplayslotPropsslot nameleafNameDisplay v-bindslotProps //templatetemplate v-slot:addTreeNodeIconslotPropsslot nameaddTreeNodeIcon v-bindslotProps //templatetemplate v-slot:addLeafNodeIconslotPropsslot nameaddLeafNodeIcon v-bindslotProps //templatetemplate v-slot:editNodeIconslotPropsslot nameeditNodeIcon v-bindslotProps //templatetemplate v-slot:delNodeIconslotPropsslot namedelNodeIcon v-bindslotProps //templatetemplate v-slot:leafNodeIconslotPropsslot nameleafNodeIcon v-bindslotProps //templatetemplate v-slot:treeNodeIconslotPropsslot nametreeNodeIcon v-bindslotProps //template/item/div/div
/templatescript
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});},// 点击左侧箭头异步加载子节点数据toggle() {if (this.model.expanded) {this.expanded false;} else {this.expanded !this.expanded;}this.model.expanded false;},// 删除节点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();}},// 异步请求数据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 || 请求失败));}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;},// 点击当前节点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});},// 新增子节点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(移动成功);this.rootNode.$emit(moveGraph);} else {// 提示错误this.$Notice.error({title: 查询失败,desc: response.message || 请求失败,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);}
};
/scriptstyle langless
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