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, data?: any,
loading?: Ref<boolean>, loading?: Ref<boolean>,
) => Promise<Result<Array<any>>> = (source, data, loading) => { ) => 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 { Result } from '@/request/Result'
import type { import type {
RoleItem, RoleItem,
RolePermissionItem,
CreateOrUpdateParams,
RoleMemberItem, RoleMemberItem,
CreateMemberParamsItem, CreateMemberParamsItem,
} from '@/api/type/role' } from '@/api/type/role'
import { RoleTypeEnum } from '@/enums/system'
import type { pageRequest, PageList } from '@/api/type/common' import type { pageRequest, PageList } from '@/api/type/common'
const prefix = '/workspace/role' const prefix = '/workspace/role'

View File

@ -56,10 +56,10 @@ const getWorkspaceMemberList: (
/** /**
* *
*/ */
const getAllMemberList: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any[]>> = ( const getAllMemberList: (
workspace_id, workspace_id: string | null,
loading, loading?: Ref<boolean>,
) => { ) => Promise<Result<any[]>> = (workspace_id, loading) => {
return get(`${prefix}/${workspace_id}/user_list`, undefined, 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 { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales' import { t } from '@/locales'
import useStore from '@/stores' import useStore from '@/stores'
const { tool, knowledge } = useStore() const { tool, knowledge, folder } = useStore()
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
const props = defineProps({ const props = defineProps({
@ -124,15 +124,17 @@ const submitHandle = async () => {
.putFolder(editId.value, sourceType.value, folderForm.value, loading) .putFolder(editId.value, sourceType.value, folderForm.value, loading)
.then((res) => { .then((res) => {
MsgSuccess(t('common.editSuccess')) MsgSuccess(t('common.editSuccess'))
emit('refresh')
clearData() clearData()
emit('refresh')
dialogVisible.value = false dialogVisible.value = false
}) })
} else { } else {
folderApi.postFolder(sourceType.value, folderForm.value, loading).then((res) => { folderApi.postFolder(sourceType.value, folderForm.value, loading).then((res) => {
MsgSuccess(t('common.createSuccess')) MsgSuccess(t('common.createSuccess'))
emit('refresh') folder.setCurrentFolder(res.data)
folder.asyncGetFolder(sourceType.value, {}, loading)
clearData() clearData()
emit('refresh')
dialogVisible.value = false dialogVisible.value = false
}) })
} }

View File

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

View File

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

View File

@ -16,5 +16,7 @@ export default {
folderNamePlaceholder: 'Please enter a name', folderNamePlaceholder: 'Please enter a name',
description: 'Description', description: 'Description',
descriptionPlaceholder: 'Please enter a 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: '自定义', custom: '自定义',
moveTo: '转移到', moveTo: '转移到',
deleteConfirm: '是否删除',
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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