refactor: improve code base of role-related (#5984)

#### What type of PR is this?

/area ui
/kind improvement
/milestone 2.16.x

#### What this PR does / why we need it:

优化和角色相关的对话框显示逻辑,减少不必要的渲染开销和请求。

#### Does this PR introduce a user-facing change?

```release-note
优化和角色相关的对话框显示逻辑,减少不必要的渲染开销和请求。
```
pull/5987/head^2
Ryan Wang 2024-05-24 15:18:51 +08:00 committed by GitHub
parent cb2138580c
commit 769b19c23c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 81 additions and 119 deletions

View File

@ -1,14 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
IconShieldUser, IconShieldUser,
VAlert,
VButton, VButton,
VCard, VCard,
VDescription,
VDescriptionItem,
VPageHeader, VPageHeader,
VTabbar, VTabbar,
VTag, VTag,
VAlert,
VDescription,
VDescriptionItem,
} from "@halo-dev/components"; } from "@halo-dev/components";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
@ -43,7 +43,7 @@ const { data: roleTemplates } = useQuery({
const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } = const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } =
useRoleTemplateSelection(roleTemplates); useRoleTemplateSelection(roleTemplates);
const { formState, saving, handleCreateOrUpdate } = useRoleForm(); const { formState, isSubmitting, handleCreateOrUpdate } = useRoleForm();
const isSystemReserved = computed(() => { const isSystemReserved = computed(() => {
return ( return (
@ -285,7 +285,7 @@ const handleUpdateRole = async () => {
</dl> </dl>
<div v-permission="['system:roles:manage']" class="p-4"> <div v-permission="['system:roles:manage']" class="p-4">
<VButton <VButton
:loading="saving" :loading="isSubmitting"
type="secondary" type="secondary"
:disabled="isSystemReserved" :disabled="isSystemReserved"
@click="handleUpdateRole" @click="handleUpdateRole"

View File

@ -5,19 +5,19 @@ import type { Role, RoleList } from "@halo-dev/api-client";
// components // components
import { import {
Dialog,
IconAddCircle, IconAddCircle,
IconShieldUser, IconShieldUser,
Dialog, Toast,
VButton, VButton,
VCard, VCard,
VPageHeader, VDropdownItem,
VTag,
VStatusDot,
VEntity, VEntity,
VEntityField, VEntityField,
VLoading, VLoading,
Toast, VPageHeader,
VDropdownItem, VStatusDot,
VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import RoleEditingModal from "./components/RoleEditingModal.vue"; import RoleEditingModal from "./components/RoleEditingModal.vue";
@ -130,6 +130,7 @@ const handleOpenEditingModal = (role: Role) => {
const onEditingModalClose = () => { const onEditingModalClose = () => {
selectedRole.value = undefined; selectedRole.value = undefined;
editingModal.value = false;
refetch(); refetch();
}; };
@ -198,7 +199,7 @@ const handleDelete = async (role: Role) => {
</script> </script>
<template> <template>
<RoleEditingModal <RoleEditingModal
v-model:visible="editingModal" v-if="editingModal"
:role="selectedRole" :role="selectedRole"
@close="onEditingModalClose" @close="onEditingModalClose"
/> />

View File

@ -1,12 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { VButton, VModal, VSpace } from "@halo-dev/components"; import { VButton, VModal, VSpace } from "@halo-dev/components";
import SubmitButton from "@/components/button/SubmitButton.vue"; import SubmitButton from "@/components/button/SubmitButton.vue";
import { computed, watch } from "vue"; import { onMounted, ref, watch } from "vue";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import type { Role } from "@halo-dev/api-client"; import type { Role } from "@halo-dev/api-client";
import { useRoleForm, useRoleTemplateSelection } from "@/composables/use-role"; import { useRoleForm, useRoleTemplateSelection } from "@/composables/use-role";
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { reset } from "@formkit/core";
import { setFocus } from "@/formkit/utils/focus"; import { setFocus } from "@/formkit/utils/focus";
import { pluginLabels, roleLabels } from "@/constants/labels"; import { pluginLabels, roleLabels } from "@/constants/labels";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -17,20 +16,19 @@ const { t } = useI18n();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
visible: boolean;
role?: Role; role?: Role;
}>(), }>(),
{ {
visible: false,
role: undefined, role: undefined,
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(event: "update:visible", visible: boolean): void;
(event: "close"): void; (event: "close"): void;
}>(); }>();
const modal = ref<InstanceType<typeof VModal>>();
const { data: roleTemplates } = useQuery({ const { data: roleTemplates } = useQuery({
queryKey: ["role-templates"], queryKey: ["role-templates"],
queryFn: async () => { queryFn: async () => {
@ -46,13 +44,7 @@ const { data: roleTemplates } = useQuery({
const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } = const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } =
useRoleTemplateSelection(roleTemplates); useRoleTemplateSelection(roleTemplates);
const { const { formState, isSubmitting, handleCreateOrUpdate } = useRoleForm();
formState,
isUpdateMode,
initialFormState,
saving,
handleCreateOrUpdate,
} = useRoleForm();
watch( watch(
() => selectedRoleTemplates.value, () => selectedRoleTemplates.value,
@ -64,67 +56,39 @@ watch(
} }
); );
watch( onMounted(() => {
() => props.visible, setFocus("displayNameInput");
(visible) => {
if (visible) { if (props.role) {
setFocus("displayNameInput"); formState.value = cloneDeep(props.role);
} else { const dependencies =
handleResetForm(); props.role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES];
if (dependencies) {
selectedRoleTemplates.value = new Set(JSON.parse(dependencies));
} }
} }
);
watch(
() => props.role,
(role) => {
if (role) {
formState.value = cloneDeep(role);
const dependencies =
role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES];
if (dependencies) {
selectedRoleTemplates.value = new Set(JSON.parse(dependencies));
}
} else {
handleResetForm();
}
}
);
const editingModalTitle = computed(() => {
return isUpdateMode.value
? t("core.role.editing_modal.titles.update")
: t("core.role.editing_modal.titles.create");
}); });
const editingModalTitle = props.role
? t("core.role.editing_modal.titles.update")
: t("core.role.editing_modal.titles.create");
const handleCreateOrUpdateRole = async () => { const handleCreateOrUpdateRole = async () => {
try { try {
await handleCreateOrUpdate(); await handleCreateOrUpdate();
onVisibleChange(false);
modal.value?.close();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}; };
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
const handleResetForm = () => {
formState.value = cloneDeep(initialFormState);
selectedRoleTemplates.value.clear();
reset("role-form");
};
</script> </script>
<template> <template>
<VModal <VModal
ref="modal"
:title="editingModalTitle" :title="editingModalTitle"
:visible="visible"
:width="700" :width="700"
@update:visible="onVisibleChange" @close="emit('close')"
> >
<div> <div>
<div class="md:grid md:grid-cols-4 md:gap-6"> <div class="md:grid md:grid-cols-4 md:gap-6">
@ -301,14 +265,13 @@ const handleResetForm = () => {
<template #footer> <template #footer>
<VSpace> <VSpace>
<SubmitButton <SubmitButton
v-if="visible" :loading="isSubmitting"
:loading="saving"
type="secondary" type="secondary"
:text="$t('core.common.buttons.submit')" :text="$t('core.common.buttons.submit')"
@submit="$formkit.submit('role-form')" @submit="$formkit.submit('role-form')"
> >
</SubmitButton> </SubmitButton>
<VButton @click="onVisibleChange(false)"> <VButton @click="modal?.close()">
{{ $t("core.common.buttons.cancel_and_shortcut") }} {{ $t("core.common.buttons.cancel_and_shortcut") }}
</VButton> </VButton>
</VSpace> </VSpace>

View File

@ -247,6 +247,12 @@ function onPasswordChangeModalClose() {
passwordChangeModal.value = false; passwordChangeModal.value = false;
refetch(); refetch();
} }
function onGrantPermissionModalClose() {
grantPermissionModal.value = false;
selectedUser.value = undefined;
refetch();
}
</script> </script>
<template> <template>
<UserEditingModal <UserEditingModal
@ -264,9 +270,9 @@ function onPasswordChangeModalClose() {
/> />
<GrantPermissionModal <GrantPermissionModal
v-model:visible="grantPermissionModal" v-if="grantPermissionModal"
:user="selectedUser" :user="selectedUser"
@close="refetch" @close="onGrantPermissionModalClose"
/> />
<VPageHeader :title="$t('core.user.title')"> <VPageHeader :title="$t('core.user.title')">

View File

@ -1,60 +1,51 @@
<script lang="ts" setup> <script lang="ts" setup>
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import type { User } from "@halo-dev/api-client"; import type { User } from "@halo-dev/api-client";
import { VModal, VSpace, VButton } from "@halo-dev/components"; import { VButton, VModal, VSpace } from "@halo-dev/components";
import SubmitButton from "@/components/button/SubmitButton.vue"; import SubmitButton from "@/components/button/SubmitButton.vue";
import { ref } from "vue"; import { ref } from "vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
visible: boolean;
user?: User; user?: User;
}>(), }>(),
{ {
visible: false,
user: undefined, user: undefined,
} }
); );
const emit = defineEmits<{ const emit = defineEmits<{
(event: "update:visible", visible: boolean): void;
(event: "close"): void; (event: "close"): void;
}>(); }>();
const modal = ref<InstanceType<typeof VModal>>();
const selectedRole = ref(""); const selectedRole = ref("");
const saving = ref(false); const isSubmitting = ref(false);
const handleGrantPermission = async () => { const handleGrantPermission = async () => {
try { try {
saving.value = true; isSubmitting.value = true;
await apiClient.user.grantPermission({ await apiClient.user.grantPermission({
name: props.user?.metadata.name as string, name: props.user?.metadata.name as string,
grantRequest: { grantRequest: {
roles: [selectedRole.value], roles: [selectedRole.value],
}, },
}); });
onVisibleChange(false); modal.value?.close();
} catch (error) { } catch (error) {
console.error("Failed to grant permission to user", error); console.error("Failed to grant permission to user", error);
} finally { } finally {
saving.value = false; isSubmitting.value = false;
}
};
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
} }
}; };
</script> </script>
<template> <template>
<VModal <VModal
ref="modal"
:title="$t('core.user.grant_permission_modal.title')" :title="$t('core.user.grant_permission_modal.title')"
:visible="visible"
:width="500" :width="500"
@update:visible="onVisibleChange" @close="emit('close')"
> >
<FormKit <FormKit
id="grant-permission-form" id="grant-permission-form"
@ -72,14 +63,13 @@ const onVisibleChange = (visible: boolean) => {
<template #footer> <template #footer>
<VSpace> <VSpace>
<SubmitButton <SubmitButton
v-if="visible" :loading="isSubmitting"
:loading="saving"
type="secondary" type="secondary"
:text="$t('core.common.buttons.submit')" :text="$t('core.common.buttons.submit')"
@submit="$formkit.submit('grant-permission-form')" @submit="$formkit.submit('grant-permission-form')"
> >
</SubmitButton> </SubmitButton>
<VButton @click="onVisibleChange(false)"> <VButton @click="modal?.close()">
{{ $t("core.common.buttons.cancel_and_shortcut") }} {{ $t("core.common.buttons.cancel_and_shortcut") }}
</VButton> </VButton>
</VSpace> </VSpace>

View File

@ -1,6 +1,12 @@
import type { Role } from "@halo-dev/api-client"; import type { Role } from "@halo-dev/api-client";
import { onUnmounted, type ComputedRef, type Ref } from "vue"; import {
import { computed, onMounted, ref } from "vue"; computed,
type ComputedRef,
onMounted,
onUnmounted,
type Ref,
ref,
} from "vue";
import { roleLabels } from "@/constants/labels"; import { roleLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
@ -13,21 +19,6 @@ interface RoleTemplateGroup {
roles: Role[]; roles: Role[];
} }
const initialFormState: Role = {
apiVersion: "v1alpha1",
kind: "Role",
metadata: {
name: "",
generateName: "role-",
labels: {},
annotations: {
[rbacAnnotations.DEPENDENCIES]: "",
[rbacAnnotations.DISPLAY_NAME]: "",
},
},
rules: [],
};
interface useFetchRoleReturn { interface useFetchRoleReturn {
roles: Ref<Role[]>; roles: Ref<Role[]>;
loading: Ref<boolean>; loading: Ref<boolean>;
@ -36,8 +27,7 @@ interface useFetchRoleReturn {
interface useRoleFormReturn { interface useRoleFormReturn {
formState: Ref<Role>; formState: Ref<Role>;
initialFormState: Role; isSubmitting: Ref<boolean>;
saving: Ref<boolean>;
isUpdateMode: ComputedRef<boolean>; isUpdateMode: ComputedRef<boolean>;
handleCreateOrUpdate: () => Promise<void>; handleCreateOrUpdate: () => Promise<void>;
} }
@ -110,8 +100,21 @@ export function useFetchRole(): useFetchRoleReturn {
export function useRoleForm(): useRoleFormReturn { export function useRoleForm(): useRoleFormReturn {
const { t } = useI18n(); const { t } = useI18n();
const formState = ref<Role>(initialFormState); const formState = ref<Role>({
const saving = ref(false); apiVersion: "v1alpha1",
kind: "Role",
metadata: {
name: "",
generateName: "role-",
labels: {},
annotations: {
[rbacAnnotations.DEPENDENCIES]: "",
[rbacAnnotations.DISPLAY_NAME]: "",
},
},
rules: [],
});
const isSubmitting = ref(false);
const isUpdateMode = computed(() => { const isUpdateMode = computed(() => {
return !!formState.value.metadata.creationTimestamp; return !!formState.value.metadata.creationTimestamp;
@ -119,7 +122,7 @@ export function useRoleForm(): useRoleFormReturn {
const handleCreateOrUpdate = async () => { const handleCreateOrUpdate = async () => {
try { try {
saving.value = true; isSubmitting.value = true;
if (isUpdateMode.value) { if (isUpdateMode.value) {
const { data } = await apiClient.extension.role.updateV1alpha1Role({ const { data } = await apiClient.extension.role.updateV1alpha1Role({
name: formState.value.metadata.name, name: formState.value.metadata.name,
@ -139,14 +142,13 @@ export function useRoleForm(): useRoleFormReturn {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} finally { } finally {
saving.value = false; isSubmitting.value = false;
} }
}; };
return { return {
formState, formState,
initialFormState, isSubmitting,
saving,
isUpdateMode, isUpdateMode,
handleCreateOrUpdate, handleCreateOrUpdate,
}; };