Skip to content

Commit f538dc8

Browse files
feat: role
1 parent 1a5e485 commit f538dc8

12 files changed

Lines changed: 276 additions & 161 deletions

File tree

ui/src/components/global/mk-table/index.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ defineExpose({ clearSelection, tableRef })
160160
<div
161161
v-else
162162
ref="tableRootRef"
163-
class="mk-table relative w-full"
163+
class="mk-table relative w-full min-h-0 flex-1"
164164
@mouseleave="clearResizeHover"
165165
@mousemove="handleTableMouseMove"
166166
>
@@ -199,7 +199,7 @@ defineExpose({ clearSelection, tableRef })
199199

200200
<footer
201201
v-if="selectedRows.length > 0"
202-
class="fixed bottom-0 z-20 flex items-center border-t bg-white w-full -ml-6 px-6 py-4"
202+
class="absolute -inset-x-6 -bottom-6 z-10 border-t bg-white px-6 py-4 flex items-center"
203203
>
204204
<div class="mr-4 flex items-center gap-3">
205205
<el-checkbox

ui/src/components/global/mk-tag-group/index.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ withDefaults(
3131
<template #reference>
3232
<el-tag type="info" class="cursor-pointer">+{{ tags.length - 1 }}</el-tag>
3333
</template>
34-
<div class="flex flex-wrap gap-1">
34+
<div class="flex-wrap gap-1">
3535
<el-tag v-for="tag in tags.slice(1)" :key="tag" type="info">
3636
{{ tag }}
3737
</el-tag>

ui/src/styles/STYLE_README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,12 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体
109109

110110
稳定重复使用的 Flex 布局组合统一定义在 `tailwind.css`。当前提供:
111111

112-
| 工具类 | 作用 |
113-
| ----------------- | ------------------------------------ |
114-
| `flex-between` | 横向排列,两侧贴边,并在垂直方向居中 |
115-
| `flex-center` | 横向排列,在水平和垂直两个方向居中 |
116-
| `flex-col-center` | 纵向排列,在水平和垂直两个方向居中 |
112+
| 工具类 | 作用 |
113+
| ------------------- | ------------------------------------ |
114+
| `flex-between` | 横向排列,两侧贴边,并在垂直方向居中 |
115+
| `flex-center` | 横向排列,在水平和垂直两个方向居中 |
116+
| `flex-col-center` | 纵向排列,在水平和垂直两个方向居中 |
117+
| `flex-wrap` | 横向排列,自动换行,行间距为 8px |
117118

118119
```vue
119120
<div class="flex-between">
@@ -129,6 +130,11 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体
129130
<MkIcon name="icon_home_filled" />
130131
<span>首页</span>
131132
</div>
133+
134+
<div class="flex-wrap">
135+
<span>选项一</span>
136+
<span>选项二</span>
137+
</div>
132138
```
133139

134140
仅在布局语义与工具类完全一致时使用。需要覆盖排列方向或对齐方式的响应式布局继续组合 Tailwind

ui/src/styles/element-plus.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,17 @@
217217
--el-checkbox-input-border: 1px solid var(--mk-N500);
218218
--el-checkbox-input-width: 16px;
219219
--el-checkbox-input-height: 16px;
220+
--el-checkbox-text-color: var(--el-text-color-primary);
221+
--el-checkbox-disabled-checked-input-fill: var(--mk-N400);
222+
--el-checkbox-disabled-checked-input-border-color: var(--mk-N400);
223+
--el-checkbox-disabled-checked-icon-color: white;
224+
--el-checkbox-checked-text-color: var(--el-text-color-primary);
220225
margin: 0;
226+
height: unset;
227+
&__label {
228+
line-height: 22px;
229+
font-weight: 400;
230+
}
221231
}
222232

223233
/* dialog */

ui/src/styles/tailwind.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,10 @@
6969
flex-direction: column;
7070
justify-content: center;
7171
}
72+
73+
/* 横向排列,自动换行且行间距为 8px */
74+
@utility flex-wrap {
75+
display: flex;
76+
flex-wrap: wrap;
77+
row-gap: calc(var(--spacing) * 2);
78+
}

ui/src/views/system/identity/roles/AddMemberDrawer.vue

Lines changed: 64 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { computed, reactive, ref } from 'vue'
3-
import type { FormInstance, FormRules } from 'element-plus'
3+
import type { FormInstance } from 'element-plus'
44
import RoleApi from '@/api/admin/system/role'
55
import UserManageApi from '@/api/admin/system/user-manage'
66
import WorkspaceApi from '@/api/admin/system/workspace'
@@ -13,93 +13,74 @@ import {
1313
} from '@/api/types'
1414
import { useStore } from '@/stores'
1515
import { MsgSuccess } from '@/utils/message'
16+
import MemberWorkspaceSetting from './components/MemberWorkspaceSetting.vue'
17+
18+
defineOptions({ name: 'AddRoleMemberDrawer' })
1619
1720
const props = defineProps<{ currentRole: RoleItem }>()
1821
const emit = defineEmits<{ refresh: [] }>()
1922
const { auth } = useStore()
2023
2124
const visible = ref(false)
2225
const loading = ref(false)
23-
const optionsLoading = ref(false)
2426
const formRef = ref<FormInstance>()
25-
const memberRows = reactive<CreateRoleMemberItem[]>([])
26-
const userOptions = ref<SystemUserOption[]>([])
27-
const workspaceOptions = ref<WorkspaceItem[]>([])
27+
const memberForm = reactive<{ members: CreateRoleMemberItem[] }>({ members: [] })
2828
const showWorkspace = computed(() => props.currentRole.type !== ROLE_TYPE.ADMIN && auth.isEE)
29-
const rules: FormRules = {
30-
user_ids: [{ required: true, message: '请选择用户', trigger: 'change' }],
31-
workspace_ids: [{ required: true, message: '请选择工作空间', trigger: 'change' }],
32-
}
33-
let searchTimer: ReturnType<typeof setTimeout> | null = null
3429
35-
function createEmptyRow(): CreateRoleMemberItem {
36-
return { user_ids: [], ...(showWorkspace.value ? { workspace_ids: [] } : {}) }
37-
}
30+
/* 成员与工作空间选项 */
31+
const userOptions = ref<SystemUserOption[]>([])
32+
const userOptionsLoading = ref(false)
33+
const workspaceOptions = ref<WorkspaceItem[]>([])
34+
const workspaceOptionsLoading = ref(false)
3835
39-
function loadOptions(query?: string) {
40-
optionsLoading.value = true
41-
return Promise.all([
42-
UserManageApi.getAllUsers(query ? { nick_name: query } : undefined),
43-
showWorkspace.value ? WorkspaceApi.getSystemWorkspaceList() : Promise.resolve([]),
44-
])
45-
.then(([users, workspaces]) => {
46-
userOptions.value = users
47-
workspaceOptions.value = workspaces
36+
function loadUserOptions(keyword = '') {
37+
userOptionsLoading.value = true
38+
return UserManageApi.getAllUsers(keyword ? { nick_name: keyword } : undefined)
39+
.then((users) => {
40+
const selectedUserIds = new Set(memberForm.members.flatMap(({ user_ids }) => user_ids))
41+
const selectedUserOptions = userOptions.value.filter(({ id }) => selectedUserIds.has(id))
42+
userOptions.value = [
43+
...new Map([...selectedUserOptions, ...users].map((user) => [user.id, user])).values(),
44+
]
4845
})
4946
.finally(() => {
50-
optionsLoading.value = false
47+
userOptionsLoading.value = false
5148
})
5249
}
5350
54-
function handleRemoteSearch(query: string) {
55-
if (searchTimer) clearTimeout(searchTimer)
56-
searchTimer = setTimeout(() => loadOptions(query), 300)
57-
}
58-
59-
function handleAddRow() {
60-
memberRows.push(createEmptyRow())
61-
}
62-
63-
function handleDeleteRow(index: number) {
64-
if (memberRows.length > 1) memberRows.splice(index, 1)
65-
}
51+
function loadWorkspaceOptions() {
52+
if (!showWorkspace.value) return Promise.resolve()
6653
67-
function resetData() {
68-
memberRows.splice(0, memberRows.length, createEmptyRow())
69-
userOptions.value = []
70-
workspaceOptions.value = []
71-
loading.value = false
72-
optionsLoading.value = false
73-
formRef.value?.clearValidate()
74-
if (searchTimer) {
75-
clearTimeout(searchTimer)
76-
searchTimer = null
77-
}
54+
workspaceOptionsLoading.value = true
55+
return WorkspaceApi.getSystemWorkspaceList()
56+
.then((workspaces) => {
57+
workspaceOptions.value = workspaces
58+
})
59+
.finally(() => {
60+
workspaceOptionsLoading.value = false
61+
})
7862
}
7963
8064
function open() {
81-
resetData()
65+
memberForm.members = [{ user_ids: [], workspace_ids: [] }]
8266
visible.value = true
83-
loadOptions()
84-
}
85-
86-
function close() {
87-
visible.value = false
88-
resetData()
67+
return Promise.all([loadUserOptions(), loadWorkspaceOptions()])
8968
}
9069
9170
function submit() {
9271
formRef.value?.validate((valid) => {
9372
if (!valid) return
94-
const members = memberRows.map((row) => ({
95-
user_ids: row.user_ids,
73+
74+
const members = memberForm.members.map((member) => ({
75+
user_ids: [...member.user_ids],
9676
workspace_ids:
9777
props.currentRole.type === ROLE_TYPE.ADMIN
9878
? ['None']
9979
: auth.isPE
10080
? ['default']
101-
: row.workspace_ids,
81+
: [...(member.workspace_ids ?? [])],
10282
}))
83+
10384
loading.value = true
10485
RoleApi.postRoleMembers(props.currentRole.id, { members })
10586
.then(() => {
@@ -113,74 +94,38 @@ function submit() {
11394
})
11495
}
11596
97+
function close() {
98+
visible.value = false
99+
resetData()
100+
}
101+
102+
function resetData() {
103+
memberForm.members = []
104+
loading.value = false
105+
userOptions.value = []
106+
userOptionsLoading.value = false
107+
workspaceOptions.value = []
108+
workspaceOptionsLoading.value = false
109+
formRef.value?.clearValidate()
110+
}
111+
116112
defineExpose({ open })
117113
</script>
118114

119115
<template>
120-
<MkDrawer v-model="visible" title="添加成员" size="600" @closed="resetData">
121-
<el-form ref="formRef" :model="memberRows" label-position="top">
122-
<el-scrollbar>
123-
<div v-for="(row, index) in memberRows" :key="index" class="mb-3 flex items-end gap-2">
124-
<el-form-item
125-
:label="index === 0 ? '用户' : ''"
126-
:prop="`${index}.user_ids`"
127-
:rules="rules.user_ids"
128-
class="mb-0 flex-1"
129-
>
130-
<el-select
131-
v-model="row.user_ids"
132-
class="w-full"
133-
filterable
134-
remote
135-
multiple
136-
collapse-tags
137-
collapse-tags-tooltip
138-
reserve-keyword
139-
:remote-method="handleRemoteSearch"
140-
:loading="optionsLoading"
141-
placeholder="请选择用户"
142-
>
143-
<el-option
144-
v-for="user in userOptions"
145-
:key="user.id"
146-
:label="user.nick_name || user.username"
147-
:value="user.id"
148-
/>
149-
</el-select>
150-
</el-form-item>
151-
<el-form-item
152-
v-if="showWorkspace"
153-
:label="index === 0 ? '工作空间' : ''"
154-
:prop="`${index}.workspace_ids`"
155-
:rules="rules.workspace_ids"
156-
class="mb-0 flex-1"
157-
>
158-
<el-select
159-
v-model="row.workspace_ids"
160-
class="w-full"
161-
filterable
162-
multiple
163-
collapse-tags
164-
collapse-tags-tooltip
165-
placeholder="请选择工作空间"
166-
>
167-
<el-option
168-
v-for="workspace in workspaceOptions"
169-
:key="workspace.id"
170-
:label="workspace.name"
171-
:value="workspace.id"
172-
/>
173-
</el-select>
174-
</el-form-item>
175-
<el-button text :disabled="memberRows.length === 1" @click="handleDeleteRow(index)"
176-
><MkIcon name="icon_delete-trash_outlined"
177-
/></el-button>
178-
</div>
179-
</el-scrollbar>
180-
<el-button text type="primary" @click="handleAddRow"
181-
><MkIcon name="icon_add_outlined" />添加</el-button
182-
>
116+
<MkDrawer v-model="visible" title="添加成员" @closed="resetData">
117+
<el-form ref="formRef" :model="memberForm" label-position="top">
118+
<MemberWorkspaceSetting
119+
v-model="memberForm.members"
120+
:remote-user-method="loadUserOptions"
121+
:show-workspace="showWorkspace"
122+
:user-loading="userOptionsLoading"
123+
:user-options="userOptions"
124+
:workspace-loading="workspaceOptionsLoading"
125+
:workspace-options="workspaceOptions"
126+
/>
183127
</el-form>
128+
184129
<template #footer>
185130
<el-button @click="close">取消</el-button>
186131
<el-button type="primary" :loading="loading" @click="submit">添加</el-button>

ui/src/views/system/identity/roles/RoleListView.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,6 @@ function handleDeleteRole(role: RoleItem) {
8585
})
8686
}
8787
88-
function handleMemberChanged() {
89-
loadRoles(currentRole.value?.id)
90-
}
91-
9288
onMounted(() => loadRoles())
9389
</script>
9490

@@ -172,7 +168,7 @@ onMounted(() => loadRoles())
172168
:current-role="currentRole"
173169
/>
174170
<!-- 成员 -->
175-
<RoleMemberList v-else :current-role="currentRole" @changed="handleMemberChanged" />
171+
<RoleMemberList v-else :current-role="currentRole" />
176172
</template>
177173
<MkEmpty v-else class="flex-1" />
178174
</template>

0 commit comments

Comments
 (0)