mirror of https://github.com/halo-dev/halo-admin
refactor: user and role editing modal
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/603/head
parent
3722a7fb36
commit
4031eb7d85
|
@ -1,4 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
// core libs
|
||||
import { ref } from "vue";
|
||||
import type { Role } from "@halo-dev/api-client";
|
||||
|
||||
// components
|
||||
import {
|
||||
IconAddCircle,
|
||||
IconArrowDown,
|
||||
|
@ -12,44 +17,34 @@ import {
|
|||
VTag,
|
||||
} from "@halo-dev/components";
|
||||
import RoleEditingModal from "./components/RoleEditingModal.vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import type { Role } from "@halo-dev/api-client";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
import { roleLabels } from "@/constants/labels";
|
||||
|
||||
// constants
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import { formatDatetime } from "@/utils/date";
|
||||
|
||||
const roles = ref<Role[]>([]);
|
||||
// hooks
|
||||
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
|
||||
|
||||
const editingModal = ref<boolean>(false);
|
||||
const selectedRole = ref<Role | null>(null);
|
||||
|
||||
const handleFetchRoles = async () => {
|
||||
try {
|
||||
const { data } = await apiClient.extension.role.listv1alpha1Role(0, 0, [
|
||||
`!${roleLabels.TEMPLATE}`,
|
||||
]);
|
||||
roles.value = data.items;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
selectedRole.value = null;
|
||||
}
|
||||
};
|
||||
const { roles, handleFetchRoles } = useFetchRole();
|
||||
|
||||
const handleOpenEditingModal = (role: Role) => {
|
||||
selectedRole.value = role;
|
||||
editingModal.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const onEditingModalClose = () => {
|
||||
selectedRole.value = null;
|
||||
handleFetchRoles();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<RoleEditingModal
|
||||
v-model:visible="editingModal"
|
||||
:role="selectedRole"
|
||||
@close="handleFetchRoles"
|
||||
@close="onEditingModalClose"
|
||||
/>
|
||||
|
||||
<VPageHeader title="角色">
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { VButton, VModal, VSpace, VTabItem, VTabs } from "@halo-dev/components";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { computed, ref, watch, watchEffect } from "vue";
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import type { Role } from "@halo-dev/api-client";
|
||||
import {
|
||||
|
@ -10,6 +10,7 @@ import {
|
|||
import cloneDeep from "lodash.clonedeep";
|
||||
import { reset, submitForm } from "@formkit/core";
|
||||
import { useMagicKeys } from "@vueuse/core";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -48,33 +49,39 @@ watch(
|
|||
}
|
||||
);
|
||||
|
||||
watch(props, (newVal) => {
|
||||
const { Command_Enter } = useMagicKeys();
|
||||
let keyboardWatcher;
|
||||
if (newVal.visible) {
|
||||
keyboardWatcher = watch(Command_Enter, (v) => {
|
||||
if (v) {
|
||||
submitForm("role-form");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
keyboardWatcher?.unwatch();
|
||||
}
|
||||
const { Command_Enter } = useMagicKeys();
|
||||
|
||||
if (newVal.visible && props.role) {
|
||||
formState.value = cloneDeep(props.role);
|
||||
const dependencies =
|
||||
props.role.metadata.annotations?.[rbacAnnotations.DEPENDENCIES];
|
||||
if (dependencies) {
|
||||
selectedRoleTemplates.value = new Set(JSON.parse(dependencies));
|
||||
}
|
||||
return;
|
||||
watchEffect(() => {
|
||||
if (Command_Enter.value && props.visible) {
|
||||
submitForm("role-form");
|
||||
}
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
selectedRoleTemplates.value.clear();
|
||||
reset("role-form");
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
handleResetForm();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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 tabActiveId = ref("general");
|
||||
|
||||
const editingModalTitle = computed(() => {
|
||||
|
@ -84,25 +91,31 @@ const editingModalTitle = computed(() => {
|
|||
const handleCreateOrUpdateRole = async () => {
|
||||
try {
|
||||
await handleCreateOrUpdate();
|
||||
handleVisibleChange(false);
|
||||
onVisibleChange(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetForm = () => {
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
formState.value.metadata.name = uuid();
|
||||
reset("role-form");
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:title="editingModalTitle"
|
||||
:visible="visible"
|
||||
:width="700"
|
||||
@update:visible="handleVisibleChange"
|
||||
@update:visible="onVisibleChange"
|
||||
>
|
||||
<VTabs v-model:active-id="tabActiveId" type="outline">
|
||||
<VTabItem id="general" label="基础信息">
|
||||
|
@ -198,7 +211,7 @@ const handleVisibleChange = (visible: boolean) => {
|
|||
>
|
||||
提交 ⌘ + ↵
|
||||
</VButton>
|
||||
<VButton @click="handleVisibleChange(false)">取消 Esc</VButton>
|
||||
<VButton @click="onVisibleChange(false)">取消 Esc</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VModal>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Role } from "@halo-dev/api-client";
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { roleLabels } from "@/constants/labels";
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
|
@ -23,7 +24,65 @@ const initialFormState: Role = {
|
|||
rules: [],
|
||||
};
|
||||
|
||||
export function useRoleForm() {
|
||||
interface useFetchRoleReturn {
|
||||
roles: Ref<Role[]>;
|
||||
loading: Ref<boolean>;
|
||||
handleFetchRoles: () => void;
|
||||
}
|
||||
|
||||
interface useRoleFormReturn {
|
||||
formState: Ref<Role>;
|
||||
initialFormState: Role;
|
||||
saving: Ref<boolean>;
|
||||
isUpdateMode: ComputedRef<boolean>;
|
||||
handleCreateOrUpdate: () => void;
|
||||
}
|
||||
|
||||
interface useRoleTemplateSelectionReturn {
|
||||
selectedRoleTemplates: Ref<Set<string>>;
|
||||
roleTemplates: Ref<Role[]>;
|
||||
roleTemplateGroups: ComputedRef<RoleTemplateGroup[]>;
|
||||
handleRoleTemplateSelect: (e: Event) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all roles(without role templates) from the API.
|
||||
*
|
||||
* @returns {useFetchRoleReturn}
|
||||
*/
|
||||
export function useFetchRole(): useFetchRoleReturn {
|
||||
const roles = ref<Role[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const handleFetchRoles = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await apiClient.extension.role.listv1alpha1Role(0, 0, [
|
||||
`!${roleLabels.TEMPLATE}`,
|
||||
]);
|
||||
roles.value = data.items;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch roles", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(handleFetchRoles);
|
||||
|
||||
return {
|
||||
roles,
|
||||
loading,
|
||||
handleFetchRoles,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a role.
|
||||
*
|
||||
* @returns {useRoleFormReturn}
|
||||
*/
|
||||
export function useRoleForm(): useRoleFormReturn {
|
||||
const formState = ref<Role>(initialFormState);
|
||||
const saving = ref(false);
|
||||
|
||||
|
@ -58,7 +117,12 @@ export function useRoleForm() {
|
|||
};
|
||||
}
|
||||
|
||||
export function useRoleTemplateSelection() {
|
||||
/**
|
||||
* Logic for selecting role templates
|
||||
*
|
||||
* @returns {useRoleTemplateSelectionReturn}
|
||||
*/
|
||||
export function useRoleTemplateSelection(): useRoleTemplateSelectionReturn {
|
||||
const roleTemplates = ref<Role[]>([] as Role[]);
|
||||
const selectedRoleTemplates = ref<Set<string>>(new Set());
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<script lang="ts" name="UserEditingModal" setup>
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
<script lang="ts" setup>
|
||||
// core libs
|
||||
import { computed, ref, watch, watchEffect } from "vue";
|
||||
import { apiClient } from "@halo-dev/admin-shared";
|
||||
import type { Role, User } from "@halo-dev/api-client";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
|
||||
// components
|
||||
import {
|
||||
IconCodeBoxLine,
|
||||
IconEye,
|
||||
|
@ -10,14 +13,21 @@ import {
|
|||
VModal,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
|
||||
// libs
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { roleLabels } from "@/constants/labels";
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
import YAML from "yaml";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { useMagicKeys } from "@vueuse/core";
|
||||
import { reset, submitForm } from "@formkit/core";
|
||||
|
||||
// constants
|
||||
import { rbacAnnotations } from "@/constants/annotations";
|
||||
|
||||
// hooks
|
||||
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
|
||||
import type { FormKitOptionsList } from "@formkit/inputs";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
|
@ -64,7 +74,6 @@ const initialFormState: FormState = {
|
|||
raw: "",
|
||||
};
|
||||
|
||||
const roles = ref<Role[]>([]);
|
||||
const formState = ref<FormState>(cloneDeep(initialFormState));
|
||||
const selectedRole = ref("");
|
||||
|
||||
|
@ -80,50 +89,59 @@ const modalWidth = computed(() => {
|
|||
return formState.value.rawMode ? 800 : 700;
|
||||
});
|
||||
|
||||
const basicRoles = computed(() => {
|
||||
return roles.value.filter(
|
||||
(role) => role.metadata?.labels?.[roleLabels.TEMPLATE] !== "true"
|
||||
);
|
||||
const { roles } = useFetchRole();
|
||||
const rolesMap = computed<FormKitOptionsList>(() => {
|
||||
return roles.value.map((role) => {
|
||||
return {
|
||||
label:
|
||||
role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
|
||||
role.metadata.name,
|
||||
value: role.metadata?.name,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const { Command_Enter } = useMagicKeys();
|
||||
|
||||
watch(props, (newVal) => {
|
||||
let keyboardWatcher;
|
||||
if (newVal.visible) {
|
||||
keyboardWatcher = watch(Command_Enter, (v) => {
|
||||
if (v) {
|
||||
submitForm("user-form");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
keyboardWatcher?.unwatch();
|
||||
watchEffect(() => {
|
||||
if (Command_Enter.value && props.visible) {
|
||||
submitForm("user-form");
|
||||
}
|
||||
|
||||
if (newVal.visible && props.user) {
|
||||
formState.value.user = cloneDeep(props.user);
|
||||
return;
|
||||
}
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
reset("user-form");
|
||||
});
|
||||
|
||||
const handleFetchRoles = async () => {
|
||||
try {
|
||||
const { data } = await apiClient.extension.role.listv1alpha1Role();
|
||||
roles.value = data.items;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
handleResetForm();
|
||||
}
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
watch(
|
||||
() => props.user,
|
||||
(user) => {
|
||||
if (user) {
|
||||
formState.value.user = cloneDeep(user);
|
||||
} else {
|
||||
handleResetForm();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onVisibleChange = (visible: boolean) => {
|
||||
emit("update:visible", visible);
|
||||
if (!visible) {
|
||||
emit("close");
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetForm = () => {
|
||||
formState.value = cloneDeep(initialFormState);
|
||||
formState.value.user.metadata.name = uuid();
|
||||
reset("user-form");
|
||||
};
|
||||
|
||||
const handleCreateUser = async () => {
|
||||
try {
|
||||
formState.value.saving = true;
|
||||
|
@ -149,7 +167,7 @@ const handleCreateUser = async () => {
|
|||
});
|
||||
}
|
||||
|
||||
handleVisibleChange(false);
|
||||
onVisibleChange(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
|
@ -166,15 +184,13 @@ const handleRawModeChange = () => {
|
|||
formState.value.user = YAML.parse(formState.value.raw);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(handleFetchRoles);
|
||||
</script>
|
||||
<template>
|
||||
<VModal
|
||||
:title="creationModalTitle"
|
||||
:visible="visible"
|
||||
:width="modalWidth"
|
||||
@update:visible="handleVisibleChange"
|
||||
@update:visible="onVisibleChange"
|
||||
>
|
||||
<template #actions>
|
||||
<div class="modal-header-action" @click="handleRawModeChange">
|
||||
|
@ -213,14 +229,7 @@ onMounted(handleFetchRoles);
|
|||
></FormKit>
|
||||
<FormKit
|
||||
v-model="selectedRole"
|
||||
:options="
|
||||
basicRoles.map((role:Role) => {
|
||||
return {
|
||||
label: role.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] || role.metadata.name,
|
||||
value: role.metadata?.name,
|
||||
};
|
||||
})
|
||||
"
|
||||
:options="rolesMap"
|
||||
label="角色"
|
||||
type="select"
|
||||
></FormKit>
|
||||
|
@ -250,7 +259,7 @@ onMounted(handleFetchRoles);
|
|||
>
|
||||
保存 ⌘ + ↵
|
||||
</VButton>
|
||||
<VButton @click="handleVisibleChange(false)">取消 Esc</VButton>
|
||||
<VButton @click="onVisibleChange(false)">取消 Esc</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VModal>
|
||||
|
|
Loading…
Reference in New Issue