halo-admin/src/modules/system/users/components/UserEditingModal.vue

258 lines
6.0 KiB
Vue

<script lang="ts" name="UserEditingModal" setup>
import { computed, onMounted, ref, watch } from "vue";
import { apiClient } from "@halo-dev/admin-shared";
import type { Role, User } from "@halo-dev/api-client";
import {
IconCodeBoxLine,
IconEye,
VButton,
VCodemirror,
VModal,
VSpace,
} from "@halo-dev/components";
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";
const props = withDefaults(
defineProps<{
visible: boolean;
user: User | null;
}>(),
{
visible: false,
user: null,
}
);
const emit = defineEmits<{
(event: "update:visible", visible: boolean): void;
(event: "close"): void;
}>();
interface FormState {
user: User;
saving: boolean;
rawMode: boolean;
raw: string;
}
const initialFormState: FormState = {
user: {
spec: {
displayName: "",
avatar: "",
email: "",
phone: "",
password: "",
bio: "",
disabled: false,
loginHistoryLimit: 0,
},
apiVersion: "v1alpha1",
kind: "User",
metadata: {
name: uuid(),
},
},
saving: false,
rawMode: false,
raw: "",
};
const roles = ref<Role[]>([]);
const formState = ref<FormState>(cloneDeep(initialFormState));
const selectedRole = ref("");
const isUpdateMode = computed(() => {
return !!formState.value.user.metadata.creationTimestamp;
});
const creationModalTitle = computed(() => {
return isUpdateMode.value ? "编辑用户" : "新增用户";
});
const modalWidth = computed(() => {
return formState.value.rawMode ? 800 : 700;
});
const basicRoles = computed(() => {
return roles.value.filter(
(role) => role.metadata?.labels?.[roleLabels.TEMPLATE] !== "true"
);
});
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();
}
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);
}
};
const handleVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
const handleCreateUser = async () => {
try {
formState.value.saving = true;
let user: User;
if (isUpdateMode.value) {
const response = await apiClient.extension.user.updatev1alpha1User(
formState.value.user.metadata.name,
formState.value.user
);
user = response.data;
} else {
const response = await apiClient.extension.user.createv1alpha1User(
formState.value.user
);
user = response.data;
}
if (selectedRole.value) {
await apiClient.user.grantPermission(user.metadata.name, {
// @ts-ignore
roles: [selectedRole.value],
});
}
handleVisibleChange(false);
} catch (e) {
console.error(e);
} finally {
formState.value.saving = false;
}
};
const handleRawModeChange = () => {
formState.value.rawMode = !formState.value.rawMode;
if (formState.value.rawMode) {
formState.value.raw = YAML.stringify(formState.value.user);
} else {
formState.value.user = YAML.parse(formState.value.raw);
}
};
onMounted(handleFetchRoles);
</script>
<template>
<VModal
:title="creationModalTitle"
:visible="visible"
:width="modalWidth"
@update:visible="handleVisibleChange"
>
<template #actions>
<div class="modal-header-action" @click="handleRawModeChange">
<IconCodeBoxLine v-if="!formState.rawMode" />
<IconEye v-else />
</div>
</template>
<VCodemirror
v-show="formState.rawMode"
v-model="formState.raw"
height="50vh"
language="yaml"
/>
<div v-show="!formState.rawMode">
<FormKit id="user-form" type="form" @submit="handleCreateUser">
<FormKit
v-model="formState.user.metadata.name"
:disabled="isUpdateMode"
label="用户名"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="formState.user.spec.displayName"
label="显示名称"
type="text"
validation="required"
></FormKit>
<FormKit
v-model="formState.user.spec.email"
label="电子邮箱"
type="email"
validation="required"
></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,
};
})
"
label="角色"
type="select"
></FormKit>
<FormKit
v-model="formState.user.spec.phone"
label="手机号"
type="text"
></FormKit>
<FormKit
v-model="formState.user.spec.avatar"
label="头像"
type="text"
></FormKit>
<FormKit
v-model="formState.user.spec.bio"
label="描述"
type="textarea"
></FormKit>
</FormKit>
</div>
<template #footer>
<VSpace>
<VButton
:loading="formState.saving"
type="secondary"
@click="$formkit.submit('user-form')"
>
保存 ⌘ + ↵
</VButton>
<VButton @click="handleVisibleChange(false)"> Esc</VButton>
</VSpace>
</template>
</VModal>
</template>