feat: folder

This commit is contained in:
wangdan-fit2cloud 2025-07-03 16:29:14 +08:00
parent 8f1b0e0da5
commit 43c7350415
17 changed files with 146 additions and 98 deletions

View File

@ -40,7 +40,7 @@ const postFolder: (
data?: any,
loading?: Ref<boolean>,
) => Promise<Result<Array<any>>> = (source, data, loading) => {
return post(`${prefix.value}/${source}/folder`, data, loading)
return post(`${prefix.value}/${source}/folder`, data, null, loading)
}
/**

View File

@ -3,12 +3,9 @@ import type { Ref } from 'vue'
import { Result } from '@/request/Result'
import type {
RoleItem,
RolePermissionItem,
CreateOrUpdateParams,
RoleMemberItem,
CreateMemberParamsItem,
} from '@/api/type/role'
import { RoleTypeEnum } from '@/enums/system'
import type { pageRequest, PageList } from '@/api/type/common'
const prefix = '/workspace/role'

View File

@ -56,10 +56,10 @@ const getWorkspaceMemberList: (
/**
*
*/
const getAllMemberList: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any[]>> = (
workspace_id,
loading,
) => {
const getAllMemberList: (
workspace_id: string | null,
loading?: Ref<boolean>,
) => Promise<Result<any[]>> = (workspace_id, loading) => {
return get(`${prefix}/${workspace_id}/user_list`, undefined, loading)
}

View File

@ -54,7 +54,7 @@ import folderApi from '@/api/folder'
import { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales'
import useStore from '@/stores'
const { tool, knowledge } = useStore()
const { tool, knowledge, folder } = useStore()
const emit = defineEmits(['refresh'])
const props = defineProps({
@ -124,15 +124,17 @@ const submitHandle = async () => {
.putFolder(editId.value, sourceType.value, folderForm.value, loading)
.then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh')
clearData()
emit('refresh')
dialogVisible.value = false
})
} else {
folderApi.postFolder(sourceType.value, folderForm.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
emit('refresh')
folder.setCurrentFolder(res.data)
folder.asyncGetFolder(sourceType.value, {}, loading)
clearData()
emit('refresh')
dialogVisible.value = false
})
}

View File

@ -51,7 +51,7 @@
>
<el-dropdown trigger="click" :teleported="false">
<el-button text class="w-full">
<el-icon class="rotate-90"><MoreFilled /></el-icon>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
@ -97,6 +97,8 @@ import folderApi from '@/api/folder'
import { EditionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
import useStore from '@/stores'
import { TreeToFlatten } from '@/utils/array'
import { MsgConfirm } from '@/utils/message'
defineOptions({ name: 'FolderTree' })
const props = defineProps({
@ -106,7 +108,7 @@ const props = defineProps({
},
currentNodeKey: {
type: String,
default: 'root',
default: 'default',
},
source: {
type: String,
@ -140,6 +142,7 @@ interface Tree {
children?: Tree[]
id?: string
show?: boolean
parent_id?: string
}
const defaultProps = {
@ -185,9 +188,23 @@ const handleSharedNodeClick = () => {
}
function deleteFolder(row: Tree) {
MsgConfirm(
`${t('common.deleteConfirm')}${row.name}`,
t('components.folder.deleteConfirmMessage'),
{
confirmButtonText: t('common.delete'),
confirmButtonClass: 'danger',
},
)
.then(() => {
folderApi.delFolder(row.id as string, props.source, loading).then(() => {
treeRef.value?.setCurrentKey(row.parent_id || 'default')
const prevFolder = TreeToFlatten(props.data).find((item: any) => item.id === row.parent_id)
folder.setCurrentFolder(prevFolder)
emit('refreshTree')
})
})
.catch(() => {})
}
const CreateFolderDialogRef = ref()

View File

@ -97,4 +97,5 @@ export default {
},
custom: 'Custom',
moveTo: 'Move To',
deleteConfirm: 'Confirm delete',
}

View File

@ -16,5 +16,7 @@ export default {
folderNamePlaceholder: 'Please enter a name',
description: 'Description',
descriptionPlaceholder: 'Please enter a description',
requiredMessage: 'Please select a folder',
deleteConfirmMessage: 'Folders with resources will be deleted, please be cautious.',
},
}

View File

@ -101,4 +101,5 @@ export default {
},
custom: '自定义',
moveTo: '转移到',
deleteConfirm: '是否删除',
}

View File

@ -17,5 +17,6 @@ export default {
description: '描述',
descriptionPlaceholder: '请输入描述',
requiredMessage: '请选择文件夹',
deleteConfirmMessage: '文件夹下的资源会被删除,请谨慎操作。'
},
}

View File

@ -97,4 +97,5 @@ export default {
},
custom: '自定義',
moveTo: '移動到',
deleteConfirm: '是否刪除',
}

View File

@ -16,5 +16,7 @@ export default {
folderNamePlaceholder: '請輸入名稱',
description: '描述',
descriptionPlaceholder: '請輸入描述',
requiredMessage: '請選擇文件夾',
deleteConfirmMessage: '文件夹下的資源會被刪除,請謹慎操作。',
},
}

View File

@ -316,10 +316,7 @@ const { folder, application, user, common } = useStore()
const loading = ref(false)
const search_type = ref('name')
const search_form = ref<{
name: string
create_user: string
}>({
const search_form = ref<any>({
name: '',
create_user: '',
})
@ -544,7 +541,6 @@ function getList() {
onMounted(() => {
getFolder(true)
WorkspaceApi.getAllMemberList(user.getWorkspaceId(), loading).then((res) => {
user_options.value = res.data
})

View File

@ -308,7 +308,7 @@
</ContentContainer>
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" v-if="!isShared" />
<CreateFolderDialog ref="CreateFolderDialogRef" v-if="!isShared" />
<CreateFolderDialog ref="CreateFolderDialogRef" v-if="!isShared" @refresh="refreshFolder" />
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" :apiType="apiType" />
<SyncWebDialog ref="SyncWebDialogRef" v-if="!isShared" />
<AuthorizedWorkspace
@ -350,6 +350,8 @@ onBeforeRouteLeave((to, from) => {
knowledge.setKnowledgeList([])
})
const emit = defineEmits(['refreshFolder'])
const apiType = computed(() => {
if (route.path.includes('shared')) {
return 'systemShare'
@ -523,6 +525,10 @@ function searchHandle() {
getList()
}
function refreshFolder() {
emit('refreshFolder')
}
onMounted(() => {
if (apiType.value !== 'workspace') {
folder.setCurrentFolder({

View File

@ -13,7 +13,7 @@
@refreshTree="refreshFolder"
/>
</template>
<KnowledgeListContainer>
<KnowledgeListContainer @refreshFolder="refreshFolder">
<template #header>
<FolderBreadcrumb :folderList="folderList" @click="folderClickHandle" />
</template>

View File

@ -3,14 +3,11 @@
<h2 class="mb-16">{{ $t('views.userManage.title') }}</h2>
<el-card>
<div class="flex-between mb-16">
<el-button type="primary" @click="createUser"
v-hasPermission="[
RoleConst.ADMIN,
PermissionConst.USER_CREATE
]"
>{{
$t('views.userManage.createUser')
}}
<el-button
type="primary"
@click="createUser"
v-hasPermission="[RoleConst.ADMIN, PermissionConst.USER_CREATE]"
>{{ $t('views.userManage.createUser') }}
</el-button>
<div class="flex-between complex-search">
<el-select
@ -19,9 +16,9 @@
style="width: 120px"
@change="search_type_change"
>
<el-option :label="$t('views.login.loginForm.username.label')" value="username"/>
<el-option :label="$t('views.userManage.userForm.nick_name.label')" value="nick_name"/>
<el-option :label="$t('views.login.loginForm.email.label')" value="email"/>
<el-option :label="$t('views.login.loginForm.username.label')" value="username" />
<el-option :label="$t('views.userManage.userForm.nick_name.label')" value="nick_name" />
<el-option :label="$t('views.login.loginForm.email.label')" value="email" />
</el-select>
<el-input
v-if="search_type === 'username'"
@ -54,15 +51,16 @@
@changePage="getList"
v-loading="loading"
>
<el-table-column prop="nick_name" :label="$t('views.userManage.userForm.nick_name.label')"/>
<el-table-column prop="username" :label="$t('views.login.loginForm.username.label')"/>
<el-table-column
prop="nick_name"
:label="$t('views.userManage.userForm.nick_name.label')"
/>
<el-table-column prop="username" :label="$t('views.login.loginForm.username.label')" />
<el-table-column prop="is_active" :label="$t('common.status.label')">
<template #default="{ row }">
<div v-if="row.is_active" class="flex align-center">
<el-icon class="color-success mr-8" style="font-size: 16px"
>
<SuccessFilled
/>
<el-icon class="color-success mr-8" style="font-size: 16px">
<SuccessFilled />
</el-icon>
<span class="color-secondary">
{{ $t('common.status.enabled') }}
@ -91,12 +89,21 @@
{{ row.phone || '-' }}
</template>
</el-table-column>
<el-table-column prop="role_name" :label="$t('views.role.member.role')" min-width="100"
v-if="user.isEE() || user.isPE()">
<el-table-column
prop="role_name"
:label="$t('views.role.member.role')"
min-width="100"
v-if="user.isEE() || user.isPE()"
>
<template #default="{ row }">
<el-popover :width="400">
<template #reference>
<TagGroup class="cursor" style="width: fit-content;" :tags="row.role_name" tooltipDisabled/>
<TagGroup
class="cursor"
style="width: fit-content"
:tags="row.role_name"
tooltipDisabled
/>
</template>
<template #default>
<el-table :data="row.role_workspace">
@ -141,72 +148,84 @@
size="small"
v-model="row.is_active"
:before-change="() => changeState(row)"
v-if="hasPermission([RoleConst.ADMIN,PermissionConst.USER_EDIT],'OR')"
v-if="hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')"
/>
</span>
<el-divider direction="vertical"/>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="$t('common.edit')" placement="top">
<span class="mr-8">
<el-button type="primary" text @click.stop="editUser(row)" :title="$t('common.edit')"
v-if="hasPermission([RoleConst.ADMIN,PermissionConst.USER_EDIT],'OR')">
<el-icon><EditPen/></el-icon>
<el-button
type="primary"
text
@click.stop="editUser(row)"
:title="$t('common.edit')"
v-if="hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')"
>
<el-icon><EditPen /></el-icon>
</el-button>
</span>
</el-tooltip>
<el-tooltip
effect="dark"
:content="$t('views.userManage.setting.updatePwd')"
placement="top"
>
<span class="mr-8">
<el-button
type="primary"
text
@click.stop="editPwdUser(row)"
:title="$t('views.userManage.setting.updatePwd')"
v-if="hasPermission([RoleConst.ADMIN,PermissionConst.USER_EDIT],'OR')"
v-if="hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')"
>
<el-icon><Lock/></el-icon>
<el-icon><Lock /></el-icon>
</el-button>
</span>
<span>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button
:disabled="row.role === 'ADMIN' || row.id === user.userInfo?.id"
type="primary"
text
@click.stop="deleteUserManage(row)"
:title="$t('common.delete')"
v-if="hasPermission([RoleConst.ADMIN,PermissionConst.USER_DELETE],'OR')"
v-if="hasPermission([RoleConst.ADMIN, PermissionConst.USER_DELETE], 'OR')"
>
<el-icon><Delete/></el-icon>
<el-icon><Delete /></el-icon>
</el-button>
</span>
</el-tooltip>
</template>
</el-table-column>
</app-table>
</el-card>
<UserDrawer :title="title" ref="UserDrawerRef" @refresh="refresh"/>
<UserPwdDialog ref="UserPwdDialogRef" @refresh="refresh"/>
<UserDrawer :title="title" ref="UserDrawerRef" @refresh="refresh" />
<UserPwdDialog ref="UserPwdDialogRef" @refresh="refresh" />
</div>
</template>
<script lang="ts" setup>
import {onMounted, ref, reactive, watch} from 'vue'
import { onMounted, ref, reactive, watch } from 'vue'
import UserDrawer from './component/UserDrawer.vue'
import UserPwdDialog from './component/UserPwdDialog.vue'
import userManageApi from '@/api/system/user-manage'
import {datetimeFormat} from '@/utils/time'
import {MsgSuccess, MsgConfirm} from '@/utils/message'
import {t} from '@/locales'
import {ValidCount, ValidType} from "@/enums/common.ts";
import useStore from "@/stores";
import {PermissionConst, RoleConst} from '@/utils/permission/data'
import {hasPermission} from '@/utils/permission/index'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
import { ValidCount, ValidType } from '@/enums/common.ts'
import useStore from '@/stores'
import { PermissionConst, RoleConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
const {user, common} = useStore()
const { user, common } = useStore()
const search_type = ref('username')
const search_form = ref<{
username: string,
nick_name?: string,
username: string
nick_name?: string
email?: string
}>({
username: '',
nick_name: '',
email: ''
email: '',
})
const UserDrawerRef = ref()
@ -222,7 +241,7 @@ const paginationConfig = reactive({
const userTableData = ref<any[]>([])
const search_type_change = () => {
search_form.value = {username: '', nick_name: '', email: ''}
search_form.value = { username: '', nick_name: '', email: '' }
}
function handleSizeChange() {
@ -234,15 +253,14 @@ function getList() {
const params = {
[search_type.value]: search_form.value[search_type.value as keyof typeof search_form.value],
}
return userManageApi
.getUserManage(paginationConfig, params, loading)
.then((res) => {
return userManageApi.getUserManage(paginationConfig, params, loading).then((res) => {
userTableData.value = res.data.records.map((item: any) => ({
...item,
role_workspace: Object.entries(item.role_workspace ?? {}).map(([role, workspaces]) => ({
role,
workspace: (workspaces as string[])?.[0] === 'None' ? '-' : (workspaces as string[])?.join(", ")
}))
workspace:
(workspaces as string[])?.[0] === 'None' ? '-' : (workspaces as string[])?.join(', '),
})),
}))
paginationConfig.total = res.data.total
})
@ -293,8 +311,7 @@ function deleteUserManage(row: any) {
getList()
})
})
.catch(() => {
})
.catch(() => {})
}
function editPwdUser(row: any) {

View File

@ -286,7 +286,7 @@
</ContentContainer>
<InitParamDrawer ref="InitParamDrawerRef" @refresh="refresh" />
<ToolFormDrawer ref="ToolFormDrawerRef" @refresh="refresh" :title="ToolDrawertitle" />
<CreateFolderDialog ref="CreateFolderDialogRef" v-if="!isShared" />
<CreateFolderDialog ref="CreateFolderDialogRef" v-if="!isShared" @refresh="refreshFolder" />
<ToolStoreDialog ref="toolStoreDialogRef" :api-type="apiType" @refresh="refresh" />
<AddInternalToolDialog ref="AddInternalToolDialogRef" @refresh="confirmAddInternalTool" />
<AuthorizedWorkspace
@ -324,6 +324,7 @@ const { folder, user, tool } = useStore()
onBeforeRouteLeave((to, from) => {
tool.setToolList([])
})
const emit = defineEmits(['refreshFolder'])
const apiType = computed(() => {
if (route.path.includes('shared')) {
@ -621,6 +622,10 @@ function clickFolder(item: any) {
folder.setCurrentFolder(item)
}
function refreshFolder() {
emit('refreshFolder')
}
function searchHandle() {
paginationConfig.current_page = 1
tool.setToolList([])

View File

@ -13,7 +13,7 @@
class="p-8"
/>
</template>
<ToolListContainer>
<ToolListContainer @refreshFolder="refreshFolder">
<template #header>
<FolderBreadcrumb :folderList="folderList" @click="folderClickHandle" />
</template>