Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ui/src/components/global/mk-table/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ defineExpose({ clearSelection, tableRef })
<div
v-else
ref="tableRootRef"
class="mk-table relative w-full"
class="mk-table relative w-full min-h-0 flex-1"
@mouseleave="clearResizeHover"
@mousemove="handleTableMouseMove"
>
Expand Down Expand Up @@ -199,7 +199,7 @@ defineExpose({ clearSelection, tableRef })

<footer
v-if="selectedRows.length > 0"
class="fixed bottom-0 z-20 flex items-center border-t bg-white w-full -ml-6 px-6 py-4"
class="absolute -inset-x-6 -bottom-6 z-10 border-t bg-white px-6 py-4 flex items-center"
>
<div class="mr-4 flex items-center gap-3">
<el-checkbox
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/global/mk-tag-group/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ withDefaults(
<template #reference>
<el-tag type="info" class="cursor-pointer">+{{ tags.length - 1 }}</el-tag>
</template>
<div class="flex flex-wrap gap-1">
<div class="flex-wrap gap-1">
<el-tag v-for="tag in tags.slice(1)" :key="tag" type="info">
{{ tag }}
</el-tag>
Expand Down
16 changes: 11 additions & 5 deletions ui/src/styles/STYLE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,12 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体

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

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

```vue
<div class="flex-between">
Expand All @@ -129,6 +130,11 @@ CSS 自定义属性同样按变量名排序。嵌套选择器、伪类和媒体
<MkIcon name="icon_home_filled" />
<span>首页</span>
</div>

<div class="flex-wrap">
<span>选项一</span>
<span>选项二</span>
</div>
```

仅在布局语义与工具类完全一致时使用。需要覆盖排列方向或对齐方式的响应式布局继续组合 Tailwind
Expand Down
10 changes: 10 additions & 0 deletions ui/src/styles/element-plus.scss
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,17 @@
--el-checkbox-input-border: 1px solid var(--mk-N500);
--el-checkbox-input-width: 16px;
--el-checkbox-input-height: 16px;
--el-checkbox-text-color: var(--el-text-color-primary);
--el-checkbox-disabled-checked-input-fill: var(--mk-N400);
--el-checkbox-disabled-checked-input-border-color: var(--mk-N400);
--el-checkbox-disabled-checked-icon-color: white;
--el-checkbox-checked-text-color: var(--el-text-color-primary);
margin: 0;
height: unset;
&__label {
line-height: 22px;
font-weight: 400;
}
}

/* dialog */
Expand Down
7 changes: 7 additions & 0 deletions ui/src/styles/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@
flex-direction: column;
justify-content: center;
}

/* 横向排列,自动换行且行间距为 8px */
@utility flex-wrap {
display: flex;
flex-wrap: wrap;
row-gap: calc(var(--spacing) * 2);
}
183 changes: 64 additions & 119 deletions ui/src/views/system/identity/roles/AddMemberDrawer.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import type { FormInstance } from 'element-plus'
import RoleApi from '@/api/admin/system/role'
import UserManageApi from '@/api/admin/system/user-manage'
import WorkspaceApi from '@/api/admin/system/workspace'
Expand All @@ -13,93 +13,74 @@ import {
} from '@/api/types'
import { useStore } from '@/stores'
import { MsgSuccess } from '@/utils/message'
import MemberWorkspaceSetting from './components/MemberWorkspaceSetting.vue'

defineOptions({ name: 'AddRoleMemberDrawer' })

const props = defineProps<{ currentRole: RoleItem }>()
const emit = defineEmits<{ refresh: [] }>()
const { auth } = useStore()

const visible = ref(false)
const loading = ref(false)
const optionsLoading = ref(false)
const formRef = ref<FormInstance>()
const memberRows = reactive<CreateRoleMemberItem[]>([])
const userOptions = ref<SystemUserOption[]>([])
const workspaceOptions = ref<WorkspaceItem[]>([])
const memberForm = reactive<{ members: CreateRoleMemberItem[] }>({ members: [] })
const showWorkspace = computed(() => props.currentRole.type !== ROLE_TYPE.ADMIN && auth.isEE)
const rules: FormRules = {
user_ids: [{ required: true, message: '请选择用户', trigger: 'change' }],
workspace_ids: [{ required: true, message: '请选择工作空间', trigger: 'change' }],
}
let searchTimer: ReturnType<typeof setTimeout> | null = null

function createEmptyRow(): CreateRoleMemberItem {
return { user_ids: [], ...(showWorkspace.value ? { workspace_ids: [] } : {}) }
}
/* 成员与工作空间选项 */
const userOptions = ref<SystemUserOption[]>([])
const userOptionsLoading = ref(false)
const workspaceOptions = ref<WorkspaceItem[]>([])
const workspaceOptionsLoading = ref(false)

function loadOptions(query?: string) {
optionsLoading.value = true
return Promise.all([
UserManageApi.getAllUsers(query ? { nick_name: query } : undefined),
showWorkspace.value ? WorkspaceApi.getSystemWorkspaceList() : Promise.resolve([]),
])
.then(([users, workspaces]) => {
userOptions.value = users
workspaceOptions.value = workspaces
function loadUserOptions(keyword = '') {
userOptionsLoading.value = true
return UserManageApi.getAllUsers(keyword ? { nick_name: keyword } : undefined)
.then((users) => {
const selectedUserIds = new Set(memberForm.members.flatMap(({ user_ids }) => user_ids))
const selectedUserOptions = userOptions.value.filter(({ id }) => selectedUserIds.has(id))
userOptions.value = [
...new Map([...selectedUserOptions, ...users].map((user) => [user.id, user])).values(),
]
})
.finally(() => {
optionsLoading.value = false
userOptionsLoading.value = false
})
}

function handleRemoteSearch(query: string) {
if (searchTimer) clearTimeout(searchTimer)
searchTimer = setTimeout(() => loadOptions(query), 300)
}

function handleAddRow() {
memberRows.push(createEmptyRow())
}

function handleDeleteRow(index: number) {
if (memberRows.length > 1) memberRows.splice(index, 1)
}
function loadWorkspaceOptions() {
if (!showWorkspace.value) return Promise.resolve()

function resetData() {
memberRows.splice(0, memberRows.length, createEmptyRow())
userOptions.value = []
workspaceOptions.value = []
loading.value = false
optionsLoading.value = false
formRef.value?.clearValidate()
if (searchTimer) {
clearTimeout(searchTimer)
searchTimer = null
}
workspaceOptionsLoading.value = true
return WorkspaceApi.getSystemWorkspaceList()
.then((workspaces) => {
workspaceOptions.value = workspaces
})
.finally(() => {
workspaceOptionsLoading.value = false
})
}

function open() {
resetData()
memberForm.members = [{ user_ids: [], workspace_ids: [] }]
visible.value = true
loadOptions()
}

function close() {
visible.value = false
resetData()
return Promise.all([loadUserOptions(), loadWorkspaceOptions()])
}

function submit() {
formRef.value?.validate((valid) => {
if (!valid) return
const members = memberRows.map((row) => ({
user_ids: row.user_ids,

const members = memberForm.members.map((member) => ({
user_ids: [...member.user_ids],
workspace_ids:
props.currentRole.type === ROLE_TYPE.ADMIN
? ['None']
: auth.isPE
? ['default']
: row.workspace_ids,
: [...(member.workspace_ids ?? [])],
}))

loading.value = true
RoleApi.postRoleMembers(props.currentRole.id, { members })
.then(() => {
Expand All @@ -113,74 +94,38 @@ function submit() {
})
}

function close() {
visible.value = false
resetData()
}

function resetData() {
memberForm.members = []
loading.value = false
userOptions.value = []
userOptionsLoading.value = false
workspaceOptions.value = []
workspaceOptionsLoading.value = false
formRef.value?.clearValidate()
}

defineExpose({ open })
</script>

<template>
<MkDrawer v-model="visible" title="添加成员" size="600" @closed="resetData">
<el-form ref="formRef" :model="memberRows" label-position="top">
<el-scrollbar>
<div v-for="(row, index) in memberRows" :key="index" class="mb-3 flex items-end gap-2">
<el-form-item
:label="index === 0 ? '用户' : ''"
:prop="`${index}.user_ids`"
:rules="rules.user_ids"
class="mb-0 flex-1"
>
<el-select
v-model="row.user_ids"
class="w-full"
filterable
remote
multiple
collapse-tags
collapse-tags-tooltip
reserve-keyword
:remote-method="handleRemoteSearch"
:loading="optionsLoading"
placeholder="请选择用户"
>
<el-option
v-for="user in userOptions"
:key="user.id"
:label="user.nick_name || user.username"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="showWorkspace"
:label="index === 0 ? '工作空间' : ''"
:prop="`${index}.workspace_ids`"
:rules="rules.workspace_ids"
class="mb-0 flex-1"
>
<el-select
v-model="row.workspace_ids"
class="w-full"
filterable
multiple
collapse-tags
collapse-tags-tooltip
placeholder="请选择工作空间"
>
<el-option
v-for="workspace in workspaceOptions"
:key="workspace.id"
:label="workspace.name"
:value="workspace.id"
/>
</el-select>
</el-form-item>
<el-button text :disabled="memberRows.length === 1" @click="handleDeleteRow(index)"
><MkIcon name="icon_delete-trash_outlined"
/></el-button>
</div>
</el-scrollbar>
<el-button text type="primary" @click="handleAddRow"
><MkIcon name="icon_add_outlined" />添加</el-button
>
<MkDrawer v-model="visible" title="添加成员" @closed="resetData">
<el-form ref="formRef" :model="memberForm" label-position="top">
<MemberWorkspaceSetting
v-model="memberForm.members"
:remote-user-method="loadUserOptions"
:show-workspace="showWorkspace"
:user-loading="userOptionsLoading"
:user-options="userOptions"
:workspace-loading="workspaceOptionsLoading"
:workspace-options="workspaceOptions"
/>
</el-form>

<template #footer>
<el-button @click="close">取消</el-button>
<el-button type="primary" :loading="loading" @click="submit">添加</el-button>
Expand Down
6 changes: 1 addition & 5 deletions ui/src/views/system/identity/roles/RoleListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,6 @@ function handleDeleteRole(role: RoleItem) {
})
}

function handleMemberChanged() {
loadRoles(currentRole.value?.id)
}

onMounted(() => loadRoles())
</script>

Expand Down Expand Up @@ -172,7 +168,7 @@ onMounted(() => loadRoles())
:current-role="currentRole"
/>
<!-- 成员 -->
<RoleMemberList v-else :current-role="currentRole" @changed="handleMemberChanged" />
<RoleMemberList v-else :current-role="currentRole" />
</template>
<MkEmpty v-else class="flex-1" />
</template>
Expand Down
Loading
Loading