feat: workspace dropdown

This commit is contained in:
teukkk 2025-06-13 17:38:37 +08:00
parent b6fb059512
commit a76c561769
12 changed files with 194 additions and 67 deletions

View File

@ -6,6 +6,13 @@ import type { pageRequest, PageList } from '@/api/type/common'
const prefix = '/system/workspace' const prefix = '/system/workspace'
/**
*
*/
const getWorkspaceListByUser: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (loading) => {
return get('/workspace/by_user', undefined, loading)
}
/** /**
* *
*/ */
@ -30,6 +37,16 @@ const CreateOrUpdateWorkspace: (
return post(`${prefix}`, data, undefined, loading) return post(`${prefix}`, data, undefined, loading)
} }
/**
*
*/
const deleteWorkspaceCheck: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
workspace_id,
loading,
) => {
return get(`${prefix}/${workspace_id}/check`, undefined, loading)
}
/** /**
* *
*/ */
@ -94,4 +111,6 @@ export default {
CreateWorkspaceMember, CreateWorkspaceMember,
deleteWorkspaceMember, deleteWorkspaceMember,
getWorkspaceRoleList, getWorkspaceRoleList,
getWorkspaceListByUser,
deleteWorkspaceCheck
} }

View File

@ -4,6 +4,8 @@
<div class="logo mt-4"> <div class="logo mt-4">
<LogoFull /> <LogoFull />
</div> </div>
<el-divider direction="vertical" class="ml-24 mr-24" />
<Workspace />
<div class="flex-between w-full"> <div class="flex-between w-full">
<div></div> <div></div>
<TopMenu></TopMenu> <TopMenu></TopMenu>
@ -16,6 +18,7 @@
import TopMenu from './top-menu/index.vue' import TopMenu from './top-menu/index.vue'
import Avatar from './avatar/index.vue' import Avatar from './avatar/index.vue'
import TopAbout from './top-about/index.vue' import TopAbout from './top-about/index.vue'
import Workspace from './workspace/index.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()

View File

@ -0,0 +1,78 @@
<template>
<el-dropdown placement="bottom-start">
<el-button text>
<AppIcon iconName="app-wordspace" style="font-size: 18px"></AppIcon>
<span class="dropdown-title ellipsis">
{{ currentWorkspace?.name }}
</span>
<el-icon class="el-icon--right">
<CaretBottom />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu v-loading="loading">
<el-dropdown-item v-for="item in workspaceList" :key="item.id"
:class="item.id === currentWorkspace?.id ? 'active' : ''" @click="changeWorkspace(item)">
<AppIcon class="mr-8" iconName="app-wordspace" style="font-size: 16px"></AppIcon>
<span class="dropdown-item ellipsis">
{{ item.name }}
</span>
<el-icon v-show="item.id === currentWorkspace?.id" class="ml-8" style="font-size: 16px;margin-right: 0;">
<Check />
</el-icon>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup lang="ts">
import { onBeforeMount, ref } from 'vue'
import WorkspaceApi from '@/api/workspace'
import type { WorkspaceItem } from '@/api/type/workspace'
const loading = ref(false)
const workspaceList = ref<WorkspaceItem[]>([])
const currentWorkspace = ref()
async function getWorkspaceList() {
try {
const res = await WorkspaceApi.getWorkspaceListByUser(loading);
workspaceList.value = res.data
} catch (e) {
console.error(e);
}
}
onBeforeMount(async () => {
await getWorkspaceList()
const id = localStorage.getItem('workspace_id') ?? 'default'
currentWorkspace.value = workspaceList.value.find(item => item.id === id)
})
function changeWorkspace(item: WorkspaceItem) {
if (item.id === currentWorkspace.value.id) return
currentWorkspace.value = item
localStorage.setItem('workspace_id', item.id as string)
window.location.reload()
}
</script>
<style lang="scss" scoped>
:deep(.el-button.is-text) {
color: var(--el-text-color-primary);
max-height: 32px;
}
.dropdown-title {
max-width: 155px;
font-size: 14px;
}
.dropdown-item {
max-width: 230px;
}
:deep(.el-dropdown-menu__item.active) {
color: var(--el-color-primary);
}
</style>

View File

@ -18,6 +18,7 @@ export default {
modifySuccess: 'Successful', modifySuccess: 'Successful',
cancel: 'Cancel', cancel: 'Cancel',
confirm: 'OK', confirm: 'OK',
close: 'Close',
tip: 'Tips', tip: 'Tips',
add: 'Add', add: 'Add',
refresh: 'Refresh', refresh: 'Refresh',

View File

@ -1,11 +1,15 @@
export default { export default {
// TODO title: 'Workspace',
title: '工作空间', list: 'Workspace list',
list: '工作空间列表', name: 'Workspace name',
name: '工作空间名称', delete: {
confirmTitle: 'Confirm to delete workspace:',
confirmContent: 'After deletion, all members in this space will be removed. Please proceed with caution.',
confirmContentNotDelete: 'This workspace contains knowledge base resources and application resources, and cannot be deleted.',
},
member: { member: {
delete: { delete: {
confirmTitle: '是否移除成员:', confirmTitle: 'Confirm to remove member:',
}
} }
} }
};

View File

@ -20,6 +20,7 @@ export default {
addSuccess: '添加成功', addSuccess: '添加成功',
cancel: '取消', cancel: '取消',
confirm: '确定', confirm: '确定',
close: '关闭',
tip: '提示', tip: '提示',
refresh: '刷新', refresh: '刷新',
search: '搜索', search: '搜索',

View File

@ -2,6 +2,11 @@ export default {
title: '工作空间', title: '工作空间',
list: '工作空间列表', list: '工作空间列表',
name: '工作空间名称', name: '工作空间名称',
delete: {
confirmTitle: '是否删除工作空间:',
confirmContent: '删除后,该空间下的成员都会被移除,请谨慎操作。',
confirmContentNotDelete: '该工作空间下存在 知识库资源、应用资源,无法删除。',
},
member: { member: {
delete: { delete: {
confirmTitle: '是否移除成员:', confirmTitle: '是否移除成员:',

View File

@ -18,6 +18,7 @@ export default {
modifySuccess: '修改成功', modifySuccess: '修改成功',
cancel: '取消', cancel: '取消',
confirm: '確認', confirm: '確認',
close: '關閉',
tip: '提示', tip: '提示',
add: '新增', add: '新增',
refresh: '重新整理', refresh: '重新整理',

View File

@ -1,11 +1,15 @@
export default { export default {
// TODO title: '工作空間',
title: '工作空间', list: '工作空間列表',
list: '工作空间列表', name: '工作空間名稱',
name: '工作空间名称', delete: {
confirmTitle: '是否刪除工作空間:',
confirmContent: '刪除後,該空間下的成員都會被移除,請謹慎操作。',
confirmContentNotDelete: '該工作空間下存在知識庫資源、應用資源,無法刪除。',
},
member: { member: {
delete: { delete: {
confirmTitle: '是否移除成员:', confirmTitle: '是否移除成員:',
}
} }
} }
};

View File

@ -12,17 +12,15 @@
:placeholder="$t('common.inputPlaceholder')" style="width: 220px" clearable /> :placeholder="$t('common.inputPlaceholder')" style="width: 220px" clearable />
</div> </div>
</div> </div>
<app-table class="mt-16" :data="tableData" :pagination-config="paginationConfig" <app-table class="mt-16" :data="tableData" :pagination-config="paginationConfig" @sizeChange="handleSizeChange"
@sizeChange="handleSizeChange"
@changePage="getList" v-loading="loading"> @changePage="getList" v-loading="loading">
<el-table-column prop="nick_name" :label="$t('views.userManage.form.nick_name.label')"/> <el-table-column prop="nick_name" :label="$t('views.userManage.userForm.nick_name.label')" />
<el-table-column prop="username" :label="$t('views.userManage.form.username.label')"/> <el-table-column prop="username" :label="$t('views.login.loginForm.username.label')" />
<el-table-column prop="workspace_name" :label="$t('views.role.member.workspace')" <el-table-column v-if="props.currentRole?.type !== RoleTypeEnum.ADMIN" prop="workspace_name"
v-if="currentRole?.type !==RoleTypeEnum.ADMIN"/> :label="$t('views.role.member.workspace')" />
<el-table-column :label="$t('common.operation')" width="100" fixed="right"> <el-table-column :label="$t('common.operation')" width="100" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-tooltip effect="dark" :content="`${$t('views.role.member.delete.button')}`" <el-tooltip effect="dark" :content="`${$t('views.role.member.delete.button')}`" placement="top">
placement="top">
<el-button type="primary" text @click.stop="handleDelete(row)"> <el-button type="primary" text @click.stop="handleDelete(row)">
<el-icon> <el-icon>
<Delete /> <Delete />
@ -43,7 +41,7 @@ import type {RoleItem, RoleMemberItem} from '@/api/type/role'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales' import { t } from '@/locales'
import AddMemberDrawer from './AddMemberDrawer.vue' import AddMemberDrawer from './AddMemberDrawer.vue'
import {RoleTypeEnum} from "@/enums/system.ts"; import { RoleTypeEnum } from '@/enums/system'
const props = defineProps<{ const props = defineProps<{
currentRole?: RoleItem currentRole?: RoleItem

View File

@ -13,8 +13,8 @@
</div> </div>
<app-table :data="tableData" :pagination-config="paginationConfig" @sizeChange="handleSizeChange" <app-table :data="tableData" :pagination-config="paginationConfig" @sizeChange="handleSizeChange"
@changePage="getList" v-loading="loading"> @changePage="getList" v-loading="loading">
<el-table-column prop="nick_name" :label="$t('views.userManage.form.nick_name.label')" /> <el-table-column prop="nick_name" :label="$t('views.userManage.userForm.nick_name.label')" />
<el-table-column prop="username" :label="$t('views.userManage.form.username.label')" /> <el-table-column prop="username" :label="$t('views.login.loginForm.username.label')" />
<el-table-column prop="role_name" :label="$t('views.role.member.role')" /> <el-table-column prop="role_name" :label="$t('views.role.member.role')" />
<el-table-column :label="$t('common.operation')" width="100" fixed="right"> <el-table-column :label="$t('common.operation')" width="100" fixed="right">
<template #default="{ row }"> <template #default="{ row }">

View File

@ -6,22 +6,18 @@
<div class="workspace-left border-r p-16"> <div class="workspace-left border-r p-16">
<div class="workspace-left_title"> <div class="workspace-left_title">
<h4 class="medium">{{ $t('views.workspace.list') }}</h4> <h4 class="medium">{{ $t('views.workspace.list') }}</h4>
<el-tooltip effect="dark" <el-tooltip effect="dark" :content="`${$t('common.create')}${$t('views.workspace.title')}`" placement="top">
:content="`${$t('common.create')}${$t('views.workspace.title')}`"
placement="top">
<el-button type="primary" text @click="createOrUpdateWorkspace()"> <el-button type="primary" text @click="createOrUpdateWorkspace()">
<AppIcon iconName="app-copy"></AppIcon> <AppIcon iconName="app-copy"></AppIcon>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="p-8"> <div class="p-8">
<el-input v-model="filterText" :placeholder="$t('common.search')" prefix-icon="Search" <el-input v-model="filterText" :placeholder="$t('common.search')" prefix-icon="Search" clearable />
clearable/>
</div> </div>
<div class="list-height-left"> <div class="list-height-left">
<el-scrollbar v-loading="loading"> <el-scrollbar v-loading="loading">
<common-list :data="filterList" @click="clickWorkspace" <common-list :data="filterList" @click="clickWorkspace" :default-active="currentWorkspace?.id">
:default-active="currentWorkspace?.id">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex-between"> <div class="flex-between">
<span>{{ row.name }}</span> <span>{{ row.name }}</span>
@ -63,8 +59,7 @@
<div class="flex align-center" style="margin-bottom: 20px;"> <div class="flex align-center" style="margin-bottom: 20px;">
<h4 class="medium">{{ currentWorkspace?.name }}</h4> <h4 class="medium">{{ currentWorkspace?.name }}</h4>
<el-divider direction="vertical" class="mr-8 ml-8" /> <el-divider direction="vertical" class="mr-8 ml-8" />
<AppIcon iconName="app-wordspace" style="font-size: 16px" <AppIcon iconName="app-wordspace" style="font-size: 16px" class="color-input-placeholder"></AppIcon>
class="color-input-placeholder"></AppIcon>
<span class="color-input-placeholder ml-4"> <span class="color-input-placeholder ml-4">
{{ currentWorkspace?.user_count }} {{ currentWorkspace?.user_count }}
</span> </span>
@ -137,11 +132,21 @@ function createOrUpdateWorkspace(item?: WorkspaceItem) {
createOrUpdateWorkspaceDialogRef.value?.open(item); createOrUpdateWorkspaceDialogRef.value?.open(item);
} }
// TODO async function check(id: string) {
function deleteWorkspace(item: WorkspaceItem) { try {
return await WorkspaceApi.deleteWorkspaceCheck(id);
} catch (error) {
console.log(error);
}
}
async function deleteWorkspace(item: WorkspaceItem) {
//
const canDelete = await check(item.id as string);
if (canDelete) {
MsgConfirm( MsgConfirm(
`${t('views.workspace.delete.confirmTitle')}${item.name} ?`, `${t('views.workspace.delete.confirmTitle')}${item.name} ?`,
t('views.workspace.delete.confirmMessage'), t('views.workspace.delete.confirmContent'),
{ {
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger', confirmButtonClass: 'danger',
@ -154,8 +159,16 @@ function deleteWorkspace(item: WorkspaceItem) {
currentWorkspace.value = item.id === currentWorkspace.value?.id ? list.value[0] : currentWorkspace.value currentWorkspace.value = item.id === currentWorkspace.value?.id ? list.value[0] : currentWorkspace.value
}) })
}) })
.catch(() => { } else {
}) MsgConfirm(
`${t('views.workspace.delete.confirmTitle')}${item.name} ?`,
t('views.workspace.delete.confirmContentNotDelete'),
{
showConfirmButton: false,
cancelButtonText: t('common.close'),
},
)
}
} }
</script> </script>