feat: system

This commit is contained in:
wangdan-fit2cloud 2025-06-20 17:02:42 +08:00
parent 66606bcaec
commit 7f1f9ccd3b
17 changed files with 252 additions and 174 deletions

View File

@ -77,6 +77,7 @@ defineExpose({
font-size: 14px;
margin-bottom: 4px;
&.active {
background: var(--el-color-primary-light-9);
border-radius: 4px;
color: var(--el-color-primary);
font-weight: 500;

View File

@ -23,6 +23,8 @@ import TagEllipsis from './tag-ellipsis/index.vue'
import CardCheckbox from './card-checkbox/index.vue'
import AiChat from './ai-chat/index.vue'
import KnowledgeIcon from './app-icon/KnowledgeIcon.vue'
import TagGroup from './tag-group/index.vue'
import WorkspaceDropdown from './workspace-dropdown/index.vue'
export default {
install(app: App) {
app.component('LogoFull', LogoFull)
@ -49,5 +51,7 @@ export default {
app.component('CardCheckbox', CardCheckbox)
app.component('AiChat', AiChat)
app.component('KnowledgeIcon', KnowledgeIcon)
app.component('TagGroup', TagGroup)
app.component('WorkspaceDropdown', WorkspaceDropdown)
},
}

View File

@ -1,10 +1,10 @@
<template>
<div class="tag-group" v-if="props.tags.length">
<el-tag class="default-tag" style="max-width: 100%;">
<span class="ellipsis" style="max-width: 100%;">{{ props.tags[0] }}</span>
<el-tag class="default-tag" style="max-width: 100%">
<span class="ellipsis" style="max-width: 100%">{{ props.tags[0] }}</span>
</el-tag>
<el-tooltip effect="light">
<el-tag class="info-tag ml-4" v-if="props.tags?.length > 1">
<el-tag class="info-tag ml-4 cursor" v-if="props.tags?.length > 1">
+{{ props.tags?.length - 1 }}
</el-tag>
<template #content>
@ -17,9 +17,8 @@
</template>
<script setup lang="ts">
const props = defineProps<{
tags: string[],
tags: string[]
}>()
</script>
<style lang="scss" scoped>
@ -28,4 +27,4 @@ const props = defineProps<{
width: 100%;
}
}
</style>
</style>

View File

@ -1,8 +1,8 @@
<template>
<el-dropdown placement="bottom-start">
<el-button text>
<el-dropdown placement="bottom-start" class="workspace-dropdown">
<el-button text style="font-size: 14px" class="workspace-dropdown__button">
<AppIcon iconName="app-wordspace" style="font-size: 18px"></AppIcon>
<span class="dropdown-title ellipsis">
<span class="ellipsis" style="max-width: 155px">
{{ currentWorkspace?.name }}
</span>
<el-icon class="el-icon--right">
@ -18,7 +18,7 @@
@click="changeWorkspace(item)"
>
<AppIcon class="mr-8" iconName="app-wordspace" style="font-size: 16px"></AppIcon>
<span class="dropdown-item ellipsis">
<span class="ellipsis" style="max-width: 230px">
{{ item.name }}
</span>
<el-icon
@ -48,27 +48,16 @@ const currentWorkspace = computed(() => {
function changeWorkspace(item: WorkspaceItem) {
if (item.id === user.workspace_id) return
user.setWorkspaceId(item.id || 'default')
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);
.workspace-dropdown {
&__button {
font-size: 14px;
padding: 0 12px !important;
max-height: 32px;
}
}
</style>

View File

@ -8,7 +8,11 @@
<div class="flex-between w-full">
<div class="ml-24 flex align-center">
<!-- 企业版: 工作空间下拉框-->
<el-divider class="mr-16" direction="vertical" v-if="hasPermission(EditionConst.IS_EE, 'OR')" />
<el-divider
class="mr-8"
direction="vertical"
v-if="hasPermission(EditionConst.IS_EE, 'OR')"
/>
<WorkspaceDropdown v-if="hasPermission(EditionConst.IS_EE, 'OR')" />
</div>
<TopMenu></TopMenu>
@ -23,12 +27,6 @@ import Avatar from './avatar/index.vue'
import TopAbout from './top-about/index.vue'
import { EditionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
import WorkspaceDropdown from './workspace-dropdown/index.vue'
import { useRouter } from 'vue-router'
import useStore from '@/stores'
const router = useRouter()
const { user } = useStore()
</script>
<style lang="scss" scoped>
.app-top-bar-container {

View File

@ -8,7 +8,6 @@ import system from './system'
import tool from './tool'
import userManage from './user-manage'
import resourceAuthorization from './resource-authorization'
import team from './team'
import model from './model'
import document from './document'
import paragraph from './paragraph'
@ -26,7 +25,6 @@ export default {
tool,
userManage,
resourceAuthorization,
team,
model,
knowledge,
applicationWorkflow,

View File

@ -1,32 +1,27 @@
export default {
title: 'Resource Authorization',
member:'Member',
member: 'Member',
manage: 'Owner',
permissionSetting: 'Permission Setting',
addMember: 'Add Member',
addSubTitle: 'After members log in,they can access the data you have authorized.',
searchBar: {
placeholder: 'Please enter the username to search'
placeholder: 'Please enter the username to search',
},
delete: {
button: 'Remove',
confirmTitle: 'Wheather to remove the member:',
confirmMessage: "After removal, the member's knowledge base and application permissions will be revoked. "
confirmMessage:
"After removal, the member's knowledge base and application permissions will be revoked. ",
},
setting: {
management: ' management',
check: 'check'
check: 'check',
authorization: 'authorization',
},
priority: {
label: 'Resource permission priority',
role: 'Role',
customize: 'Customize',
},
teamForm: {
form: {
userName: {
label: 'Username/Email',
placeholder: "Please enter the member's username or email",
requiredMessage: 'Please enter Username/Email',
},
},
}
}

View File

@ -1,30 +0,0 @@
export default {
title: 'Team Members',
member: 'Member',
manage: 'Owner',
permissionSetting: 'Permission Settings',
addMember: 'Add Member',
addSubTitle: 'Members can access the data you authorize after logging in.',
searchBar: {
placeholder: 'Enter username to search'
},
delete: {
button: 'Remove',
confirmTitle: 'Confirm removal of member:',
confirmMessage:
'Removing the member will revoke their access to knowledge and APP.'
},
setting: {
management: 'Manage',
check: 'View'
},
teamForm: {
form: {
userName: {
label: 'Username/Email',
placeholder: "Enter the member's username or email",
requiredMessage: 'Enter the username/email'
}
}
}
}

View File

@ -1,3 +1,5 @@
import role from './role'
export default {
title: '资源授权',
member: '成员',
@ -6,26 +8,21 @@ export default {
addMember: '添加成员',
addSubTitle: '成员登录后可以访问到您授权的数据。',
searchBar: {
placeholder: '请输入用户名搜索'
placeholder: '请输入用户名搜索',
},
delete: {
button: '移除',
confirmTitle: '是否移除成员:',
confirmMessage: '移除后将会取消成员拥有的知识库和应用权限。'
confirmMessage: '移除后将会取消成员拥有的知识库和应用权限。',
},
setting: {
management: '管理',
check: '查看'
check: '查看',
authorization: '授权',
},
priority: {
label: '资源权限优先级',
role: '按角色',
customize: '自定义',
},
teamForm: {
form: {
userName: {
label: '用户名/邮箱',
placeholder: '请输入成员的用户名或邮箱',
requiredMessage: '请输入用户名/邮箱'
},
},
}
}

View File

@ -8,7 +8,6 @@ import system from './system'
import tool from './tool'
import userManage from './user-manage'
import resourceAuthorization from './resource-authorization'
import team from './team'
import model from './model'
import document from './document'
import paragraph from './paragraph'
@ -26,7 +25,6 @@ export default {
tool,
userManage,
resourceAuthorization,
team,
model,
knowledge,
applicationWorkflow,

View File

@ -1,3 +1,21 @@
export default {
title: '资源授权',
member: '成员',
manage: '所有者',
permissionSetting: '资源权限配置',
addMember: '添加成员',
addSubTitle: '成员登录后可以访问到您授权的数据。',
searchBar: {
placeholder: '请输入用户名搜索',
},
delete: {
button: '移除',
confirmTitle: '是否移除成员:',
confirmMessage: '移除后将会取消成员拥有知识库和应用权限。',
},
setting: {
management: '管理',
check: '查看',
authorization: '授权',
},
}

View File

@ -1,29 +0,0 @@
export default {
title: '團隊成員',
member: '成員',
manage: '所有者',
permissionSetting: '權限設定',
addMember: '新增成員',
addSubTitle: '成員登入後可以存取您授權的資料。',
searchBar: {
placeholder: '請輸入使用者名稱搜尋'
},
delete: {
button: '移除',
confirmTitle: '是否移除成員:',
confirmMessage: '移除後將會取消成員擁有之知識庫和應用程式權限。'
},
setting: {
management: '管理',
check: '查看'
},
teamForm: {
form: {
userName: {
label: '使用者名稱/電子郵件',
placeholder: '請輸入成員的使用者名稱或電子郵件',
requiredMessage: '請輸入使用者名稱/電子郵件'
}
}
}
}

View File

@ -81,7 +81,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole], [PermissionConst.ROLE_READ], [EditionConst.IS_EE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],
[PermissionConst.ROLE_READ],
[EditionConst.IS_EE],
'OR',
),
],
},
component: () => import('@/views/role/index.vue'),
},
@ -95,7 +102,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN], [PermissionConst.WORKSPACE_WORKSPACE_READ], [EditionConst.IS_EE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.WORKSPACE_WORKSPACE_READ],
[EditionConst.IS_EE],
'OR',
),
],
},
component: () => import('@/views/workspace/index.vue'),
},
@ -157,7 +171,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN], [PermissionConst.WORKSPACE_USER_GROUP_READ], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.WORKSPACE_USER_GROUP_READ],
[EditionConst.IS_EE, EditionConst.IS_PE],
'OR',
),
],
},
children: [
{
@ -168,7 +189,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN], [PermissionConst.WORKSPACE_CHAT_USER_READ], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.WORKSPACE_CHAT_USER_READ],
[EditionConst.IS_EE, EditionConst.IS_PE],
'OR',
),
],
},
component: () => import('@/views/system-chat-user/user-manage/index.vue'),
},
@ -180,7 +208,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN], [PermissionConst.WORKSPACE_USER_GROUP_READ], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.WORKSPACE_USER_GROUP_READ],
[EditionConst.IS_EE, EditionConst.IS_PE],
'OR',
),
],
},
component: () => import('@/views/system-chat-user/group/index.vue'),
},
@ -192,8 +227,14 @@ const systemRouter = {
activeMenu: '/system',
parentPath: '/system',
parentName: 'system',
permission: [new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.CHAT_USER_AUTH_READ], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR'),],
permission: [
new ComplexPermission(
[RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.ADMIN],
[PermissionConst.CHAT_USER_AUTH_READ],
[EditionConst.IS_EE, EditionConst.IS_PE],
'OR',
),
],
},
component: () => import('@/views/system-chat-user/authentication/index.vue'),
},

View File

@ -65,8 +65,11 @@
}
&:focus {
color: var(--el-button-text-color);
background-color: var(--el-button-bg-color);
border-color: var(--el-button-border-color);
background-color: none;
border-color: none;
}
&:focus-visible {
outline: none !important;
}
&.is-link:focus {
background: none;
@ -105,6 +108,9 @@
color: var(--el-menu-active-color);
background: var(--el-color-primary-light-9);
}
&.active {
color: var(--el-color-primary);
}
}
// message

View File

@ -1,13 +1,35 @@
<template>
<el-input
v-model="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
class="mb-16 mt-4 1"
clearable
/>
<div class="pt-0">
<el-table default-expand-all row-key="id" :data="filterData" :max-height="tableHeight">
<div class="w-full">
<div class="flex-between mb-16">
<div class="flex align-center" v-if="hasPermission(EditionConst.IS_EE, 'OR')">
<!-- 企业版: 选优先级-->
<span class="lighter mr-16">{{ $t('views.resourceAuthorization.priority.label') }}</span>
<el-radio-group v-model="isRole">
<el-radio :value="true" size="large">{{
$t('views.resourceAuthorization.priority.role')
}}</el-radio>
<el-radio :value="false" size="large">{{
$t('views.resourceAuthorization.priority.customize')
}}</el-radio>
</el-radio-group>
</div>
<el-input
v-model="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
class="mt-4"
:class="hasPermission(EditionConst.IS_EE, 'OR') ? 'w-240' : ''"
clearable
/>
</div>
<el-table
row-key="id"
:data="filterData"
:max-height="tableHeight"
:expand-row-keys="['default']"
style="width: 100%"
>
<el-table-column class-name="folder-flex" prop="name" :label="$t('common.name')">
<template #default="{ row }">
<div class="flex align-center">
@ -49,10 +71,40 @@
</template>
</el-table-column>
<el-table-column
v-if="isRole"
:label="$t('views.resourceAuthorization.setting.authorization')"
align="center"
width="100"
>
<!-- <template #header>
<el-checkbox
:disabled="props.manage"
v-model="allChecked[AuthorizationEnum.MANAGE]"
:indeterminate="allIndeterminate[AuthorizationEnum.MANAGE]"
:label="$t('views.resourceAuthorization.setting.management')"
/>
</template> -->
<template #default="{ row }">
<el-checkbox
v-if="row.isFolder"
:disabled="props.manage"
v-model="row.permission[AuthorizationEnum.ROLE]"
:indeterminate="row.permissionHalf[AuthorizationEnum.ROLE]"
@change="(e: boolean) => checkedOperateChange(AuthorizationEnum.ROLE, row, e)"
/>
<el-checkbox
v-else
:disabled="props.manage"
v-model="row.permission[AuthorizationEnum.ROLE]"
@change="(e: boolean) => checkedOperateChange(AuthorizationEnum.ROLE, row, e)"
/>
</template>
</el-table-column>
<el-table-column
v-if="!isRole"
:label="$t('views.resourceAuthorization.setting.management')"
align="center"
width="100"
fixed="right"
>
<!-- <template #header>
<el-checkbox
@ -79,10 +131,10 @@
</template>
</el-table-column>
<el-table-column
v-if="!isRole"
:label="$t('views.resourceAuthorization.setting.check')"
align="center"
width="100"
fixed="right"
>
<!-- <template #header>
<el-checkbox
@ -115,6 +167,8 @@
import { ref, onMounted, watch, computed } from 'vue'
import { AuthorizationEnum } from '@/enums/system'
import { isAppIcon } from '@/utils/common'
import { EditionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
const props = defineProps({
data: {
@ -127,6 +181,8 @@ const props = defineProps({
manage: Boolean,
})
const isRole = ref(false)
const isKnowledge = computed(() => props.type === AuthorizationEnum.KNOWLEDGE)
const isApplication = computed(() => props.type === AuthorizationEnum.APPLICATION)

View File

@ -1,24 +1,42 @@
<template>
<div class="resource-authorization p-16-24">
<h2 class="mb-16">{{ $t('views.userManage.title') }}</h2>
<div class="flex align-center mb-16">
<h2>{{ $t('views.resourceAuthorization.title') }}</h2>
<!-- 企业版: 工作空间下拉框-->
<el-divider
class="mr-16"
direction="vertical"
v-if="hasPermission(EditionConst.IS_EE, 'OR')"
/>
<WorkspaceDropdown v-if="hasPermission(EditionConst.IS_EE, 'OR')" />
</div>
<el-card style="--el-card-padding: 0">
<div class="flex main-calc-height">
<div class="resource-authorization__left border-r p-8">
<div class="p-8">
<h4 class="mb-12">{{ $t('views.resourceAuthorization.member') }}</h4>
<el-input v-model="filterText" :placeholder="$t('common.search')" prefix-icon="Search" clearable />
<el-input
v-model="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
clearable
/>
</div>
<div class="list-height-left">
<el-scrollbar>
<common-list :data="filterMember" class="mt-8" v-loading="loading" @click="clickMemberHandle"
:default-active="currentUser">
<common-list
:data="filterMember"
class="mt-8"
v-loading="loading"
@click="clickMemberHandle"
:default-active="currentUser"
>
<template #default="{ row }">
<div class="flex-between">
<div>
<div class="flex">
<span class="mr-8">{{ row.nick_name }}</span>
<el-tag v-if="isManage(row.type)" class="default-tag">{{
$t('views.resourceAuthorization.manage')
}}</el-tag>
<TagGroup :tags="row.roles" />
</div>
</div>
</template>
@ -30,10 +48,20 @@
<div class="resource-authorization__table">
<h4 class="mb-4">{{ $t('views.resourceAuthorization.permissionSetting') }}</h4>
<el-tabs v-model="activeName" class="resource-authorization__tabs">
<el-tab-pane v-for="(item, index) in settingTags" :key="item.value" :label="item.label"
:name="item.value">
<PermissionSetting :key="index" :data="item.data" :type="item.value" :tableHeight="tableHeight"
:manage="isManage(currentType)" @refreshData="refreshData"></PermissionSetting>
<el-tab-pane
v-for="(item, index) in settingTags"
:key="item.value"
:label="item.label"
:name="item.value"
>
<PermissionSetting
:key="index"
:data="item.data"
:type="item.value"
:tableHeight="tableHeight"
:manage="isManage(currentType)"
@refreshData="refreshData"
></PermissionSetting>
</el-tab-pane>
</el-tabs>
</div>
@ -56,6 +84,8 @@ import { AuthorizationEnum } from '@/enums/system'
import { t } from '@/locales'
import useStore from '@/stores'
import { cloneDeep } from 'lodash'
import { EditionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
const loading = ref(false)
const rLoading = ref(false)
@ -97,7 +127,7 @@ function isManage(type: string) {
}
const flotTree = (tree: Array<any>, result: Array<any>) => {
tree.forEach(tItem => {
tree.forEach((tItem) => {
result.push(tItem)
if (tItem.children) {
flotTree(tItem.children, result)
@ -106,19 +136,27 @@ const flotTree = (tree: Array<any>, result: Array<any>) => {
return result
}
function submitPermissions() {
const user_resource_permission_list = settingTags.map((item: any) => {
return flotTree(item.data, []).filter((v: any) => !v.isFolder).map((v: any) => {
return {
target_id: v.id,
auth_target_type: item.value,
permission: v.permission,
auth_type: 'RESOURCE_PERMISSION_GROUP',
}
const user_resource_permission_list = settingTags
.map((item: any) => {
return flotTree(item.data, [])
.filter((v: any) => !v.isFolder)
.map((v: any) => {
return {
target_id: v.id,
auth_target_type: item.value,
permission: v.permission,
auth_type: 'RESOURCE_PERMISSION_GROUP',
}
})
})
}).reduce((pre, next) => {
return [...pre, ...next]
}, [])
AuthorizationApi.putResourceAuthorization(currentUser.value, { user_resource_permission_list: user_resource_permission_list }, rLoading).then(() => {
.reduce((pre, next) => {
return [...pre, ...next]
}, [])
AuthorizationApi.putResourceAuthorization(
currentUser.value,
{ user_resource_permission_list: user_resource_permission_list },
rLoading,
).then(() => {
MsgSuccess(t('common.submitSuccess'))
getWholeTree(currentUser.value)
})

View File

@ -79,7 +79,7 @@
{{ row.phone || '-' }}
</template>
</el-table-column>
<el-table-column prop="user_group_names" :label="$t('views.chatUser.group.title')">
<el-table-column prop="user_group_names" :label="$t('views.chatUser.group.title')" min-width="100">
<template #default="{ row }">
<TagGroup :tags="row.user_group_names" />
</template>
@ -172,7 +172,6 @@ import userManageApi from '@/api/system/chat-user'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
import TagGroup from '@/components/tag-group/index.vue'
import iconMap from '@/components/app-icon/icons/common'
import type { ChatUserItem } from '@/api/type/systemChatUser'
import SystemGroupApi from '@/api/system/user-group'