feat: document

This commit is contained in:
wangdan-fit2cloud 2025-06-18 20:24:29 +08:00
parent 34448a2623
commit 516c88a510
15 changed files with 611 additions and 485 deletions

View File

@ -6,7 +6,6 @@ const ModelRouter = {
redirect: '/knowledge',
component: () => import('@/layout/layout-template/SimpleLayout.vue'),
children: [
{
path: '/knowledge/system/document/upload/shared',
name: 'UploadDocumentSharedSystem',
@ -14,6 +13,13 @@ const ModelRouter = {
component: () => import('@/views/shared/document-shared/UploadDocument.vue'),
hidden: true,
},
{
path: '/knowledge/system/import/shared',
name: 'ImportLarkDocumentShared',
meta: { activeMenu: '/knowledge' },
component: () => import('@/views/shared/document-shared/ImportLarkDocument.vue'),
hidden: true,
},
],
}

View File

@ -20,6 +20,13 @@ const ModelRouter = {
component: () => import('@/views/document/UploadDocument.vue'),
hidden: true,
},
{
path: '/knowledge/import',
name: 'ImportLarkDocument',
meta: { activeMenu: '/knowledge' },
component: () => import('@/views/document/ImportLarkDocument.vue'),
hidden: true
},
],
}

View File

@ -218,7 +218,7 @@ const systemRouter = {
},
{
path: '/system/authentication',
name: 'authentication',
name: 'SystemAuthentication',
meta: {
title: 'views.system.authentication.title',
activeMenu: '/system',

View File

@ -58,18 +58,6 @@ const useKnowledgeStore = defineStore('knowledg', {
})
})
},
async asyncSyncKnowledge(id: string, sync_type: string, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
knowledgeApi
.putSyncWebKnowledge(id, sync_type, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
},
})

View File

@ -3,6 +3,7 @@ import type { knowledgeData } from '@/api/type/knowledge'
import type { UploadUserFile } from 'element-plus'
import knowledgeApi from '@/api/knowledge/knowledge'
import { type Ref } from 'vue'
import useFolderStore from './folder'
export interface knowledgeStateTypes {
baseInfo: knowledgeData | null
@ -46,6 +47,46 @@ const useKnowledgeStore = defineStore('knowledge', {
})
})
},
async asyncGetTreeRootKnowledge(loading?: Ref<boolean>) {
const folder = useFolderStore()
return Promise.all([
folder.asyncGetFolder('KNOWLEDGE', {}, loading),
this.asyncGetRootKnowledge(loading),
])
.then((res: any) => {
const folderList = res[0].data
const knowledgeList = res[1].data
const arrMap: any = {}
function buildIdMap(arr: any) {
arr.forEach((item: any) => {
arrMap[item.id] = item
// 递归处理子节点
if (item.children && item.children.length > 0) {
buildIdMap(item.children)
}
})
}
buildIdMap(folderList)
knowledgeList
.filter((v: any) => v.resource_type !== 'folder')
.forEach((item: any) => {
const targetFolder = arrMap[item.folder_id]
if (targetFolder) {
// 检查是否已有相同ID的子节点避免重复插入
const existingChild = targetFolder.children.find(
(child: any) => child.id === item.id,
)
if (!existingChild) {
targetFolder.children.push(item)
}
}
})
return Promise.resolve(folderList)
})
.catch((error) => {
return Promise.reject(error)
})
},
async asyncGetKnowledgeDetail(knowledge_id: string, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
knowledgeApi
@ -58,18 +99,6 @@ const useKnowledgeStore = defineStore('knowledge', {
})
})
},
async asyncSyncKnowledge(id: string, sync_type: string, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => {
knowledgeApi
.putSyncWebKnowledge(id, sync_type, loading)
.then((data) => {
resolve(data)
})
.catch((error) => {
reject(error)
})
})
},
},
})

View File

@ -215,7 +215,7 @@ defineExpose({ open })
}
.max-height {
max-height: calc(100vh - 260px);
min-height: 300px;
min-height: 500px;
}
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<div class="create-knowledge p-12-24">
<div class="flex align-center mb-16">
<back-button to="-1" style="margin-left: -4px"></back-button>
<h3 style="display: inline-block">{{ $t('views.document.importDocument') }}</h3>
</div>
<el-card style="--el-card-padding: 0">
<div class="create-knowledge__main flex" v-loading="loading">
<div class="create-knowledge__component main-calc-height">
<div class="upload-document p-24" style="min-width: 850px">
<h4 class="title-decoration-1 mb-8">
{{ $t('views.document.feishu.selectDocument') }}
</h4>
<el-form
ref="FormRef"
:model="form"
:rules="rules"
label-position="top"
require-asterisk-position="right"
>
<div class="mt-16 mb-16">
<el-radio-group v-model="form.fileType" class="app-radio-button-group">
<el-radio-button value="txt"
>{{ $t('views.document.fileType.txt.label') }}
</el-radio-button>
</el-radio-group>
</div>
<div class="update-info flex p-8-12 border-r-4 mb-16">
<div class="mt-4">
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
</div>
<div class="ml-16 lighter">
<p>{{ $t('views.document.feishu.tip1') }}</p>
<p>{{ $t('views.document.feishu.tip2') }}</p>
</div>
</div>
<div class="card-never border-r-4 mb-16">
<el-checkbox
v-model="allCheck"
:label="$t('views.document.feishu.allCheck')"
size="large"
class="ml-24"
@change="handleAllCheckChange"
/>
</div>
<div style="height: calc(100vh - 450px)">
<el-scrollbar>
<el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
node-key="token"
ref="treeRef"
>
<template #default="{ node, data }">
<div class="flex align-center lighter">
<img
src="@/assets/fileType/file-icon.svg"
alt=""
height="20"
v-if="data.type === 'folder'"
/>
<img
src="@/assets/fileType/docx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'docx' || data.name.endsWith('.docx')"
/>
<img
src="@/assets/fileType/xlsx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'sheet' || data.name.endsWith('.xlsx')"
/>
<img
src="@/assets/fileType/xls-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('xls')"
/>
<img
src="@/assets/fileType/csv-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('csv')"
/>
<img
src="@/assets/fileType/pdf-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.pdf')"
/>
<img
src="@/assets/fileType/html-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.html')"
/>
<img
src="@/assets/fileType/txt-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.txt')"
/>
<img
src="@/assets/fileType/zip-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.zip')"
/>
<img
src="@/assets/fileType/md-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.md')"
/>
<span class="ml-4">{{ node.label }}</span>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
</el-form>
</div>
</div>
</div>
</el-card>
<div class="create-knowledge__footer text-right border-t">
<el-button @click="router.go(-1)">{{ $t('common.cancel') }}</el-button>
<el-button @click="submit" type="primary" :disabled="disabled">
{{ $t('views.document.buttons.import') }}
</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { MsgConfirm, MsgSuccess, MsgWarning } from '@/utils/message'
import { getImgUrl } from '@/utils/utils'
import { t } from '@/locales'
import type Node from 'element-plus/es/components/tree/src/model/node'
import documentApi from '@/api/knowledge/document'
const router = useRouter()
const route = useRoute()
const {
query: { id, folder_token }, // idknowledgeIDid folder_tokentoken
} = route
const knowledgeId = id as string
const folderToken = folder_token as string
const loading = ref(false)
const disabled = ref(false)
const allCheck = ref(false)
const treeRef = ref<any>(null)
interface Tree {
name: string
leaf?: boolean
type: string
token: string
is_exist: boolean
}
const form = ref({
fileType: 'txt',
fileList: [] as any,
})
const rules = reactive({
fileList: [
{ required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },
],
})
const props = {
label: 'name',
children: 'zones',
isLeaf: (data: any) => data.type !== 'folder',
disabled: (data: any) => data.is_exist,
}
const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => {
const token = node.level === 0 ? folderToken : node.data.token // 使 folder_token使 node.data.token
documentApi
.getLarkDocumentList(knowledgeId, token, {}, loading)
.then((res: any) => {
const nodes = res.data.files as Tree[]
resolve(nodes)
nodes.forEach((childNode) => {
if (childNode.is_exist) {
treeRef.value?.setChecked(childNode.token, true, false)
}
})
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
}
const handleAllCheckChange = (checked: boolean) => {
if (checked) {
//
const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]
nodes.forEach((node) => {
//
if (!node.disabled) {
treeRef.value?.setChecked(node.data, true, false)
}
})
} else {
treeRef.value?.setCheckedKeys([])
}
}
function submit() {
loading.value = true
disabled.value = true
// token
const checkedNodes = treeRef.value?.getCheckedNodes() || []
const filteredNodes = checkedNodes.filter((node: any) => !node.is_exist)
const newList = filteredNodes.map((node: any) => {
return {
name: node.name,
token: node.token,
type: node.type,
}
})
if (newList.length === 0) {
disabled.value = false
MsgWarning(t('views.document.feishu.errorMessage1'))
loading.value = false
return
}
documentApi
.importLarkDocument(knowledgeId, newList, loading)
.then((res) => {
MsgSuccess(t('views.document.tip.importMessage'))
disabled.value = false
router.go(-1)
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
.finally(() => {
disabled.value = false
})
loading.value = false
}
function back() {
router.go(-1)
}
</script>
<style lang="scss" scoped>
.create-knowledge {
&__component {
width: 100%;
margin: 0 auto;
overflow: hidden;
}
&__footer {
padding: 16px 24px;
position: fixed;
bottom: 0;
left: 0;
background: #ffffff;
width: 100%;
box-sizing: border-box;
}
.upload-document {
width: 70%;
margin: 0 auto;
margin-bottom: 20px;
}
}
.xlsx-icon {
svg {
width: 24px;
height: 24px;
stroke: #000000 !important;
fill: #ffffff !important;
}
}
</style>

View File

@ -8,18 +8,49 @@
:close-on-press-escape="false"
>
<template #header="{ titleId, titleClass }">
<h4 :id="titleId" :class="titleClass">{{ $t('views.chatLog.selectKnowledge') }}</h4>
<h4 :id="titleId" :class="titleClass">{{ '文档迁移到' }}</h4>
</template>
<el-form
class="p-24"
ref="FormRef"
:model="form"
label-position="top"
require-asterisk-position="right"
v-loading="loading"
>
<el-form-item :label="$t('views.chatLog.selectKnowledge')" required>
<el-tree-select
v-model="form.selectKnowledge"
:data="knowledgeList"
:props="defaultProps"
node-key="id"
>
<template #default="{ data }">
<div class="flex align-center">
<KnowledgeIcon class="mr-12" :size="20" v-if="data.resource_type" :type="data.type" />
<el-avatar v-else class="mr-12" shape="square" :size="20" style="background: none">
<img
src="@/assets/knowledge/icon_file-folder_colorful.svg"
style="width: 100%"
alt=""
/>
</el-avatar>
{{ data.name }}
</div>
</template>
</el-tree-select>
</el-form-item>
</el-form>
<el-tree-select v-model="selectKnowledge" :data="knowledgeList" style="width: 240px">
<template #default="{ data: { label } }">
{{ label }}<span style="color: gray">(suffix)</span>
</template>
</el-tree-select>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submitHandle" :disabled="!selectKnowledge || loading">
<el-button
type="primary"
@click="submitHandle"
:disabled="!form.selectKnowledge || loading"
>
{{ $t('common.confirm') }}
</el-button>
</span>
@ -43,13 +74,25 @@ const emit = defineEmits(['refresh'])
const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const selectKnowledge = ref('')
const knowledgeList = ref<any>([])
const documentList = ref<any>([])
const defaultProps = {
children: 'children',
label: 'name',
disabled: (data: any, node: any) => {
console.log(data, node)
return data.id === id || (node?.isLeaf && !data.resource_type)
},
}
const form = ref<any>({
selectKnowledge: '',
})
watch(dialogVisible, (bool) => {
if (!bool) {
selectKnowledge.value = ''
form.value.selectKnowledge = ''
knowledgeList.value = []
documentList.value = []
}
@ -62,7 +105,7 @@ const open = (list: any) => {
}
const submitHandle = () => {
documentApi
.putMigrateMulDocument(id, selectKnowledge.value, documentList.value, loading)
.putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading)
.then((res) => {
emit('refresh')
dialogVisible.value = false
@ -70,15 +113,12 @@ const submitHandle = () => {
}
function getKnowledge() {
knowledge.asyncGetRootKnowledge(loading).then((res: any) => {
knowledgeList.value = res.data?.filter((v: any) => v.id !== id)
knowledge.asyncGetTreeRootKnowledge(loading).then((res: any) => {
knowledgeList.value = res || []
console.log(knowledgeList.value)
})
}
const refresh = () => {
getKnowledge()
}
defineExpose({ open })
</script>
<style lang="scss">

View File

@ -9,30 +9,70 @@
<el-button
v-if="knowledgeDetail.type === 0"
type="primary"
@click="router.push({ path: `/knowledge/document/upload/${folderId}`, query: { id: id } })"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getWorkspacePermission]"
@click="
router.push({ path: `/knowledge/document/upload/${folderId}`, query: { id: id } })
"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getWorkspacePermission,
]"
>{{ $t('views.document.uploadDocument') }}
</el-button>
<el-button v-if="knowledgeDetail.type === 1" type="primary" @click="importDoc"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getWorkspacePermission]"
>{{ $t('views.document.importDocument') }}
<el-button
v-if="knowledgeDetail.type === 1"
type="primary"
@click="importDoc"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getWorkspacePermission,
]"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button @click="batchRefresh" :disabled="multipleSelection.length === 0"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]"
>{{ $t('views.knowledge.setting.vectorization') }}
<el-button
v-if="knowledgeDetail.type === 2"
type="primary"
@click="
router.push({
path: `/knowledge/import`,
query: { id: id, folder_token: knowledgeDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button @click="openGenerateDialog()" :disabled="multipleSelection.length === 0"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getWorkspacePermission]"
>{{ $t('views.document.generateQuestion.title') }}
<el-button
@click="batchRefresh"
:disabled="multipleSelection.length === 0"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>{{ $t('views.knowledge.setting.vectorization') }}
</el-button>
<el-button @click="openknowledgeDialog()" :disabled="multipleSelection.length === 0"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_MIGRATE.getWorkspacePermission]"
>{{ $t('views.document.setting.migration') }}
<el-button
@click="openGenerateDialog()"
:disabled="multipleSelection.length === 0"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getWorkspacePermission,
]"
>{{ $t('views.document.generateQuestion.title') }}
</el-button>
<el-button
@click="openknowledgeDialog()"
:disabled="multipleSelection.length === 0"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_MIGRATE.getWorkspacePermission,
]"
>{{ $t('views.document.setting.migration') }}
</el-button>
<el-dropdown>
<el-button class="ml-12 mr-12"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission]"
<el-button
class="ml-12 mr-12"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission,
]"
>
<el-icon><MoreFilled /></el-icon>
</el-button>
@ -51,18 +91,6 @@
v-if="knowledgeDetail.type === 1"
>{{ $t('views.document.syncDocument') }}
</el-dropdown-item>
<el-dropdown-item
divided
v-if="knowledgeDetail.type === 2"
type="primary"
@click="
router.push({
path: '/knowledge/import',
query: { id: id, folder_token: knowledgeDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}
</el-dropdown-item>
<el-dropdown-item
divided
@click="syncLarkMulDocument"
@ -357,7 +385,10 @@
text
@click.stop="cancelTask(row, TaskType.EMBEDDING)"
:title="$t('views.document.setting.cancelVectorization')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>
<AppIcon iconName="app-close" style="font-size: 16px"></AppIcon>
</el-button>
@ -368,7 +399,10 @@
text
@click.stop="refreshDocument(row)"
:title="$t('views.knowledge.setting.vectorization')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>
<AppIcon iconName="app-document-refresh" style="font-size: 16px"></AppIcon>
</el-button>
@ -379,15 +413,23 @@
text
@click.stop="settingDoc(row)"
:title="$t('common.setting')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission]"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission,
]"
>
<el-icon><Setting /></el-icon>
</el-button>
</span>
<span @click.stop>
<el-dropdown trigger="click">
<el-button text type="primary"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission]"
<el-button
text
type="primary"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission,
]"
>
<el-icon><MoreFilled /></el-icon>
</el-button>
@ -399,7 +441,8 @@
getTaskState(row.status, TaskType.GENERATE_PROBLEM),
)
"
@click="cancelTask(row, TaskType.GENERATE_PROBLEM)">
@click="cancelTask(row, TaskType.GENERATE_PROBLEM)"
>
<el-icon><Connection /></el-icon>
{{ $t('views.document.setting.cancelGenerateQuestion') }}
</el-dropdown-item>
@ -420,7 +463,8 @@
{{ $t('views.document.setting.export') }} Zip
</el-dropdown-item>
<el-dropdown-item icon="Delete" @click.stop="deleteDocument(row)">
{{$t('common.delete')}}</el-dropdown-item>
{{ $t('common.delete') }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -433,7 +477,10 @@
text
@click.stop="syncDocument(row)"
:title="$t('views.knowledge.setting.sync')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_SYNC.getWorkspacePermission]"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_SYNC.getWorkspacePermission,
]"
>
<el-icon><Refresh /></el-icon>
</el-button>
@ -449,8 +496,11 @@
text
@click.stop="cancelTask(row, TaskType.EMBEDDING)"
:title="$t('views.document.setting.cancelVectorization')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]"
>
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>
<AppIcon iconName="app-close" style="font-size: 16px"></AppIcon>
</el-button>
@ -460,23 +510,30 @@
text
@click.stop="refreshDocument(row)"
:title="$t('views.knowledge.setting.vectorization')"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]"
>
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>
<AppIcon iconName="app-document-refresh" style="font-size: 16px"></AppIcon>
</el-button>
</span>
<span @click.stop>
<el-dropdown trigger="click">
<el-button text type="primary"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission]"
<el-button
text
type="primary"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermission,
]"
>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Setting" @click="settingDoc(row)"
>{{
<el-dropdown-item icon="Setting" @click="settingDoc(row)">{{
$t('common.setting')
}}</el-dropdown-item>
<el-dropdown-item
@ -490,29 +547,24 @@
<el-icon><Connection /></el-icon>
{{ $t('views.document.setting.cancelGenerateQuestion') }}
</el-dropdown-item>
<el-dropdown-item v-else @click="openGenerateDialog(row)"
>
<el-dropdown-item v-else @click="openGenerateDialog(row)">
<el-icon><Connection /></el-icon>
{{ $t('views.document.generateQuestion.title') }}
</el-dropdown-item>
<el-dropdown-item @click="openknowledgeDialog(row)"
>
<el-dropdown-item @click="openknowledgeDialog(row)">
<AppIcon iconName="app-migrate"></AppIcon>
{{ $t('views.document.setting.migration') }}
</el-dropdown-item>
<el-dropdown-item @click="exportDocument(row)"
>
<el-dropdown-item @click="exportDocument(row)">
<AppIcon iconName="app-export"></AppIcon>
{{ $t('views.document.setting.export') }} Excel
</el-dropdown-item>
<el-dropdown-item @click="exportDocumentZip(row)"
>
<el-dropdown-item @click="exportDocumentZip(row)">
<AppIcon iconName="app-export"></AppIcon>
{{ $t('views.document.setting.export') }} Zip
</el-dropdown-item>
<el-dropdown-item icon="Delete" @click.stop="deleteDocument(row)"
>
{{$t('common.delete')}}
<el-dropdown-item icon="Delete" @click.stop="deleteDocument(row)">
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
@ -526,14 +578,24 @@
</div>
</el-card>
<div class="mul-operation w-full flex" v-if="multipleSelection.length !== 0">
<el-button :disabled="multipleSelection.length === 0" @click="cancelTaskHandle(1, row)"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission]">
<el-button
:disabled="multipleSelection.length === 0"
@click="cancelTaskHandle(1, row)"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermission,
]"
>
{{ $t('views.document.setting.cancelVectorization') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0" @click="cancelTaskHandle(2, row)"
v-hasPermission="[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getWorkspacePermission]"
<el-button
:disabled="multipleSelection.length === 0"
@click="cancelTaskHandle(2, row)"
v-hasPermission="[
RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,
PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getWorkspacePermission,
]"
>
{{ $t('views.document.setting.cancelGenerate') }}
</el-button>
<el-text type="info" class="secondary ml-24">

View File

@ -1,290 +0,0 @@
<template>
<LayoutContainer :header="$t('views.document.importDocument')" class="create-knowledge">
<template #backButton>
<back-button @click="back"></back-button>
</template>
<div class="create-knowledge__main flex" v-loading="loading">
<div class="create-knowledge__component main-calc-height">
<div class="upload-document p-24" style="min-width: 850px">
<h4 class="title-decoration-1 mb-8">
{{ $t('views.document.feishu.selectDocument') }}
</h4>
<el-form
ref="FormRef"
:model="form"
:rules="rules"
label-position="top"
require-asterisk-position="right"
>
<div class="mt-16 mb-16">
<el-radio-group v-model="form.fileType" class="app-radio-button-group">
<el-radio-button value="txt"
>{{ $t('views.document.fileType.txt.label') }}
</el-radio-button>
</el-radio-group>
</div>
<div class="update-info flex p-8-12 border-r-4 mb-16">
<div class="mt-4">
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
</div>
<div class="ml-16 lighter">
<p>{{ $t('views.document.feishu.tip1') }}</p>
<p>{{ $t('views.document.feishu.tip2') }}</p>
</div>
</div>
<div class="card-never border-r-4 mb-16">
<el-checkbox
v-model="allCheck"
:label="$t('views.document.feishu.allCheck')"
size="large"
class="ml-24"
@change="handleAllCheckChange"
/>
</div>
<div style="height: calc(100vh - 450px)">
<el-scrollbar>
<el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
node-key="token"
ref="treeRef"
>
<template #default="{ node, data }">
<div class="flex align-center lighter">
<img
src="@/assets/fileType/file-icon.svg"
alt=""
height="20"
v-if="data.type === 'folder'"
/>
<img
src="@/assets/fileType/docx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'docx' || data.name.endsWith('.docx')"
/>
<img
src="@/assets/fileType/xlsx-icon.svg"
alt=""
height="22"
v-else-if="data.type === 'sheet' || data.name.endsWith('.xlsx')"
/>
<img
src="@/assets/fileType/xls-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('xls')"
/>
<img
src="@/assets/fileType/csv-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('csv')"
/>
<img
src="@/assets/fileType/pdf-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.pdf')"
/>
<img
src="@/assets/fileType/html-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.html')"
/>
<img
src="@/assets/fileType/txt-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.txt')"
/>
<img
src="@/assets/fileType/zip-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.zip')"
/>
<img
src="@/assets/fileType/md-icon.svg"
alt=""
height="22"
v-else-if="data.name.endsWith('.md')"
/>
<span class="ml-4">{{ node.label }}</span>
</div>
</template>
</el-tree>
</el-scrollbar>
</div>
</el-form>
</div>
</div>
</div>
<div class="create-knowledge__footer text-right border-t">
<el-button @click="router.go(-1)">{{ $t('common.cancel') }}</el-button>
<el-button @click="submit" type="primary" :disabled="disabled">
{{ $t('views.document.buttons.import') }}
</el-button>
</div>
</LayoutContainer>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { MsgConfirm, MsgSuccess, MsgWarning } from '@/utils/message'
import { getImgUrl } from '@/utils/utils'
import { t } from '@/locales'
import type Node from 'element-plus/es/components/tree/src/model/node'
import documentApi from '@/api/knowledge/document'
const router = useRouter()
const route = useRoute()
const {
query: { id, folder_token }, // idknowledgeIDid folder_tokentoken
} = route
const knowledgeId = id as string
const folderToken = folder_token as string
const loading = ref(false)
const disabled = ref(false)
const allCheck = ref(false)
const treeRef = ref<any>(null)
interface Tree {
name: string
leaf?: boolean
type: string
token: string
is_exist: boolean
}
const form = ref({
fileType: 'txt',
fileList: [] as any,
})
const rules = reactive({
fileList: [
{ required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },
],
})
const props = {
label: 'name',
children: 'zones',
isLeaf: (data: any) => data.type !== 'folder',
disabled: (data: any) => data.is_exist,
}
const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => {
const token = node.level === 0 ? folderToken : node.data.token // 使 folder_token使 node.data.token
documentApi
.getLarkDocumentList(knowledgeId, token, {}, loading)
.then((res: any) => {
const nodes = res.data.files as Tree[]
resolve(nodes)
nodes.forEach((childNode) => {
if (childNode.is_exist) {
treeRef.value?.setChecked(childNode.token, true, false)
}
})
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
}
const handleAllCheckChange = (checked: boolean) => {
if (checked) {
//
const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]
nodes.forEach((node) => {
//
if (!node.disabled) {
treeRef.value?.setChecked(node.data, true, false)
}
})
} else {
treeRef.value?.setCheckedKeys([])
}
}
function submit() {
loading.value = true
disabled.value = true
// token
const checkedNodes = treeRef.value?.getCheckedNodes() || []
const filteredNodes = checkedNodes.filter((node: any) => !node.is_exist)
const newList = filteredNodes.map((node: any) => {
return {
name: node.name,
token: node.token,
type: node.type,
}
})
if (newList.length === 0) {
disabled.value = false
MsgWarning(t('views.document.feishu.errorMessage1'))
loading.value = false
return
}
documentApi
.importLarkDocument(knowledgeId, newList, loading)
.then((res) => {
MsgSuccess(t('views.document.tip.importMessage'))
disabled.value = false
router.go(-1)
})
.catch((err) => {
console.error('Failed to load tree nodes:', err)
})
.finally(() => {
disabled.value = false
})
loading.value = false
}
function back() {
router.go(-1)
}
</script>
<style lang="scss" scoped>
.create-knowledge {
&__component {
width: 100%;
margin: 0 auto;
overflow: hidden;
}
&__footer {
padding: 16px 24px;
position: fixed;
bottom: 0;
left: 0;
background: #ffffff;
width: 100%;
box-sizing: border-box;
}
.upload-document {
width: 70%;
margin: 0 auto;
margin-bottom: 20px;
}
}
.xlsx-icon {
svg {
width: 24px;
height: 24px;
stroke: #000000 !important;
fill: #ffffff !important;
}
}
</style>

View File

@ -36,9 +36,7 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import useStore from '@/stores'
const { knowledge } = useStore()
import knowledgeApi from '@/api/knowledge/knowledge'
const emit = defineEmits(['refresh'])
const loading = ref<boolean>(false)
@ -59,7 +57,7 @@ const open = (id: string) => {
}
const submit = () => {
knowledge.asyncSyncKnowledge(knowledgeId.value, method.value, loading).then((res: any) => {
knowledgeApi.putSyncWebKnowledge(knowledgeId.value, method.value, loading).then((res: any) => {
emit('refresh', res.data)
dialogVisible.value = false
})

View File

@ -1,70 +1,56 @@
<template>
<el-dialog
:title="$t('views.log.selectDataset')"
:title="$t('views.chatLog.selectKnowledge')"
v-model="dialogVisible"
width="600"
class="select-dataset-dialog"
class="select-knowledge-dialog"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<template #header="{ titleId, titleClass }">
<div class="my-header flex">
<h4 :id="titleId" :class="titleClass">{{ $t('views.log.selectDataset') }}</h4>
<el-button link class="ml-16" @click="refresh">
<el-icon class="mr-4"><Refresh /></el-icon>{{ $t('common.refresh') }}
</el-button>
</div>
<h4 :id="titleId" :class="titleClass">{{ '文档迁移到' }}</h4>
</template>
<div class="content-height">
<el-radio-group v-model="selectDataset" class="card__radio">
<el-scrollbar height="500">
<div class="p-16">
<el-row :gutter="12" v-loading="loading">
<el-col :span="12" v-for="(item, index) in datasetList" :key="index" class="mb-16">
<el-card shadow="never" :class="item.id === selectDataset ? 'active' : ''">
<el-radio :value="item.id" size="large">
<div class="flex align-center">
<el-avatar
v-if="item?.type === '0'"
class="mr-8 avatar-blue"
shape="square"
:size="32"
>
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
</el-avatar>
<el-avatar
v-if="item?.type === '1'"
class="mr-8 avatar-purple"
shape="square"
:size="32"
>
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
</el-avatar>
<el-avatar
v-if="item?.type === '2'"
class="mr-8 avatar-purple"
shape="square"
:size="32"
style="background: none"
>
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
</el-avatar>
<span class="ellipsis" :title="item.name">
{{ item.name }}
</span>
</div>
</el-radio>
</el-card>
</el-col>
</el-row>
</div>
</el-scrollbar>
</el-radio-group>
</div>
<el-form
class="p-24"
ref="FormRef"
:model="form"
label-position="top"
require-asterisk-position="right"
v-loading="loading"
>
<el-form-item :label="$t('views.chatLog.selectKnowledge')" required>
<el-tree-select
v-model="form.selectKnowledge"
:data="knowledgeList"
:props="defaultProps"
node-key="id"
>
<template #default="{ data }">
<div class="flex align-center">
<KnowledgeIcon class="mr-12" :size="20" v-if="data.resource_type" :type="data.type" />
<el-avatar v-else class="mr-12" shape="square" :size="20" style="background: none">
<img
src="@/assets/knowledge/icon_file-folder_colorful.svg"
style="width: 100%"
alt=""
/>
</el-avatar>
{{ data.name }}
</div>
</template>
</el-tree-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submitHandle" :disabled="!selectDataset || loading">
<el-button
type="primary"
@click="submitHandle"
:disabled="!form.selectKnowledge || loading"
>
{{ $t('common.confirm') }}
</el-button>
</span>
@ -80,7 +66,7 @@ import useStore from '@/stores/modules-shared-system'
const { knowledge } = useStore()
const route = useRoute()
const {
params: { id } // iddatasetID
params: { id }, // idknowledgeID
} = route as any
const emit = defineEmits(['refresh'])
@ -88,46 +74,55 @@ const emit = defineEmits(['refresh'])
const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const selectDataset = ref('')
const datasetList = ref<any>([])
const knowledgeList = ref<any>([])
const documentList = ref<any>([])
const defaultProps = {
children: 'children',
label: 'name',
disabled: (data: any, node: any) => {
console.log(data, node)
return data.id === id || (node?.isLeaf && !data.resource_type)
},
}
const form = ref<any>({
selectKnowledge: '',
})
watch(dialogVisible, (bool) => {
if (!bool) {
selectDataset.value = ''
datasetList.value = []
form.value.selectKnowledge = ''
knowledgeList.value = []
documentList.value = []
}
})
const open = (list: any) => {
documentList.value = list
getDataset()
getKnowledge()
dialogVisible.value = true
}
const submitHandle = () => {
documentApi
.putMigrateMulDocument(id, selectDataset.value, documentList.value, loading)
.putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading)
.then((res) => {
emit('refresh')
dialogVisible.value = false
})
}
function getDataset() {
knowledge.asyncGetRootKnowledge(loading).then((res: any) => {
datasetList.value = res.data?.filter((v: any) => v.id !== id)
function getKnowledge() {
knowledge.asyncGetTreeRootKnowledge(loading).then((res: any) => {
knowledgeList.value = res || []
console.log(knowledgeList.value)
})
}
const refresh = () => {
getDataset()
}
defineExpose({ open })
</script>
<style lang="scss">
.select-dataset-dialog {
.select-knowledge-dialog {
padding: 0;
.el-dialog__header {
padding: 24px 24px 0 24px;

View File

@ -21,6 +21,18 @@
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button
v-if="knowledgeDetail.type === 2"
type="primary"
@click="
router.push({
path: `/knowledge/import`,
query: { id: id, folder_token: knowledgeDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button @click="batchRefresh" :disabled="multipleSelection.length === 0">
{{ $t('views.knowledge.setting.vectorization') }}
</el-button>
@ -49,18 +61,7 @@
v-if="knowledgeDetail.type === 1"
>{{ $t('views.document.syncDocument') }}</el-dropdown-item
>
<el-dropdown-item
divided
v-if="knowledgeDetail.type === 2"
type="primary"
@click="
router.push({
path: '/knowledge/import',
query: { id: id, folder_token: knowledgeDetail.meta.folder_token },
})
"
>{{ $t('views.document.importDocument') }}</el-dropdown-item
>
<el-dropdown-item
divided
@click="syncLarkMulDocument"

View File

@ -36,9 +36,7 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import useStore from '@/stores/modules-shared-system'
const { knowledge } = useStore()
import knowledgeApi from '@/api/shared/knowledge'
const emit = defineEmits(['refresh'])
const loading = ref<boolean>(false)
@ -57,9 +55,8 @@ const open = (id: string) => {
knowledgeId.value = id
dialogVisible.value = true
}
const submit = () => {
knowledge.asyncSyncKnowledge(knowledgeId.value, method.value, loading).then((res: any) => {
knowledgeApi.putSyncWebKnowledge(knowledgeId.value, method.value, loading).then((res: any) => {
emit('refresh', res.data)
dialogVisible.value = false
})