Skip to content

Commit b9d93cb

Browse files
feat: permission
1 parent ee652bd commit b9d93cb

6 files changed

Lines changed: 107 additions & 32 deletions

File tree

‎ui/src/components/COMPONENT_README.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,12 @@ Dialog、Drawer、Popover、嵌套区域等其他大、小表格均禁止开启
357357

358358
手动导入 `@/components/mk-edit-avatar/index.vue`,不参与全局注册。字符串 `v-model` 为当前
359359
自定义头像 URL,空字符串表示使用默认图标;不再接收 `defaultIcon`。
360-
`size` 默认为 `32`(px),控制触发区传给插槽的尺寸;弹层预览和上传区域固定为 80px。
360+
`size` 默认为 `32`(px),只设置 `referenceRef` 触发容器的宽高;弹层预览和上传区域固定为 80px。
361361
`editable` 默认为 `true`,设为 `false` 时仅展示头像。
362362

363-
必填默认插槽同时渲染触发区和默认 Logo 预览,透出 `{ icon, size }`。触发区传入当前头像
364-
(空值转为 `undefined`)和 `props.size`;默认预览始终传入 `icon: undefined`、固定的 `size: 80`,不读取组件的
365-
`props.size`。插槽参数由调用方按需使用,不会自动覆盖插槽内组件的尺寸。
363+
必填默认插槽同时渲染触发区和默认 Logo 预览,只透出 `{ icon }`。触发区传入当前头像
364+
(空值转为 `undefined`);默认预览始终传入 `icon: undefined`。两处容器通过样式让插槽的
365+
直接子元素填满各自宽高,不向插槽传递 `size`,也不修改插槽组件的 Props。
366366
调用方必须使用插槽的 `icon`,由 `PortalIcon`、`ToolIcon` 等资源组件自行展示默认图标,
367367
不要在插槽内直接绑定外部头像值。
368368
触发区由 `MkEditAvatar` 的 `span` 容器承载,统一处理悬停,插槽内只放展示内容。
@@ -384,8 +384,8 @@ const avatar = ref('')
384384
385385
<template>
386386
<MkEditAvatar v-model="avatar">
387-
<template #default="{ icon, size: previewSize }">
388-
<PortalIcon :icon="icon" :size="previewSize" />
387+
<template #default="{ icon }">
388+
<PortalIcon :icon="icon" />
389389
</template>
390390
</MkEditAvatar>
391391
</template>

‎ui/src/components/mk-edit-avatar/index.vue‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const props = withDefaults(defineProps<{ editable?: boolean; size?: number }>(),
1111
})
1212
const avatar = defineModel<string>({ default: '' })
1313
const emit = defineEmits<{ change: [icon: string, file: File | null] }>()
14-
defineSlots<{ default(props: { icon: string | undefined; size: number }): unknown }>()
14+
defineSlots<{ default(props: { icon: string | undefined }): unknown }>()
1515
1616
// 头像编辑草稿:确认前不修改外部值。
1717
const visible = ref(false)
@@ -101,8 +101,14 @@ onBeforeUnmount(() => fileReader?.abort())
101101
<el-popover :visible="visible" :width="414" placement="bottom-start" @after-leave="clearDraft">
102102
<template #reference>
103103
<!-- 悬停修改头像 -->
104-
<span ref="referenceRef" class="inline-flex" :class="{ 'cursor-pointer': props.editable }" @mouseenter="open">
105-
<slot :icon="avatar || undefined" :size="props.size" />
104+
<span
105+
ref="referenceRef"
106+
class="inline-flex shrink-0 [&>*]:h-full! [&>*]:w-full!"
107+
:class="{ 'cursor-pointer': props.editable }"
108+
:style="{ width: `${props.size}px`, height: `${props.size}px` }"
109+
@mouseenter="open"
110+
>
111+
<slot :icon="avatar || undefined" />
106112
</span>
107113
</template>
108114

@@ -112,8 +118,8 @@ onBeforeUnmount(() => fileReader?.abort())
112118
<el-radio value="default">默认 Logo</el-radio>
113119
<el-radio value="custom">自定义上传</el-radio>
114120
</el-radio-group>
115-
<div v-if="logoMode === 'default'">
116-
<slot :icon="undefined" :size="80" />
121+
<div v-if="logoMode === 'default'" class="h-20 w-20 [&>*]:h-full! [&>*]:w-full!">
122+
<slot :icon="undefined" />
117123
</div>
118124

119125
<div v-else>
@@ -128,7 +134,7 @@ onBeforeUnmount(() => fileReader?.abort())
128134
:on-change="selectImage"
129135
>
130136
<el-avatar v-if="draftIcon" :size="80" shape="square">
131-
<img :src="draftIcon" alt="自定义头像预览" class="object-cover" />
137+
<img :src="draftIcon" class="object-cover" />
132138
</el-avatar>
133139

134140
<div v-else class="flex-center h-20 w-20 rounded-md border border-dashed bg-N200 hover:border-primary">

‎ui/src/views/system/chat-management/portal-setting/PortalSettingView.vue‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ onMounted(() => loadPortalSetting())
127127
@change="handleAccessChange('enable_public_access', $event)"
128128
/>
129129
</div>
130-
<el-input :model-value="portalAccessUrl">
130+
<el-input :model-value="portalAccessUrl" readonly>
131131
<template #suffix>
132132
<!-- 复制门户公开访问链接 -->
133133
<el-button text @click="copyText(portalAccessUrl)" class="-mr-1">
@@ -142,7 +142,7 @@ onMounted(() => loadPortalSetting())
142142
<span>智能体后端 API 访问</span>
143143
<el-switch :model-value="portalSetting.enable_api" size="small" @change="handleAccessChange('enable_api', $event)" />
144144
</div>
145-
<el-input :model-value="portalApiUrl">
145+
<el-input :model-value="portalApiUrl" readonly>
146146
<template #suffix>
147147
<!-- 复制智能体后端 API 访问 -->
148148
<el-button text @click="copyText(portalApiUrl)" class="-mr-1">
@@ -161,7 +161,7 @@ onMounted(() => loadPortalSetting())
161161
@change="handleAccessChange('enable_knowledge_base_api', $event)"
162162
/>
163163
</div>
164-
<el-input :model-value="portalApiUrl">
164+
<el-input :model-value="portalApiUrl" readonly>
165165
<template #suffix>
166166
<!-- 复制知识库后端 API 检索 -->
167167
<el-button text @click="copyText(portalApiUrl)" class="-mr-1">

‎ui/src/views/system/identity/resource-authorization/ResourceAuthorizationView.vue‎

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
<script setup lang="ts">
2-
import { onMounted, ref } from 'vue'
2+
import { computed, onMounted, ref } from 'vue'
33
import { useRoute } from 'vue-router'
44
import WorkspaceApi from '@/api/admin/system/workspace'
55
import CommonSystemApi from '@/api/admin/system/common'
66
import UserGroupsApi from '@/api/admin/system/user-groups'
77
import ResourceAuthorizationApi from '@/api/admin/system/resource-authorization'
88
import { RESOURCE_TYPE, RESOURCE_PERMISSION } from '@/api/enums'
9-
import type { ResourceAuthorizationType, ResourcePermissionItem, ResourcePermissionPayload, CommonUserOption, OptionItem, SystemUserGroup, WorkspaceItem } from '@/api/types'
9+
import type {
10+
ResourceAuthorizationType,
11+
ResourcePermissionItem,
12+
ResourcePermissionPayload,
13+
CommonUserOption,
14+
OptionItem,
15+
SystemUserGroup,
16+
WorkspaceItem,
17+
} from '@/api/types'
1018
import { useStore } from '@/stores'
19+
import { perm } from '@/permission'
1120
import { MsgSuccess } from '@/utils/message'
1221
import WorkspaceDropdown from '@/components/business/workspace-dropdown/index.vue'
1322
import PermissionTable from './components/PermissionTable.vue'
@@ -19,7 +28,12 @@ const { auth } = useStore()
1928
const route = useRoute()
2029
2130
const resourceType = ref<ResourceAuthorizationType>((route.meta.resource as ResourceAuthorizationType) ?? RESOURCE_TYPE.APPLICATION)
22-
const resourceTypeOptions: OptionItem<ResourceAuthorizationType>[] = [RESOURCE_TYPE.APPLICATION, RESOURCE_TYPE.KNOWLEDGE, RESOURCE_TYPE.TOOL, RESOURCE_TYPE.MODEL].map((value) => ({
31+
const resourceTypeOptions: OptionItem<ResourceAuthorizationType>[] = [
32+
RESOURCE_TYPE.APPLICATION,
33+
RESOURCE_TYPE.KNOWLEDGE,
34+
RESOURCE_TYPE.TOOL,
35+
RESOURCE_TYPE.MODEL,
36+
].map((value) => ({
2337
label: RESOURCE_AUTHORIZATION_LABELS[value],
2438
value,
2539
}))
@@ -51,7 +65,10 @@ const selectedUserGroupId = ref('')
5165
const selectedUserId = ref('')
5266
5367
function loadAuthorizationTargets() {
54-
return Promise.all([UserGroupsApi.getSystemUserGroups(selectedWorkspaceId.value), CommonSystemApi.getWorkspaceMembers(selectedWorkspaceId.value)]).then(([groups, users]) => {
68+
return Promise.all([
69+
UserGroupsApi.getSystemUserGroups(selectedWorkspaceId.value),
70+
CommonSystemApi.getWorkspaceMembers(selectedWorkspaceId.value),
71+
]).then(([groups, users]) => {
5572
userGroups.value = groups
5673
workspaceMembers.value = users
5774
selectedUserGroupId.value = groups[0]?.id ?? ''
@@ -75,6 +92,7 @@ function handleUserSelect(user: CommonUserOption) {
7592
}
7693
7794
/* 资源权限 */
95+
7896
const loadingPermissions = ref(false)
7997
const resourcePermissions = ref<ResourcePermissionItem[]>([])
8098
@@ -129,6 +147,8 @@ function buildResourceTree(resourceItems: ResourcePermissionItem[]) {
129147
/* 保存权限 */
130148
131149
function handlePermissionsSubmit(permissions: ResourcePermissionPayload[]) {
150+
if (!canEditResourcePermission.value || loadingPermissions.value) return
151+
132152
const targetId = targetType.value === 'user-group' ? selectedUserGroupId.value : selectedUserId.value
133153
134154
if (!targetId) return
@@ -155,6 +175,15 @@ onMounted(() => {
155175
loadingView.value = false
156176
})
157177
})
178+
179+
// 权限
180+
const resourceEditPermissions: Record<ResourceAuthorizationType, () => boolean> = {
181+
[RESOURCE_TYPE.APPLICATION]: perm.system.authorization.application.edit,
182+
[RESOURCE_TYPE.KNOWLEDGE]: perm.system.authorization.knowledge.edit,
183+
[RESOURCE_TYPE.TOOL]: perm.system.authorization.tool.edit,
184+
[RESOURCE_TYPE.MODEL]: perm.system.authorization.model.edit,
185+
}
186+
const canEditResourcePermission = computed(() => resourceEditPermissions[resourceType.value]())
158187
</script>
159188

160189
<template>
@@ -171,7 +200,12 @@ onMounted(() => {
171200
</el-tabs>
172201
</component>
173202

174-
<UserGroupAuthorizationList v-if="targetType === 'user-group'" :active-id="selectedUserGroupId" :user-groups="userGroups" @select="handleUserGroupSelect" />
203+
<UserGroupAuthorizationList
204+
v-if="targetType === 'user-group'"
205+
:active-id="selectedUserGroupId"
206+
:user-groups="userGroups"
207+
@select="handleUserGroupSelect"
208+
/>
175209
<UserAuthorizationList v-else :active-id="selectedUserId" :users="workspaceMembers" @select="handleUserSelect" />
176210
</template>
177211

@@ -180,12 +214,23 @@ onMounted(() => {
180214
<div class="w-full">
181215
<h4 class="mb-4">资源权限配置</h4>
182216
<el-tabs v-model="resourceType" class="w-full" @tab-change="loadResourcePermissions">
183-
<el-tab-pane v-for="resourceTypeOption in resourceTypeOptions" :key="resourceTypeOption.value" :label="resourceTypeOption.label" :name="resourceTypeOption.value" />
217+
<el-tab-pane
218+
v-for="resourceTypeOption in resourceTypeOptions"
219+
:key="resourceTypeOption.value"
220+
:label="resourceTypeOption.label"
221+
:name="resourceTypeOption.value"
222+
/>
184223
</el-tabs>
185224
</div>
186225
</component>
187226

188-
<PermissionTable v-loading="loadingPermissions" :data="resourcePermissions" :resource-type="resourceType" @submit="handlePermissionsSubmit" />
227+
<PermissionTable
228+
v-loading="loadingPermissions"
229+
:data="resourcePermissions"
230+
:resource-type="resourceType"
231+
:disabled="!canEditResourcePermission || loadingPermissions"
232+
@submit="handlePermissionsSubmit"
233+
/>
189234
</template>
190235
</MkViewLayout>
191236
</template>

‎ui/src/views/system/identity/resource-authorization/components/PermissionTable.vue‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import BatchSetPermissionDialog from '../dialog/BatchSetPermissionDialog.vue'
1616
1717
defineOptions({ name: 'ResourcePermissionTable' })
1818
19-
const props = defineProps<{ data: ResourcePermissionItem[]; resourceType: ResourceAuthorizationType }>()
19+
const props = defineProps<{ data: ResourcePermissionItem[]; resourceType: ResourceAuthorizationType; disabled: boolean }>()
2020
const emit = defineEmits<{ submit: [permissions: ResourcePermissionPayload[]] }>()
2121
2222
const permissionSearchFields = computed<OptionItem<string>[]>(() => [
@@ -99,6 +99,8 @@ function buildPermissionPayload(permission: ResourcePermission, resource: Resour
9999
}
100100
101101
function handlePermissionChange(permission: ResourcePermission, resource: ResourcePermissionItem) {
102+
if (props.disabled) return
103+
102104
emit('submit', [...buildPermissionPayload(permission, resource).values()])
103105
}
104106
@@ -113,10 +115,14 @@ function handleSelectionChange(selection: unknown[]) {
113115
const batchPermissionDialogRef = useTemplateRef<InstanceType<typeof BatchSetPermissionDialog>>('batchPermissionDialogRef')
114116
115117
function handleOpenBatchDialog() {
118+
if (props.disabled || !batchSelectedResources.value.length) return
119+
116120
batchPermissionDialogRef.value?.open()
117121
}
118122
119123
function handleBatchSubmit(permission: ResourcePermission) {
124+
if (props.disabled || !batchSelectedResources.value.length) return
125+
120126
const payloadMap = new Map<string, ResourcePermissionPayload>()
121127
batchSelectedResources.value.forEach((resource) => {
122128
buildPermissionPayload(permission, resource).forEach((payload, id) => {
@@ -128,6 +134,15 @@ function handleBatchSubmit(permission: ResourcePermission) {
128134
batchSelectedResources.value = []
129135
}
130136
137+
watch(
138+
() => props.resourceType,
139+
() => {
140+
tableRef.value?.clearSelection()
141+
batchSelectedResources.value = []
142+
batchPermissionDialogRef.value?.close()
143+
},
144+
)
145+
131146
/* 模型供应商图标 */
132147
const modelProviders = ref<ModelProviderItem[]>([])
133148
function getModelProviderIcon(resource: ResourcePermissionItem) {
@@ -150,8 +165,8 @@ watch(
150165
<div class="flex-column min-h-0 flex-1">
151166
<div class="mb-4 flex-between">
152167
<div>
153-
<!-- 配置权限 -->
154-
<el-button type="primary" :disabled="batchSelectedResources.length === 0" @click="handleOpenBatchDialog">
168+
<!-- 批量配置权限 -->
169+
<el-button type="primary" :disabled="disabled || batchSelectedResources.length === 0" @click="handleOpenBatchDialog">
155170
<MkIcon name="icon-lock" />
156171
<span>配置权限</span>
157172
</el-button>
@@ -168,7 +183,7 @@ watch(
168183
show-overflow-tooltip
169184
@selection-change="handleSelectionChange"
170185
>
171-
<el-table-column type="selection" width="40" reserve-selection />
186+
<el-table-column type="selection" width="40" reserve-selection :selectable="() => !disabled" />
172187
<el-table-column class-name="resource-name-column" label="名称" min-width="260" prop="name">
173188
<template #default="{ row }: { row: ResourcePermissionItem }">
174189
<div class="flex-align-center min-w-0 flex-1 gap-2">
@@ -185,7 +200,7 @@ watch(
185200
</el-table-column>
186201
<el-table-column label="操作权限" width="450">
187202
<template #default="{ row }: { row: ResourcePermissionItem }">
188-
<el-radio-group :model-value="row.permission" @change="handlePermissionChange($event as ResourcePermission, row)">
203+
<el-radio-group :model-value="row.permission" :disabled="disabled" @change="handlePermissionChange($event as ResourcePermission, row)">
189204
<template v-for="permissionOption in getPermissionOptions()" :key="permissionOption.value">
190205
<el-radio :value="permissionOption.value">{{ permissionOption.label }}</el-radio>
191206
</template>
@@ -194,7 +209,7 @@ watch(
194209
</el-table-column>
195210
</MkTable>
196211

197-
<BatchSetPermissionDialog ref="batchPermissionDialogRef" @submit="handleBatchSubmit" />
212+
<BatchSetPermissionDialog ref="batchPermissionDialogRef" :disabled="disabled" @submit="handleBatchSubmit" />
198213
</div>
199214
</template>
200215

‎ui/src/views/system/identity/resource-authorization/dialog/BatchSetPermissionDialog.vue‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,39 @@ import { getPermissionOptions } from '../constants'
55
66
defineOptions({ name: 'BatchSetPermissionDialog' })
77
8+
const props = defineProps<{ disabled: boolean }>()
89
const emit = defineEmits<{ submit: [permission: ResourcePermission] }>()
910
1011
const visible = ref(false)
1112
const permission = ref<ResourcePermission>()
1213
1314
function open() {
15+
if (props.disabled) return
16+
1417
visible.value = true
1518
}
1619
20+
function close() {
21+
visible.value = false
22+
}
23+
1724
function resetData() {
1825
permission.value = undefined
1926
}
2027
2128
function handleSubmit() {
22-
if (!permission.value) return
29+
if (props.disabled || !permission.value) return
2330
2431
emit('submit', permission.value)
2532
visible.value = false
2633
}
2734
28-
defineExpose({ open })
35+
defineExpose({ open, close })
2936
</script>
3037

3138
<template>
3239
<MkDialog v-model="visible" title="配置权限" @closed="resetData">
33-
<el-radio-group v-model="permission" class="vertical-radio-group">
40+
<el-radio-group v-model="permission" class="vertical-radio-group" :disabled="disabled">
3441
<el-radio v-for="permissionOption in getPermissionOptions()" :key="permissionOption.value" :value="permissionOption.value">
3542
<p>{{ permissionOption.label }}</p>
3643
<p v-if="permissionOption.description" class="text-N500 mt-1">
@@ -39,8 +46,10 @@ defineExpose({ open })
3946
</el-radio>
4047
</el-radio-group>
4148
<template #footer>
49+
<!-- 取消批量配置 -->
4250
<el-button plain @click="visible = false">取消</el-button>
43-
<el-button type="primary" :disabled="!permission" @click="handleSubmit">确认</el-button>
51+
<!-- 确认批量配置 -->
52+
<el-button type="primary" :disabled="disabled || !permission" @click="handleSubmit">确认</el-button>
4453
</template>
4554
</MkDialog>
4655
</template>

0 commit comments

Comments
 (0)