效果展示
定义内部样式组件
这里只是定义基本的样式
<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)
}