Bootstrap

vue 树状结构文件夹格式 (details summary)

效果展示

 定义内部样式组件

这里只是定义基本的样式

<template>
  <details class="details" :class="{noPrefix}" :open="fileTree.open" ref="detailsRef">
    <summary class="summary">
      <!--   基本图标   -->
      <el-icon v-if="fileTree.elIcon &&  (!isEdit || fileTree.notEdit)" class="treeIcon"
               :style="{color:fileTitleColor,fontSize: fileTree.fontSize || '16px'}">
        <component :is="fileTree.elIcon || Folder"></component>
      </el-icon>
      <!--  编辑图标    -->
      <el-icon v-else class="treeIcon"
               :style="{color:fileTitleColor,fontSize: fileTree.fontSize || '16px'}" @click.stop="editHandle">
        <EditPen/>
      </el-icon>
      <!--  内容展示   -->
      <template v-if="contentFlag">
        <span :style="{color: fileTitleColor,fontSize: fileTree.fontSize || '16px'}" @click="handleClick">
          {{ fileTree.label }}
          <el-icon v-if="isEdit && !fileTree.notEdit" style="color:#FF0000" @click="removeHandle"><Remove/></el-icon>
        </span>
      </template>
      <!--  输入框  这个输入框是自定义的样式  -->
      <template v-else>
        <self-input v-model="fileTree.label" :allocation="{fouc:true}" @blur="handleBlur"/>
      </template>
    </summary>
    <slot name="fileTree" v-if="$slots.fileTree"></slot>
  </details>
</template>


<script lang="ts" setup>
import {Folder, Remove, EditPen} from '@element-plus/icons-vue'
import type {UploadTreeType} from "@/types/stricture";
import {computed, onMounted, ref} from "vue";
import SelfInput from "@/components/CockpitForm/SelfInput.vue";

const props = defineProps<{
  fileTree: UploadTreeType,
  noPrefix?: boolean
  isEdit?: boolean
}>()

const openState = ref<boolean>(false)

const fileTitleColor = computed(() => {
  let defaultColor = props.fileTree.color || '#fff';
  return openState.value ? (props.fileTree.openColor || defaultColor) : defaultColor
})

const detailsRef = ref<HTMLElement>()
const initOpenStatus = () => {
  detailsRef.value && detailsRef.value?.addEventListener("toggle", (event) => {
    openState.value = detailsRef.value?.open
  });
}

onMounted(initOpenStatus)

const emit = defineEmits(['removeHandle'])

const removeHandle = () => {
  emit('removeHandle', props.fileTree)
}

const contentFlag = ref<boolean>(true)

const editHandle = (e) => {
  // 阻止默认事件 不然会触发展开合起事件
  e.preventDefault()
  contentFlag.value = !contentFlag.value
}

//  失去焦点
const handleBlur = () => {
  contentFlag.value = true
}

//点击事件
const handleClick = () => {
  emit('itemClick')
}
</script>
<style lang="scss" scoped>


details {
  position: relative;
  color: #fff;
  padding-top: 10px;
  padding-left: 28px;
  font-size: 16px;

  &::before {
    position: absolute;
    top: -10px;
    left: 7px;
    content: '';
    width: 1px;
    height: 100%;
    border-right: 2px dashed #2373c1;
    //background: #fff;
  }

  summary {
    position: relative;
    display: flex;
    align-items: center;
    list-style: none;
    cursor: pointer;
    z-index: 1;
    background: #1c3e71;


    &::before {
      position: absolute;
      top: 50%;
      left: -15px;
      content: '';
      width: 15px;
      height: 1px;
      border-bottom: 2px dashed #2373c1;
      //background: #fff;
    }
  }

  &.noPrefix {
    padding-top: 0px;
    padding-left: 0px;

    &::before {
      display: none;
    }

    & > summary {
      &::before {
        display: none;
      }
    }
  }
}

.treeIcon {
  font-size: 16px;
  vertical-align: middle;
  margin-right: 6px;
}
</style>

定义树状结构组件

这里循环组件

<template>
  <div class="fileItem" v-for="(item,index) in fileList" :class="{firstLevel}">
    <selfFileTree :fileTree="item" :noPrefix="!!firstLevel" :isEdit="!firstLevel  &&   isProjectSetEdit"
                  @removeHandle="removeHandle(item,index,fileList)" @itemClick="itemClick(item,fileList)">
      <template #fileTree v-if="item.children?.length">
        <Tree :fileList="item.children" :isEdit="isProjectSetEdit" @itemClick="itemClick"></Tree>
      </template>
    </selfFileTree>
  </div>
</template>


<script lang="ts" setup>
import selfFileTree from '@/components/CockpitStructure/FileTree.vue'
import Tree from '@/components/CockpitStructure/Tree.vue'; // 这个是本组件 

import {UploadTreeType} from "@/types/stricture";
import {isProjectSetEdit} from "@/views/CockpitManagement/hooks/ProjectSetting";

const props = defineProps<{
  fileList: UploadTreeType[],
  firstLevel?: boolean,
  isEdit?: boolean
}>()


const removeHandle = (item: UploadTreeType, index: number, fileList: UploadTreeType[]) => {
  fileList?.splice?.(index, 1)
}

const emits = defineEmits(['itemClick'])
const itemClick = (item: UploadTreeType, fileList: UploadTreeType[]) => {
  emits('itemClick', item, fileList)
}
</script>

<style lang="scss" scoped>
.fileItem {
  &.firstLevel {
    margin-bottom: 15px;
  }
}
</style>

组件应用


    <div class="attachment boxBorder">
      <div class="attachmentTitle">
        <el-icon class="elIcon">
          <Paperclip/>
        </el-icon>
        附件
      </div>
      <div class="attachmentBody">
        <Tree :fileList="fileList" :firstLevel="true" :isEdit="isProjectSetEdit" @itemClick="addFileHandle"/>
      </div>
    </div>

编辑状态下的新增和添加文件方法

export const fileList = ref<UploadTreeType[]>([])
// 编辑按钮
const editId = 'editId'
export const setIsProjectSetEdit = (value?: boolean) => {
    value = isBoolean(value) ? value : !isProjectSetEdit.value
    isProjectSetEdit.value = value
    let editObj = {
        id: editId,
        label: '新增',
        elIcon: markRaw(CirclePlus),
        color: '#FFF',
        notEdit: true
    }
    const notEdit = (el: UploadTreeType) => {
        el.children = el.children?.filter(el => el.id !== editId)
    }
    // 因为业务需求这里改成了单层的了 如果要无限的文件夹可以进行递归处理
    const setChildren = (item: UploadTreeType) => {
        if (!item?.children) item.children = []
        value ? item.children.push(editObj) : notEdit(item)
    }
    fileList.value.forEach(setChildren)
}

// 添加数组内容
export const addFileHandle = (item: UploadTreeType, list: UploadTreeType[]) => {
    if (item.id !== editId) return
    let obj = {
        id: guid(),
        label: '未命名文件',
        elIcon: markRaw(Folder),
        color: '#FFF',
        openColor: '#FFE500',
        open: true,
    }
    list.splice(list.length - 1, 0, obj)
}

;