mirror of https://github.com/halo-dev/halo
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
parent
cb2138580c
commit
769b19c23c
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue