feat: folder

This commit is contained in:
wangdan-fit2cloud 2025-06-18 02:10:36 +08:00
parent 449aa63f85
commit de22ffabc1
8 changed files with 135 additions and 26 deletions

View File

@ -1,6 +1,6 @@
<template> <template>
<el-dialog <el-dialog
:title="$t('components.folder.addFolder')" :title="title"
v-model="dialogVisible" v-model="dialogVisible"
width="720" width="720"
append-to-body append-to-body
@ -54,11 +54,19 @@ import { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales' import { t } from '@/locales'
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
const props = defineProps({
title: {
type: String,
default: t('components.folder.addFolder'),
},
})
const FolderFormRef = ref() const FolderFormRef = ref()
const loading = ref(false) const loading = ref(false)
const dialogVisible = ref<boolean>(false) const dialogVisible = ref<boolean>(false)
const sourceType = ref<any>('') const sourceType = ref<any>('')
const isEdit = ref<boolean>(false)
const folderForm = ref<any>({ const folderForm = ref<any>({
name: '', name: '',
@ -84,23 +92,37 @@ watch(dialogVisible, (bool) => {
desc: '', desc: '',
parent_id: '', parent_id: '',
} }
isEdit.value = false
} }
}) })
const open = (source: string, id: string) => { const open = (source: string, id: string, data?: any) => {
sourceType.value = source sourceType.value = source
folderForm.value.parent_id = id folderForm.value.parent_id = id
if (data) {
folderForm.value.name = data.name
folderForm.value.desc = data.desc
isEdit.value = true
}
dialogVisible.value = true dialogVisible.value = true
} }
const submitHandle = async () => { const submitHandle = async () => {
await FolderFormRef.value.validate((valid: any) => { await FolderFormRef.value.validate((valid: any) => {
if (valid) { if (valid) {
folderApi.postFolder( sourceType.value, folderForm.value, loading).then((res) => { if (isEdit.value) {
MsgSuccess(t('common.createSuccess')) folderApi.putFolder(sourceType.value, folderForm.value, loading).then((res) => {
emit('refresh') MsgSuccess(t('common.editSuccess'))
dialogVisible.value = false emit('refresh')
}) dialogVisible.value = false
})
} else {
folderApi.postFolder(sourceType.value, folderForm.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
} }
}) })
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div> <div class="folder-tree">
<el-input <el-input
v-model="filterText" v-model="filterText"
:placeholder="$t('common.search')" :placeholder="$t('common.search')"
@ -28,19 +28,54 @@
node-key="id" node-key="id"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="custom-tree-node flex align-center"> <div class="flex-between w-full" @mouseenter.stop="handleMouseEnter(data)">
<AppIcon iconName="app-folder" style="font-size: 16px"></AppIcon> <div class="flex align-center">
<span class="ml-8">{{ node.label }}</span> <AppIcon iconName="app-folder" style="font-size: 16px"></AppIcon>
<span class="ml-8">{{ node.label }}</span>
</div>
<div
@click.stop
v-show="hoverNodeId === data.id"
@mouseenter.stop="handleMouseEnter(data)"
@mouseleave.stop="handleMouseleave"
class="mr-16"
>
<el-dropdown trigger="click" :teleported="false">
<el-button text class="w-full">
<el-icon class="rotate-90"><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click.stop="openCreateFolder(data)">
<el-icon><EditPen /></el-icon>
{{ '添加子文件夹' }}
</el-dropdown-item>
<el-dropdown-item @click.stop="openEditFolder(data)">
<el-icon><EditPen /></el-icon>
{{ $t('common.edit') }}
</el-dropdown-item>
<el-dropdown-item divided @click.stop="deleteFolder(data)">
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div> </div>
</template> </template>
</el-tree> </el-tree>
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" :title="title" />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import type { TreeInstance } from 'element-plus' import type { TreeInstance } from 'element-plus'
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
import { t } from '@/locales' import { t } from '@/locales'
import folderApi from '@/api/folder'
defineOptions({ name: 'FolderTree' }) defineOptions({ name: 'FolderTree' })
const props = defineProps({ const props = defineProps({
data: { data: {
@ -51,6 +86,10 @@ const props = defineProps({
type: String, type: String,
default: 'root', default: 'root',
}, },
source: {
type: String,
default: 'APPLICATION',
},
isShared: { isShared: {
type: Boolean, type: Boolean,
default: false, default: false,
@ -68,6 +107,7 @@ interface Tree {
name: string name: string
children?: Tree[] children?: Tree[]
id?: string id?: string
show?: boolean
} }
const defaultProps = { const defaultProps = {
@ -75,15 +115,23 @@ const defaultProps = {
label: 'name', label: 'name',
} }
const emit = defineEmits(['handleNodeClick']) const emit = defineEmits(['handleNodeClick', 'refreshTree'])
const treeRef = ref<TreeInstance>() const treeRef = ref<TreeInstance>()
const filterText = ref('') const filterText = ref('')
const hoverNodeId = ref<string | undefined>('')
const title = ref('')
watch(filterText, (val) => { watch(filterText, (val) => {
treeRef.value!.filter(val) treeRef.value!.filter(val)
}) })
function handleMouseEnter(data: Tree) {
hoverNodeId.value = data.id
}
function handleMouseleave() {
hoverNodeId.value = ''
}
const filterNode = (value: string, data: Tree) => { const filterNode = (value: string, data: Tree) => {
if (!value) return true if (!value) return true
return data.name.includes(value) return data.name.includes(value)
@ -97,6 +145,26 @@ const handleSharedNodeClick = () => {
treeRef.value?.setCurrentKey(undefined) treeRef.value?.setCurrentKey(undefined)
emit('handleNodeClick', { id: 'share', name: t(props.shareTitle) }) emit('handleNodeClick', { id: 'share', name: t(props.shareTitle) })
} }
function deleteFolder(row: Tree) {
folderApi.delFolder(row.id as string, props.source).then(() => {
emit('refreshTree')
})
}
const CreateFolderDialogRef = ref()
function openCreateFolder(row: Tree) {
title.value = '添加子文件夹'
CreateFolderDialogRef.value.open(props.source, row.id)
}
function openEditFolder(row: Tree) {
title.value = '编辑文件夹'
CreateFolderDialogRef.value.open(props.source, row.id, row)
}
function refreshFolder() {
emit('refreshTree')
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.shared-knowledge { .shared-knowledge {

View File

@ -1,16 +1,22 @@
export enum DeviceType { export enum DeviceType {
Mobile = 'Mobile', Mobile = 'Mobile',
Desktop = 'Desktop' Desktop = 'Desktop',
} }
export enum ValidType { export enum ValidType {
Application = 'application', Application = 'application',
Knowledge = 'knowledge', Knowledge = 'knowledge',
User = 'user' User = 'user',
} }
export enum ValidCount { export enum ValidCount {
Application = 5, Application = 5,
Knowledge = 50, Knowledge = 50,
User = 2 User = 2,
}
export enum FolderSource {
KNOWLEDGE = 'KNOWLEDGE',
APPLICATION = 'APPLICATION',
TOOL = 'TOOL',
} }

View File

@ -52,9 +52,9 @@
> >
<template #default="{ row }"> <template #default="{ row }">
<div class="flex-between"> <div class="flex-between">
<auto-tooltip :content="row.abstract"> <span :title="row.abstract">
{{ row.abstract }} {{ row.abstract }}
</auto-tooltip> </span>
<div @click.stop v-show="mouseId === row.id && row.id !== 'new'"> <div @click.stop v-show="mouseId === row.id && row.id !== 'new'">
<el-dropdown trigger="click" :teleported="false"> <el-dropdown trigger="click" :teleported="false">
<el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon> <el-icon class="rotate-90 mt-4"><MoreFilled /></el-icon>

View File

@ -52,7 +52,7 @@
ref="treeRef" ref="treeRef"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="custom-tree-node flex align-center lighter"> <div class="flex align-center lighter">
<img <img
src="@/assets/fileType/file-icon.svg" src="@/assets/fileType/file-icon.svg"
alt="" alt=""

View File

@ -3,11 +3,13 @@
<template #left> <template #left>
<h4 class="p-16 pb-0">{{ $t('views.knowledge.title') }}</h4> <h4 class="p-16 pb-0">{{ $t('views.knowledge.title') }}</h4>
<folder-tree <folder-tree
:source="FolderSource.KNOWLEDGE"
:data="folderList" :data="folderList"
:currentNodeKey="currentFolder?.id" :currentNodeKey="currentFolder?.id"
@handleNodeClick="folderClickHandel" @handleNodeClick="folderClickHandel"
class="p-8" class="p-8"
isShared isShared
@refreshTree="refreshFolder"
/> />
</template> </template>
<SharedWorkspace v-if="currentFolder.id === 'share'"></SharedWorkspace> <SharedWorkspace v-if="currentFolder.id === 'share'"></SharedWorkspace>
@ -44,8 +46,13 @@
</el-select> </el-select>
</div> </div>
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<el-button type="primary" class="ml-8" <el-button
v-hasPermission="[RoleConst.ADMIN.getWorkspaceRole,PermissionConst.KNOWLEDGE_CREATE.getWorkspacePermission]" type="primary"
class="ml-8"
v-hasPermission="[
RoleConst.ADMIN.getWorkspaceRole,
PermissionConst.KNOWLEDGE_CREATE.getWorkspacePermission,
]"
> >
{{ $t('common.create') }} {{ $t('common.create') }}
<el-icon class="el-icon--right"> <el-icon class="el-icon--right">
@ -217,8 +224,13 @@
<template #mouseEnter> <template #mouseEnter>
<div @click.stop> <div @click.stop>
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<el-button text @click.stop <el-button
v-hasPermission="[RoleConst.ADMIN.getWorkspaceRole,PermissionConst.KNOWLEDGE_EDIT.getWorkspacePermission]" text
@click.stop
v-hasPermission="[
RoleConst.ADMIN.getWorkspaceRole,
PermissionConst.KNOWLEDGE_EDIT.getWorkspacePermission,
]"
> >
<el-icon> <el-icon>
<MoreFilled /> <MoreFilled />
@ -297,6 +309,7 @@ import useStore from '@/stores'
import { numberFormat } from '@/utils/common' import { numberFormat } from '@/utils/common'
import { t } from '@/locales' import { t } from '@/locales'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { FolderSource } from '@/enums/common'
import { PermissionConst, RoleConst } from '@/utils/permission/data' import { PermissionConst, RoleConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index' import { hasPermission } from '@/utils/permission/index'
@ -378,7 +391,7 @@ function getList() {
function getFolder() { function getFolder() {
const params = {} const params = {}
folder.asyncGetFolder('KNOWLEDGE', params, loading).then((res: any) => { folder.asyncGetFolder(FolderSource.KNOWLEDGE, params, loading).then((res: any) => {
folderList.value = res.data folderList.value = res.data
currentFolder.value = res.data?.[0] || {} currentFolder.value = res.data?.[0] || {}
getList() getList()
@ -401,7 +414,7 @@ function clickFolder(item: any) {
const CreateFolderDialogRef = ref() const CreateFolderDialogRef = ref()
function openCreateFolder() { function openCreateFolder() {
CreateFolderDialogRef.value.open('KNOWLEDGE', currentFolder.value.parent_id) CreateFolderDialogRef.value.open(FolderSource.KNOWLEDGE, currentFolder.value.parent_id)
} }
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>() const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()

View File

@ -169,7 +169,7 @@ const searchType = ref('title')
const handleClick = (e: MouseEvent, ele: any) => { const handleClick = (e: MouseEvent, ele: any) => {
e.preventDefault() e.preventDefault()
document.querySelector(`${ele}`).scrollIntoView({ behavior: 'smooth', block: 'start' }) document.querySelector(`${ele}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })
} }
// //

View File

@ -52,7 +52,7 @@
ref="treeRef" ref="treeRef"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<div class="custom-tree-node flex align-center lighter"> <div class="flex align-center lighter">
<img <img
src="@/assets/fileType/file-icon.svg" src="@/assets/fileType/file-icon.svg"
alt="" alt=""