123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- <template>
- <div class="vtl-node" :id="model.id" :class="{ 'vtl-leaf-node': !isFolder, 'vtl-tree-node': isFolder }">
- <div
- :class="treeNodeClass"
- :draggable="draggable"
- @dragover="dragOver"
- @drop="drop"
- @dragstart="dragStart"
- @mouseover="mouseOver"
- @dragenter="dragEnter"
- @dragleave="dragLeave"
- @mouseout="mouseOut"
- @click.stop="toggle"
- >
- <div class="vtl-border-text">
- <template v-if="isFolder">
- <slot v-if="expanded" :item="{ title: model.title, isFolder: true, expanded: true }" name="icon"> </slot>
- <slot v-else :item="{ title: model.title, isFolder: true, expanded: false }" name="icon"></slot>
- </template>
- <slot v-else :item="{ title: model.title, isFolder: false }" name="icon"></slot>
- <span class="vtl-node-content ellipsis" v-if="!editable && !model.isAdd">
- {{ model.title }}
- </span>
- <input v-else class="vtl-input" type="text" ref="nodeInput" v-model="model.title" @blur="setUnEditable" />
- </div>
- <div class="vtl-operation" v-show="isHover && !editable && !model.isAdd">
- <!-- <span @click.stop.prevent="addChildFolder" v-if="isFolder">
- <slot name="operation" type="addFolder"></slot>
- </span> -->
- <span @click.stop.prevent="addChildDocument" v-if="isFolder">
- <slot name="operation" type="addDocument"></slot>
- </span>
- <span @click.stop.prevent="setEditable">
- <slot name="operation" type="Editable"></slot>
- </span>
- <span @click.stop.prevent="delNode">
- <slot name="operation" type="deleteNode"></slot>
- </span>
- </div>
- </div>
- </div>
- <div class="vtl-tree-margin" v-show="expanded" v-if="isFolder">
- <!-- 这里无法使用$attr来透传属性官方还未解决此bug -->
- <treeList
- @on-click="(depth) => $emit('onClick', depth)"
- @change-name="(depth) => $emit('changeName', depth)"
- @delete-node="(depth) => $emit('deleteNode', depth)"
- @add-node="(depth) => $emit('addNode', depth)"
- @on-drop="(depth) => $emit('onDrop', depth)"
- @add-folder="(depth) => $emit('addFolder', depth)"
- @dragStart="(depth) => $emit('dragStart', depth)"
- @setDragEnterNode="setDragEnterNode"
- @setDragFile="setDragFile"
- @setDragFolder="setDragFolder"
- v-for="newmodel in model.children"
- :selected="selected"
- :model="newmodel"
- :key="newmodel.id"
- >
- <template #icon="slotProps">
- <slot name="icon" v-bind="slotProps"></slot>
- </template>
- <template #operation="slotProps">
- <slot name="operation" v-bind="slotProps"></slot>
- </template>
- </treeList>
- </div>
- </template>
- <script setup lang="ts">
- import { computed, ref, watchEffect } from 'vue';
- interface IFileSystem {
- id: string;
- title: string;
- pid: string;
- isFolder: boolean;
- isAdd: boolean;
- children?: IFileSystem[];
- }
- // 吐出去的事件
- const emit = defineEmits([
- 'onClick',
- 'changeName',
- 'deleteNode',
- 'addNode',
- 'addFolder',
- 'onDrop',
- 'setDragEnterNode',
- 'setDragFile',
- 'setDragFolder',
- 'dragStart',
- ]);
- // 拿到传入的值
- const props = withDefaults(
- defineProps<{
- model: IFileSystem;
- draggable?: boolean;
- selected?: IFileSystem;
- }>(),
- {
- draggable: true,
- }
- );
- //是否移入
- const isHover = ref(false);
- // 修改目录名字
- const editable = ref(false);
- // 拖拽移入
- const isDragEnterNode = ref(false);
- // 是否拖拽文件
- const isDragFile = ref(false);
- // 是否展开
- const expanded = ref(true);
- // inputRef
- const nodeInput = ref(null);
- // 是否是文件夹
- const isFolder = computed(() => {
- return props.model.isFolder;
- });
- const isSelected = computed(() => props.selected.id === props.model.id);
- // 拖拽样式
- const treeNodeClass = computed(() => {
- return {
- 'vtl-node-main': true,
- 'vtl-active': isDragEnterNode.value,
- 'vtl-active-file': isDragFile.value,
- selected: isSelected.value,
- };
- });
- // 最后一个移入的内容保存为了防止重复移入
- let lastenter = null;
- // 删除目录
- const delNode = () => {
- emit('deleteNode', {
- ...props.model,
- eventType: 'delete',
- });
- };
- // 选中effect
- watchEffect(() => {
- const $input = nodeInput.value;
- if ($input) {
- // 获取焦点
- $input.focus();
- // 设置光标位置
- $input.setSelectionRange(0, $input.value.length);
- }
- });
- // 编辑目录名字
- const setEditable = () => {
- editable.value = true;
- props.model.isAdd = false; //lxh
- };
- // 修改目录名字
- const setUnEditable = (e) => {
- if (props.model.isAdd) {
- console.log('新增文档失去焦点');
- props.model.isAdd = false;
- emit('addNode', {
- id: props.model.id,
- isFolder: false,
- newName: props.model.title,
- });
- } else if (editable.value) {
- console.log('编辑文档失去焦点');
- editable.value = false;
- props.model.title = e.target.value;
- emit('changeName', {
- id: props.model.id,
- pid: props.model.pid,
- isAdd: props.model.isAdd,
- newName: e.target.value,
- eventType: 'blur',
- isFolder: isFolder.value,
- });
- }
- };
- // 展开收起
- const toggle = () => {
- if (isFolder.value) {
- expanded.value = !expanded.value;
- emit('onClick', {
- ...props.model,
- }); //lxh
- } else {
- emit('onClick', {
- ...props.model,
- });
- }
- };
- // 拖拽结束
- const mouseOver = () => {
- isHover.value = true;
- };
- // 移出
- const mouseOut = () => {
- isHover.value = false;
- };
- // // 添加目录
- // const addChildFolder = () => {
- // props.model.isAdd = true; //lxh
- // props.model.title = '';//lxh
- // emit('addFolder', {
- // id: props.model.id,
- // isFolder: true,
- // });
- // };
- // 添加文件
- const addChildDocument = (node) => {
- props.model.title = ''; //lxh
- props.model.isAdd = true; //lxh
- editable.value = false; //
- };
- // 拖拽开始
- const dragStart = () => {
- console.log(0);
- emit('dragStart', {
- ...props.model,
- });
- };
- const dragOver = (e) => {
- e.preventDefault();
- return true;
- };
- const dragEnter = (e) => {
- lastenter = e.target;
- console.log('进入', props.model.id);
- // 由于 dragEnter 发生在 dragLeave 之前,导致必须要使用定时器做一个延时
- setTimeout(() => {
- if (isFolder.value) {
- expanded.value = true;
- isDragFile.value = true;
- } else {
- emit('setDragFile', true);
- }
- isDragEnterNode.value = true;
- emit('setDragEnterNode', true);
- });
- };
- const dragLeave = (e) => {
- // 为了防止多次选中问题
- if (lastenter == e.target) {
- console.log('离开', props.model.id);
- if (isFolder.value) {
- isDragFile.value = false;
- } else {
- emit('setDragFile', false);
- }
- emit('setDragEnterNode', false);
- isDragEnterNode.value = false;
- }
- };
- const drop = (e) => {
- isDragFile.value = false;
- isDragEnterNode.value = false;
- emit('setDragEnterNode', false);
- emit('setDragFile', false);
- // 为了获取路径需要判断是不是文件夹,如果不是文件夹向上找
- if (isFolder.value) {
- emit('onDrop', props.model);
- } else {
- if (props.model.pid) {
- emit('setDragFolder');
- } else {
- emit('onDrop', props.model);
- }
- }
- };
- const setDragEnterNode = (bol) => {
- isDragEnterNode.value = bol;
- };
- const setDragFile = (bol) => {
- isDragFile.value = bol;
- };
- // 找到文件夹
- const setDragFolder = () => {
- emit('onDrop', props.model);
- };
- </script>
- <style lang="less">
- .vtl-node {
- .vtl-node-main {
- display: flex;
- align-items: center;
- padding: 2px 0 2px 1rem;
- cursor: pointer;
- &:hover {
- .vtl-border-text {
- width: 80%;
- }
- }
- .vtl-border-text {
- display: flex; //lxh
- flex: 1;
- align-items: center; //lxh
- width: 100%;
- .iconfont {
- width: 16px;
- height: 16px;
- vertical-align: text-bottom;
- }
- }
- &.selected {
- background-color: rgba(53, 147, 255, 0.2);
- }
- .vtl-input {
- border: none;
- max-width: 150px;
- padding: 5px 0;
- padding-left: 5px;
- margin-left: 5px;
- &:focus {
- outline: none;
- }
- }
- .vtl-node-content {
- color: #fff;
- padding-left: 5px;
- font-size: 14px;
- width: 80%;
- display: inline-block;
- vertical-align: bottom;
- }
- &:hover {
- .vtl-node-content {
- color: #fff;
- overflow: hidden;
- }
- }
- &.vtl-active {
- * {
- pointer-events: none;
- }
- }
- &.vtl-active-file {
- outline: 2px dashed #353f51;
- }
- .vtl-operation {
- padding-right: 10px;
- }
- }
- }
- .vtl-tree-margin {
- padding-left: 1em;
- }
- </style>
|