feat: workspace

This commit is contained in:
teukkk 2025-06-13 11:02:03 +08:00
parent 625b4d6b5d
commit 64b1f3d33a
21 changed files with 789 additions and 57 deletions

View File

@ -1,9 +1,9 @@
import { get, post, del } from '@/request/index'
import type { Ref } from 'vue'
import { Result } from '@/request/Result'
import type { RoleItem, RolePermissionItem, CreateOrUpdateParams, PageList, RoleMemberItem, CreateMemberParamsItem } from '@/api/type/role'
import type { RoleItem, RolePermissionItem, CreateOrUpdateParams, RoleMemberItem, CreateMemberParamsItem } from '@/api/type/role'
import { RoleTypeEnum } from '@/enums/system'
import type { pageRequest } from '@/api/type/common'
import type { pageRequest, PageList } from '@/api/type/common'
const prefix = '/system/role'
/**

View File

@ -11,4 +11,11 @@ interface pageRequest {
page_size: number
}
export type { KeyValue, Dict, pageRequest }
interface PageList<T> {
current: number,
size: number,
total: number,
records: T
}
export type { KeyValue, Dict, pageRequest, PageList }

View File

@ -51,14 +51,7 @@ interface RoleMemberItem {
interface CreateMemberParamsItem {
user_ids: string[],
workspace_ids: string[]
}
interface PageList<T> {
current: number,
size: number,
total: number,
records: T
workspace_ids?: string[]
}
type Arrayable<T> = T | T[]
@ -72,4 +65,4 @@ interface FormItemModel {
}
}
export type { RoleItem, FormItemModel, RolePermissionItem, RoleTableDataItem, CreateOrUpdateParams, PageList, ChildrenPermissionItem, RoleMemberItem, CreateMemberParamsItem }
export type { RoleItem, FormItemModel, RolePermissionItem, RoleTableDataItem, CreateOrUpdateParams, ChildrenPermissionItem, RoleMemberItem, CreateMemberParamsItem }

View File

@ -0,0 +1,19 @@
interface WorkspaceItem {
name: string,
id?: string
}
interface CreateWorkspaceMemberParamsItem {
user_ids: string[],
role_ids: string[]
}
interface WorkspaceMemberItem {
user_relation_id: string,
user_id: string,
username: string,
nick_name: string,
role_id: string,
role_name: string,
}
export type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem }

97
ui/src/api/workspace.ts Normal file
View File

@ -0,0 +1,97 @@
import { Result } from '@/request/Result'
import type { Ref } from 'vue'
import { get, post, del } from '@/request/index'
import type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem } from '@/api/type/workspace'
import type { pageRequest, PageList } from '@/api/type/common'
const prefix = '/system/workspace'
/**
*
*/
const getWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (loading) => {
return get('/workspace/current_user', undefined, loading)
}
/**
*
*/
const getSystemWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (loading) => {
return get(`${prefix}`, undefined, loading)
}
/**
*
*/
const CreateOrUpdateWorkspace: (
data: WorkspaceItem,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (data, loading) => {
return post(`${prefix}`, data, undefined, loading)
}
/**
*
*/
const deleteWorkspace: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (
workspace_id,
loading,
) => {
return del(`${prefix}/${workspace_id}`, undefined, {}, loading)
}
/**
*
*/
const getWorkspaceMemberList: (
workspace_id: string,
page: pageRequest,
param: any,
loading?: Ref<boolean>,
) => Promise<Result<PageList<WorkspaceMemberItem[]>>> = (workspace_id, page, param, loading) => {
return get(
`${prefix}/${workspace_id}/user_list/${page.current_page}/${page.page_size}`,
param,
loading,
)
}
/**
*
*/
const CreateWorkspaceMember: (
workspace_id: string,
data: CreateWorkspaceMemberParamsItem[],
loading?: Ref<boolean>,
) => Promise<Result<any>> = (workspace_id, data, loading) => {
return post(`${prefix}/${workspace_id}/add_member`, data, undefined, loading)
}
/**
*
*/
const deleteWorkspaceMember: (workspace_id: string, user_relation_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
workspace_id,
user_relation_id,
loading,
) => {
return post(`${prefix}/${workspace_id}/remove_member/${user_relation_id}`, undefined, {}, loading)
}
/**
*
*/
const getWorkspaceRoleList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (loading) => {
return get('/role_list/current_user', undefined, loading)
}
export default {
getWorkspaceList,
getSystemWorkspaceList,
CreateOrUpdateWorkspace,
deleteWorkspace,
getWorkspaceMemberList,
CreateWorkspaceMember,
deleteWorkspaceMember,
getWorkspaceRoleList,
}

View File

@ -1,6 +1,7 @@
import notFound from './404'
import application from './application'
import role from './role'
import workspace from './workspace'
import applicationOverview from './application-overview'
import knowledge from './knowledge'
import system from './system'
@ -34,5 +35,6 @@ export default {
chatLog,
login,
operateLog,
role
role,
workspace
}

View File

@ -0,0 +1,11 @@
export default {
// TODO
title: '工作空间',
list: '工作空间列表',
name: '工作空间名称',
member: {
delete: {
confirmTitle: '是否移除成员:',
}
}
}

View File

@ -7,6 +7,7 @@ import system from './system'
import userManage from './user-manage'
import resourceAuthorization from './resource-authorization'
import role from './role'
import workspace from './workspace'
import application from './application'
import problem from './problem'
import applicationOverview from './application-overview'
@ -26,6 +27,7 @@ export default {
userManage,
resourceAuthorization,
role,
workspace,
application,
problem,
applicationOverview,

View File

@ -0,0 +1,10 @@
export default {
title: '工作空间',
list: '工作空间列表',
name: '工作空间名称',
member: {
delete: {
confirmTitle: '是否移除成员:',
}
}
}

View File

@ -1,6 +1,7 @@
import notFound from './404'
import application from './application'
import role from './role'
import workspace from './workspace'
import applicationOverview from './application-overview'
import knowledge from './knowledge'
import system from './system'
@ -34,5 +35,6 @@ export default {
chatLog,
login,
operateLog,
role
role,
workspace
}

View File

@ -0,0 +1,11 @@
export default {
// TODO
title: '工作空间',
list: '工作空间列表',
name: '工作空间名称',
member: {
delete: {
confirmTitle: '是否移除成员:',
}
}
}

View File

@ -45,6 +45,19 @@ const systemRouter = {
},
component: () => import('@/views/role/index.vue'),
},
{
path: '/system/workspace',
name: 'workspace',
meta: {
icon: 'app-resource-authorization', // TODO
iconActive: 'app-resource-authorization-active', // TODO
title: 'views.workspace.title',
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
},
component: () => import('@/views/workspace/index.vue'),
},
{
path: '/system/shared',
name: 'shared',

View File

@ -109,6 +109,9 @@ h5 {
.bold {
font-weight: 600;
}
.medium {
font-weight: 500;
}
.lighter {
font-weight: 400;
}

View File

@ -4,7 +4,8 @@
<h4>{{ $t('views.role.member.add') }}</h4>
</template>
<template #default>
<MemberFormContent ref="memberFormContentRef" :models="formItemModel" v-model:form="list" />
<MemberFormContent ref="memberFormContentRef" :models="formItemModel" v-model:form="list"
v-loading="memberFormContentLoading" />
</template>
<template #footer>
<div style="flex: auto">
@ -22,12 +23,15 @@ import { onBeforeMount, ref } from 'vue'
import type { CreateMemberParamsItem, FormItemModel } from '@/api/type/role'
import RoleApi from '@/api/system/role'
import UserApi from '@/api/user/user'
import WorkspaceApi from '@/api/workspace'
import MemberFormContent from './MemberFormContent.vue'
import { t } from '@/locales'
import type { RoleItem } from '@/api/type/role'
import { MsgSuccess } from '@/utils/message'
import { RoleTypeEnum } from '@/enums/system'
const props = defineProps<{
roleId: string
currentRole?: RoleItem
}>()
const emit = defineEmits<{
@ -38,55 +42,95 @@ const loading = ref(false)
const visible = ref(false)
const list = ref<CreateMemberParamsItem[]>([]);
const formItemModel = ref<FormItemModel[]>([
{
path: 'user_ids',
label: t('views.role.member.title'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,
},
],
selectProps: {
options: [],
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`
}
},
{
path: 'workspace_ids',
label: t('views.role.member.workspace'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,
},
],
selectProps: {
options: [], // TODO
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`
}
},
]);
const memberFormContentLoading = ref(false);
const formItemModel = ref<FormItemModel[]>([]);
const userFormItem = ref<FormItemModel[]>([]);
const workspaceFormItem = ref<FormItemModel[]>([]);
async function getUserFormItem() {
try {
const res = await UserApi.getUserList(memberFormContentLoading);
userFormItem.value = [{
path: 'user_ids',
label: t('views.role.member.title'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,
},
],
selectProps: {
options: res.data?.map(item => ({
label: item.nick_name,
value: item.id
})) || [],
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`
}
}];
} catch (e) {
console.error(e);
}
}
async function getWorkspaceFormItem() {
try {
const res = await WorkspaceApi.getWorkspaceList(memberFormContentLoading);
workspaceFormItem.value = [{
path: 'workspace_ids',
label: t('views.role.member.workspace'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,
},
],
selectProps: {
options: res.data?.map(item => ({
label: item.name,
value: item.id
})) || [],
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`
}
}]
} catch (e) {
console.error(e);
}
}
function init() {
if (props.currentRole?.type !== RoleTypeEnum.ADMIN) {
formItemModel.value = [...userFormItem.value, ...workspaceFormItem.value]
list.value = [{ user_ids: [], workspace_ids: [] }]
} else {
formItemModel.value = [...userFormItem.value]
list.value = [{ user_ids: [] }]
}
}
onBeforeMount(async () => {
const res = await UserApi.getUserList();
formItemModel.value[0].selectProps.options = res.data?.map(item => ({ label: item.nick_name, value: item.id }))
await getUserFormItem();
await getWorkspaceFormItem();
init()
})
function open() {
init();
visible.value = true
}
function handleCancel() {
visible.value = false
list.value = []
}
const memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()
function handleAdd() {
memberFormContentRef.value?.validate().then(async (valid) => {
if (valid) {
await RoleApi.CreateMember(props.roleId, { members: list.value }, loading)
let params;
if (props.currentRole?.type === RoleTypeEnum.ADMIN) {
params = list.value.map(item => ({ user_ids: item.user_ids, workspace_ids: ['None'] }))
}
await RoleApi.CreateMember(props.currentRole?.id as string, { members: params ?? list.value }, loading)
MsgSuccess(t('common.addSuccess'))
handleCancel();
emit('refresh')

View File

@ -30,7 +30,7 @@
</el-table-column>
</app-table>
</div>
<AddMemberDrawer ref="addMemberDrawerRef" :role-id="props.currentRole?.id as string" @refresh="getList" />
<AddMemberDrawer ref="addMemberDrawerRef" :currentRole="props.currentRole" @refresh="getList" />
</template>
<script setup lang="ts">

View File

@ -6,7 +6,7 @@
:rules="model.rules" :label="index === 0 && model.label ? model.label : ''" class="mr-8" style="flex: 1">
<el-select v-model="element[model.path]"
:placeholder="model.selectProps.placeholder ?? $t('common.selectPlaceholder')" clearable filterable multiple
style="width: 100%">
style="width: 100%" collapse-tags collapse-tags-tooltip>
<el-option v-for="opt in model.selectProps.options" :key="opt.value" :label="opt.label"
:value="opt.value" />
</el-select>
@ -53,7 +53,6 @@ watch(() => props.models, () => {
props.models.forEach((e) => {
formItem[e.path] = [];
});
handleAdd();
}, { immediate: true })
function handleDelete(index: number) {
@ -68,5 +67,4 @@ const validate = () => {
}
defineExpose({ validate })
</script>

View File

@ -25,9 +25,11 @@
<template #dropdown>
<el-dropdown-menu style="min-width: 80px">
<el-dropdown-item @click.stop="createOrUpdateRole(row)" class="p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('common.rename') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteRole(row)" class="border-t p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
@ -66,10 +68,13 @@
</el-button>
<template #dropdown>
<el-dropdown-menu style="min-width: 80px">
<el-dropdown-item @click.stop="createOrUpdateRole(row)" class="p-8"> {{ $t('common.rename') }}
<el-dropdown-item @click.stop="createOrUpdateRole(row)" class="p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('common.rename') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteRole(row)" class="border-t p-8"> {{ $t('common.delete')
}}
<el-dropdown-item @click.stop="deleteRole(row)" class="border-t p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>

View File

@ -0,0 +1,131 @@
<template>
<el-drawer v-model="visible" size="600" :destroy-on-close="true" :before-close="handleCancel">
<template #header>
<h4>{{ $t('views.role.member.add') }}</h4>
</template>
<template #default>
<MemberFormContent ref="memberFormContentRef" :models="formItemModel" v-model:form="list"
v-loading="memberFormContentLoading" />
</template>
<template #footer>
<div style="flex: auto">
<el-button @click="handleCancel">{{ $t('common.cancel') }}</el-button>
<el-button type="primary" @click="handleAdd()" :loading="loading">
{{ $t('common.add') }}
</el-button>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { onBeforeMount, ref } from 'vue'
import UserApi from '@/api/user/user'
import WorkspaceApi from '@/api/workspace'
import MemberFormContent from '@/views/role/component/MemberFormContent.vue'
import { t } from '@/locales'
import { MsgSuccess } from '@/utils/message'
import type { CreateWorkspaceMemberParamsItem, WorkspaceItem } from '@/api/type/workspace'
import type { FormItemModel } from '@/api/type/role'
const props = defineProps<{
currentWorkspace?: WorkspaceItem
}>()
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
const loading = ref(false)
const visible = ref(false)
const list = ref<CreateWorkspaceMemberParamsItem[]>([]);
const memberFormContentLoading = ref(false);
const formItemModel = ref<FormItemModel[]>([]);
const userFormItem = ref<FormItemModel[]>([]);
const roleFormItem = ref<FormItemModel[]>([]);
async function getUserFormItem() {
try {
const res = await UserApi.getUserList(memberFormContentLoading);
userFormItem.value = [{
path: 'user_ids',
label: t('views.role.member.title'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,
},
],
selectProps: {
options: res.data?.map(item => ({
label: item.nick_name,
value: item.id
})) || [],
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`
}
}];
} catch (e) {
console.error(e);
}
}
async function getRoleFormItem() {
try {
const res = await WorkspaceApi.getWorkspaceRoleList(memberFormContentLoading);
roleFormItem.value = [{
path: 'role_ids',
label: t('views.role.member.role'),
rules: [
{
required: true,
message: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,
},
],
selectProps: {
options: res.data?.map(item => ({
label: item.name,
value: item.id
})) || [],
placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`
}
}]
} catch (e) {
console.error(e);
}
}
function init() {
formItemModel.value = [...userFormItem.value, ...roleFormItem.value]
list.value = [{ user_ids: [], role_ids: [] }]
}
onBeforeMount(async () => {
await getUserFormItem();
await getRoleFormItem();
init()
})
function open() {
init();
visible.value = true
}
function handleCancel() {
visible.value = false
}
const memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()
function handleAdd() {
memberFormContentRef.value?.validate().then(async (valid) => {
if (valid) {
await WorkspaceApi.CreateWorkspaceMember(props.currentWorkspace?.id as string, list.value, loading)
MsgSuccess(t('common.addSuccess'))
handleCancel();
emit('refresh')
}
})
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,70 @@
<template>
<el-dialog :title="`${!form.id ? $t('common.create') : $t('common.rename')}${$t('views.workspace.title')}`"
v-model="dialogVisible" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-form label-position="top" ref="formRef" :rules="rules" :model="form" require-asterisk-position="right">
<el-form-item :label="$t('views.workspace.name')" prop="name">
<el-input v-model="form.name" maxlength="64"
:placeholder="`${$t('common.inputPlaceholder')}${$t('views.workspace.name')}`" />
</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="submit(formRef)" :loading="loading">
{{ !form.id ? $t('common.create') : $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import type { WorkspaceItem } from '@/api/type/workspace'
import WorkspaceApi from '@/api/workspace'
const emit = defineEmits<{
(e: 'refresh', currentWorkspace: WorkspaceItem): void;
}>();
const dialogVisible = ref<boolean>(false)
const defaultForm = {
name: ''
}
const form = ref<WorkspaceItem>({
...defaultForm,
})
function open(item?: WorkspaceItem) {
if (item) {
form.value = { id: item.id, name: item.name }
} else {
form.value = { ...defaultForm }
}
dialogVisible.value = true
}
const formRef = ref<FormInstance>();
const rules = reactive({
name: [{ required: true, message: `${t('common.inputPlaceholder')}${t('views.workspace.name')}`, trigger: 'blur' }],
})
const loading = ref<boolean>(false)
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
WorkspaceApi.CreateOrUpdateWorkspace(form.value, loading).then((res: any) => {
MsgSuccess(!form.value.id ? t('common.createSuccess') : t('common.renameSuccess'))
emit('refresh', res.data)
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,109 @@
<template>
<div class="flex-between mb-16">
<el-button type="primary" @click="handleAdd">
{{ $t('views.role.member.add') }}
</el-button>
<div class="flex complex-search">
<el-select class="complex-search__left" v-model="searchType" style="width: 120px">
<el-option :label="$t('views.login.loginForm.username.label')" value="username" />
</el-select>
<el-input v-if="searchType === 'username'" v-model="searchForm.username" @change="getList"
:placeholder="$t('common.inputPlaceholder')" style="width: 220px" clearable />
</div>
</div>
<app-table :data="tableData" :pagination-config="paginationConfig" @sizeChange="handleSizeChange"
@changePage="getList" v-loading="loading">
<el-table-column prop="nick_name" :label="$t('views.userManage.form.nick_name.label')" />
<el-table-column prop="username" :label="$t('views.userManage.form.username.label')" />
<el-table-column prop="role_name" :label="$t('views.role.member.role')" />
<el-table-column :label="$t('common.operation')" width="100" fixed="right">
<template #default="{ row }">
<el-tooltip effect="dark" :content="`${$t('views.role.member.delete.button')}`" placement="top">
<el-button type="primary" text @click.stop="handleDelete(row)">
<el-icon>
<EditPen />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</app-table>
<AddMemberDrawer ref="addMemberDrawerRef" :currentWorkspace="props.currentWorkspace" @refresh="getList" />
</template>
<script setup lang="ts">
import { onMounted, ref, reactive, watch } from 'vue'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
import AddMemberDrawer from './AddMemberDrawer.vue'
import WorkspaceApi from '@/api/workspace'
import type { WorkspaceMemberItem, WorkspaceItem } from '@/api/type/workspace'
const props = defineProps<{
currentWorkspace?: WorkspaceItem
}>()
const loading = ref(false)
const searchType = ref('username')
const searchForm = ref<Record<string, any>>({
username: '',
})
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
total: 0,
})
const tableData = ref<WorkspaceMemberItem[]>([])
async function getList() {
if (!props.currentWorkspace?.id) return
try {
const params = {
[searchType.value]: searchForm.value[searchType.value],
}
const res = await WorkspaceApi.getWorkspaceMemberList(props.currentWorkspace?.id, paginationConfig, params, loading)
tableData.value = res.data.records
paginationConfig.total = res.data.total
} catch (error) {
console.error(error)
}
}
function handleSizeChange() {
paginationConfig.current_page = 1
getList()
}
onMounted(() => {
getList()
})
watch(() => props.currentWorkspace?.id, () => {
getList()
})
const addMemberDrawerRef = ref<InstanceType<typeof AddMemberDrawer>>()
function handleAdd() {
addMemberDrawerRef.value?.open();
}
function handleDelete(row: WorkspaceMemberItem) {
MsgConfirm(
`${t('views.workspace.member.delete.confirmTitle')}${row.nick_name} ?`, '',
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
},
)
.then(() => {
loading.value = true
WorkspaceApi.deleteWorkspaceMember(props.currentWorkspace?.id as string, row.user_relation_id, loading).then(() => {
MsgSuccess(t('common.deleteSuccess'))
getList()
})
})
.catch(() => { })
}
</script>

View File

@ -0,0 +1,205 @@
<template>
<div class="workspace">
<h2 class="mb-16">{{ $t('views.workspace.title') }}</h2>
<el-card style="--el-card-padding: 0" body-class="workspace-card">
<div class="flex h-full">
<div class="workspace-left border-r p-16">
<div class="workspace-left_title">
<h4 class="medium">{{ $t('views.workspace.list') }}</h4>
<el-tooltip effect="dark" :content="`${$t('common.create')}${$t('views.workspace.title')}`" placement="top">
<el-button type="primary" text @click="createOrUpdateWorkspace()">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
</div>
<div class="p-8">
<el-input v-model="filterText" :placeholder="$t('common.search')" prefix-icon="Search" clearable />
</div>
<div class="list-height-left">
<el-scrollbar v-loading="loading">
<common-list :data="filterList" @click="clickWorkspace" :default-active="currentWorkspace?.id">
<template #default="{ row }">
<div class="flex-between">
<span>{{ row.name }}</span>
<el-dropdown :teleported="false">
<el-button text>
<el-icon class="color-secondary">
<MoreFilled />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu style="min-width: 80px">
<el-dropdown-item @click.stop="createOrUpdateWorkspace(row)" class="p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{
$t('common.rename') }}
</el-dropdown-item>
<el-dropdown-item @click.stop="deleteWorkspace(row)" class="border-t p-8">
<AppIcon iconName="app-copy"></AppIcon>
{{
$t('common.delete')
}}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<template #empty>
<span></span>
</template>
</common-list>
</el-scrollbar>
</div>
</div>
<!-- 右边 -->
<div class="workspace-right" v-loading="loading">
<div class="flex align-center" style="margin-bottom: 20px;">
<h4 class="medium">{{ currentWorkspace?.name }}</h4>
<el-divider direction="vertical" class="mr-8 ml-8" />
<AppIcon iconName="app-wordspace" style="font-size: 16px" class="color-input-placeholder"></AppIcon>
<span class="color-input-placeholder ml-4">
数字
</span>
</div>
<Member :currentWorkspace="currentWorkspace" />
</div>
</div>
</el-card>
<CreateOrUpdateWorkspaceDialog ref="createOrUpdateWorkspaceDialogRef" @refresh="refresh" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'
import WorkspaceApi from '@/api/workspace'
import { t } from '@/locales'
import Member from './component/Member.vue'
import CreateOrUpdateWorkspaceDialog from './component/CreateOrUpdateWorkspaceDialog.vue'
import type { WorkspaceItem } from '@/api/type/workspace'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
const filterText = ref('')
const loading = ref(false)
const list = ref<WorkspaceItem[]>([])
const filterList = ref<WorkspaceItem[]>([]) //
const currentWorkspace = ref<WorkspaceItem>()
async function getWorkspace() {
try {
const res = await WorkspaceApi.getSystemWorkspaceList(loading)
list.value = res.data
filterList.value = filter(list.value, filterText.value)
} catch (error) {
console.error(error)
}
}
onMounted(async () => {
await getWorkspace()
currentWorkspace.value = list.value[0]
})
async function refresh(workspace?: WorkspaceItem) {
await getWorkspace();
//
currentWorkspace.value = workspace ? workspace : currentWorkspace.value
}
function filter(list: WorkspaceItem[], filterText: string) {
if (!filterText.length) {
return list
}
return list.filter((v: WorkspaceItem) =>
v.name.toLowerCase().includes(filterText.toLowerCase()),
)
}
watch(filterText, (val: string) => {
filterList.value = filter(list.value, val)
})
function clickWorkspace(item: WorkspaceItem) {
currentWorkspace.value = item
}
const createOrUpdateWorkspaceDialogRef = ref<InstanceType<typeof CreateOrUpdateWorkspaceDialog>>()
function createOrUpdateWorkspace(item?: WorkspaceItem) {
createOrUpdateWorkspaceDialogRef.value?.open(item);
}
// TODO
function deleteWorkspace(item: WorkspaceItem) {
MsgConfirm(
`${t('views.workspace.delete.confirmTitle')}${item.name} ?`,
t('views.workspace.delete.confirmMessage'),
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
},
)
.then(() => {
WorkspaceApi.deleteWorkspace(item.id as string, loading).then(async () => {
MsgSuccess(t('common.deleteSuccess'))
await getWorkspace()
currentWorkspace.value = item.id === currentWorkspace.value?.id ? list.value[0] : currentWorkspace.value
})
})
.catch(() => { })
}
</script>
<style lang="scss" scoped>
.workspace {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
box-sizing: border-box;
padding: 16px 24px;
:deep(.workspace-card) {
height: 100%;
overflow: hidden;
}
.workspace-left {
box-sizing: border-box;
width: var(--setting-left-width);
min-width: var(--setting-left-width);
.workspace-left_title {
padding: 8px;
display: flex;
justify-content: space-between;
}
.list-height-left {
height: calc(100vh - 255px);
:deep(.common-list li) {
padding-right: 4px;
padding-left: 8px;
}
}
.workspace-left_divider {
padding: 0 8px;
:deep(.el-divider) {
margin: 4px 0;
}
}
}
.workspace-right {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 24px;
}
}
</style>