refactor: logic for role creation form

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/591/head
Ryan Wang 2022-07-26 20:21:11 +08:00
parent 05375a1a7b
commit fad809fec2
4 changed files with 198 additions and 160 deletions

View File

@ -9,49 +9,43 @@ import {
VTabbar, VTabbar,
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import { useRoute, useRouter } from "vue-router"; import { useRoute } from "vue-router";
import { onMounted, ref } from "vue"; import { onMounted, ref, watch } from "vue";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
import type { Role, User } from "@halo-dev/api-client"; import type { User } from "@halo-dev/api-client";
import { pluginLabels } from "@/constants/labels"; import { pluginLabels } from "@/constants/labels";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { useRoleTemplateSelection } from "@/modules/system/roles/composables/use-role"; import {
useRoleForm,
interface FormState { useRoleTemplateSelection,
role: Role; } from "@/modules/system/roles/composables/use-role";
saving: boolean;
}
const route = useRoute(); const route = useRoute();
const users = ref<User[]>([]); const users = ref<User[]>([]);
const roleActiveId = ref("detail"); const tabActiveId = ref("detail");
const formState = ref<FormState>({
role: {
apiVersion: "v1alpha1",
kind: "Role",
metadata: {
name: "",
labels: {},
annotations: {
[rbacAnnotations.DEPENDENCIES]: "",
[rbacAnnotations.DISPLAY_NAME]: "",
},
},
rules: [],
},
saving: false,
});
const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } = const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } =
useRoleTemplateSelection(); useRoleTemplateSelection();
const { formState, saving, handleCreateOrUpdate } = useRoleForm();
watch(
() => selectedRoleTemplates.value,
(newValue) => {
if (formState.value.metadata.annotations) {
formState.value.metadata.annotations[rbacAnnotations.DEPENDENCIES] =
JSON.stringify(Array.from(newValue));
}
}
);
const handleFetchRole = async () => { const handleFetchRole = async () => {
try { try {
const response = await apiClient.extension.role.getv1alpha1Role( const response = await apiClient.extension.role.getv1alpha1Role(
route.params.name as string route.params.name as string
); );
formState.value.role = response.data; formState.value = response.data;
selectedRoleTemplates.value = new Set( selectedRoleTemplates.value = new Set(
JSON.parse( JSON.parse(
response.data.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] || response.data.metadata.annotations?.[rbacAnnotations.DEPENDENCIES] ||
@ -73,28 +67,8 @@ const handleFetchUsers = async () => {
}; };
const handleUpdateRole = async () => { const handleUpdateRole = async () => {
try { await handleCreateOrUpdate();
formState.value.saving = true;
if (formState.value.role.metadata.annotations) {
formState.value.role.metadata.annotations[rbacAnnotations.DEPENDENCIES] =
JSON.stringify(Array.from(selectedRoleTemplates.value));
}
await apiClient.extension.role.updatev1alpha1Role(
route.params.name as string,
formState.value.role
);
} catch (e) {
console.error(e);
} finally {
formState.value.saving = false;
await handleFetchRole(); await handleFetchRole();
}
};
const router = useRouter();
const handleRouteToUser = (name: string) => {
router.push({ name: "UserDetail", params: { name } });
}; };
onMounted(() => { onMounted(() => {
@ -105,8 +79,8 @@ onMounted(() => {
<template> <template>
<VPageHeader <VPageHeader
:title="`角色:${ :title="`角色:${
formState.role?.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] || formState.metadata?.annotations?.[rbacAnnotations.DISPLAY_NAME] ||
formState.role?.metadata?.name formState.metadata?.name
}`" }`"
> >
<template #icon> <template #icon>
@ -125,7 +99,7 @@ onMounted(() => {
<VCard :body-class="['!p-0']"> <VCard :body-class="['!p-0']">
<template #header> <template #header>
<VTabbar <VTabbar
v-model:active-id="roleActiveId" v-model:active-id="tabActiveId"
:items="[ :items="[
{ id: 'detail', label: '详情' }, { id: 'detail', label: '详情' },
{ id: 'permissions', label: '权限设置' }, { id: 'permissions', label: '权限设置' },
@ -134,7 +108,7 @@ onMounted(() => {
type="outline" type="outline"
></VTabbar> ></VTabbar>
</template> </template>
<div v-if="roleActiveId === 'detail'"> <div v-if="tabActiveId === 'detail'">
<div class="px-4 py-4 sm:px-6"> <div class="px-4 py-4 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">权限信息</h3> <h3 class="text-lg font-medium leading-6 text-gray-900">权限信息</h3>
<p <p
@ -144,7 +118,7 @@ onMounted(() => {
>包含 >包含
{{ {{
JSON.parse( JSON.parse(
formState.role.metadata.annotations?.[ formState.metadata.annotations?.[
rbacAnnotations.DEPENDENCIES rbacAnnotations.DEPENDENCIES
] || "[]" ] || "[]"
).length ).length
@ -161,9 +135,9 @@ onMounted(() => {
<dt class="text-sm font-medium text-gray-900">名称</dt> <dt class="text-sm font-medium text-gray-900">名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ {{
formState.role?.metadata?.annotations?.[ formState.metadata?.annotations?.[
rbacAnnotations.DISPLAY_NAME rbacAnnotations.DISPLAY_NAME
] || formState.role?.metadata?.name ] || formState.metadata?.name
}} }}
</dd> </dd>
</div> </div>
@ -172,7 +146,7 @@ onMounted(() => {
> >
<dt class="text-sm font-medium text-gray-900">别名</dt> <dt class="text-sm font-medium text-gray-900">别名</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ formState.role?.metadata?.name }} {{ formState.metadata?.name }}
</dd> </dd>
</div> </div>
<div <div
@ -196,7 +170,7 @@ onMounted(() => {
> >
<dt class="text-sm font-medium text-gray-900">创建时间</dt> <dt class="text-sm font-medium text-gray-900">创建时间</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ formState.role?.metadata?.creationTimestamp }} {{ formState.metadata?.creationTimestamp }}
</dd> </dd>
</div> </div>
<div <div
@ -208,12 +182,15 @@ onMounted(() => {
class="h-96 overflow-y-auto overflow-x-hidden rounded-sm bg-white shadow-sm transition-all hover:shadow" class="h-96 overflow-y-auto overflow-x-hidden rounded-sm bg-white shadow-sm transition-all hover:shadow"
> >
<ul class="divide-y divide-gray-100" role="list"> <ul class="divide-y divide-gray-100" role="list">
<li <RouterLink
v-for="(user, index) in users" v-for="(user, index) in users"
:key="index" :key="index"
class="block cursor-pointer hover:bg-gray-50" :to="{
@click="handleRouteToUser(user.metadata.name)" name: 'UserDetail',
params: { name: user.metadata.name },
}"
> >
<li class="block cursor-pointer hover:bg-gray-50">
<div class="flex items-center px-4 py-4"> <div class="flex items-center px-4 py-4">
<div class="flex min-w-0 flex-1 items-center"> <div class="flex min-w-0 flex-1 items-center">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
@ -251,6 +228,7 @@ onMounted(() => {
</div> </div>
</div> </div>
</li> </li>
</RouterLink>
</ul> </ul>
</div> </div>
</dd> </dd>
@ -259,7 +237,7 @@ onMounted(() => {
</div> </div>
</div> </div>
<div v-if="roleActiveId === 'permissions'"> <div v-if="tabActiveId === 'permissions'">
<div> <div>
<dl class="divide-y divide-gray-100"> <dl class="divide-y divide-gray-100">
<div <div
@ -342,7 +320,7 @@ onMounted(() => {
</dl> </dl>
<div v-permission="['system:roles:manage']" class="p-4"> <div v-permission="['system:roles:manage']" class="p-4">
<VButton <VButton
:loading="formState.saving" :loading="saving"
type="secondary" type="secondary"
@click="handleUpdateRole" @click="handleUpdateRole"
>保存 >保存

View File

@ -12,15 +12,15 @@ import {
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import RoleEditingModal from "./components/RoleEditingModal.vue"; import RoleEditingModal from "./components/RoleEditingModal.vue";
import { useRouter } from "vue-router";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import type { Role } from "@halo-dev/api-client"; import type { Role } from "@halo-dev/api-client";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
import { roleLabels } from "@/constants/labels"; import { roleLabels } from "@/constants/labels";
import { pluginAnnotations, rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
const createVisible = ref(false);
const roles = ref<Role[]>([]); const roles = ref<Role[]>([]);
const editingModal = ref<boolean>(false);
const selectedRole = ref<Role | null>(null);
const basicRoles = computed(() => { const basicRoles = computed(() => {
return roles.value.filter( return roles.value.filter(
@ -28,19 +28,20 @@ const basicRoles = computed(() => {
); );
}); });
const router = useRouter();
const handleFetchRoles = async () => { const handleFetchRoles = async () => {
try { try {
const { data } = await apiClient.extension.role.listv1alpha1Role(); const { data } = await apiClient.extension.role.listv1alpha1Role();
roles.value = data.items; roles.value = data.items;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} finally {
selectedRole.value = null;
} }
}; };
const handleRouteToDetail = (name: string) => { const handleOpenEditingModal = (role: Role) => {
router.push({ name: "RoleDetail", params: { name } }); selectedRole.value = role;
editingModal.value = true;
}; };
onMounted(() => { onMounted(() => {
@ -48,7 +49,11 @@ onMounted(() => {
}); });
</script> </script>
<template> <template>
<RoleEditingModal v-model:visible="createVisible" @close="handleFetchRoles" /> <RoleEditingModal
v-model:visible="editingModal"
:role="selectedRole"
@close="handleFetchRoles"
/>
<VPageHeader title="角色"> <VPageHeader title="角色">
<template #icon> <template #icon>
@ -58,7 +63,7 @@ onMounted(() => {
<VButton <VButton
v-permission="['system:roles:manage']" v-permission="['system:roles:manage']"
type="secondary" type="secondary"
@click="createVisible = true" @click="editingModal = true"
> >
<template #icon> <template #icon>
<IconAddCircle class="h-full w-full" /> <IconAddCircle class="h-full w-full" />
@ -164,24 +169,31 @@ onMounted(() => {
</div> </div>
</template> </template>
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list"> <ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li <li v-for="(role, index) in basicRoles" :key="index">
v-for="(role, index) in basicRoles"
:key="index"
@click="handleRouteToDetail(role.metadata.name)"
>
<div <div
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50" class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
> >
<div class="relative flex flex-row items-center"> <div class="relative flex flex-row items-center">
<div class="flex-1"> <div class="flex-1">
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<span class="mr-2 truncate text-sm font-medium text-gray-900"> <RouterLink
:to="{
name: 'RoleDetail',
params: {
name: role.metadata.name,
},
}"
>
<span
class="mr-2 truncate text-sm font-medium text-gray-900"
>
{{ {{
role.metadata.annotations?.[ role.metadata.annotations?.[
pluginAnnotations.DISPLAY_NAME rbacAnnotations.DISPLAY_NAME
] || role.metadata.name ] || role.metadata.name
}} }}
</span> </span>
</RouterLink>
</div> </div>
<div class="mt-2 flex"> <div class="mt-2 flex">
<span class="text-xs text-gray-500"> <span class="text-xs text-gray-500">
@ -208,14 +220,14 @@ onMounted(() => {
0 个用户 0 个用户
</a> </a>
<VTag> 系统保留</VTag> <VTag> 系统保留</VTag>
<time class="text-sm text-gray-500" datetime="2020-01-07"> <time class="text-sm text-gray-500">
2020-01-07 {{ role.metadata.creationTimestamp }}
</time> </time>
<span <span
v-permission="['system:roles:manage']" v-permission="['system:roles:manage']"
class="cursor-pointer" class="cursor-pointer"
> >
<IconSettings /> <IconSettings @click="handleOpenEditingModal(role)" />
</span> </span>
</div> </div>
</div> </div>

View File

@ -1,59 +1,59 @@
<script lang="ts" setup> <script lang="ts" setup>
import { VButton, VModal, VTabItem, VTabs } from "@halo-dev/components"; import { VButton, VModal, VTabItem, VTabs } from "@halo-dev/components";
import { ref } from "vue"; import type { PropType } from "vue";
import { apiClient } from "@halo-dev/admin-shared"; import { ref, watch } from "vue";
import type { Role } from "@halo-dev/api-client";
import { rbacAnnotations } from "@/constants/annotations"; import { rbacAnnotations } from "@/constants/annotations";
import { useRoleTemplateSelection } from "@/modules/system/roles/composables/use-role"; import type { Role } from "@halo-dev/api-client";
import {
useRoleForm,
useRoleTemplateSelection,
} from "@/modules/system/roles/composables/use-role";
import cloneDeep from "lodash.clonedeep";
interface FormState { const props = defineProps({
role: Role;
saving: boolean;
}
defineProps({
visible: { visible: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
role: {
type: Object as PropType<Role | null>,
default: null,
},
}); });
const emit = defineEmits(["update:visible", "close"]); const emit = defineEmits(["update:visible", "close"]);
const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } = const { roleTemplateGroups, handleRoleTemplateSelect, selectedRoleTemplates } =
useRoleTemplateSelection(); useRoleTemplateSelection();
const { formState, initialFormState, saving, handleCreateOrUpdate } =
useRoleForm();
const activeId = ref("general"); watch(
const formState = ref<FormState>({ () => selectedRoleTemplates.value,
role: { (newValue) => {
apiVersion: "v1alpha1", if (formState.value.metadata.annotations) {
kind: "Role", formState.value.metadata.annotations[rbacAnnotations.DEPENDENCIES] =
metadata: { JSON.stringify(Array.from(newValue));
name: "", }
labels: {}, }
annotations: { );
[rbacAnnotations.DEPENDENCIES]: "",
[rbacAnnotations.DISPLAY_NAME]: "", watch(props, (newVal) => {
}!, if (newVal.visible && props.role) {
}, formState.value = cloneDeep(props.role);
rules: [], return;
}, }
saving: false, formState.value = cloneDeep(initialFormState);
}); });
const handleCreateRole = async () => { const tabActiveId = ref("general");
const handleCreateOrUpdateRole = async () => {
try { try {
formState.value.saving = true; await handleCreateOrUpdate();
if (formState.value.role.metadata.annotations) {
formState.value.role.metadata.annotations[rbacAnnotations.DEPENDENCIES] =
JSON.stringify(Array.from(selectedRoleTemplates.value));
}
await apiClient.extension.role.createv1alpha1Role(formState.value.role);
handleVisibleChange(false); handleVisibleChange(false);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} finally {
formState.value.saving = false;
} }
}; };
@ -71,25 +71,25 @@ const handleVisibleChange = (visible: boolean) => {
title="创建角色" title="创建角色"
@update:visible="handleVisibleChange" @update:visible="handleVisibleChange"
> >
<VTabs v-model:active-id="activeId" type="outline"> <VTabs v-model:active-id="tabActiveId" type="outline">
<VTabItem id="general" label="基础信息"> <VTabItem id="general" label="基础信息">
<FormKit <FormKit
v-if="formState.role.metadata.annotations" v-if="formState.metadata.annotations"
id="role-form" id="role-form"
:actions="false" :actions="false"
type="form" type="form"
@submit="handleCreateRole" @submit="handleCreateOrUpdateRole"
> >
<FormKit <FormKit
v-model=" v-model="
formState.role.metadata.annotations[rbacAnnotations.DISPLAY_NAME] formState.metadata.annotations[rbacAnnotations.DISPLAY_NAME]
" "
label="名称" label="名称"
type="text" type="text"
validation="required" validation="required"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="formState.role.metadata.name" v-model="formState.metadata.name"
help="角色别名,用于区分角色,不能重复,创建之后不能修改" help="角色别名,用于区分角色,不能重复,创建之后不能修改"
label="别名" label="别名"
type="text" type="text"
@ -158,7 +158,7 @@ const handleVisibleChange = (visible: boolean) => {
</VTabs> </VTabs>
<template #footer> <template #footer>
<VButton <VButton
:loading="formState.saving" :loading="saving"
type="secondary" type="secondary"
@click="$formkit.submit('role-form')" @click="$formkit.submit('role-form')"
>创建 >创建

View File

@ -9,6 +9,54 @@ interface RoleTemplateGroup {
roles: Role[]; roles: Role[];
} }
const initialFormState: Role = {
apiVersion: "v1alpha1",
kind: "Role",
metadata: {
name: "",
labels: {},
annotations: {
[rbacAnnotations.DEPENDENCIES]: "",
[rbacAnnotations.DISPLAY_NAME]: "",
},
},
rules: [],
};
export function useRoleForm() {
const formState = ref<Role>(initialFormState);
const saving = ref(false);
const isUpdateMode = computed(() => {
return !!formState.value.metadata.creationTimestamp;
});
const handleCreateOrUpdate = async () => {
try {
saving.value = true;
if (isUpdateMode.value) {
await apiClient.extension.role.updatev1alpha1Role(
formState.value.metadata.name,
formState.value
);
} else {
await apiClient.extension.role.createv1alpha1Role(formState.value);
}
} catch (e) {
console.error(e);
} finally {
saving.value = false;
}
};
return {
formState,
initialFormState,
saving,
handleCreateOrUpdate,
};
}
export function useRoleTemplateSelection() { export function useRoleTemplateSelection() {
const rawRoles = ref<Role[]>([] as Role[]); const rawRoles = ref<Role[]>([] as Role[]);
const selectedRoleTemplates = ref<Set<string>>(new Set()); const selectedRoleTemplates = ref<Set<string>>(new Set());